Import kactivities-kf5_5.96.0.orig.tar.xz
authorAurélien COUDERC <coucouf@debian.org>
Sun, 31 Jul 2022 11:33:00 +0000 (12:33 +0100)
committerAurélien COUDERC <coucouf@debian.org>
Sun, 31 Jul 2022 11:33:00 +0000 (12:33 +0100)
[dgit import orig kactivities-kf5_5.96.0.orig.tar.xz]

115 files changed:
.git-blame-ignore-revs [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.gitlab-ci.yml [new file with mode: 0644]
.kde-ci.yml [new file with mode: 0644]
.videproject/project.conf [new file with mode: 0644]
.videproject/vimrc [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
KF5ActivitiesConfig.cmake.in [new file with mode: 0644]
LICENSES/CC0-1.0.txt [new file with mode: 0644]
LICENSES/GPL-2.0-or-later.txt [new file with mode: 0644]
LICENSES/LGPL-2.0-or-later.txt [new file with mode: 0644]
LICENSES/LGPL-2.1-only.txt [new file with mode: 0644]
LICENSES/LGPL-3.0-only.txt [new file with mode: 0644]
LICENSES/LicenseRef-KDE-Accepted-LGPL.txt [new file with mode: 0644]
LICENSES/MIT.txt [new file with mode: 0644]
MAINTAINER [new file with mode: 0644]
README [new file with mode: 0644]
README.developers [new file with mode: 0644]
README.md [new file with mode: 0644]
README.packagers [new file with mode: 0644]
TODO [new file with mode: 0644]
autotests/CMakeLists.txt [new file with mode: 0644]
autotests/common/test.cpp [new file with mode: 0644]
autotests/common/test.h [new file with mode: 0644]
autotests/core/CMakeLists.txt [new file with mode: 0644]
autotests/core/CleanOnlineTest.cpp [new file with mode: 0644]
autotests/core/CleanOnlineTest.h [new file with mode: 0644]
autotests/core/OfflineTest.cpp [new file with mode: 0644]
autotests/core/OfflineTest.h [new file with mode: 0644]
autotests/core/Process.cpp [new file with mode: 0644]
autotests/core/Process.h [new file with mode: 0644]
autotests/core/main.cpp [new file with mode: 0644]
contrib/bash/next-activity.sh [new file with mode: 0755]
contrib/bash/prev-activity.sh [new file with mode: 0755]
contrib/commit.sh [new file with mode: 0755]
contrib/run-krazy.sh [new file with mode: 0755]
contrib/update-todo.hs [new file with mode: 0755]
contrib/zsh/kamd-functions [new file with mode: 0644]
docs/Doxyfile.local [new file with mode: 0644]
logo.png [new file with mode: 0644]
metainfo.yaml [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/cli/CMakeLists.txt [new file with mode: 0644]
src/cli/main.cpp [new file with mode: 0644]
src/cli/utils.h [new file with mode: 0644]
src/common/dbus/common.h [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.Activities.cpp [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.Activities.h [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.Activities.xml [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.Application.xml [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.Features.xml [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.Resources.xml [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml [new file with mode: 0644]
src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml [new file with mode: 0644]
src/imports/CMakeLists.txt [new file with mode: 0644]
src/imports/README [new file with mode: 0644]
src/imports/activitiesextensionplugin.cpp [new file with mode: 0644]
src/imports/activitiesextensionplugin.h [new file with mode: 0644]
src/imports/activityinfo.cpp [new file with mode: 0644]
src/imports/activityinfo.h [new file with mode: 0644]
src/imports/activitymodel.cpp [new file with mode: 0644]
src/imports/activitymodel.h [new file with mode: 0644]
src/imports/resourceinstance.cpp [new file with mode: 0644]
src/imports/resourceinstance.h [new file with mode: 0644]
src/imports/resourcemodel.cpp [new file with mode: 0644]
src/imports/resourcemodel.h [new file with mode: 0644]
src/kactivities-features.h.cmake [new file with mode: 0644]
src/lib/CMakeLists.txt [new file with mode: 0644]
src/lib/activitiescache_p.cpp [new file with mode: 0644]
src/lib/activitiescache_p.h [new file with mode: 0644]
src/lib/activitiesmodel.cpp [new file with mode: 0644]
src/lib/activitiesmodel.h [new file with mode: 0644]
src/lib/activitiesmodel_p.h [new file with mode: 0644]
src/lib/consumer.cpp [new file with mode: 0644]
src/lib/consumer.h [new file with mode: 0644]
src/lib/consumer_p.h [new file with mode: 0644]
src/lib/controller.cpp [new file with mode: 0644]
src/lib/controller.h [new file with mode: 0644]
src/lib/info.cpp [new file with mode: 0644]
src/lib/info.h [new file with mode: 0644]
src/lib/info_p.h [new file with mode: 0644]
src/lib/mainthreadexecutor_p.cpp [new file with mode: 0644]
src/lib/mainthreadexecutor_p.h [new file with mode: 0644]
src/lib/manager_p.cpp [new file with mode: 0644]
src/lib/manager_p.h [new file with mode: 0644]
src/lib/resourceinstance.cpp [new file with mode: 0644]
src/lib/resourceinstance.h [new file with mode: 0644]
src/lib/version.cpp [new file with mode: 0644]
src/lib/version.h [new file with mode: 0644]
src/utils/continue_with.h [new file with mode: 0644]
src/utils/dbusfuture_p.cpp [new file with mode: 0644]
src/utils/dbusfuture_p.h [new file with mode: 0644]
src/utils/model_updaters.h [new file with mode: 0644]
src/utils/optional_view.h [new file with mode: 0644]
src/utils/ptr_to.h [new file with mode: 0644]
src/utils/qflatset.h [new file with mode: 0644]
src/utils/range.h [new file with mode: 0644]
src/utils/remove_if.h [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/activities-model/CMakeLists.txt [new file with mode: 0644]
tests/activities-model/main.cpp [new file with mode: 0644]
tests/activities-model/window.cpp [new file with mode: 0644]
tests/activities-model/window.h [new file with mode: 0644]
tests/activities-model/window.ui [new file with mode: 0644]
tests/imports/activities.qml [new file with mode: 0644]
tests/imports/org.kde.listactivitiestest/contents/ui/main.qml [new file with mode: 0644]
tests/imports/org.kde.listactivitiestest/metadata.desktop [new file with mode: 0644]
tests/imports/plasma-applet-org.kde.listactivitiestest.desktop [new file with mode: 0644]
tests/imports/resources.qml [new file with mode: 0644]
tests/slc-interface/CMakeLists.txt [new file with mode: 0644]
tests/slc-interface/main.cpp [new file with mode: 0644]
tests/slc-interface/window.cpp [new file with mode: 0644]
tests/slc-interface/window.h [new file with mode: 0644]
tests/slc-interface/window.ui [new file with mode: 0644]
vim-extrarc [new file with mode: 0644]

diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644 (file)
index 0000000..eb23904
--- /dev/null
@@ -0,0 +1,5 @@
+#clang-format/tidy
+964b4dae478d9b7a03949718f18afa002286f433
+9b987f0e87a88896d549249577cce51ceb13a543
+e2abd678e4fc38552d3360759e4e47b02de0bf9d
+07a6b88bbd4c74d0bae94111770ba5020def0fef
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5efb7b3
--- /dev/null
@@ -0,0 +1,25 @@
+.debug
+tags
+_tests
+.videproject/ctags
+*swp
+*~
+.kdev4
+.cmake-params
+.ycm_extra_conf.py
+.ycm_extra_conf.pyc
+.clang_complete
+kactivities.kdev4
+compile_commands.json
+/apidocs
+GPATH
+GRTAGS
+GSYMS
+GTAGS
+.cmake/
+/.clang-format
+/build*
+.idea
+.clangd
+/cmake-build*
+.cache
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644 (file)
index 0000000..d2a9f84
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
+# SPDX-License-Identifier: CC0-1.0
+
+include:
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
diff --git a/.kde-ci.yml b/.kde-ci.yml
new file mode 100644 (file)
index 0000000..e5474bf
--- /dev/null
@@ -0,0 +1,12 @@
+Dependencies:
+- 'on': ['Linux', 'FreeBSD', 'Windows', 'macOS']
+  'require':
+    'frameworks/extra-cmake-modules': '@same'
+    'frameworks/kconfig' : '@same'
+    'frameworks/kwindowsystem' : '@same'
+    'frameworks/kcoreaddons' : '@same'
+    'frameworks/kio' : '@same'
+
+Options:
+  test-before-installing: True
+  require-passing-tests-on: [ 'Linux', 'FreeBSD' ]
diff --git a/.videproject/project.conf b/.videproject/project.conf
new file mode 100644 (file)
index 0000000..fb52c7b
--- /dev/null
@@ -0,0 +1,30 @@
+[Project]
+Name=KDE Activities
+Location=/opt/kde/src/libs/kactivities
+
+MakeCommand='OBJ_REPLACEMENT="s:/opt/kde/src/core/libs/kactivities:/opt/kde/build-clang/core/libs/kactivities:" makeobj'
+
+RunLocation=./
+RunCommand=./
+
+[General]
+OverrideVimCommands=1
+AutoInsertModeOnOpen=0
+Tags=
+SourcePaths=.
+
+[KeyBindings]
+SidePanelOpen='<C-A>'
+MakeProject='<F5>'
+RunProject='<F6>'
+
+[Grep]
+Root=.
+
+[Find]
+Root=.
+
+[QuickBrowser]
+iNotifyEnabled=0
+Root=.
+TagsSystem=ctags
diff --git a/.videproject/vimrc b/.videproject/vimrc
new file mode 100644 (file)
index 0000000..a8dbe5c
--- /dev/null
@@ -0,0 +1,2 @@
+
+set tags+=/opt/kde4trunk/ctags/libQt.tags
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..306eaec
--- /dev/null
@@ -0,0 +1,130 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+cmake_minimum_required(VERSION 3.16)
+
+# KDE Frameworks
+set(KF_VERSION "5.96.0") # handled by release scripts
+set(KF_DEP_VERSION "5.96.0") # handled by release scripts
+project (KActivities VERSION ${KF_VERSION})
+
+option (KACTIVITIES_LIBRARY_ONLY "If true, compiles only the KActivities library, without the QML imports." OFF)
+option (KACTIVITIES_ENABLE_EXCEPTIONS "If you have Boost 1.53, you need to build KActivities with exceptions enabled. This is UNTESTED and EXPERIMENTAL!" OFF)
+
+set (REQUIRED_QT_VERSION 5.15.2)
+
+# We don't build in-source
+if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
+   message (
+      FATAL_ERROR
+      "kactivities require an out of source build. Please create a separate build directory and run 'cmake path_to_sources [options]' there."
+   )
+endif ()
+
+set (KACTIVITIES_CURRENT_ROOT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+# Extra CMake stuff
+include(FeatureSummary)
+find_package(ECM 5.96.0  NO_MODULE)
+set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
+feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
+
+set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
+
+include (KDEInstallDirs)
+include (KDECMakeSettings)
+include(KDEGitCommitHooks)
+include (KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
+include (GenerateExportHeader)
+include (ECMGenerateHeaders)
+include (ECMGeneratePkgConfigFile)
+include (ECMQtDeclareLoggingCategory)
+include (ECMAddQch)
+include (ECMMarkNonGuiExecutable)
+include(ECMDeprecationSettings)
+include (ECMQmlModule)
+
+option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
+add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
+
+# Qt
+set (CMAKE_AUTOMOC ON)
+find_package (Qt${QT_MAJOR_VERSION} ${REQUIRED_QT_VERSION} CONFIG REQUIRED COMPONENTS Core DBus)
+
+# Basic includes
+include (CPack)
+
+include (CMakePackageConfigHelpers)
+include (ECMSetupVersion)
+
+message ("We are using the ${CMAKE_CXX_COMPILER_ID} compiler")
+if ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (KACTIVITIES_OVERRIDE_VISIBILITY STREQUAL "default"))
+   message ("Setting visibility preset to default")
+   set(CMAKE_CXX_VISIBILITY_PRESET default)
+   set(CMAKE_VISIBILITY_INLINES_HIDDEN 0)
+   string (REPLACE "-fvisibility-inlines-hidden" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+   string (REPLACE "-fvisibility=hidden" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+endif ()
+
+# libKActivities
+
+ecm_setup_version (
+   PROJECT
+   VARIABLE_PREFIX KACTIVITIES
+   VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kactivities_version.h"
+   PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfigVersion.cmake"
+   SOVERSION 5
+   )
+
+ecm_set_disabled_deprecation_versions(
+    QT 5.15.2
+    KF 5.95.0
+)
+
+add_subdirectory (src)
+if (BUILD_TESTING)
+    add_subdirectory (autotests)
+    add_subdirectory (tests)
+endif()
+
+set (CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Activities")
+
+if (BUILD_QCH)
+    ecm_install_qch_export(
+        TARGETS KF5Activities_QCH
+        FILE KF5ActivitiesLibraryQchTargets.cmake
+        DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+        COMPONENT Devel
+    )
+    set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ActivitiesLibraryQchTargets.cmake\")")
+endif()
+
+install (
+   EXPORT KF5ActivitiesLibraryTargets
+   DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+   FILE KF5ActivitiesLibraryTargets.cmake
+   NAMESPACE KF5::
+   )
+
+configure_package_config_file (
+   "${CMAKE_CURRENT_SOURCE_DIR}/KF5ActivitiesConfig.cmake.in"
+   "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfig.cmake"
+   INSTALL_DESTINATION  ${CMAKECONFIG_INSTALL_DIR}
+   )
+
+install (
+   FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfig.cmake"
+         "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfigVersion.cmake"
+   DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+   COMPONENT Devel
+   )
+
+install (
+   FILES ${CMAKE_CURRENT_BINARY_DIR}/kactivities_version.h
+   DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities COMPONENT Devel
+   )
+
+# Write out the features
+feature_summary (WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
+
+
+kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/KF5ActivitiesConfig.cmake.in b/KF5ActivitiesConfig.cmake.in
new file mode 100644 (file)
index 0000000..476726b
--- /dev/null
@@ -0,0 +1,7 @@
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+find_dependency(Qt@QT_MAJOR_VERSION@Core @REQUIRED_QT_VERSION@)
+
+include("${CMAKE_CURRENT_LIST_DIR}/KF5ActivitiesLibraryTargets.cmake")
+@PACKAGE_INCLUDE_QCHTARGETS@
diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt
new file mode 100644 (file)
index 0000000..0e259d4
--- /dev/null
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt
new file mode 100644 (file)
index 0000000..1d80ac3
--- /dev/null
@@ -0,0 +1,319 @@
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 
+
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to
+most of the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software
+is covered by the GNU Lesser General Public License instead.) You can apply
+it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish), that you receive source code or can get it if you want it, that you
+can change the software or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of
+the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or
+for a fee, you must give the recipients all the rights that you have. You
+must make sure that they, too, receive or can get the source code. And you
+must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If
+the software is modified by someone else and passed on, we want its recipients
+to know that what they have is not the original, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that redistributors of a free program will individually
+obtain patent licenses, in effect making the program proprietary. To prevent
+this, we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms
+of this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation
+in the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running the Program
+is not restricted, and the output from the Program is covered only if its
+contents constitute a work based on the Program (independent of having been
+made by running the Program). Whether that is true depends on what the Program
+does.
+
+1. You may copy and distribute verbatim copies of the Program's source code
+as you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence
+of any warranty; and give any other recipients of the Program a copy of this
+License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it,
+thus forming a work based on the Program, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all
+of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that
+you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or
+in part contains or is derived from the Program or any part thereof, to be
+licensed as a whole at no charge to all third parties under the terms of this
+License.
+
+c) If the modified program normally reads commands interactively when run,
+you must cause it, when started running for such interactive use in the most
+ordinary way, to print or display an announcement including an appropriate
+copyright notice and a notice that there is no warranty (or else, saying that
+you provide a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this License.
+(Exception: if the Program itself is interactive but does not normally print
+such an announcement, your work based on the Program is not required to print
+an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be
+on the terms of this License, whose permissions for other licensees extend
+to the entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works based
+on the Program.
+
+In addition, mere aggregation of another work not based on the Program with
+the Program (or with a work based on the Program) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under Section
+2) in object code or executable form under the terms of Sections 1 and 2 above
+provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source code,
+which must be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give
+any third party, for a charge no more than your cost of physically performing
+source distribution, a complete machine-readable copy of the corresponding
+source code, to be distributed under the terms of Sections 1 and 2 above on
+a medium customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all
+the source code for all modules it contains, plus any associated interface
+definition files, plus the scripts used to control compilation and installation
+of the executable. However, as a special exception, the source code distributed
+need not include anything that is normally distributed (in either source or
+binary form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component itself
+accompanies the executable.
+
+If distribution of executable or object code is made by offering access to
+copy from a designated place, then offering equivalent access to copy the
+source code from the same place counts as distribution of the source code,
+even though third parties are not compelled to copy the source along with
+the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except
+as expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses terminated
+so long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute the
+Program or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the Program
+(or any work based on the Program), you indicate your acceptance of this License
+to do so, and all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor
+to copy, distribute or modify the Program subject to these terms and conditions.
+You may not impose any further restrictions on the recipients' exercise of
+the rights granted herein. You are not responsible for enforcing compliance
+by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed
+on you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of
+this License. If you cannot distribute so as to satisfy simultaneously your
+obligations under this License and any other pertinent obligations, then as
+a consequence you may not distribute the Program at all. For example, if a
+patent license would not permit royalty-free redistribution of the Program
+by all those who receive copies directly or indirectly through you, then the
+only way you could satisfy both it and this License would be to refrain entirely
+from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents
+or other property right claims or to contest validity of any such claims;
+this section has the sole purpose of protecting the integrity of the free
+software distribution system, which is implemented by public license practices.
+Many people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose
+that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is
+permitted only in or among countries not thus excluded. In such case, this
+License incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of
+the General Public License from time to time. Such new versions will be similar
+in spirit to the present version, but may differ in detail to address new
+problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that version
+or of any later version published by the Free Software Foundation. If the
+Program does not specify a version number of this License, you may choose
+any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing and reuse
+of software generally.
+
+   NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
+OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
+OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
+OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH
+HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach
+them to the start of each source file to most effectively convey the exclusion
+of warranty; and each file should have at least the "copyright" line and a
+pointer to where the full notice is found.
+
+<one line to give the program's name and an idea of what it does.>
+
+Copyright (C) <yyyy> <name of author>
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, 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 General Public License for more details.
+
+You should have received a copy of the GNU 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when
+it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
+with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software,
+and you are welcome to redistribute it under certain conditions; type `show
+c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may be
+called something other than `show w' and `show c'; they could even be mouse-clicks
+or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the program, if necessary. Here
+is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision'
+(which makes passes at compilers) written by James Hacker.
+
+<signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General
+Public License does not permit incorporating your program into proprietary
+programs. If your program is a subroutine library, you may consider it more
+useful to permit linking proprietary applications with the library. If this
+is what you want to do, use the GNU Lesser General Public License instead
+of this License.
diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt
new file mode 100644 (file)
index 0000000..5c96471
--- /dev/null
@@ -0,0 +1,446 @@
+GNU LIBRARY GENERAL PUBLIC LICENSE
+
+Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc.
+
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is numbered 2 because
+it goes with version 2 of the ordinary GPL.]
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public Licenses are intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users.
+
+This license, the Library General Public License, applies to some specially
+designated Free Software Foundation software, and to any other libraries whose
+authors decide to use it. You can use it for your libraries, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish), that you receive source code or can get it if you want it, that you
+can change the software or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of
+the library, or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for
+a fee, you must give the recipients all the rights that we gave you. You must
+make sure that they, too, receive or can get the source code. If you link
+a program with the library, you must provide complete object files to the
+recipients so that they can relink them with the library, after making changes
+to the library and recompiling it. And you must show them these terms so they
+know their rights.
+
+Our method of protecting your rights has two steps: (1) copyright the library,
+and (2) offer you this license which gives you legal permission to copy, distribute
+and/or modify the library.
+
+Also, for each distributor's protection, we want to make certain that everyone
+understands that there is no warranty for this free library. If the library
+is modified by someone else and passed on, we want its recipients to know
+that what they have is not the original version, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that companies distributing free software will individually
+obtain patent licenses, thus in effect transforming the program into proprietary
+software. To prevent this, we have made it clear that any patent must be licensed
+for everyone's free use or not licensed at all.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU
+General Public License, which was designed for utility programs. This license,
+the GNU Library General Public License, applies to certain designated libraries.
+This license is quite different from the ordinary one; be sure to read it
+in full, and don't assume that anything in it is the same as in the ordinary
+license.
+
+The reason we have a separate public license for some libraries is that they
+blur the distinction we usually make between modifying or adding to a program
+and simply using it. Linking a program with a library, without changing the
+library, is in some sense simply using the library, and is analogous to running
+a utility program or application program. However, in a textual and legal
+sense, the linked executable is a combined work, a derivative of the original
+library, and the ordinary General Public License treats it as such.
+
+Because of this blurred distinction, using the ordinary General Public License
+for libraries did not effectively promote software sharing, because most developers
+did not use the libraries. We concluded that weaker conditions might promote
+sharing better.
+
+However, unrestricted linking of non-free programs would deprive the users
+of those programs of all benefit from the free status of the libraries themselves.
+This Library General Public License is intended to permit developers of non-free
+programs to use free libraries, while preserving your freedom as a user of
+such programs to change the free libraries that are incorporated in them.
+(We have not seen how to achieve this as regards changes in header files,
+but we have achieved it as regards changes in the actual functions of the
+Library.) The hope is that this will lead to faster development of free libraries.
+
+The precise terms and conditions for copying, distribution and modification
+follow. Pay close attention to the difference between a "work based on the
+library" and a "work that uses the library". The former contains code derived
+from the library, while the latter only works together with the library.
+
+Note that it is possible for a library to be covered by the ordinary General
+Public License rather than by this special one.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License Agreement applies to any software library which contains a
+notice placed by the copyright holder or other authorized party saying it
+may be distributed under the terms of this Library General Public License
+(also called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or data prepared
+so as to be conveniently linked with application programs (which use some
+of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has
+been distributed under these terms. A "work based on the Library" means either
+the Library or any derivative work under copyright law: that is to say, a
+work containing the Library or a portion of it, either verbatim or with modifications
+and/or translated straightforwardly into another language. (Hereinafter, translation
+is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications
+to it. For a library, complete source code means all the source code for all
+modules it contains, plus any associated interface definition files, plus
+the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running a program
+using the Library is not restricted, and output from such a program is covered
+only if its contents constitute a work based on the Library (independent of
+the use of the Library in a tool for writing it). Whether that is true depends
+on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and disclaimer
+of warranty; keep intact all the notices that refer to this License and to
+the absence of any warranty; and distribute a copy of this License along with
+the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it,
+thus forming a work based on the Library, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all
+of these conditions:
+
+      a) The modified work must itself be a software library.
+
+b) You must cause the files modified to carry prominent notices stating that
+you changed the files and the date of any change.
+
+c) You must cause the whole of the work to be licensed at no charge to all
+third parties under the terms of this License.
+
+d) If a facility in the modified Library refers to a function or a table of
+data to be supplied by an application program that uses the facility, other
+than as an argument passed when the facility is invoked, then you must make
+a good faith effort to ensure that, in the event an application does not supply
+such function or table, the facility still operates, and performs whatever
+part of its purpose remains meaningful.
+
+(For example, a function in a library to compute square roots has a purpose
+that is entirely well-defined independent of the application. Therefore, Subsection
+2d requires that any application-supplied function or table used by this function
+must be optional: if the application does not supply it, the square root function
+must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Library, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Library, the distribution of the whole must be
+on the terms of this License, whose permissions for other licensees extend
+to the entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works based
+on the Library.
+
+In addition, mere aggregation of another work not based on the Library with
+the Library (or with a work based on the Library) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License
+instead of this License to a given copy of the Library. To do this, you must
+alter all the notices that refer to this License, so that they refer to the
+ordinary GNU General Public License, version 2, instead of to this License.
+(If a newer version than version 2 of the ordinary GNU General Public License
+has appeared, then you can specify that version instead if you wish.) Do not
+make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy,
+so the ordinary GNU General Public License applies to all subsequent copies
+and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library
+into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of
+it, under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you accompany it with the complete corresponding
+machine-readable source code, which must be distributed under the terms of
+Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated
+place, then offering equivalent access to copy the source code from the same
+place satisfies the requirement to distribute the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but
+is designed to work with the Library by being compiled or linked with it,
+is called a "work that uses the Library". Such a work, in isolation, is not
+a derivative work of the Library, and therefore falls outside the scope of
+this License.
+
+However, linking a "work that uses the Library" with the Library creates an
+executable that is a derivative of the Library (because it contains portions
+of the Library), rather than a "work that uses the library". The executable
+is therefore covered by this License. Section 6 states terms for distribution
+of such executables.
+
+When a "work that uses the Library" uses material from a header file that
+is part of the Library, the object code for the work may be a derivative work
+of the Library even though the source code is not. Whether this is true is
+especially significant if the work can be linked without the Library, or if
+the work is itself a library. The threshold for this to be true is not precisely
+defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts
+and accessors, and small macros and small inline functions (ten lines or less
+in length), then the use of the object file is unrestricted, regardless of
+whether it is legally a derivative work. (Executables containing this object
+code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute
+the object code for the work under the terms of Section 6. Any executables
+containing that work also fall under Section 6, whether or not they are linked
+directly with the Library itself.
+
+6. As an exception to the Sections above, you may also compile or link a "work
+that uses the Library" with the Library to produce a work containing portions
+of the Library, and distribute that work under terms of your choice, provided
+that the terms permit modification of the work for the customer's own use
+and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library
+is used in it and that the Library and its use are covered by this License.
+You must supply a copy of this License. If the work during execution displays
+copyright notices, you must include the copyright notice for the Library among
+them, as well as a reference directing the user to the copy of this License.
+Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source
+code for the Library including whatever changes were used in the work (which
+must be distributed under Sections 1 and 2 above); and, if the work is an
+executable linked with the Library, with the complete machine-readable "work
+that uses the Library", as object code and/or source code, so that the user
+can modify the Library and then relink to produce a modified executable containing
+the modified Library. (It is understood that the user who changes the contents
+of definitions files in the Library will not necessarily be able to recompile
+the application to use the modified definitions.)
+
+b) Accompany the work with a written offer, valid for at least three years,
+to give the same user the materials specified in Subsection 6a, above, for
+a charge no more than the cost of performing this distribution.
+
+c) If distribution of the work is made by offering access to copy from a designated
+place, offer equivalent access to copy the above specified materials from
+the same place.
+
+d) Verify that the user has already received a copy of these materials or
+that you have already sent this user a copy.
+
+For an executable, the required form of the "work that uses the Library" must
+include any data and utility programs needed for reproducing the executable
+from it. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the operating
+system on which the executable runs, unless that component itself accompanies
+the executable.
+
+It may happen that this requirement contradicts the license restrictions of
+other proprietary libraries that do not normally accompany the operating system.
+Such a contradiction means you cannot use both them and the Library together
+in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side
+in a single library together with other library facilities not covered by
+this License, and distribute such a combined library, provided that the separate
+distribution of the work based on the Library and of the other library facilities
+is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the
+Library, uncombined with any other library facilities. This must be distributed
+under the terms of the Sections above.
+
+b) Give prominent notice with the combined library of the fact that part of
+it is a work based on the Library, and explaining where to find the accompanying
+uncombined form of the same work.
+
+8. You may not copy, modify, sublicense, link with, or distribute the Library
+except as expressly provided under this License. Any attempt otherwise to
+copy, modify, sublicense, link with, or distribute the Library is void, and
+will automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will not
+have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute the
+Library or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the Library
+(or any work based on the Library), you indicate your acceptance of this License
+to do so, and all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library),
+the recipient automatically receives a license from the original licensor
+to copy, distribute, link with or modify the Library subject to these terms
+and conditions. You may not impose any further restrictions on the recipients'
+exercise of the rights granted herein. You are not responsible for enforcing
+compliance by third parties to this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed
+on you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of
+this License. If you cannot distribute so as to satisfy simultaneously your
+obligations under this License and any other pertinent obligations, then as
+a consequence you may not distribute the Library at all. For example, if a
+patent license would not permit royalty-free redistribution of the Library
+by all those who receive copies directly or indirectly through you, then the
+only way you could satisfy both it and this License would be to refrain entirely
+from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents
+or other property right claims or to contest validity of any such claims;
+this section has the sole purpose of protecting the integrity of the free
+software distribution system which is implemented by public license practices.
+Many people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose
+that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Library under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is
+permitted only in or among countries not thus excluded. In such case, this
+License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of
+the Library General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that version
+or of any later version published by the Free Software Foundation. If the
+Library does not specify a license version number, you may choose any version
+ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs
+whose distribution conditions are incompatible with these, write to the author
+to ask for permission. For software which is copyrighted by the Free Software
+Foundation, write to the Free Software Foundation; we sometimes make exceptions
+for this. Our decision will be guided by the two goals of preserving the free
+status of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+   NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
+THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
+OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
+OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
+OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
+HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Libraries
+
+If you develop a new library, and you want it to be of the greatest possible
+use to the public, we recommend making it free software that everyone can
+redistribute and change. You can do so by permitting redistribution under
+these terms (or, alternatively, under the terms of the ordinary General Public
+License).
+
+To apply these terms, attach the following notices to the library. It is safest
+to attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the "copyright"
+line and a pointer to where the full notice is found.
+
+one line to give the library's name and an idea of what it does.
+
+Copyright (C) year name of author
+
+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; if not, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the library, if necessary. Here
+is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in
+
+the library `Frob' (a library for tweaking knobs) written
+
+by James Random Hacker.
+
+signature of Ty Coon, 1 April 1990
+
+Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/LICENSES/LGPL-2.1-only.txt b/LICENSES/LGPL-2.1-only.txt
new file mode 100644 (file)
index 0000000..130dffb
--- /dev/null
@@ -0,0 +1,467 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts as the
+successor of the GNU Library Public License, version 2, hence the version
+number 2.1.]
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public Licenses are intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users.
+
+This license, the Lesser General Public License, applies to some specially
+designated software packages--typically libraries--of the Free Software Foundation
+and other authors who decide to use it. You can use it too, but we suggest
+you first think carefully about whether this license or the ordinary General
+Public License is the better strategy to use in any particular case, based
+on the explanations below.
+
+When we speak of free software, we are referring to freedom of use, not price.
+Our General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish); that you receive source code or can get it if you want it; that you
+can change the software and use pieces of it in new free programs; and that
+you are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid distributors
+to deny you these rights or to ask you to surrender these rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of
+the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for
+a fee, you must give the recipients all the rights that we gave you. You must
+make sure that they, too, receive or can get the source code. If you link
+other code with the library, you must provide complete object files to the
+recipients, so that they can relink them with the library after making changes
+to the library and recompiling it. And you must show them these terms so they
+know their rights.
+
+We protect your rights with a two-step method: (1) we copyright the library,
+and (2) we offer you this license, which gives you legal permission to copy,
+distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that there is no
+warranty for the free library. Also, if the library is modified by someone
+else and passed on, the recipients should know that what they have is not
+the original version, so that the original author's reputation will not be
+affected by problems that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence of any free
+program. We wish to make sure that a company cannot effectively restrict the
+users of a free program by obtaining a restrictive license from a patent holder.
+Therefore, we insist that any patent license obtained for a version of the
+library must be consistent with the full freedom of use specified in this
+license.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU
+General Public License. This license, the GNU Lesser General Public License,
+applies to certain designated libraries, and is quite different from the ordinary
+General Public License. We use this license for certain libraries in order
+to permit linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using a shared
+library, the combination of the two is legally speaking a combined work, a
+derivative of the original library. The ordinary General Public License therefore
+permits such linking only if the entire combination fits its criteria of freedom.
+The Lesser General Public License permits more lax criteria for linking other
+code with the library.
+
+We call this license the "Lesser" General Public License because it does Less
+to protect the user's freedom than the ordinary General Public License. It
+also provides other free software developers Less of an advantage over competing
+non-free programs. These disadvantages are the reason we use the ordinary
+General Public License for many libraries. However, the Lesser license provides
+advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to encourage the
+widest possible use of a certain library, so that it becomes a de-facto standard.
+To achieve this, non-free programs must be allowed to use the library. A more
+frequent case is that a free library does the same job as widely used non-free
+libraries. In this case, there is little to gain by limiting the free library
+to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free programs
+enables a greater number of people to use a large body of free software. For
+example, permission to use the GNU C Library in non-free programs enables
+many more people to use the whole GNU operating system, as well as its variant,
+the GNU/Linux operating system.
+
+Although the Lesser General Public License is Less protective of the users'
+freedom, it does ensure that the user of a program that is linked with the
+Library has the freedom and the wherewithal to run that program using a modified
+version of the Library.
+
+The precise terms and conditions for copying, distribution and modification
+follow. Pay close attention to the difference between a "work based on the
+library" and a "work that uses the library". The former contains code derived
+from the library, whereas the latter must be combined with the library in
+order to run.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License Agreement applies to any software library or other program
+which contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Lesser General
+Public License (also called "this License"). Each licensee is addressed as
+"you".
+
+A "library" means a collection of software functions and/or data prepared
+so as to be conveniently linked with application programs (which use some
+of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has
+been distributed under these terms. A "work based on the Library" means either
+the Library or any derivative work under copyright law: that is to say, a
+work containing the Library or a portion of it, either verbatim or with modifications
+and/or translated straightforwardly into another language. (Hereinafter, translation
+is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications
+to it. For a library, complete source code means all the source code for all
+modules it contains, plus any associated interface definition files, plus
+the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running a program
+using the Library is not restricted, and output from such a program is covered
+only if its contents constitute a work based on the Library (independent of
+the use of the Library in a tool for writing it). Whether that is true depends
+on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and disclaimer
+of warranty; keep intact all the notices that refer to this License and to
+the absence of any warranty; and distribute a copy of this License along with
+the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it,
+thus forming a work based on the Library, and copy and distribute such modifications
+or work under the terms of Section 1 above, provided that you also meet all
+of these conditions:
+
+      a) The modified work must itself be a software library.
+
+b) You must cause the files modified to carry prominent notices stating that
+you changed the files and the date of any change.
+
+c) You must cause the whole of the work to be licensed at no charge to all
+third parties under the terms of this License.
+
+d) If a facility in the modified Library refers to a function or a table of
+data to be supplied by an application program that uses the facility, other
+than as an argument passed when the facility is invoked, then you must make
+a good faith effort to ensure that, in the event an application does not supply
+such function or table, the facility still operates, and performs whatever
+part of its purpose remains meaningful.
+
+(For example, a function in a library to compute square roots has a purpose
+that is entirely well-defined independent of the application. Therefore, Subsection
+2d requires that any application-supplied function or table used by this function
+must be optional: if the application does not supply it, the square root function
+must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Library, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Library, the distribution of the whole must be
+on the terms of this License, whose permissions for other licensees extend
+to the entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works based
+on the Library.
+
+In addition, mere aggregation of another work not based on the Library with
+the Library (or with a work based on the Library) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License
+instead of this License to a given copy of the Library. To do this, you must
+alter all the notices that refer to this License, so that they refer to the
+ordinary GNU General Public License, version 2, instead of to this License.
+(If a newer version than version 2 of the ordinary GNU General Public License
+has appeared, then you can specify that version instead if you wish.) Do not
+make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy,
+so the ordinary GNU General Public License applies to all subsequent copies
+and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library
+into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of
+it, under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you accompany it with the complete corresponding
+machine-readable source code, which must be distributed under the terms of
+Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated
+place, then offering equivalent access to copy the source code from the same
+place satisfies the requirement to distribute the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but
+is designed to work with the Library by being compiled or linked with it,
+is called a "work that uses the Library". Such a work, in isolation, is not
+a derivative work of the Library, and therefore falls outside the scope of
+this License.
+
+However, linking a "work that uses the Library" with the Library creates an
+executable that is a derivative of the Library (because it contains portions
+of the Library), rather than a "work that uses the library". The executable
+is therefore covered by this License. Section 6 states terms for distribution
+of such executables.
+
+When a "work that uses the Library" uses material from a header file that
+is part of the Library, the object code for the work may be a derivative work
+of the Library even though the source code is not. Whether this is true is
+especially significant if the work can be linked without the Library, or if
+the work is itself a library. The threshold for this to be true is not precisely
+defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts
+and accessors, and small macros and small inline functions (ten lines or less
+in length), then the use of the object file is unrestricted, regardless of
+whether it is legally a derivative work. (Executables containing this object
+code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute
+the object code for the work under the terms of Section 6. Any executables
+containing that work also fall under Section 6, whether or not they are linked
+directly with the Library itself.
+
+6. As an exception to the Sections above, you may also combine or link a "work
+that uses the Library" with the Library to produce a work containing portions
+of the Library, and distribute that work under terms of your choice, provided
+that the terms permit modification of the work for the customer's own use
+and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library
+is used in it and that the Library and its use are covered by this License.
+You must supply a copy of this License. If the work during execution displays
+copyright notices, you must include the copyright notice for the Library among
+them, as well as a reference directing the user to the copy of this License.
+Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source
+code for the Library including whatever changes were used in the work (which
+must be distributed under Sections 1 and 2 above); and, if the work is an
+executable linked with the Library, with the complete machine-readable "work
+that uses the Library", as object code and/or source code, so that the user
+can modify the Library and then relink to produce a modified executable containing
+the modified Library. (It is understood that the user who changes the contents
+of definitions files in the Library will not necessarily be able to recompile
+the application to use the modified definitions.)
+
+b) Use a suitable shared library mechanism for linking with the Library. A
+suitable mechanism is one that (1) uses at run time a copy of the library
+already present on the user's computer system, rather than copying library
+functions into the executable, and (2) will operate properly with a modified
+version of the library, if the user installs one, as long as the modified
+version is interface-compatible with the version that the work was made with.
+
+c) Accompany the work with a written offer, valid for at least three years,
+to give the same user the materials specified in Subsection 6a, above, for
+a charge no more than the cost of performing this distribution.
+
+d) If distribution of the work is made by offering access to copy from a designated
+place, offer equivalent access to copy the above specified materials from
+the same place.
+
+e) Verify that the user has already received a copy of these materials or
+that you have already sent this user a copy.
+
+For an executable, the required form of the "work that uses the Library" must
+include any data and utility programs needed for reproducing the executable
+from it. However, as a special exception, the materials to be distributed
+need not include anything that is normally distributed (in either source or
+binary form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component itself
+accompanies the executable.
+
+It may happen that this requirement contradicts the license restrictions of
+other proprietary libraries that do not normally accompany the operating system.
+Such a contradiction means you cannot use both them and the Library together
+in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side
+in a single library together with other library facilities not covered by
+this License, and distribute such a combined library, provided that the separate
+distribution of the work based on the Library and of the other library facilities
+is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the
+Library, uncombined with any other library facilities. This must be distributed
+under the terms of the Sections above.
+
+b) Give prominent notice with the combined library of the fact that part of
+it is a work based on the Library, and explaining where to find the accompanying
+uncombined form of the same work.
+
+8. You may not copy, modify, sublicense, link with, or distribute the Library
+except as expressly provided under this License. Any attempt otherwise to
+copy, modify, sublicense, link with, or distribute the Library is void, and
+will automatically terminate your rights under this License. However, parties
+who have received copies, or rights, from you under this License will not
+have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed
+it. However, nothing else grants you permission to modify or distribute the
+Library or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the Library
+(or any work based on the Library), you indicate your acceptance of this License
+to do so, and all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library),
+the recipient automatically receives a license from the original licensor
+to copy, distribute, link with or modify the Library subject to these terms
+and conditions. You may not impose any further restrictions on the recipients'
+exercise of the rights granted herein. You are not responsible for enforcing
+compliance by third parties with this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed
+on you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of
+this License. If you cannot distribute so as to satisfy simultaneously your
+obligations under this License and any other pertinent obligations, then as
+a consequence you may not distribute the Library at all. For example, if a
+patent license would not permit royalty-free redistribution of the Library
+by all those who receive copies directly or indirectly through you, then the
+only way you could satisfy both it and this License would be to refrain entirely
+from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents
+or other property right claims or to contest validity of any such claims;
+this section has the sole purpose of protecting the integrity of the free
+software distribution system which is implemented by public license practices.
+Many people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose
+that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Library under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is
+permitted only in or among countries not thus excluded. In such case, this
+License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of
+the Lesser General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that version
+or of any later version published by the Free Software Foundation. If the
+Library does not specify a license version number, you may choose any version
+ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs
+whose distribution conditions are incompatible with these, write to the author
+to ask for permission. For software which is copyrighted by the Free Software
+Foundation, write to the Free Software Foundation; we sometimes make exceptions
+for this. Our decision will be guided by the two goals of preserving the free
+status of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+   NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
+THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
+OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
+OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
+OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
+HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Libraries
+
+If you develop a new library, and you want it to be of the greatest possible
+use to the public, we recommend making it free software that everyone can
+redistribute and change. You can do so by permitting redistribution under
+these terms (or, alternatively, under the terms of the ordinary General Public
+License).
+
+To apply these terms, attach the following notices to the library. It is safest
+to attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the "copyright"
+line and a pointer to where the full notice is found.
+
+< one line to give the library's name and an idea of what it does. >
+
+Copyright (C) < year > < name of author >
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 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 Lesser General Public License for more
+details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to the Free Software Foundation, Inc., 51
+Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information
+on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the library, if necessary. Here
+is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in
+
+the library `Frob' (a library for tweaking knobs) written
+
+by James Random Hacker.
+
+< signature of Ty Coon > , 1 April 1990
+
+Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt
new file mode 100644 (file)
index 0000000..bd405af
--- /dev/null
@@ -0,0 +1,163 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+This version of the GNU Lesser General Public License incorporates the terms
+and conditions of version 3 of the GNU General Public License, supplemented
+by the additional permissions listed below.
+
+   0. Additional Definitions.
+
+      
+
+As used herein, "this License" refers to version 3 of the GNU Lesser General
+Public License, and the "GNU GPL" refers to version 3 of the GNU General Public
+License.
+
+      
+
+"The Library" refers to a covered work governed by this License, other than
+an Application or a Combined Work as defined below.
+
+      
+
+An "Application" is any work that makes use of an interface provided by the
+Library, but which is not otherwise based on the Library. Defining a subclass
+of a class defined by the Library is deemed a mode of using an interface provided
+by the Library.
+
+      
+
+A "Combined Work" is a work produced by combining or linking an Application
+with the Library. The particular version of the Library with which the Combined
+Work was made is also called the "Linked Version".
+
+      
+
+The "Minimal Corresponding Source" for a Combined Work means the Corresponding
+Source for the Combined Work, excluding any source code for portions of the
+Combined Work that, considered in isolation, are based on the Application,
+and not on the Linked Version.
+
+      
+
+The "Corresponding Application Code" for a Combined Work means the object
+code and/or source code for the Application, including any data and utility
+programs needed for reproducing the Combined Work from the Application, but
+excluding the System Libraries of the Combined Work.
+
+   1. Exception to Section 3 of the GNU GPL.
+
+You may convey a covered work under sections 3 and 4 of this License without
+being bound by section 3 of the GNU GPL.
+
+   2. Conveying Modified Versions.
+
+If you modify a copy of the Library, and, in your modifications, a facility
+refers to a function or data to be supplied by an Application that uses the
+facility (other than as an argument passed when the facility is invoked),
+then you may convey a copy of the modified version:
+
+a) under this License, provided that you make a good faith effort to ensure
+that, in the event an Application does not supply the function or data, the
+facility still operates, and performs whatever part of its purpose remains
+meaningful, or
+
+b) under the GNU GPL, with none of the additional permissions of this License
+applicable to that copy.
+
+   3. Object Code Incorporating Material from Library Header Files.
+
+The object code form of an Application may incorporate material from a header
+file that is part of the Library. You may convey such object code under terms
+of your choice, provided that, if the incorporated material is not limited
+to numerical parameters, data structure layouts and accessors, or small macros,
+inline functions and templates (ten or fewer lines in length), you do both
+of the following:
+
+a) Give prominent notice with each copy of the object code that the Library
+is used in it and that the Library and its use are covered by this License.
+
+b) Accompany the object code with a copy of the GNU GPL and this license document.
+
+   4. Combined Works.
+
+You may convey a Combined Work under terms of your choice that, taken together,
+effectively do not restrict modification of the portions of the Library contained
+in the Combined Work and reverse engineering for debugging such modifications,
+if you also do each of the following:
+
+a) Give prominent notice with each copy of the Combined Work that the Library
+is used in it and that the Library and its use are covered by this License.
+
+b) Accompany the Combined Work with a copy of the GNU GPL and this license
+document.
+
+c) For a Combined Work that displays copyright notices during execution, include
+the copyright notice for the Library among these notices, as well as a reference
+directing the user to the copies of the GNU GPL and this license document.
+
+      d) Do one of the following:
+
+0) Convey the Minimal Corresponding Source under the terms of this License,
+and the Corresponding Application Code in a form suitable for, and under terms
+that permit, the user to recombine or relink the Application with a modified
+version of the Linked Version to produce a modified Combined Work, in the
+manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
+
+1) Use a suitable shared library mechanism for linking with the Library. A
+suitable mechanism is one that (a) uses at run time a copy of the Library
+already present on the user's computer system, and (b) will operate properly
+with a modified version of the Library that is interface-compatible with the
+Linked Version.
+
+e) Provide Installation Information, but only if you would otherwise be required
+to provide such information under section 6 of the GNU GPL, and only to the
+extent that such information is necessary to install and execute a modified
+version of the Combined Work produced by recombining or relinking the Application
+with a modified version of the Linked Version. (If you use option 4d0, the
+Installation Information must accompany the Minimal Corresponding Source and
+Corresponding Application Code. If you use option 4d1, you must provide the
+Installation Information in the manner specified by section 6 of the GNU GPL
+for conveying Corresponding Source.)
+
+   5. Combined Libraries.
+
+You may place library facilities that are a work based on the Library side
+by side in a single library together with other library facilities that are
+not Applications and are not covered by this License, and convey such a combined
+library under terms of your choice, if you do both of the following:
+
+a) Accompany the combined library with a copy of the same work based on the
+Library, uncombined with any other library facilities, conveyed under the
+terms of this License.
+
+b) Give prominent notice with the combined library that part of it is a work
+based on the Library, and explaining where to find the accompanying uncombined
+form of the same work.
+
+   6. Revised Versions of the GNU Lesser General Public License.
+
+The Free Software Foundation may publish revised and/or new versions of the
+GNU Lesser General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library as you
+received it specifies that a certain numbered version of the GNU Lesser General
+Public License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that published version or of
+any later version published by the Free Software Foundation. If the Library
+as you received it does not specify a version number of the GNU Lesser General
+Public License, you may choose any version of the GNU Lesser General Public
+License ever published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide whether
+future versions of the GNU Lesser General Public License shall apply, that
+proxy's public statement of acceptance of any version is permanent authorization
+for you to choose that version for the Library.
diff --git a/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt
new file mode 100644 (file)
index 0000000..232b3c5
--- /dev/null
@@ -0,0 +1,12 @@
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 3 of the license or (at your option) any later version
+that is accepted by the membership of KDE e.V. (or its successor
+approved by the membership of KDE e.V.), which shall act as a
+proxy as defined in Section 6 of version 3 of the license.
+
+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 General Public License for more details.
diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt
new file mode 100644 (file)
index 0000000..204b93d
--- /dev/null
@@ -0,0 +1,19 @@
+MIT License Copyright (c) <year> <copyright holders>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/MAINTAINER b/MAINTAINER
new file mode 100644 (file)
index 0000000..51d257d
--- /dev/null
@@ -0,0 +1,3 @@
+
+Current kactivities maintainer is: Ivan Čukić <ivan.cukic@kde.org>
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..43a57c6
--- /dev/null
+++ b/README
@@ -0,0 +1,6 @@
+See README.developers and README.packagers (the former is also for users).
+
+In order to properly display the files, use the GNU man command.
+
+man README.developers
+man README.packagers
diff --git a/README.developers b/README.developers
new file mode 100644 (file)
index 0000000..575d704
--- /dev/null
@@ -0,0 +1,87 @@
+
+
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+.\"                                                     "
+.\" It is best to view this file with the man tool:     "
+.\" man ./README.developers                             "
+.\"                                                     "
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+
+
+.TH README KAMD  2012-08-29 "KDE" "KActivities Developers"
+
+.SH COMMIT POLICY
+
+Every non-trivial patch must go through the review before it goes into the
+master branch.
+
+    http://git.reviewboard.kde.org
+        repository: kactivities
+        groups:     plasma
+        people:     Ivan Cukic
+
+If you don't have an account for identity.kde.org, you can send smaller
+patches to the plasma-devel@kde.org mailing list, or (please don't) directly
+to the repository maintainer (see MAINTAINER file).
+
+
+.SH CODE POLICY
+
+The code needs to follow KDElibs coding style in *all* parts of the project,
+not only the library. You can find more information about the style here:
+http://techbase.kde.org/Policies/Kdelibs_Coding_Style
+
+Macros in CMakeLists.txt should be lowercase throughout the project,
+and indentation should be 4, with the closing parenthesis in the same level
+as the content.
+
+.SH COMPILER COMPATIBILITY
+
+The library (src/lib) needs to be compilable with these:
+    - GCC 4.5
+    - MSVC 2010
+    - Clang 3.1
+      (or newer)
+This is the same policy the KDE Frameworks have.
+
+Other parts require modern compilers. You can (and should) use more modern
+C++ coding practices. Including auto, lambdas, smart pointers etc. You can
+use anything that GCC 4.7 can compile.
+
+These are the compilers you need to test your patches against:
+    - GCC 4.7
+    - LLVM/Clang 3.1
+
+When you set up different builds alongside the main one, you can use
+scripts/commit.sh to build them all before committing. The script
+calls git commit if all builds finished successfully. See the script
+for more info.
+
+
+.SH FILE NAMING
+
+The library files are lower-case, apart from the "pretty" headers.
+The service, and the rest of the repository should be in camel-case
+(with the exception of source files that don't have corresponding
+headers, or vice-versa).
+
+
+.SH CONVENIENCE MACROS AND METHODS
+
+There are some convenience macros and methods defined in the headers placed
+in the service/utils/ directory.
+
+.TP
+.B D_PTR
+d_ptr.h and d_ptr_implementation.h define a smart pointer way of doing
+the d-ptr (aka pimpl) idiom.
+
+.TP
+.B remove_if
+remove_if.h is a generic implementation of the erase-remove idiom
+
+.TP
+.B for_each_assoc, find_if_assoc
+for_each_assoc.h and find_if_assoc.h define the for_each and find_if
+algorithms for associative containers. Works with both Qt and STL containers.
+
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..21bc0b8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+# KActivities
+
+Core components for the KDE Activity concept
+
+## Introduction
+
+When a user is interacting with a computer, there are three main areas of
+contextual information that may affect the behaviour of the system: who the user
+is, where they are, and what they are doing.
+
+*Activities* deal with the last one. An activity might be "developing a KDE
+application", "studying 19th century art", "composing music" or "watching funny
+videos". Each of these activities may involve multiple applications, and a single
+application may be used in multiple activities (for example, most activities are
+likely to involve using a web browser, but different activities will probably
+involve different websites).
+
+KActivities provides the infrastructure needed to manage a user's activities,
+allowing them to switch between tasks, and for applications to update their
+state to match the user's current activity. This includes a daemon, a library
+for interacting with that daemon, and plugins for integration with other
+frameworks.
+
+## Usage
+
+Most applications that wish to be activity-aware will want to use
+KActivities::Consumer to keep track of the user's current activity, and
+KActivities::ResourceInstance to notify the activity manager of resources the
+user has accessed (this is not necessary for resources accessed via KIO, as a
+plugin is provided to do that automatically).
+
+The other classes available in the API are primarily intended for use by the
+workspace to allow the user to view and manage available activities.
diff --git a/README.packagers b/README.packagers
new file mode 100644 (file)
index 0000000..6b7155b
--- /dev/null
@@ -0,0 +1,23 @@
+
+
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+.\"                                                     "
+.\" It is best to view this file with the man tool:     "
+.\" man ./README.packagers                              "
+.\"                                                     "
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+
+
+.\" Best to view this file with the man tool
+.TH README KAMD  2012-08-29 "KDE" "KActivities Packagers"
+
+.SH LIBRARY
+
+The library can be used even if the service is not running nor installed.
+The dependencies are minimal since it is only a thin wrapper for the d-bus
+service.
+
+.SH SERVICE
+
+.TP
+
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..10e0973
--- /dev/null
+++ b/TODO
@@ -0,0 +1,28 @@
+src/lib/resourceinstance.cpp:149:
+ TODO: update the service info
+src/lib/resourceinstance.cpp:161:
+ TODO: update the service info
+
+src/imports/resourcemodel.cpp:103:
+ NOTE: What to do if the file does not exist?
+       Ignoring that case since the daemon creates it on startup.
+       Is it plausible that somebody will instantiate the ResourceModel
+       before the daemon is started?
+src/imports/resourcemodel.cpp:133:
+ TODO: Database connection naming could be smarter (thread-id-based,
+       reusing connections...?)
+src/imports/resourcemodel.cpp:335:
+ TODO: Will probably need some more special handling -
+       for application:/ and a few more
+src/imports/resourcemodel.cpp:551:
+ TODO: This might be smarter possibly, but might collide
+       with the SQL model. Implement a custom model with internal
+       cache instead of basing it on QSqlModel.
+
+src/imports/activitiesextensionplugin.cpp:30:
+ TODO: Clean up unused classes from the imports module
+src/imports/activitiesextensionplugin.cpp:32:
+ TODO: Since plasma is now dealing with activity model wallpapers,
+       replace ActivityModel with the KActivities::ActivitiesModel
+       (but keep the name)
+
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c2d5775
--- /dev/null
@@ -0,0 +1,7 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+string (REPLACE "-fno-exceptions" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+add_definitions (-fexceptions)
+
+add_subdirectory (core)
+
diff --git a/autotests/common/test.cpp b/autotests/common/test.cpp
new file mode 100644 (file)
index 0000000..b10a481
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+    SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "test.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+
+#include "common/dbus/common.h"
+
+Test::Test(QObject *parent)
+    : QObject(parent)
+{
+}
+
+bool Test::inEmptySession()
+{
+    const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames();
+
+    for (const QString &service : services) {
+        bool kdeServiceAndNotKAMD = service.startsWith(QLatin1String("org.kde")) && service != KAMD_DBUS_SERVICE;
+
+        if (kdeServiceAndNotKAMD) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool Test::isActivityManagerRunning()
+{
+    return QDBusConnection::sessionBus().interface()->isServiceRegistered(KAMD_DBUS_SERVICE);
+}
diff --git a/autotests/common/test.h b/autotests/common/test.h
new file mode 100644 (file)
index 0000000..2a1eae6
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+    SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef COMMON_TEST_H
+#define COMMON_TEST_H
+
+#include <QCoreApplication>
+#include <QFuture>
+#include <QFutureWatcher>
+#include <QObject>
+#include <QTest>
+
+class Test : public QObject
+{
+    Q_OBJECT
+public:
+    Test(QObject *parent = nullptr);
+
+protected:
+    enum WhenToFail {
+        DontFail = 0,
+        FailIfTrue = 1,
+        FailIfFalse = 2,
+    };
+
+    template<typename _ReturnType, typename _Continuation>
+    void continue_future(const QFuture<_ReturnType> &future, _Continuation &&continuation)
+    {
+        if (!future.isFinished()) {
+            auto watcher = new QFutureWatcher<decltype(future.result())>();
+            QObject::connect(
+                watcher,
+                &QFutureWatcherBase::finished,
+                watcher,
+                [=] {
+                    continuation(watcher->result());
+                    watcher->deleteLater();
+                },
+                Qt::QueuedConnection);
+
+            watcher->setFuture(future);
+
+        } else {
+            continuation(future.result());
+        }
+    }
+
+    template<typename _Continuation>
+    void continue_future(const QFuture<void> &future, _Continuation &&continuation)
+    {
+        if (!future.isFinished()) {
+            auto watcher = new QFutureWatcher<void>();
+            QObject::connect(
+                watcher,
+                &QFutureWatcherBase::finished,
+                watcher,
+                [=] {
+                    continuation();
+                    watcher->deleteLater();
+                },
+                Qt::QueuedConnection);
+
+            watcher->setFuture(future);
+
+        } else {
+            continuation();
+        }
+    }
+
+    template<typename T>
+    static inline void wait_until(T condition, const char *msg, int msecs = 300)
+    {
+        auto start = QTime::currentTime();
+
+        while (!condition()) {
+            QCoreApplication::processEvents();
+
+            auto now = QTime::currentTime();
+            QVERIFY2(start.msecsTo(now) < msecs, msg);
+            if (start.msecsTo(now) >= msecs)
+                break;
+        }
+    }
+// clang-format off
+#define TEST_WAIT_UNTIL(C)                                                     \
+    wait_until([&] () -> bool { return C; }, "Timeout waiting for: " #C);
+#define TEST_WAIT_UNTIL_WITH_TIMEOUT(C, T)                                     \
+    wait_until([&] () ->bool { return C; }, "Timeout waiting for: " #C, T);
+    // clang-format on
+
+    template<typename T>
+    static bool check(T what, WhenToFail wtf = DontFail, const char *msg = nullptr)
+    {
+        bool result = what();
+
+        if ((wtf == FailIfTrue && result) || (wtf == FailIfFalse && !result)) {
+            qFatal(
+                "\n"
+                "\n"
+                "!!! > \n"
+                "!!! > %s\n"
+                "!!! > \n",
+                msg);
+        }
+
+        return result;
+    }
+
+    static bool inEmptySession();
+    static bool isActivityManagerRunning();
+
+Q_SIGNALS:
+    void testFinished();
+};
+
+#define CHECK_CONDITION(A, B) check(A, B, #A " raised " #B)
+
+// Pretty print
+#include <iostream>
+
+#if defined(Q_NO_DEBUG) || !defined(Q_OS_LINUX)
+#define TEST_CHUNK(Name)
+#else
+inline void _test_chunk(const QString &message)
+{
+    std::cerr << '\n' << message.toStdString() << "\n" << std::string(message.length(), '-') << '\n';
+}
+#define TEST_CHUNK(Name) _test_chunk(Name);
+#endif
+
+#endif /* TEST_H */
diff --git a/autotests/core/CMakeLists.txt b/autotests/core/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c2a26dc
--- /dev/null
@@ -0,0 +1,37 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesTest)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Test Core DBus)
+
+include_directories (
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/tests/
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+   ${CMAKE_BINARY_DIR}/src/
+   )
+
+set (
+   KActivitiesTest_SRCS
+   main.cpp
+   Process.cpp
+   OfflineTest.cpp
+   CleanOnlineTest.cpp
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/common/test.cpp
+   )
+
+if (NOT WIN32)
+
+   add_executable (
+      KActivitiesTest
+      ${KActivitiesTest_SRCS}
+      )
+
+   target_link_libraries (
+      KActivitiesTest
+      Qt${QT_MAJOR_VERSION}::Core
+      Qt${QT_MAJOR_VERSION}::Test
+      Qt${QT_MAJOR_VERSION}::DBus
+      KF5::Activities
+      )
+
+endif ()
diff --git a/autotests/core/CleanOnlineTest.cpp b/autotests/core/CleanOnlineTest.cpp
new file mode 100644 (file)
index 0000000..9f1a587
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+    SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "CleanOnlineTest.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDebug>
+#include <QString>
+#include <QTest>
+
+QString CleanOnlineSetup::id1;
+QString CleanOnlineSetup::id2;
+
+CleanOnlineTest::CleanOnlineTest(QObject *parent)
+    : Test(parent)
+{
+    activities.reset(new KActivities::Controller());
+}
+
+CleanOnlineSetup::CleanOnlineSetup(QObject *parent)
+    : Test(parent)
+{
+    activities.reset(new KActivities::Controller());
+}
+
+OnlineTest::OnlineTest(QObject *parent)
+    : Test(parent)
+{
+    activities.reset(new KActivities::Controller());
+}
+
+void CleanOnlineTest::testCleanOnlineActivityListing()
+{
+    // Waiting for the service to start, and for us to sync
+    TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running);
+
+    QCOMPARE(activities->activities(), QStringList());
+    QCOMPARE(activities->serviceStatus(), KActivities::Consumer::Running);
+    QCOMPARE(activities->currentActivity(), QString());
+
+    QCOMPARE(activities->activities(), QStringList());
+    QCOMPARE(activities->activities(KActivities::Info::Running), QStringList());
+    QCOMPARE(activities->activities(KActivities::Info::Stopped), QStringList());
+}
+
+void CleanOnlineSetup::testCleanOnlineActivityControl()
+{
+    // Waiting for the service to start, and for us to sync
+    TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running);
+
+    auto activity1 = activities->addActivity(QStringLiteral("The first one"));
+    TEST_WAIT_UNTIL(activity1.isFinished());
+    id1 = activity1.result();
+
+    auto activity2 = activities->addActivity(QStringLiteral("The second one"));
+    TEST_WAIT_UNTIL(activity2.isFinished());
+    id2 = activity2.result();
+
+    TEST_WAIT_UNTIL(activities->currentActivity() == id1);
+
+    auto f1 = activities->setCurrentActivity(activity2.result());
+    TEST_WAIT_UNTIL(f1.isFinished());
+    TEST_WAIT_UNTIL(activities->currentActivity() == id2);
+
+    auto f2 = activities->setCurrentActivity(id1);
+    TEST_WAIT_UNTIL(f2.isFinished());
+    TEST_WAIT_UNTIL(activities->currentActivity() == id1);
+
+    auto f3 = activities->setActivityName(id1, QStringLiteral("Renamed activity"));
+    TEST_WAIT_UNTIL(f3.isFinished());
+
+    KActivities::Info ac(id1);
+    TEST_WAIT_UNTIL(ac.name() == QLatin1String("Renamed activity"));
+
+    TEST_WAIT_UNTIL(activities->activities().size() == 2);
+}
+
+void OnlineTest::testOnlineActivityListing()
+{
+    // Waiting for the service to start, and for us to sync
+    TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running);
+
+    KActivities::Info i1(CleanOnlineSetup::id1);
+    KActivities::Info i2(CleanOnlineSetup::id2);
+
+    TEST_WAIT_UNTIL(i1.name() == QLatin1String("Renamed activity"));
+    TEST_WAIT_UNTIL(i2.name() == QLatin1String("The second one"));
+
+    qDebug() << CleanOnlineSetup::id1 << i1.name();
+    qDebug() << CleanOnlineSetup::id2 << i2.name();
+}
+
+void CleanOnlineTest::cleanupTestCase()
+{
+    Q_EMIT testFinished();
+}
+
+void CleanOnlineSetup::cleanupTestCase()
+{
+    Q_EMIT testFinished();
+}
+
+void OnlineTest::cleanupTestCase()
+{
+    Q_EMIT testFinished();
+}
diff --git a/autotests/core/CleanOnlineTest.h b/autotests/core/CleanOnlineTest.h
new file mode 100644 (file)
index 0000000..aa7abbb
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+    SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef CLEANONLINETEST_H
+#define CLEANONLINETEST_H
+
+#include <common/test.h>
+
+#include <controller.h>
+
+#include <QScopedPointer>
+
+class CleanOnlineTest : public Test
+{
+    Q_OBJECT
+public:
+    CleanOnlineTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+    void testCleanOnlineActivityListing();
+
+    void cleanupTestCase();
+
+private:
+    QScopedPointer<KActivities::Controller> activities;
+    QString id1;
+    QString id2;
+};
+
+class CleanOnlineSetup : public Test
+{
+    Q_OBJECT
+public:
+    CleanOnlineSetup(QObject *parent = nullptr);
+
+private Q_SLOTS:
+    void testCleanOnlineActivityControl();
+
+    void cleanupTestCase();
+
+private:
+    QScopedPointer<KActivities::Controller> activities;
+
+public:
+    static QString id1;
+    static QString id2;
+};
+
+class OnlineTest : public Test
+{
+    Q_OBJECT
+public:
+    OnlineTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+    void testOnlineActivityListing();
+
+    void cleanupTestCase();
+
+private:
+    QScopedPointer<KActivities::Controller> activities;
+};
+
+#endif /* CLEANONLINETEST_H */
diff --git a/autotests/core/OfflineTest.cpp b/autotests/core/OfflineTest.cpp
new file mode 100644 (file)
index 0000000..f08d493
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+    SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "OfflineTest.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QString>
+#include <QTest>
+
+QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000");
+
+OfflineTest::OfflineTest(QObject *parent)
+    : Test(parent)
+{
+    QCoreApplication::instance()->setProperty("org.kde.KActivities.core.disableAutostart", true);
+
+    activities.reset(new KActivities::Controller());
+}
+
+void OfflineTest::testOfflineActivityListing()
+{
+    // The service is not running
+
+    TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::NotRunning);
+    QCOMPARE(activities->currentActivity(), nulluuid);
+
+    QCOMPARE(activities->activities(), QStringList() << nulluuid);
+    QCOMPARE(activities->activities(KActivities::Info::Running), QStringList() << nulluuid);
+    QCOMPARE(activities->activities(KActivities::Info::Stopped), QStringList());
+}
+
+void OfflineTest::testOfflineActivityControl()
+{
+    continue_future(activities->addActivity(QStringLiteral("Activity")), [](const QString &newid) {
+        QCOMPARE(newid, QString());
+    });
+
+    continue_future(activities->setCurrentActivity(QStringLiteral("Activity")), [](bool success) {
+        QCOMPARE(success, false);
+    });
+
+    // Test whether the responses are immediate
+    static bool inMethod = false;
+
+    inMethod = true;
+
+    continue_future(activities->setActivityName(QStringLiteral("Activity"), QStringLiteral("Activity")), []() {
+        QCOMPARE(inMethod, true);
+    });
+    continue_future(activities->setActivityIcon(QStringLiteral("Activity"), QStringLiteral("Activity")), []() {
+        QCOMPARE(inMethod, true);
+    });
+    continue_future(activities->removeActivity(QStringLiteral("Activity")), []() {
+        QCOMPARE(inMethod, true);
+    });
+    continue_future(activities->startActivity(QStringLiteral("Activity")), []() {
+        QCOMPARE(inMethod, true);
+    });
+    continue_future(activities->stopActivity(QStringLiteral("Activity")), []() {
+        QCOMPARE(inMethod, true);
+    });
+
+    inMethod = false;
+}
+
+void OfflineTest::initTestCase()
+{
+    CHECK_CONDITION(inEmptySession, FailIfFalse);
+    CHECK_CONDITION(isActivityManagerRunning, FailIfTrue);
+}
+
+void OfflineTest::cleanupTestCase()
+{
+    Q_EMIT testFinished();
+}
diff --git a/autotests/core/OfflineTest.h b/autotests/core/OfflineTest.h
new file mode 100644 (file)
index 0000000..89e1689
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef OFFLINETEST_H
+#define OFFLINETEST_H
+
+#include <common/test.h>
+
+#include <controller.h>
+
+#include <QScopedPointer>
+
+class OfflineTest : public Test
+{
+    Q_OBJECT
+public:
+    OfflineTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+    void initTestCase();
+
+    void testOfflineActivityListing();
+    void testOfflineActivityControl();
+
+    void cleanupTestCase();
+
+private:
+    QScopedPointer<KActivities::Controller> activities;
+};
+
+#endif /* OFFLINETEST_H */
diff --git a/autotests/core/Process.cpp b/autotests/core/Process.cpp
new file mode 100644 (file)
index 0000000..d8d7642
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+    SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "Process.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDebug>
+#include <QProcess>
+#include <QRegularExpression>
+#include <QStandardPaths>
+#include <QString>
+#include <QTemporaryDir>
+#include <QTest>
+
+#include <signal.h>
+#include <sys/types.h>
+
+#include "common/dbus/common.h"
+
+namespace Process
+{
+QProcess *Modifier::s_process = nullptr;
+QTemporaryDir *Modifier::s_tempDir = nullptr;
+QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000");
+
+Modifier::Modifier(Action action)
+    : Test()
+    , m_action(action)
+{
+    if (!s_process) {
+        s_process = new QProcess();
+        s_tempDir = new QTemporaryDir();
+
+        if (!s_tempDir->isValid()) {
+            qFatal("Can not create a temporary dir");
+        }
+
+        qDebug() << "Running KAMD in " << s_tempDir->path();
+        s_process->setProcessChannelMode(QProcess::ForwardedChannels);
+    }
+}
+
+void Modifier::initTestCase()
+{
+    const auto state = s_process->state();
+
+    if (state != QProcess::NotRunning && m_action == Start) {
+        qFatal("Already running");
+    }
+
+    switch (m_action) {
+    case Start: {
+        qDebug() << "Starting...";
+
+        QRegularExpression nonxdg(QStringLiteral("^[^X][^D][^G].*$"));
+
+        auto env = QProcessEnvironment::systemEnvironment().toStringList().filter(nonxdg)
+            << QStringLiteral("XDG_DATA_HOME=") + s_tempDir->path() + QStringLiteral("/")
+            << QStringLiteral("XDG_CONFIG_HOME=") + s_tempDir->path() + QStringLiteral("/")
+            << QStringLiteral("XDG_CACHE_HOME=") + s_tempDir->path() + QStringLiteral("/");
+
+        // qDebug() << env;
+
+        s_process->setEnvironment(env);
+        const QString exec = QStandardPaths::findExecutable(QStringLiteral("kactivitymanagerd"));
+        QVERIFY(!exec.isEmpty());
+        s_process->start(exec, QStringList());
+        s_process->waitForStarted();
+
+        break;
+    }
+
+    case Stop:
+    case Kill:
+    case Crash: {
+        qDebug() << "Stopping...";
+
+        const auto dbus = QDBusConnection::sessionBus().interface();
+        const auto kamd = KAMD_DBUS_SERVICE;
+
+        if (!dbus->isServiceRegistered(kamd)) {
+            break;
+        }
+
+        uint pid = dbus->servicePid(kamd);
+        // clang-format off
+            ::kill(pid,
+                    m_action == Stop ? SIGQUIT :
+                    m_action == Kill ? SIGKILL :
+                    /* else */         SIGSEGV
+                    );
+        // clang-format on
+
+        while (Test::isActivityManagerRunning()) {
+            QCoreApplication::processEvents();
+        }
+
+        if (s_process->state() == QProcess::Running) {
+            s_process->terminate();
+            s_process->waitForFinished();
+        }
+
+        break;
+    }
+    }
+}
+
+void Modifier::testProcess()
+{
+    const auto state = s_process->state();
+
+    switch (m_action) {
+    case Start:
+        QCOMPARE(state, QProcess::Running);
+        break;
+
+    case Stop:
+        QCOMPARE(state, QProcess::NotRunning);
+        break;
+
+    case Kill:
+        QCOMPARE(state, QProcess::NotRunning);
+        break;
+
+    case Crash:
+        break;
+    }
+}
+
+void Modifier::cleanupTestCase()
+{
+    Q_EMIT testFinished();
+}
+
+Modifier *exec(Action action)
+{
+    return new Modifier(action);
+}
+
+} // namespace Process
diff --git a/autotests/core/Process.h b/autotests/core/Process.h
new file mode 100644 (file)
index 0000000..dc574fd
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef PROCESS_H
+#define PROCESS_H
+
+#include <common/test.h>
+
+#include <controller.h>
+
+class QProcess;
+class QTemporaryDir;
+
+namespace Process
+{
+enum Action {
+    Start,
+    Stop,
+    Kill,
+    Crash,
+};
+
+class Modifier : public Test
+{
+    Q_OBJECT
+public:
+    Modifier(Action action);
+
+private Q_SLOTS:
+    void initTestCase();
+    void testProcess();
+    void cleanupTestCase();
+
+private:
+    Action m_action;
+    static QProcess *s_process;
+    static QTemporaryDir *s_tempDir;
+};
+
+Modifier *exec(Action action);
+
+} // namespace Process
+
+#endif /* PROCESS_H */
diff --git a/autotests/core/main.cpp b/autotests/core/main.cpp
new file mode 100644 (file)
index 0000000..843a0db
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+    SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <QCoreApplication>
+#include <QList>
+
+#include <common/test.h>
+
+#include "CleanOnlineTest.h"
+#include "OfflineTest.h"
+#include "Process.h"
+
+class TestRunner : public QObject
+{
+public:
+    TestRunner()
+        : m_nextToStart(0)
+    {
+    }
+
+    TestRunner &addTest(Test *test)
+    {
+        if (m_nextToStart == 0) {
+            m_tests << test;
+        }
+        return *this;
+    }
+
+    TestRunner &operator<<(Test *test)
+    {
+        addTest(test);
+        return *this;
+    }
+
+    void start()
+    {
+        if (m_nextToStart) {
+            return;
+        }
+
+        next();
+    }
+
+private:
+    void next()
+    {
+        if (m_nextToStart >= m_tests.size()) {
+            QCoreApplication::exit(0);
+            return;
+        }
+
+        Test *test = m_tests[m_nextToStart++];
+
+        QObject::connect(test, &Test::testFinished, this, &TestRunner::next, Qt::QueuedConnection);
+
+        QTest::qExec(test);
+    }
+
+private:
+    QList<Test *> m_tests;
+    int m_nextToStart;
+};
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    TestRunner &runner = *(new TestRunner());
+
+    (runner << Process::exec(Process::Kill)
+
+            // Running the tests for when the service is offline
+            << new OfflineTest()
+
+            // Running the offline tests again so that we are sure
+            // nothing has changed -- no activities created, changed etc.
+            << new OfflineTest()
+
+            // Starting the manager
+            << Process::exec(Process::Start)
+
+            // Starting the online tests
+            << new CleanOnlineTest() << new CleanOnlineSetup()
+            << new OnlineTest()
+
+            // Starting the manager
+            << Process::exec(Process::Stop)
+
+            << new OfflineTest() << new OfflineTest()
+
+            << Process::exec(Process::Start) << new OnlineTest()
+
+            << Process::exec(Process::Stop)
+
+         )
+        .start();
+
+    return app.exec();
+    // QTest::qExec(&tc, argc, argv);
+}
diff --git a/contrib/bash/next-activity.sh b/contrib/bash/next-activity.sh
new file mode 100755 (executable)
index 0000000..5b0964d
--- /dev/null
@@ -0,0 +1,30 @@
+#! /bin/bash
+#
+# next-activity.sh
+# SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+#
+# SPDX-License-Identifier: MIT
+#
+
+current_activity=($(qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity))
+activities=($(qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities))
+found="0"
+
+for ((i=0; i < ${#activities[@]}; ++i)); do
+    if [ "$current_activity" = "${activities[$i]}" ]; then
+        found="1"
+    else
+        if [ "$found" == "1" ]; then
+            echo "Switching to ${activities[$i]}"
+            qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity ${activities[$i]}
+            found="0"
+        fi
+    fi
+done
+
+if [ "$found" == "1" ]; then
+    echo "Switching to ${activities[0]}"
+    qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity ${activities[0]}
+fi
+
+
diff --git a/contrib/bash/prev-activity.sh b/contrib/bash/prev-activity.sh
new file mode 100755 (executable)
index 0000000..05415df
--- /dev/null
@@ -0,0 +1,30 @@
+#! /bin/bash
+#
+# next-activity.sh
+# SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+#
+# SPDX-License-Identifier: MIT
+#
+
+current_activity=($(qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity))
+activities=($(qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities))
+found="0"
+
+previous_activity=""
+
+for ((i=0; i < ${#activities[@]}; ++i)); do
+    if [ "$current_activity" = "${activities[$i]}" ]; then
+        if [ "$previous_activity" != "" ]; then
+            echo "Switching to $previous_activity"
+            qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity $previous_activity
+            exit
+        else
+            echo "Switching to ${activities[-1]}"
+            qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity ${activities[-1]}
+            exit
+        fi
+    else
+        previous_activity="${activities[$i]}"
+    fi
+done
+
diff --git a/contrib/commit.sh b/contrib/commit.sh
new file mode 100755 (executable)
index 0000000..3377ff3
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+# The script finds build directories for the current
+# src directory and builds them
+#
+# For example, for the source dir:
+#   /some/path/kde/src/project/whatever
+# It finds:
+#   /some/path/kde/build*/project/whatever
+
+current_dir=`pwd`
+
+all_root_dir=`pwd | sed 's#/src/.*##'`
+src_root_dir=$all_root_dir/src
+
+echo "src:   $src_root_dir"
+
+for build_root_dir in $all_root_dir/build*; do
+    echo "building in $build_root_dir"
+
+    cd $current_dir
+    current_dir_log=`OBJ_REPLACEMENT=s#$src_root_dir#$build_root_dir# makeobj`
+    if [ "$?" = "0" ]
+    then
+        echo "... success"
+    else
+        echo "... FAILED"
+        echo $current_dir_log
+        exit
+    fi
+
+done
+
+git commit
+
diff --git a/contrib/run-krazy.sh b/contrib/run-krazy.sh
new file mode 100755 (executable)
index 0000000..1c5635d
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+if [ ! -f "contrib/run-krazy.sh" ];
+then
+    echo "This script needs to be started from KAMD's root directory"
+    exit
+fi
+
+DIRS=$1
+
+if [ ! -n "$1" ];
+then
+    DIRS="lib service utils workspace"
+fi
+
+
+
+echo $DIRS
+CURRENT_DIRECTORY=$PWD
+
+for dir in $DIRS;
+do
+    echo "Running krazy2 on $dir ..."
+    cd $CURRENT_DIRECTORY/src/$dir && krazy2all --exclude license > /tmp/$dir.krazy
+done
diff --git a/contrib/update-todo.hs b/contrib/update-todo.hs
new file mode 100755 (executable)
index 0000000..e012cd4
--- /dev/null
@@ -0,0 +1,115 @@
+#! /usr/bin/env runhaskell
+
+{-# LANGUAGE LambdaCase #-}
+
+import System.Directory (doesFileExist, getDirectoryContents)
+import System.FilePath ((</>))
+
+import Data.String.Utils
+import Data.List
+
+
+-- Util methods
+
+mapSnd :: (a -> b) -> [(c, a)] -> [(c, b)]
+mapSnd f xys = map ( \case (x, y) -> (x, f y) ) xys
+
+mapDir :: (FilePath -> IO ()) -> FilePath -> IO ()
+mapDir proc fp = do
+        isFile <- doesFileExist fp -- is a file of fp
+        if isFile then proc fp -- process the file
+                  else getDirectoryContents fp >>=
+                  mapM_ (mapDir proc . (fp </>)) . filter (`notElem` [".", ".."])
+
+printTitle :: String -> IO()
+printTitle title = do
+        putStrLn ""
+        putStrLn title
+        putStrLn $ map (\_ -> '=') title
+
+main :: IO ()
+main = do
+    printTitle "libKActivities"
+    mapDir process "src/lib/core"
+
+    printTitle "libKActivitiesStats"
+    mapDir process "src/lib/stats"
+
+    printTitle "KActivityManagerD"
+    mapDir process "src/service"
+
+    printTitle "QML imports"
+    mapDir process "src/imports"
+
+    printTitle "Workspace plugins"
+    mapDir process "src/workspace"
+
+    printTitle "Other"
+    mapDir process "src/common"
+    mapDir process "src/utils"
+
+-- Parsing methods
+
+extractBlock :: [String] -> [String]
+extractBlock =
+        takeWhile (startswith "//")
+
+isTodoBlock :: (Integer, [String]) -> Bool
+isTodoBlock (_, block) =
+        (not $ null block) && (
+            (startswith "// TODO: " $ head block) ||
+            (startswith "// FIXME: " $ head block) ||
+            (startswith "// NOTE: " $ head block)
+        )
+
+joinBlock :: [String] -> String
+joinBlock block =
+        unlines $
+        map ( \line ->
+            ( dropWhile (== '/') line )
+        ) $
+        block
+
+
+-- File processing
+
+process :: FilePath -> IO ()
+process filename = do
+        -- Getting the file contents
+        content <- readFile filename
+
+        -- Items with line numbers
+        let items :: [(Integer, [String])]
+            items =
+                zip [1..] $
+                tails $
+                map strip $
+                lines content
+
+        -- Only those starting with TODO
+        let todoBlocks :: [(Integer, [String])]
+            todoBlocks =
+                -- Getting only the comment block
+                mapSnd extractBlock $
+                -- Getting comment blocks that define a todo item
+                filter isTodoBlock $
+                items
+
+        -- Todo items
+        let todoItems :: [(Integer, String)]
+            todoItems =
+                -- Getting the item blocks into actual items
+                mapSnd joinBlock todoBlocks
+
+
+        if (not $ null todoItems)
+            then
+                putStrLn $
+                    concat $
+                    map (\case (lineNo, todoItem) ->
+                                filename ++ ":" ++ (show lineNo) ++ ":\n" ++ todoItem
+                        ) todoItems
+            else
+                return ()
+
+
diff --git a/contrib/zsh/kamd-functions b/contrib/zsh/kamd-functions
new file mode 100644 (file)
index 0000000..6afd3b8
--- /dev/null
@@ -0,0 +1,141 @@
+# Module: KDE Aliases
+# Priority: 10
+
+autoload -U colors
+colors
+
+# Defining aliases for common kamd dbus objects
+alias kamd_dbus="qdbus org.kde.ActivityManager"
+
+alias kamd_activities="qdbus org.kde.ActivityManager /ActivityManager/Activities"
+alias kamd_resources="qdbus org.kde.ActivityManager /ActivityManager/Resources"
+alias kamd_resources_linking="qdbus org.kde.ActivityManager /ActivityManager/Resources/Linking"
+alias kamd_features="qdbus org.kde.ActivityManager /ActivityManager/Features"
+
+alias kamd_addactivity="qdbus org.kde.ActivityManager /ActivityManager/Activities AddActivity"
+alias kamd_removeactivity="qdbus org.kde.ActivityManager /ActivityManager/Activities RemoveActivity"
+
+# Open the KAMD database
+alias kamd_database="sqlite3 ~/.local/share/kactivitymanagerd/resources/database"
+
+# Lists the existing activities, along with their state
+function kamd_listactivities() {
+    echo -n "Service version: "
+    qdbus org.kde.ActivityManager /ActivityManager serviceVersion
+
+    CURRENT_ACTIVITY=`qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity`
+
+    for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities`; do
+
+        STATE=""
+
+        if [ "$CURRENT_ACTIVITY" = "$activity" ]; then
+            STATE="$fg[green][CURRENT]"
+
+        else
+            STATE=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityState $activity`
+
+            case "state$STATE" in
+                state0)
+                    STATE="$fg[red]$bg[black][INVALID]"
+                    ;;
+                state2)
+                    STATE="$fg[blue][RUNNING]"
+                    ;;
+                state3)
+                    STATE="$fg[red]$bg[black][STARTING]"
+                    ;;
+                state4)
+                    STATE="$fg[black][STOPPED]"
+                    ;;
+                state5)
+                    STATE="$fg[red]$bg[black][STOPPING]"
+                    ;;
+            esac
+        fi
+
+        echo -n "$STATE$reset_color $activity "
+
+        ACTIVITY_NAME=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityName $activity`
+        ACTIVITY_DESC=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityDescription $activity`
+        ACTIVITY_ICON=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityIcon $activity`
+
+        if [ -n "$ACTIVITY_DESC" ]; then
+            echo "$ACTIVITY_NAME ($ACTIVITY_DESC, $ACTIVITY_ICON)"
+        else
+            echo "$ACTIVITY_NAME ($ACTIVITY_ICON)"
+        fi
+    done
+}
+
+# Shows the information about the current activity
+function kamd_currentactivity() {
+    for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity`; do
+        STATE=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityState $activity`
+
+        case "state$STATE" in
+            state0)
+                STATE="[INVALID] "
+                ;;
+            state2)
+                STATE="[RUNNING] "
+                ;;
+            state3)
+                STATE="[STARTING]"
+                ;;
+            state4)
+                STATE="[STOPPED] "
+                ;;
+            state5)
+                STATE="[STOPPING]"
+                ;;
+        esac
+
+        echo -n "$STATE $activity "
+        qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityName $activity
+    done
+}
+
+# Returns the current activity ID
+function kamd_get_currentactivity_id() {
+    qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity
+}
+
+# Returns the current activity name
+function kamd_get_currentactivity_name() {
+    CURRENT_ACTIVITY_ID=$(kamd_get_currentactivity_id)
+    qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityName $CURRENT_ACTIVITY_ID
+}
+
+# Returns the current activity name
+function kamd_get_currentactivity_name_normalized() {
+    CURRENT_ACTIVITY_NAME=$(kamd_get_currentactivity_name)
+    echo $CURRENT_ACTIVITY_NAME | tr '[:upper:] ' '[:lower:]-'
+}
+
+# Returns the current activity ID
+function kamd_STOP_ALL_BUT_CURRENT() {
+    CURRENT_ACTIVITY_ID=$(kamd_get_currentactivity_id)
+
+    for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities`; do
+        if [ "$activity" != "$CURRENT_ACTIVITY_ID" ]; then
+            echo "Stop: $activity"
+            qdbus org.kde.ActivityManager /ActivityManager/Activities StopActivity $activity
+            sleep 1
+        fi
+    done
+}
+
+# Returns the current activity ID
+function kamd_REMOVE_ALL_BUT_CURRENT() {
+    CURRENT_ACTIVITY_ID=$(kamd_get_currentactivity_id)
+
+    for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities`; do
+        if [ "$activity" != "$CURRENT_ACTIVITY_ID" ]; then
+            echo "Remove: $activity"
+            qdbus org.kde.ActivityManager /ActivityManager/Activities RemoveActivity $activity
+            sleep 1
+        fi
+    done
+}
+
diff --git a/docs/Doxyfile.local b/docs/Doxyfile.local
new file mode 100644 (file)
index 0000000..1d9d027
--- /dev/null
@@ -0,0 +1 @@
+EXCLUDE_PATTERNS += */service/* */scripts/* */workspace/*
diff --git a/logo.png b/logo.png
new file mode 100644 (file)
index 0000000..7e401e0
Binary files /dev/null and b/logo.png differ
diff --git a/metainfo.yaml b/metainfo.yaml
new file mode 100644 (file)
index 0000000..cfd05bf
--- /dev/null
@@ -0,0 +1,21 @@
+maintainer: ivan
+description: Runtime and library to organize the user work in separate activities
+tier: 2
+type: solution
+platforms:
+    - name: Linux
+    - name: FreeBSD
+    - name: Windows
+    - name: macOS
+      note: Needs QtDBus
+portingAid: false
+deprecated: false
+release: true
+libraries:
+ - qmake: KActivities
+   cmake: "KF5::Activities"
+cmakename: KF5Activities
+
+public_lib: true
+group: Frameworks
+subgroup: Tier 2
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..45aec93
--- /dev/null
@@ -0,0 +1,46 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+# Boosting us a bit
+
+if (NOT KACTIVITIES_LIBRARY_ONLY)
+   find_package (Boost 1.49 REQUIRED)
+
+   string (REGEX MATCH "1053.." BOOST_VERSION_BLACKLISTED ${Boost_VERSION})
+
+   if (BOOST_VERSION_BLACKLISTED AND NOT KACTIVITIES_ENABLE_EXCEPTIONS)
+      message (
+         WARNING
+         "Boost.Container 1.53 has issues when exceptions are disabled. "
+         "We will set the KACTIVITIES_ENABLE_EXCEPTIONS option."
+         )
+      set (KACTIVITIES_ENABLE_EXCEPTIONS ON)
+   endif ()
+endif ()
+
+if (KACTIVITIES_ENABLE_EXCEPTIONS)
+   string (REPLACE "-fno-exceptions" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+   add_definitions (-fexceptions)
+endif ()
+
+# =======================================================
+# Starting the actual project definition
+
+# The libraries do not depend on any compile-time features
+add_subdirectory (lib)
+
+if (NOT KACTIVITIES_LIBRARY_ONLY)
+   include_directories (
+      ${CMAKE_CURRENT_BINARY_DIR}
+      ${CMAKE_CURRENT_SOURCE_DIR}
+      ${Boost_INCLUDE_DIRS}
+      )
+   add_subdirectory (imports)
+endif ()
+
+add_subdirectory (cli)
+
+ecm_qt_install_logging_categories(
+    EXPORT KACTIVITIES
+    FILE kactivities.categories
+    DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
+)
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8296dcb
--- /dev/null
@@ -0,0 +1,40 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesCLI)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+find_package (KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS WindowSystem)
+
+include_directories (
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+   )
+
+set (
+   KActivitiesCLI_SRCS
+   main.cpp
+   )
+
+qt_wrap_ui(
+   KActivitiesCLI_SRCS
+   )
+
+add_executable (
+   kactivities-cli
+   ${KActivitiesCLI_SRCS}
+   )
+
+target_link_libraries (
+   kactivities-cli
+   Qt${QT_MAJOR_VERSION}::Core
+   KF5::Activities
+   )
+
+ecm_mark_nongui_executable(
+   kactivities-cli
+   )
+
+install (TARGETS
+   kactivities-cli
+   ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}
+   )
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
new file mode 100644 (file)
index 0000000..0532015
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+    SPDX-FileCopyrightText: 2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QTimer>
+
+#include <KActivities/Controller>
+
+#include "utils.h"
+
+// Output modifiers
+
+DEFINE_COMMAND(bare, 0)
+{
+    flags.bare = true;
+    return 0;
+}
+
+DEFINE_COMMAND(noBare, 0)
+{
+    flags.bare = false;
+    return 0;
+}
+
+DEFINE_COMMAND(color, 0)
+{
+    flags.color = true;
+    return 0;
+}
+
+DEFINE_COMMAND(noColor, 0)
+{
+    flags.color = false;
+    return 0;
+}
+
+// Activity management
+
+DEFINE_COMMAND(createActivity, 1)
+{
+    auto result = awaitFuture(controller->addActivity(args(1)));
+
+    qDebug().noquote() << result;
+
+    return 1;
+}
+
+DEFINE_COMMAND(removeActivity, 1)
+{
+    awaitFuture(controller->removeActivity(args(1)));
+
+    return 1;
+}
+
+DEFINE_COMMAND(startActivity, 1)
+{
+    awaitFuture(controller->startActivity(args(1)));
+
+    return 1;
+}
+
+DEFINE_COMMAND(stopActivity, 1)
+{
+    awaitFuture(controller->stopActivity(args(1)));
+
+    return 1;
+}
+
+DEFINE_COMMAND(listActivities, 0)
+{
+    for (const auto &activity : controller->activities()) {
+        printActivity(activity);
+    }
+
+    return 0;
+}
+
+DEFINE_COMMAND(currentActivity, 0)
+{
+    printActivity(controller->currentActivity());
+
+    return 0;
+}
+
+DEFINE_COMMAND(setActivityProperty, 3)
+{
+    const auto what = args(1);
+    const auto id = args(2);
+    const auto value = args(3);
+
+    // clang-format off
+    awaitFuture(
+        what == QLatin1String("name")        ? controller->setActivityName(id, value) :
+        what == QLatin1String("description") ? controller->setActivityDescription(id, value) :
+        what == QLatin1String("icon")        ? controller->setActivityIcon(id, value) :
+                                QFuture<void>()
+        );
+    // clang-format on
+
+    return 3;
+}
+
+DEFINE_COMMAND(activityProperty, 2)
+{
+    const auto what = args(1);
+    const auto id = args(2);
+
+    KActivities::Info info(id);
+    // clang-format off
+    out << (
+        what == QLatin1String("name")        ? info.name() :
+        what == QLatin1String("description") ? info.description() :
+        what == QLatin1String("icon")        ? info.icon() :
+                                QString()
+        ) << "\n";
+    // clang-format on
+    return 2;
+}
+
+// Activity switching
+
+DEFINE_COMMAND(setCurrentActivity, 1)
+{
+    switchToActivity(args(1));
+
+    return 1;
+}
+
+DEFINE_COMMAND(nextActivity, 0)
+{
+    controller->nextActivity();
+    return 0;
+}
+
+DEFINE_COMMAND(previousActivity, 0)
+{
+    controller->previousActivity();
+    return 0;
+}
+
+void printHelp()
+{
+    if (!flags.bare) {
+        qDebug() << "\nModifiers (applied only to trailing commands):"
+                 << "\n    --bare, --no-bare        - show minimal info vs show everything"
+                 << "\n    --color, --no-color      - make the output pretty"
+
+                 << "\n\nCommands:"
+                 << "\n    --list-activities        - lists all activities"
+                 << "\n    --create-activity Name   - creates a new activity with the specified name"
+                 << "\n    --remove-activity ID     - removes the activity with the specified id"
+                 << "\n    --start-activity ID      - starts the specified activity"
+                 << "\n    --stop-activity ID       - stops the specified activity"
+
+                 << "\n    --current-activity       - show the current activity"
+                 << "\n    --set-current-activity   - sets the current activity"
+                 << "\n    --next-activity          - switches to the next activity (in list-activities order)"
+                 << "\n    --previous-activity      - switches to the previous activity (in list-activities order)"
+
+                 << "\n    --activity-property What ID"
+                 << "\n                             - gets activity name, icon or description"
+                 << "\n    --set-activity-property What ID Value"
+                 << "\n                             - changes activity name, icon or description";
+
+    } else {
+        qDebug() << "\n--bare"
+                 << "\n--no-bare"
+                 << "\n--color"
+                 << "\n--no-color"
+                 << "\n--list-activities"
+                 << "\n--create-activity NAME"
+                 << "\n--remove-activity ID"
+
+                 << "\n--current-activity"
+                 << "\n--set-current-activity"
+                 << "\n--next-activity"
+                 << "\n--previous-activity";
+    }
+}
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    QTimer::singleShot(0, &app, [] {
+        const auto args = QCoreApplication::arguments();
+
+        controller = new KActivities::Controller();
+
+        while (controller->serviceStatus() != KActivities::Controller::Running) {
+            QCoreApplication::processEvents();
+        }
+
+// clang-format off
+        #define MATCH_COMMAND(Command)                                         \
+            else if (args[argId] == QLatin1String("--") + toDashes(QStringLiteral(#Command))) \
+            {                                                                  \
+                argId += 1 + Command##_command({ args, argId })();             \
+            }
+        // clang-format on
+        if (args.count() <= 1) {
+            printHelp();
+
+        } else {
+            for (int argId = 1; argId < args.count();) {
+                if (args[argId] == QLatin1String("--help")) {
+                    printHelp();
+                    argId++;
+                }
+
+                MATCH_COMMAND(bare)
+                MATCH_COMMAND(noBare)
+                MATCH_COMMAND(color)
+                MATCH_COMMAND(noColor)
+
+                MATCH_COMMAND(listActivities)
+
+                MATCH_COMMAND(currentActivity)
+                MATCH_COMMAND(setCurrentActivity)
+                MATCH_COMMAND(activityProperty)
+                MATCH_COMMAND(setActivityProperty)
+                MATCH_COMMAND(nextActivity)
+                MATCH_COMMAND(previousActivity)
+
+                MATCH_COMMAND(createActivity)
+                MATCH_COMMAND(removeActivity)
+                MATCH_COMMAND(startActivity)
+                MATCH_COMMAND(stopActivity)
+
+                else
+                {
+                    qDebug() << "Skipping unknown argument" << args[argId];
+                    argId++;
+                }
+            }
+        }
+
+        delete controller;
+
+        QCoreApplication::quit();
+    });
+
+    return app.exec();
+}
diff --git a/src/cli/utils.h b/src/cli/utils.h
new file mode 100644 (file)
index 0000000..a1140af
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+    SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KACTIVITIES_UTILS_H
+#define KACTIVITIES_UTILS_H
+
+QTextStream out(stdout);
+
+class StringListView
+{
+public:
+    StringListView(const QStringList &list, int start, int end = -1)
+        : m_list(list)
+        , m_start(start)
+        , m_size((end == -1 ? list.count() : end) - start)
+    {
+    }
+
+    const QString &operator()(int index) const
+    {
+        return m_list[m_start + index];
+    }
+
+    int count() const
+    {
+        return m_size;
+    }
+
+private:
+    const QStringList &m_list;
+    int m_start;
+    int m_size;
+};
+
+KActivities::Controller *controller = nullptr;
+
+class Flags
+{
+public:
+    Flags()
+        : bare(false)
+        , color(true)
+    {
+    }
+
+    bool bare;
+    bool color;
+
+} flags;
+
+QString toDashes(const QString &command)
+{
+    QString result(command);
+
+    for (int i = 0; i < result.size() - 1; ++i) {
+        if (result[i].isLower() && result[i + 1].isUpper()) {
+            result[i + 1] = result[i + 1].toLower();
+            result.insert(i + 1, QStringLiteral("-"));
+        }
+    }
+
+    return result;
+}
+
+void printActivity(const QString &id)
+{
+    // clang-format off
+    if (flags.bare) {
+        out << id << "\n";
+
+    } else {
+        using namespace KActivities;
+        Info info(id);
+
+        out
+            << (
+                info.id() == controller->currentActivity() ? "[CURRENT] " :
+                info.state() == Info::Running    ? "[RUNNING] " :
+                info.state() == Info::Stopped    ? "[STOPPED] " :
+                info.state() == Info::Starting   ? "[STARTING]" :
+                info.state() == Info::Stopping   ? "[STOPPING]" :
+                                                   "unknown   "
+            )
+            << info.id()
+            << " "
+            << info.name()
+            << " ("
+            << info.icon()
+            << ")\n"
+             ;
+
+        if (info.id() == controller->currentActivity()
+            && info.state() != Info::Running) {
+            qWarning()
+                 << "Activity is the current one, but its state is"
+                 << (
+                    info.state() == Info::Running  ? "running"  :
+                    info.state() == Info::Stopped  ? "stopped"  :
+                    info.state() == Info::Starting ? "starting" :
+                    info.state() == Info::Stopping ? "stopping" :
+                                                     "unknown   "
+                 );
+        }
+    }
+    // clang-format on
+}
+
+template<typename T>
+T awaitFuture(const QFuture<T> &future)
+{
+    while (!future.isFinished()) {
+        QCoreApplication::processEvents();
+    }
+
+    return future.result();
+}
+
+void awaitFuture(const QFuture<void> &future)
+{
+    while (!future.isFinished()) {
+        QCoreApplication::processEvents();
+    }
+}
+
+void switchToActivity(const QString &id)
+{
+    auto result = awaitFuture(controller->setCurrentActivity(id));
+
+    if (!flags.bare) {
+        if (result) {
+            qDebug() << "Current activity is" << id;
+        } else {
+            qDebug() << "Failed to change the activity";
+        }
+    }
+}
+
+// clang-format off
+#define DEFINE_COMMAND(Command, MinArgCount)                                   \
+    struct Command##_command {                                                 \
+        const StringListView &args;                                            \
+        Command##_command(const StringListView &args)                          \
+            : args(args)                                                       \
+        {                                                                      \
+            if (args.count() < MinArgCount + 1) {                              \
+                qFatal("not enough arguments for " #Command);                  \
+            }                                                                  \
+        }                                                                      \
+                                                                               \
+        int operator()();                                                      \
+    };                                                                         \
+                                                                               \
+    int Command##_command::operator()()
+
+#endif
+// clang-format on
\ No newline at end of file
diff --git a/src/common/dbus/common.h b/src/common/dbus/common.h
new file mode 100644 (file)
index 0000000..db27b5c
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef DBUS_COMMON_H
+#define DBUS_COMMON_H
+
+#include <QDBusConnection>
+#include <QDBusInterface>
+
+// clang-format off
+
+#define KAMD_DBUS_SERVICE                                                      \
+    QStringLiteral("org.kde.ActivityManager")
+
+#define KAMD_DBUS_OBJECT_PATH(A)                                               \
+    (sizeof(A) > 2 ? QLatin1String("/ActivityManager/" A)                      \
+                   : QLatin1String("/ActivityManager"))
+
+#define KAMD_DBUS_OBJECT(A)                                                    \
+    QLatin1String("org.kde.ActivityManager." A)
+
+#define KAMD_DBUS_INTERFACE(OBJECT_PATH, OBJECT, PARENT)                       \
+    QDBusInterface(KAMD_DBUS_SERVICE,                                          \
+                   KAMD_DBUS_OBJECT_PATH(OBJECT_PATH),                         \
+                   KAMD_DBUS_OBJECT(OBJECT),                                   \
+                   QDBusConnection::sessionBus(),                              \
+                   PARENT)
+
+#define KAMD_DBUS_DECL_INTERFACE(VAR, OBJECT_PATH, OBJECT)                     \
+    QDBusInterface VAR(KAMD_DBUS_SERVICE,                                      \
+                   KAMD_DBUS_OBJECT_PATH(OBJECT_PATH),                         \
+                   KAMD_DBUS_OBJECT(OBJECT),                                   \
+                   QDBusConnection::sessionBus(),                              \
+                   nullptr)
+
+#define KAMD_DBUS_CLASS_INTERFACE(OBJECT_PATH, OBJECT, PARENT)                 \
+    org::kde::ActivityManager::OBJECT(                                         \
+                KAMD_DBUS_SERVICE,                                             \
+                KAMD_DBUS_OBJECT_PATH(OBJECT_PATH),                            \
+                QDBusConnection::sessionBus(),                                 \
+                PARENT)
+
+#endif // DBUS_COMMON_H
+
diff --git a/src/common/dbus/org.kde.ActivityManager.Activities.cpp b/src/common/dbus/org.kde.ActivityManager.Activities.cpp
new file mode 100644 (file)
index 0000000..41c954c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "org.kde.ActivityManager.Activities.h"
+
+#include <QDBusMetaType>
+#include <QMetaType>
+
+namespace details
+{
+class ActivityInfoStaticInit
+{
+public:
+    ActivityInfoStaticInit()
+    {
+        qDBusRegisterMetaType<ActivityInfo>();
+        qDBusRegisterMetaType<ActivityInfoList>();
+    }
+
+    static ActivityInfoStaticInit _instance;
+};
+
+ActivityInfoStaticInit ActivityInfoStaticInit::_instance;
+
+} // namespace details
+
+QDBusArgument &operator<<(QDBusArgument &arg, const ActivityInfo r)
+{
+    arg.beginStructure();
+
+    arg << r.id;
+    arg << r.name;
+    arg << r.description;
+    arg << r.icon;
+    arg << r.state;
+
+    arg.endStructure();
+
+    return arg;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &arg, ActivityInfo &r)
+{
+    arg.beginStructure();
+
+    arg >> r.id;
+    arg >> r.name;
+    arg >> r.description;
+    arg >> r.icon;
+    arg >> r.state;
+
+    arg.endStructure();
+
+    return arg;
+}
+
+QDebug operator<<(QDebug dbg, const ActivityInfo &r)
+{
+    dbg << "ActivityInfo(" << r.id << r.name << ")";
+    return dbg.space();
+}
diff --git a/src/common/dbus/org.kde.ActivityManager.Activities.h b/src/common/dbus/org.kde.ActivityManager.Activities.h
new file mode 100644 (file)
index 0000000..1aeaaf8
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KAMD_ORG_KDE_ACTIVITYMANAGER_ACTIVITIES_H
+#define KAMD_ORG_KDE_ACTIVITYMANAGER_ACTIVITIES_H
+
+#include <QDBusArgument>
+#include <QDebug>
+#include <QList>
+#include <QString>
+
+struct ActivityInfo {
+    QString id;
+    QString name;
+    QString description;
+    QString icon;
+    int state;
+
+    ActivityInfo(const QString &id = QString(),
+                 const QString &name = QString(),
+                 const QString &description = QString(),
+                 const QString &icon = QString(),
+                 int state = 0)
+        : id(id)
+        , name(name)
+        , description(description)
+        , icon(icon)
+        , state(state)
+    {
+    }
+};
+
+typedef QList<ActivityInfo> ActivityInfoList;
+
+Q_DECLARE_METATYPE(ActivityInfo)
+Q_DECLARE_METATYPE(ActivityInfoList)
+
+QDBusArgument &operator<<(QDBusArgument &arg, const ActivityInfo);
+const QDBusArgument &operator>>(const QDBusArgument &arg, ActivityInfo &rec);
+
+QDebug operator<<(QDebug dbg, const ActivityInfo &r);
+
+#endif // KAMD_ORG_KDE_ACTIVITYMANAGER_ACTIVITIES_H
diff --git a/src/common/dbus/org.kde.ActivityManager.Activities.xml b/src/common/dbus/org.kde.ActivityManager.Activities.xml
new file mode 100644 (file)
index 0000000..8d3e074
--- /dev/null
@@ -0,0 +1,115 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.kde.ActivityManager.Activities">
+
+    <method name="CurrentActivity">
+      <arg type="s" direction="out"/>
+    </method>
+    <method name="SetCurrentActivity">
+      <arg type="b" direction="out"/>
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+
+    <method name="AddActivity">
+      <arg type="s" direction="out"/>
+      <arg name="name" type="s" direction="in"/>
+    </method>
+    <method name="StartActivity">
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="StopActivity">
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="ActivityState">
+      <arg type="i" direction="out"/>
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="RemoveActivity">
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+
+    <method name="ListActivities">
+      <arg type="as" direction="out"/>
+    </method>
+    <method name="ListActivities">
+      <arg type="as" direction="out"/>
+      <arg name="state" type="i" direction="in"/>
+    </method>
+
+    <method name="ListActivitiesWithInformation">
+      <arg type="a(sssd)" direction="out"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfoList" />
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfoList" />
+    </method>
+    <method name="ActivityInformation">
+      <arg type="(sssd)" direction="out"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfo" />
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfo" />
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+
+    <method name="ActivityName">
+      <arg type="s" direction="out"/>
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="SetActivityName">
+      <arg name="activity" type="s" direction="in"/>
+      <arg name="name" type="s" direction="in"/>
+    </method>
+
+    <method name="ActivityDescription">
+      <arg type="s" direction="out"/>
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="SetActivityDescription">
+      <arg name="activity" type="s" direction="in"/>
+      <arg name="description" type="s" direction="in"/>
+    </method>
+
+    <method name="ActivityIcon">
+      <arg type="s" direction="out"/>
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="SetActivityIcon">
+      <arg name="activity" type="s" direction="in"/>
+      <arg name="icon" type="s" direction="in"/>
+    </method>
+
+    <signal name="CurrentActivityChanged">
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+
+    <signal name="ActivityAdded">
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityStarted">
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityStopped">
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityRemoved">
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityChanged">
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityNameChanged">
+      <arg name="activity" type="s" direction="out"/>
+      <arg name="name" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityDescriptionChanged">
+      <arg name="activity" type="s" direction="out"/>
+      <arg name="description" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityIconChanged">
+      <arg name="activity" type="s" direction="out"/>
+      <arg name="icon" type="s" direction="out"/>
+    </signal>
+    <signal name="ActivityStateChanged">
+      <arg name="activity" type="s" direction="out"/>
+      <arg name="state" type="i" direction="out"/>
+    </signal>
+
+  </interface>
+</node>
diff --git a/src/common/dbus/org.kde.ActivityManager.Application.xml b/src/common/dbus/org.kde.ActivityManager.Application.xml
new file mode 100644 (file)
index 0000000..fc4a36b
--- /dev/null
@@ -0,0 +1,14 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.kde.ActivityManager.Application">
+    <method name="quit">
+    </method>
+    <method name="serviceVersion">
+      <arg type="s" direction="out"/>
+    </method>
+    <method name="loadPlugin">
+      <arg type="b" direction="out"/>
+      <arg name="plugin" type="s" direction="in"/>
+    </method>
+  </interface>
+</node>
diff --git a/src/common/dbus/org.kde.ActivityManager.Features.xml b/src/common/dbus/org.kde.ActivityManager.Features.xml
new file mode 100644 (file)
index 0000000..e45f046
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.kde.ActivityManager.Features">
+    <method name="IsFeatureOperational">
+      <arg type="b" direction="out"/>
+      <arg name="feature" type="s" direction="in"/>
+    </method>
+    <method name="ListFeatures">
+      <arg type="as" direction="out"/>
+      <arg name="module" type="s" direction="in"/>
+    </method>
+    <method name="GetValue">
+      <arg type="v" direction="out"/>
+      <arg name="property" type="s" direction="in"/>
+    </method>
+    <method name="SetValue">
+      <arg name="property" type="s" direction="in"/>
+      <arg name="value" type="v" direction="in"/>
+    </method>
+  </interface>
+</node>
diff --git a/src/common/dbus/org.kde.ActivityManager.Resources.xml b/src/common/dbus/org.kde.ActivityManager.Resources.xml
new file mode 100644 (file)
index 0000000..5f8e725
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.kde.ActivityManager.Resources">
+
+    <method name="RegisterResourceEvent">
+      <arg name="application" type="s" direction="in"/>
+      <arg name="windowId" type="u" direction="in"/>
+      <arg name="uri" type="s" direction="in"/>
+      <arg name="event" type="u" direction="in"/>
+    </method>
+
+    <method name="RegisterResourceMimetype">
+      <arg name="uri" type="s" direction="in"/>
+      <arg name="mimetype" type="s" direction="in"/>
+    </method>
+
+    <method name="RegisterResourceTitle">
+      <arg name="uri" type="s" direction="in"/>
+      <arg name="title" type="s" direction="in"/>
+    </method>
+
+  </interface>
+</node>
diff --git a/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml b/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml
new file mode 100644 (file)
index 0000000..9d04cdd
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.kde.ActivityManager.ResourcesLinking">
+
+    <method name="LinkResourceToActivity">
+      <arg name="agent" type="s" direction="in"/>
+      <arg name="resource" type="s" direction="in"/>
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="LinkResourceToActivity">
+      <arg name="agent" type="s" direction="in"/>
+      <arg name="resource" type="s" direction="in"/>
+    </method>
+
+    <method name="UnlinkResourceFromActivity">
+      <arg name="agent" type="s" direction="in"/>
+      <arg name="resource" type="s" direction="in"/>
+      <arg name="activity" type="s" direction="in"/>
+    </method>
+    <method name="UnlinkResourceFromActivity">
+      <arg name="agent" type="s" direction="in"/>
+      <arg name="resource" type="s" direction="in"/>
+    </method>
+
+    <method name="IsResourceLinkedToActivity">
+      <arg name="agent" type="s" direction="in"/>
+      <arg name="resource" type="s" direction="in"/>
+      <arg name="activity" type="s" direction="in"/>
+      <arg type="b" direction="out"/>
+    </method>
+    <method name="IsResourceLinkedToActivity">
+      <arg name="agent" type="s" direction="in"/>
+      <arg name="resource" type="s" direction="in"/>
+      <arg type="b" direction="out"/>
+    </method>
+
+    <signal name="ResourceLinkedToActivity">
+      <arg name="agent" type="s" direction="out"/>
+      <arg name="resource" type="s" direction="out"/>
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+    <signal name="ResourceUnlinkedFromActivity">
+      <arg name="agent" type="s" direction="out"/>
+      <arg name="resource" type="s" direction="out"/>
+      <arg name="activity" type="s" direction="out"/>
+    </signal>
+
+  </interface>
+</node>
diff --git a/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml b/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml
new file mode 100644 (file)
index 0000000..0190ff7
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+    <interface name="org.kde.ActivityManager.ResourcesScoring">
+
+        <signal name="ResourceScoreUpdated">
+            <arg name="activity" type="s" direction="out"/>
+            <arg name="client" type="s" direction="out"/>
+            <arg name="resource" type="s" direction="out"/>
+            <arg name="score" type="d" direction="out"/>
+            <arg name="lastUpdate" type="u" direction="out"/>
+            <arg name="firstUpdate" type="u" direction="out"/>
+        </signal>
+        <signal name="ResourceScoreDeleted">
+            <arg name="activity" type="s" direction="out"/>
+            <arg name="client" type="s" direction="out"/>
+            <arg name="resource" type="s" direction="out"/>
+        </signal>
+
+        <signal name="RecentStatsDeleted">
+            <arg name="activity" type="s" direction="out"/>
+            <arg name="count" type="i" direction="out"/>
+            <arg name="what" type="s" direction="out"/>
+        </signal>
+        <signal name="EarlierStatsDeleted">
+            <arg name="activity" type="s" direction="out"/>
+            <arg name="months" type="i" direction="out"/>
+        </signal>
+
+        <method name="DeleteStatsForResource">
+            <arg name="activity" type="s" direction="in"/>
+            <arg name="client" type="s" direction="in"/>
+            <arg name="resource" type="s" direction="in"/>
+        </method>
+        <method name="DeleteRecentStats">
+            <arg name="activity" type="s" direction="in"/>
+            <arg name="count" type="i" direction="in"/>
+            <arg name="what" type="s" direction="in"/>
+        </method>
+        <method name="DeleteEarlierStats">
+            <arg name="activity" type="s" direction="in"/>
+            <arg name="months" type="i" direction="in"/>
+        </method>
+
+    </interface>
+</node>
diff --git a/src/imports/CMakeLists.txt b/src/imports/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ebb01d9
--- /dev/null
@@ -0,0 +1,34 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+project (kactivities-imports)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Gui Qml Quick Sql)
+find_package (KF5Config     ${KF_DEP_VERSION} CONFIG REQUIRED)
+find_package (KF5CoreAddons ${KF_DEP_VERSION} CONFIG REQUIRED)
+
+ecm_add_qml_module(kactivitiesextensionplugin URI "org.kde.activities" VERSION 0.1)
+
+target_sources(kactivitiesextensionplugin PRIVATE
+   activitiesextensionplugin.cpp
+   activitymodel.cpp
+   activityinfo.cpp
+#  resourcemodel.cpp
+   resourceinstance.cpp
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/utils/dbusfuture_p.cpp
+)
+
+target_link_libraries(
+   kactivitiesextensionplugin
+   Qt${QT_MAJOR_VERSION}::Core
+   Qt${QT_MAJOR_VERSION}::DBus
+   Qt${QT_MAJOR_VERSION}::Gui
+   Qt${QT_MAJOR_VERSION}::Qml
+   Qt${QT_MAJOR_VERSION}::Quick
+   Qt${QT_MAJOR_VERSION}::Sql
+   KF5::Activities
+   KF5::ConfigCore
+   KF5::CoreAddons
+)
+
+ecm_finalize_qml_module(kactivitiesextensionplugin DESTINATION ${KDE_INSTALL_QMLDIR})
diff --git a/src/imports/README b/src/imports/README
new file mode 100644 (file)
index 0000000..bb33c28
--- /dev/null
@@ -0,0 +1,7 @@
+org.kde.activities namespace does not guarantee a
+stable api.
+
+If you want to use components from it, make sure
+you follow the further developments closely.
+
+
diff --git a/src/imports/activitiesextensionplugin.cpp b/src/imports/activitiesextensionplugin.cpp
new file mode 100644 (file)
index 0000000..b33660f
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "activitiesextensionplugin.h"
+
+#include "activityinfo.h"
+#include "activitymodel.h"
+#include "resourceinstance.h"
+
+// #include "resourcemodel.h"
+
+// TODO: Clean up unused classes from the imports module
+
+// TODO: Since plasma is now dealing with activity model wallpapers,
+//       replace ActivityModel with the KActivities::ActivitiesModel
+//       (but keep the name)
+
+ActivitiesExtensionPlugin::ActivitiesExtensionPlugin(QObject *parent)
+    : QQmlExtensionPlugin(parent)
+{
+}
+
+void ActivitiesExtensionPlugin::registerTypes(const char *uri)
+{
+    Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.activities"));
+
+    // Used by applets/activitybar
+    qmlRegisterType<KActivities::Imports::ActivityModel>(uri, 0, 1, "ActivityModel");
+
+    qmlRegisterType<KActivities::Imports::ActivityInfo>(uri, 0, 1, "ActivityInfo");
+    qmlRegisterType<KActivities::Imports::ResourceInstance>(uri, 0, 1, "ResourceInstance");
+
+    // This one is removed in favor of KActivities::Stats::ResultModel.
+    // Subclass it, and make it do what you want.
+    // qmlRegisterType<KActivities::Imports::ResourceModel>(uri, 0, 1, "ResourceModel");
+}
diff --git a/src/imports/activitiesextensionplugin.h b/src/imports/activitiesextensionplugin.h
new file mode 100644 (file)
index 0000000..17cc8f4
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+    SPDX-FileCopyrightText: 2011, 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H
+#define KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H
+
+#include <QQmlExtensionPlugin>
+
+class ActivitiesExtensionPlugin : public QQmlExtensionPlugin
+{
+    Q_OBJECT
+    Q_PLUGIN_METADATA(IID "org.kde.activities")
+
+public:
+    explicit ActivitiesExtensionPlugin(QObject *parent = nullptr);
+    void registerTypes(const char *uri) override;
+};
+
+#endif // KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H
diff --git a/src/imports/activityinfo.cpp b/src/imports/activityinfo.cpp
new file mode 100644 (file)
index 0000000..9cce4a4
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "activityinfo.h"
+
+namespace KActivities
+{
+namespace Imports
+{
+ActivityInfo::ActivityInfo(QObject *parent)
+    : QObject(parent)
+    , m_showCurrentActivity(false)
+{
+    connect(&m_service, &KActivities::Controller::currentActivityChanged, this, &ActivityInfo::setCurrentActivity);
+}
+
+ActivityInfo::~ActivityInfo()
+{
+}
+
+void ActivityInfo::setCurrentActivity(const QString &id)
+{
+    if (!m_showCurrentActivity) {
+        return;
+    }
+
+    setIdInternal(id);
+
+    Q_EMIT nameChanged(m_info->name());
+    Q_EMIT descriptionChanged(m_info->description());
+    Q_EMIT iconChanged(m_info->icon());
+}
+
+void ActivityInfo::setActivityId(const QString &id)
+{
+    m_showCurrentActivity = (id == QLatin1String(":current"));
+
+    setIdInternal(m_showCurrentActivity ? m_service.currentActivity() : id);
+}
+
+void ActivityInfo::setIdInternal(const QString &id)
+{
+    using namespace KActivities;
+
+    // We are killing the old info object, if any
+    m_info.reset(new KActivities::Info(id));
+
+    auto ptr = m_info.get();
+
+    connect(ptr, &Info::nameChanged, this, &ActivityInfo::nameChanged);
+    connect(ptr, &Info::descriptionChanged, this, &ActivityInfo::descriptionChanged);
+    connect(ptr, &Info::iconChanged, this, &ActivityInfo::iconChanged);
+}
+// clang-format off
+#define CREATE_GETTER_AND_SETTER(WHAT, What)                                   \
+    QString ActivityInfo::What() const                                         \
+    {                                                                          \
+        return m_info ? m_info->What() : QString();                            \
+    }                                                                          \
+                                                                               \
+    void ActivityInfo::set##WHAT(const QString &value)                         \
+    {                                                                          \
+        if (!m_info)                                                           \
+            return;                                                            \
+                                                                               \
+        m_service.setActivity##WHAT(m_info->id(), value);                      \
+    }
+// clang-format on
+
+CREATE_GETTER_AND_SETTER(Name, name)
+CREATE_GETTER_AND_SETTER(Description, description)
+CREATE_GETTER_AND_SETTER(Icon, icon)
+
+#undef CREATE_GETTER_AND_SETTER
+
+QString ActivityInfo::activityId() const
+{
+    return m_info ? m_info->id() : QString();
+}
+
+bool ActivityInfo::valid() const
+{
+    return true;
+}
+
+} // namespace Imports
+} // namespace KActivities
+
+// #include "activityinfo.moc"
diff --git a/src/imports/activityinfo.h b/src/imports/activityinfo.h
new file mode 100644 (file)
index 0000000..ff4a919
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_IMPORTS_ACTIVITY_INFO_H
+#define KACTIVITIES_IMPORTS_ACTIVITY_INFO_H
+
+// Qt
+#include <QObject>
+
+// STL
+#include <memory>
+
+// Local
+#include <lib/controller.h>
+#include <lib/info.h>
+
+namespace KActivities
+{
+namespace Imports
+{
+/**
+ * ActivityInfo
+ */
+
+class ActivityInfo : public QObject
+{
+    Q_OBJECT
+
+    /**
+     * Unique identifier of the activity
+     */
+    Q_PROPERTY(QString activityId READ activityId WRITE setActivityId NOTIFY activityIdChanged)
+
+    /**
+     * Name of the activity
+     */
+    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+
+    /**
+     * Name of the activity
+     */
+    Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
+
+    /**
+     * Activity icon
+     */
+    Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged)
+
+    /**
+     * Is the activity a valid one - does it exist?
+     */
+    Q_PROPERTY(bool valid READ valid NOTIFY validChanged)
+
+public:
+    explicit ActivityInfo(QObject *parent = nullptr);
+    ~ActivityInfo() override;
+
+public Q_SLOTS:
+    void setActivityId(const QString &id);
+    QString activityId() const;
+
+    void setName(const QString &name);
+    QString name() const;
+
+    void setDescription(const QString &description);
+    QString description() const;
+
+    void setIcon(const QString &icon);
+    QString icon() const;
+
+    bool valid() const;
+
+Q_SIGNALS:
+    void activityIdChanged(const QString &id);
+    void nameChanged(const QString &name);
+    void descriptionChanged(const QString &description);
+    void iconChanged(const QString &icon);
+    void validChanged(bool valid);
+
+private Q_SLOTS:
+    void setCurrentActivity(const QString &id);
+
+private:
+    void setIdInternal(const QString &id);
+
+    KActivities::Controller m_service;
+    std::unique_ptr<KActivities::Info> m_info;
+    bool m_showCurrentActivity;
+};
+
+} // namespace Imports
+} // namespace KActivities
+
+#endif // KACTIVITIES_IMPORTS_ACTIVITY_INFO_H
diff --git a/src/imports/activitymodel.cpp b/src/imports/activitymodel.cpp
new file mode 100644 (file)
index 0000000..930a459
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "activitymodel.h"
+
+// Qt
+#include <QByteArray>
+#include <QDBusPendingCall>
+#include <QDBusPendingCallWatcher>
+#include <QDebug>
+#include <QFutureWatcher>
+#include <QHash>
+#include <QIcon>
+#include <QList>
+#include <QModelIndex>
+
+// KDE
+#include <KConfig>
+#include <KConfigGroup>
+#include <KDirWatch>
+
+// Boost
+#include <boost/optional.hpp>
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/algorithm/binary_search.hpp>
+#include <boost/range/algorithm/find_if.hpp>
+
+// Local
+#include "utils/remove_if.h"
+#define ENABLE_QJSVALUE_CONTINUATION
+#include "utils/continue_with.h"
+#include "utils/model_updaters.h"
+
+using kamd::utils::continue_with;
+
+namespace KActivities
+{
+namespace Imports
+{
+class ActivityModel::Private
+{
+public:
+    DECLARE_RAII_MODEL_UPDATERS(ActivityModel)
+
+    /**
+     * Returns whether the activity has a desired state.
+     * If the state is 0, returns true
+     */
+    template<typename T>
+    static inline bool matchingState(InfoPtr activity, T states)
+    {
+        // Are we filtering activities on their states?
+        if (!states.empty() && !boost::binary_search(states, activity->state())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Searches for the activity.
+     * Returns an option(index, iterator) for the found activity.
+     */
+    template<typename _Container>
+    static inline boost::optional<std::pair<unsigned int, typename _Container::const_iterator>> activityPosition(const _Container &container,
+                                                                                                                 const QString &activityId)
+    {
+        using ActivityPosition = decltype(activityPosition(container, activityId));
+        using ContainerElement = typename _Container::value_type;
+
+        auto position = boost::find_if(container, [&](const ContainerElement &activity) {
+            return activity->id() == activityId;
+        });
+
+        return (position != container.end()) ? ActivityPosition(std::make_pair(position - container.begin(), position)) : ActivityPosition();
+    }
+
+    /**
+     * Notifies the model that an activity was updated
+     */
+    template<typename _Model, typename _Container>
+    static inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role)
+    {
+        const auto activity = static_cast<Info *>(activityInfo);
+        emitActivityUpdated(model, container, activity->id(), role);
+    }
+
+    /**
+     * Notifies the model that an activity was updated
+     */
+    template<typename _Model, typename _Container>
+    static inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role)
+    {
+        auto position = Private::activityPosition(container, activity);
+
+        if (position) {
+            Q_EMIT model->dataChanged(model->index(position->first),
+                                      model->index(position->first),
+                                      role == Qt::DecorationRole ? QVector<int>{role, ActivityModel::ActivityIcon} : QVector<int>{role});
+        }
+    }
+
+    class BackgroundCache
+    {
+    public:
+        BackgroundCache()
+            : initialized(false)
+            , plasmaConfig(QStringLiteral("plasma-org.kde.plasma.desktop-appletsrc"))
+        {
+            using namespace std::placeholders;
+
+            const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + plasmaConfig.name();
+
+            KDirWatch::self()->addFile(configFile);
+
+            connect(KDirWatch::self(), &KDirWatch::dirty, std::bind(&BackgroundCache::settingsFileChanged, this, _1));
+            connect(KDirWatch::self(), &KDirWatch::created, std::bind(&BackgroundCache::settingsFileChanged, this, _1));
+        }
+
+        void settingsFileChanged(const QString &file)
+        {
+            if (!file.endsWith(plasmaConfig.name())) {
+                return;
+            }
+
+            plasmaConfig.reparseConfiguration();
+
+            if (initialized) {
+                reload(false);
+            }
+        }
+
+        void subscribe(ActivityModel *model)
+        {
+            if (!initialized) {
+                reload(true);
+            }
+
+            models << model;
+        }
+
+        void unsubscribe(ActivityModel *model)
+        {
+            models.removeAll(model);
+
+            if (models.isEmpty()) {
+                initialized = false;
+                forActivity.clear();
+            }
+        }
+
+        QString backgroundFromConfig(const KConfigGroup &config) const
+        {
+            auto wallpaperPlugin = config.readEntry("wallpaperplugin");
+            auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General");
+
+            if (wallpaperConfig.hasKey("Image")) {
+                // Trying for the wallpaper
+                auto wallpaper = wallpaperConfig.readEntry("Image", QString());
+                if (!wallpaper.isEmpty()) {
+                    return wallpaper;
+                }
+            }
+            if (wallpaperConfig.hasKey("Color")) {
+                auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0));
+                return backgroundColor.name();
+            }
+
+            return QString();
+        }
+
+        void reload(bool fullReload)
+        {
+            QHash<QString, QString> newBackgrounds;
+
+            if (fullReload) {
+                forActivity.clear();
+            }
+
+            QStringList changedBackgrounds;
+
+            for (const auto &cont : plasmaConfigContainments().groupList()) {
+                auto config = plasmaConfigContainments().group(cont);
+                auto activityId = config.readEntry("activityId", QString());
+
+                // Ignore if it has no assigned activity
+                if (activityId.isEmpty()) {
+                    continue;
+                }
+
+                // Ignore if we have already found the background
+                if (newBackgrounds.contains(activityId) && newBackgrounds[activityId][0] != QLatin1Char('#')) {
+                    continue;
+                }
+
+                auto newBackground = backgroundFromConfig(config);
+
+                if (forActivity[activityId] != newBackground) {
+                    changedBackgrounds << activityId;
+                    if (!newBackground.isEmpty()) {
+                        newBackgrounds[activityId] = newBackground;
+                    }
+                }
+            }
+
+            initialized = true;
+
+            if (!changedBackgrounds.isEmpty()) {
+                forActivity = newBackgrounds;
+
+                for (auto model : models) {
+                    model->backgroundsUpdated(changedBackgrounds);
+                }
+            }
+        }
+
+        KConfigGroup plasmaConfigContainments()
+        {
+            return plasmaConfig.group("Containments");
+        }
+
+        QHash<QString, QString> forActivity;
+        QList<ActivityModel *> models;
+
+        bool initialized;
+        KConfig plasmaConfig;
+    };
+
+    static BackgroundCache &backgrounds()
+    {
+        // If you convert this to a shared pointer,
+        // fix the connections to KDirWatcher
+        static BackgroundCache cache;
+        return cache;
+    }
+};
+
+ActivityModel::ActivityModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+    // Initializing role names for qml
+    connect(&m_service, &Consumer::serviceStatusChanged, this, &ActivityModel::setServiceStatus);
+
+    connect(&m_service, &KActivities::Consumer::activityAdded, this, [this](const QString &id) {
+        onActivityAdded(id);
+    });
+    connect(&m_service, &KActivities::Consumer::activityRemoved, this, &ActivityModel::onActivityRemoved);
+    connect(&m_service, &KActivities::Consumer::currentActivityChanged, this, &ActivityModel::onCurrentActivityChanged);
+
+    setServiceStatus(m_service.serviceStatus());
+
+    Private::backgrounds().subscribe(this);
+}
+
+ActivityModel::~ActivityModel()
+{
+    Private::backgrounds().unsubscribe(this);
+}
+
+QHash<int, QByteArray> ActivityModel::roleNames() const
+{
+    return {{Qt::DisplayRole, "name"},
+            {Qt::DecorationRole, "icon"},
+
+            {ActivityState, "state"},
+            {ActivityId, "id"},
+            {ActivityIcon, "iconSource"},
+            {ActivityDescription, "description"},
+            {ActivityBackground, "background"},
+            {ActivityCurrent, "current"}};
+}
+
+void ActivityModel::setServiceStatus(Consumer::ServiceStatus)
+{
+    replaceActivities(m_service.activities());
+}
+
+void ActivityModel::replaceActivities(const QStringList &activities)
+{
+    // qDebug() << m_shownStatesString << "New list of activities: "
+    //          << activities;
+    // qDebug() << m_shownStatesString << " -- RESET MODEL -- ";
+
+    Private::model_reset m(this);
+
+    m_knownActivities.clear();
+    m_shownActivities.clear();
+
+    for (const QString &activity : activities) {
+        onActivityAdded(activity, false);
+    }
+}
+
+void ActivityModel::onActivityAdded(const QString &id, bool notifyClients)
+{
+    auto info = registerActivity(id);
+
+    // qDebug() << m_shownStatesString << "Added a new activity:" << info->id()
+    //          << " " << info->name();
+
+    showActivity(info, notifyClients);
+}
+
+void ActivityModel::onActivityRemoved(const QString &id)
+{
+    // qDebug() << m_shownStatesString << "Removed an activity:" << id;
+
+    hideActivity(id);
+    unregisterActivity(id);
+}
+
+void ActivityModel::onCurrentActivityChanged(const QString &id)
+{
+    Q_UNUSED(id);
+
+    for (const auto &activity : m_shownActivities) {
+        Private::emitActivityUpdated(this, m_shownActivities, activity->id(), ActivityCurrent);
+    }
+}
+
+ActivityModel::InfoPtr ActivityModel::registerActivity(const QString &id)
+{
+    auto position = Private::activityPosition(m_knownActivities, id);
+
+    // qDebug() << m_shownStatesString << "Registering activity: " << id
+    //          << " new? not " << (bool)position;
+
+    if (position) {
+        return *(position->second);
+
+    } else {
+        auto activityInfo = std::make_shared<Info>(id);
+
+        auto ptr = activityInfo.get();
+
+        connect(ptr, &Info::nameChanged, this, &ActivityModel::onActivityNameChanged);
+        connect(ptr, &Info::descriptionChanged, this, &ActivityModel::onActivityDescriptionChanged);
+        connect(ptr, &Info::iconChanged, this, &ActivityModel::onActivityIconChanged);
+        connect(ptr, &Info::stateChanged, this, &ActivityModel::onActivityStateChanged);
+
+        m_knownActivities.insert(InfoPtr(activityInfo));
+
+        return activityInfo;
+    }
+}
+
+void ActivityModel::unregisterActivity(const QString &id)
+{
+    // qDebug() << m_shownStatesString << "Deregistering activity: " << id;
+
+    auto position = Private::activityPosition(m_knownActivities, id);
+
+    if (position) {
+        if (auto shown = Private::activityPosition(m_shownActivities, id)) {
+            Private::model_remove(this, QModelIndex(), shown->first, shown->first);
+            m_shownActivities.erase(shown->second);
+        }
+
+        m_knownActivities.erase(position->second);
+    }
+}
+
+void ActivityModel::showActivity(InfoPtr activityInfo, bool notifyClients)
+{
+    // Should it really be shown?
+    if (!Private::matchingState(activityInfo, m_shownStates)) {
+        return;
+    }
+
+    // Is it already shown?
+    if (boost::binary_search(m_shownActivities, activityInfo, InfoPtrComparator())) {
+        return;
+    }
+
+    auto registeredPosition = Private::activityPosition(m_knownActivities, activityInfo->id());
+
+    if (!registeredPosition) {
+        qDebug() << "Got a request to show an unknown activity, ignoring";
+        return;
+    }
+
+    auto activityInfoPtr = *(registeredPosition->second);
+
+    // qDebug() << m_shownStatesString << "Setting activity visibility to true:"
+    //     << activityInfoPtr->id() << activityInfoPtr->name();
+
+    auto position = m_shownActivities.insert(activityInfoPtr);
+
+    if (notifyClients) {
+        unsigned int index = (position.second ? position.first : m_shownActivities.end()) - m_shownActivities.begin();
+
+        // qDebug() << m_shownStatesString << " -- MODEL INSERT -- " << index;
+        Private::model_insert(this, QModelIndex(), index, index);
+    }
+}
+
+void ActivityModel::hideActivity(const QString &id)
+{
+    auto position = Private::activityPosition(m_shownActivities, id);
+
+    // qDebug() << m_shownStatesString
+    //          << "Setting activity visibility to false: " << id;
+
+    if (position) {
+        // qDebug() << m_shownStatesString << " -- MODEL REMOVE -- "
+        //          << position->first;
+        Private::model_remove(this, QModelIndex(), position->first, position->first);
+        m_shownActivities.erase(position->second);
+    }
+}
+// clang-format off
+#define CREATE_SIGNAL_EMITTER(What,Role)                                      \
+    void ActivityModel::onActivity##What##Changed(const QString &)             \
+    {                                                                          \
+        Private::emitActivityUpdated(this, m_shownActivities, sender(), Role); \
+    }
+// clang-format on
+
+CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole)
+CREATE_SIGNAL_EMITTER(Description, ActivityDescription)
+CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole)
+
+#undef CREATE_SIGNAL_EMITTER
+
+void ActivityModel::onActivityStateChanged(Info::State state)
+{
+    if (m_shownStates.empty()) {
+        Private::emitActivityUpdated(this, m_shownActivities, sender(), ActivityState);
+
+    } else {
+        auto info = findActivity(sender());
+
+        if (!info) {
+            return;
+        }
+
+        if (boost::binary_search(m_shownStates, state)) {
+            showActivity(info, true);
+        } else {
+            hideActivity(info->id());
+        }
+    }
+}
+
+void ActivityModel::backgroundsUpdated(const QStringList &activities)
+{
+    for (const auto &activity : activities) {
+        Private::emitActivityUpdated(this, m_shownActivities, activity, ActivityBackground);
+    }
+}
+
+void ActivityModel::setShownStates(const QString &states)
+{
+    m_shownStates.clear();
+    m_shownStatesString = states;
+
+    for (const auto &state : states.split(QLatin1Char(','))) {
+        if (state == QLatin1String("Running")) {
+            m_shownStates.insert(Running);
+
+        } else if (state == QLatin1String("Starting")) {
+            m_shownStates.insert(Starting);
+
+        } else if (state == QLatin1String("Stopped")) {
+            m_shownStates.insert(Stopped);
+
+        } else if (state == QLatin1String("Stopping")) {
+            m_shownStates.insert(Stopping);
+        }
+    }
+
+    replaceActivities(m_service.activities());
+
+    Q_EMIT shownStatesChanged(states);
+}
+
+QString ActivityModel::shownStates() const
+{
+    return m_shownStatesString;
+}
+
+int ActivityModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+
+    return m_shownActivities.size();
+}
+
+QVariant ActivityModel::data(const QModelIndex &index, int role) const
+{
+    const int row = index.row();
+    const auto &item = *(m_shownActivities.cbegin() + row);
+
+    switch (role) {
+    case Qt::DisplayRole:
+        return item->name();
+
+    case Qt::DecorationRole:
+        return QIcon::fromTheme(data(index, ActivityIcon).toString());
+
+    case ActivityId:
+        return item->id();
+
+    case ActivityState:
+        return item->state();
+
+    case ActivityIcon: {
+        const QString &icon = item->icon();
+
+        // We need a default icon for activities
+        return icon.isEmpty() ? QStringLiteral("activities") : icon;
+    }
+
+    case ActivityDescription:
+        return item->description();
+
+    case ActivityCurrent:
+        return m_service.currentActivity() == item->id();
+
+    case ActivityBackground:
+        return Private::backgrounds().forActivity[item->id()];
+
+    default:
+        return QVariant();
+    }
+}
+
+QVariant ActivityModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    Q_UNUSED(section);
+    Q_UNUSED(orientation);
+    Q_UNUSED(role);
+
+    return QVariant();
+}
+
+ActivityModel::InfoPtr ActivityModel::findActivity(QObject *ptr) const
+{
+    auto info = boost::find_if(m_knownActivities, [ptr](const InfoPtr &info) {
+        return ptr == info.get();
+    });
+
+    if (info == m_knownActivities.end()) {
+        return nullptr;
+    } else {
+        return *info;
+    }
+}
+
+// clang-format off
+// QFuture<void> Controller::setActivityWhat(id, value)
+#define CREATE_SETTER(What)                                                    \
+    void ActivityModel::setActivity##What(                                     \
+        const QString &id, const QString &value, const QJSValue &callback)     \
+    {                                                                          \
+        continue_with(m_service.setActivity##What(id, value), callback);       \
+    }
+// clang-format on
+
+CREATE_SETTER(Name)
+CREATE_SETTER(Description)
+CREATE_SETTER(Icon)
+
+#undef CREATE_SETTER
+
+// QFuture<bool> Controller::setCurrentActivity(id)
+void ActivityModel::setCurrentActivity(const QString &id, const QJSValue &callback)
+{
+    continue_with(m_service.setCurrentActivity(id), callback);
+}
+
+// QFuture<QString> Controller::addActivity(name)
+void ActivityModel::addActivity(const QString &name, const QJSValue &callback)
+{
+    continue_with(m_service.addActivity(name), callback);
+}
+
+// QFuture<void> Controller::removeActivity(id)
+void ActivityModel::removeActivity(const QString &id, const QJSValue &callback)
+{
+    continue_with(m_service.removeActivity(id), callback);
+}
+
+// QFuture<void> Controller::stopActivity(id)
+void ActivityModel::stopActivity(const QString &id, const QJSValue &callback)
+{
+    continue_with(m_service.stopActivity(id), callback);
+}
+
+// QFuture<void> Controller::startActivity(id)
+void ActivityModel::startActivity(const QString &id, const QJSValue &callback)
+{
+    continue_with(m_service.startActivity(id), callback);
+}
+
+} // namespace Imports
+} // namespace KActivities
+
+// #include "activitymodel.moc"
diff --git a/src/imports/activitymodel.h b/src/imports/activitymodel.h
new file mode 100644 (file)
index 0000000..ff62e3e
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H
+#define KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H
+
+// Qt
+#include <QAbstractListModel>
+#include <QCollator>
+#include <QJSValue>
+#include <QObject>
+
+// STL and Boost
+#include <boost/container/flat_set.hpp>
+#include <memory>
+
+// Local
+#include <lib/consumer.h>
+#include <lib/controller.h>
+#include <lib/info.h>
+
+class QModelIndex;
+class QDBusPendingCallWatcher;
+
+namespace KActivities
+{
+namespace Imports
+{
+/**
+ * ActivityModel
+ */
+
+class ActivityModel : public QAbstractListModel
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QString shownStates READ shownStates WRITE setShownStates NOTIFY shownStatesChanged)
+
+public:
+    explicit ActivityModel(QObject *parent = nullptr);
+    ~ActivityModel() override;
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+    QHash<int, QByteArray> roleNames() const override;
+
+    enum Roles {
+        ActivityId = Qt::UserRole,
+        ActivityDescription = Qt::UserRole + 1,
+        ActivityIcon = Qt::UserRole + 2,
+        ActivityState = Qt::UserRole + 3,
+        ActivityBackground = Qt::UserRole + 4,
+        ActivityCurrent = Qt::UserRole + 5,
+    };
+
+    enum State {
+        All = 0,
+        Invalid = 0,
+        Running = 2,
+        Starting = 3,
+        Stopped = 4,
+        Stopping = 5,
+    };
+    Q_ENUM(State)
+
+public Q_SLOTS:
+    // Activity control methods
+    void setActivityName(const QString &id, const QString &name, const QJSValue &callback);
+    void setActivityDescription(const QString &id, const QString &description, const QJSValue &callback);
+    void setActivityIcon(const QString &id, const QString &icon, const QJSValue &callback);
+
+    void setCurrentActivity(const QString &id, const QJSValue &callback);
+
+    void addActivity(const QString &name, const QJSValue &callback);
+    void removeActivity(const QString &id, const QJSValue &callback);
+
+    void stopActivity(const QString &id, const QJSValue &callback);
+    void startActivity(const QString &id, const QJSValue &callback);
+
+    // Model property getters and setters
+    void setShownStates(const QString &states);
+    QString shownStates() const;
+
+Q_SIGNALS:
+    void shownStatesChanged(const QString &state);
+
+private Q_SLOTS:
+    void onActivityNameChanged(const QString &name);
+    void onActivityDescriptionChanged(const QString &description);
+    void onActivityIconChanged(const QString &icon);
+    void onActivityStateChanged(KActivities::Info::State state);
+
+    void replaceActivities(const QStringList &activities);
+    void onActivityAdded(const QString &id, bool notifyClients = true);
+    void onActivityRemoved(const QString &id);
+    void onCurrentActivityChanged(const QString &id);
+
+    void setServiceStatus(KActivities::Consumer::ServiceStatus status);
+
+private:
+    KActivities::Controller m_service;
+    boost::container::flat_set<State> m_shownStates;
+    QString m_shownStatesString;
+
+    typedef std::shared_ptr<Info> InfoPtr;
+
+    struct InfoPtrComparator {
+        bool operator()(const InfoPtr &left, const InfoPtr &right) const
+        {
+            QCollator c;
+            c.setCaseSensitivity(Qt::CaseInsensitive);
+            c.setNumericMode(true);
+            int rc = c.compare(left->name(), right->name());
+            if (rc == 0) {
+                return left->id() < right->id();
+            }
+            return rc < 0;
+        }
+    };
+
+    boost::container::flat_set<InfoPtr, InfoPtrComparator> m_knownActivities;
+    boost::container::flat_set<InfoPtr, InfoPtrComparator> m_shownActivities;
+
+    InfoPtr registerActivity(const QString &id);
+    void unregisterActivity(const QString &id);
+    void showActivity(InfoPtr activityInfo, bool notifyClients);
+    void hideActivity(const QString &id);
+    void backgroundsUpdated(const QStringList &activities);
+
+    InfoPtr findActivity(QObject *ptr) const;
+
+    class Private;
+    friend class Private;
+};
+
+} // namespace Imports
+} // namespace KActivities
+
+#endif // KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H
diff --git a/src/imports/resourceinstance.cpp b/src/imports/resourceinstance.cpp
new file mode 100644 (file)
index 0000000..d88768f
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+    SPDX-FileCopyrightText: 2011-2015 Marco Martin <mart@kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "resourceinstance.h"
+
+#include <QQuickWindow>
+#include <QTimer>
+
+#include <KActivities/ResourceInstance>
+#include <QDebug>
+
+namespace KActivities
+{
+namespace Imports
+{
+ResourceInstance::ResourceInstance(QQuickItem *parent)
+    : QQuickItem(parent)
+{
+    m_syncTimer = new QTimer(this);
+    m_syncTimer->setSingleShot(true);
+    connect(m_syncTimer, &QTimer::timeout, this, &ResourceInstance::syncWid);
+}
+
+ResourceInstance::~ResourceInstance()
+{
+}
+
+void ResourceInstance::syncWid()
+{
+    QWindow *w = window();
+    if (!w) {
+        return;
+    }
+
+    WId wid = w->winId();
+    if (!m_resourceInstance || m_resourceInstance->winId() != wid) {
+        // qDebug() << "Creating a new instance of the resource" << m_uri << "window id" << wid;
+        m_resourceInstance.reset(new KActivities::ResourceInstance(wid, m_uri, m_mimetype, m_title));
+    } else {
+        m_resourceInstance->setUri(m_uri);
+        m_resourceInstance->setMimetype(m_mimetype);
+        m_resourceInstance->setTitle(m_title);
+    }
+}
+
+QUrl ResourceInstance::uri() const
+{
+    return m_uri;
+}
+
+void ResourceInstance::setUri(const QUrl &uri)
+{
+    if (m_uri == uri) {
+        return;
+    }
+
+    m_uri = uri.adjusted(QUrl::StripTrailingSlash);
+    m_syncTimer->start(100);
+}
+
+QString ResourceInstance::mimetype() const
+{
+    return m_mimetype;
+}
+
+void ResourceInstance::setMimetype(const QString &mimetype)
+{
+    if (m_mimetype == mimetype) {
+        return;
+    }
+    m_mimetype = mimetype;
+    m_syncTimer->start(100);
+}
+
+QString ResourceInstance::title() const
+{
+    return m_title;
+}
+
+void ResourceInstance::setTitle(const QString &title)
+{
+    if (m_title == title) {
+        return;
+    }
+    m_title = title;
+    m_syncTimer->start(100);
+}
+
+void ResourceInstance::notifyModified()
+{
+    // ensure the resource instance exists
+    syncWid();
+    m_resourceInstance->notifyModified();
+}
+
+void ResourceInstance::notifyFocusedIn()
+{
+    // ensure the resource instance exists
+    syncWid();
+    m_resourceInstance->notifyFocusedIn();
+}
+
+void ResourceInstance::notifyFocusedOut()
+{
+    // ensure the resource instance exists
+    syncWid();
+    m_resourceInstance->notifyFocusedOut();
+}
+
+}
+}
diff --git a/src/imports/resourceinstance.h b/src/imports/resourceinstance.h
new file mode 100644 (file)
index 0000000..bd59212
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+    SPDX-FileCopyrightText: 2011-2015 Marco Martin <mart@kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef RESOURCEINSTANCE_H
+#define RESOURCEINSTANCE_H
+
+// Qt
+#include <QQuickItem>
+#include <QUrl>
+
+// STL
+#include <memory>
+
+namespace KActivities
+{
+class ResourceInstance;
+}
+
+class QTimer;
+
+namespace KActivities
+{
+namespace Imports
+{
+class ResourceInstance : public QQuickItem
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QUrl uri READ uri WRITE setUri NOTIFY uriChanged)
+    Q_PROPERTY(QString mimetype READ mimetype WRITE setMimetype NOTIFY mimetypeChanged)
+    Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
+
+public:
+    explicit ResourceInstance(QQuickItem *parent = nullptr);
+    ~ResourceInstance() override;
+
+    QUrl uri() const;
+    void setUri(const QUrl &uri);
+
+    QString mimetype() const;
+    void setMimetype(const QString &mimetype);
+
+    QString title() const;
+    void setTitle(const QString &title);
+
+protected Q_SLOTS:
+    void syncWid();
+
+Q_SIGNALS:
+    void uriChanged();
+    void mimetypeChanged();
+    void titleChanged();
+
+public Q_SLOTS:
+    /**
+     * Call this method to notify the system that you modified
+     * (the contents of) the resource
+     */
+    void notifyModified();
+
+    /**
+     * Call this method to notify the system that the resource
+     * has the focus in your application
+     * @note You only need to call this in MDI applications
+     */
+    void notifyFocusedIn();
+
+    /**
+     * Call this method to notify the system that the resource
+     * lost the focus in your application
+     * @note You only need to call this in MDI applications
+     */
+    void notifyFocusedOut();
+
+private:
+    std::unique_ptr<KActivities::ResourceInstance> m_resourceInstance;
+    QUrl m_uri;
+    QString m_mimetype;
+    QString m_title;
+    QTimer *m_syncTimer;
+};
+
+}
+}
+
+#endif
diff --git a/src/imports/resourcemodel.cpp b/src/imports/resourcemodel.cpp
new file mode 100644 (file)
index 0000000..3065a5f
--- /dev/null
@@ -0,0 +1,624 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "resourcemodel.h"
+
+// Qt
+#include <QByteArray>
+#include <QCoreApplication>
+#include <QDebug>
+#include <QModelIndex>
+#include <QSqlQuery>
+#include <QUuid>
+
+// KDE
+#include <KConfig>
+#include <KDesktopFile>
+#include <KFileItem>
+#include <ksharedconfig.h>
+
+// STL and Boost
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+#include <boost/range/algorithm/find_if.hpp>
+#include <boost/range/numeric.hpp>
+#include <mutex>
+
+// Local
+#include "common/dbus/common.h"
+#include "utils/dbusfuture_p.h"
+#include "utils/range.h"
+
+#define ENABLE_QJSVALUE_CONTINUATION
+#include "utils/continue_with.h"
+
+#define ACTIVITY_COLUMN 0
+#define AGENT_COLUMN 1
+#define RESOURCE_COLUMN 2
+#define UNKNOWN_COLUMN 3
+
+using kamd::utils::continue_with;
+
+namespace KActivities
+{
+namespace Imports
+{
+class ResourceModel::LinkerService : public QDBusInterface
+{
+private:
+    LinkerService()
+        : KAMD_DBUS_INTERFACE("Resources/Linking", ResourcesLinking, nullptr)
+    {
+    }
+
+public:
+    static std::shared_ptr<LinkerService> self()
+    {
+        static std::weak_ptr<LinkerService> s_instance;
+        static std::mutex singleton;
+
+        std::lock_guard<std::mutex> singleton_lock(singleton);
+
+        auto result = s_instance.lock();
+
+        if (s_instance.expired()) {
+            result.reset(new LinkerService());
+            s_instance = result;
+        }
+
+        return result;
+    }
+};
+
+ResourceModel::ResourceModel(QObject *parent)
+    : QSortFilterProxyModel(parent)
+    , m_shownActivities(QStringLiteral(":current"))
+    , m_shownAgents(QStringLiteral(":current"))
+    , m_defaultItemsLoaded(false)
+    , m_linker(LinkerService::self())
+    , m_config(KSharedConfig::openConfig("kactivitymanagerd-resourcelinkingrc")->group("Order"))
+{
+    // NOTE: What to do if the file does not exist?
+    //       Ignoring that case since the daemon creates it on startup.
+    //       Is it plausible that somebody will instantiate the ResourceModel
+    //       before the daemon is started?
+
+    const QString databaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/");
+
+    m_databaseFile = databaseDir + QStringLiteral("database");
+
+    loadDatabase();
+
+    connect(&m_service, &KActivities::Consumer::currentActivityChanged, this, &ResourceModel::onCurrentActivityChanged);
+
+    connect(m_linker.get(), SIGNAL(ResourceLinkedToActivity(QString, QString, QString)), this, SLOT(onResourceLinkedToActivity(QString, QString, QString)));
+    connect(m_linker.get(),
+            SIGNAL(ResourceUnlinkedFromActivity(QString, QString, QString)),
+            this,
+            SLOT(onResourceUnlinkedFromActivity(QString, QString, QString)));
+
+    setDynamicSortFilter(true);
+    sort(0);
+}
+
+bool ResourceModel::loadDatabase()
+{
+    if (m_database.isValid())
+        return true;
+    if (!QFile(m_databaseFile).exists())
+        return false;
+
+    // TODO: Database connection naming could be smarter (thread-id-based,
+    //       reusing connections...?)
+    m_database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("kactivities_db_resources_") + QString::number((quintptr)this));
+
+    // qDebug() << "Database file is: " << m_databaseFile;
+    m_database.setDatabaseName(m_databaseFile);
+
+    m_database.open();
+
+    m_databaseModel = new QSqlTableModel(this, m_database);
+    m_databaseModel->setTable("ResourceLink");
+    m_databaseModel->select();
+
+    setSourceModel(m_databaseModel);
+
+    reloadData();
+
+    return true;
+}
+
+ResourceModel::~ResourceModel()
+{
+}
+
+QVariant ResourceModel::dataForColumn(const QModelIndex &index, int column) const
+{
+    if (!m_database.isValid())
+        return QVariant();
+
+    return m_databaseModel->data(index.sibling(index.row(), column), Qt::DisplayRole);
+}
+
+bool ResourceModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+    const auto leftResource = dataForColumn(left, RESOURCE_COLUMN).toString();
+    const auto rightResource = dataForColumn(right, RESOURCE_COLUMN).toString();
+
+    const bool hasLeft = m_sorting.contains(leftResource);
+    const bool hasRight = m_sorting.contains(rightResource);
+
+    return (hasLeft && !hasRight) ? true
+        : (!hasLeft && hasRight)  ? false
+        : (hasLeft && hasRight)   ? m_sorting.indexOf(leftResource) < m_sorting.indexOf(rightResource)
+                                  : QString::compare(leftResource, rightResource, Qt::CaseInsensitive) < 0;
+}
+
+QHash<int, QByteArray> ResourceModel::roleNames() const
+{
+    return {{Qt::DisplayRole, "display"},
+            {Qt::DecorationRole, "decoration"},
+            {ResourceRole, "uri"},
+            {AgentRole, "agent"},
+            {ActivityRole, "activity"},
+            {DescriptionRole, "subtitle"}};
+}
+
+template<typename Validator>
+inline QStringList validateList(const QString &values, Validator validator)
+{
+    using boost::adaptors::filtered;
+    using kamd::utils::as_collection;
+
+    auto result = as_collection<QStringList>(values.split(',') | filtered(validator));
+
+    if (result.isEmpty()) {
+        result.append(QStringLiteral(":current"));
+    }
+
+    return result;
+}
+
+void ResourceModel::setShownActivities(const QString &activities)
+{
+    m_shownActivities = validateList(activities, [&](const QString &activity) {
+        return activity == ":current" || activity == ":any" || activity == ":global" || !QUuid(activity).isNull();
+    });
+
+    reloadData();
+    Q_EMIT shownActivitiesChanged();
+}
+
+void ResourceModel::setShownAgents(const QString &agents)
+{
+    m_shownAgents = validateList(agents, [&](const QString &agent) {
+        return agent == ":current" || agent == ":any" || agent == ":global" || (!agent.isEmpty() && !agent.contains('\'') && !agent.contains('"'));
+    });
+
+    loadDefaultsIfNeeded();
+    reloadData();
+    Q_EMIT shownAgentsChanged();
+}
+
+QString ResourceModel::shownActivities() const
+{
+    return m_shownActivities.join(',');
+}
+
+QString ResourceModel::shownAgents() const
+{
+    return m_shownAgents.join(',');
+}
+
+QString ResourceModel::defaultItemsConfig() const
+{
+    return m_defaultItemsConfig;
+}
+
+void ResourceModel::setDefaultItemsConfig(const QString &defaultItemsConfig)
+{
+    m_defaultItemsConfig = defaultItemsConfig;
+    loadDefaultsIfNeeded();
+}
+
+QString ResourceModel::activityToWhereClause(const QString &shownActivity) const
+{
+    return QStringLiteral(" OR usedActivity=")
+        + (shownActivity == ":current"      ? "'" + m_service.currentActivity() + "'"
+               : shownActivity == ":any"    ? "usedActivity"
+               : shownActivity == ":global" ? "''"
+                                            : "'" + shownActivity + "'");
+}
+
+QString ResourceModel::agentToWhereClause(const QString &shownAgent) const
+{
+    return QStringLiteral(" OR initiatingAgent=")
+        + (shownAgent == ":current"      ? "'" + QCoreApplication::applicationName() + "'"
+               : shownAgent == ":any"    ? "initiatingAgent"
+               : shownAgent == ":global" ? "''"
+                                         : "'" + shownAgent + "'");
+}
+
+QString ResourceModel::whereClause(const QStringList &activities, const QStringList &agents) const
+{
+    using boost::accumulate;
+    using namespace kamd::utils;
+
+    // qDebug() << "Getting the where clause for: " << activities << " " << agents;
+
+    // Defining the transformation functions for generating the SQL WHERE clause
+    // from the specified activity/agent. They also resolve the special values
+    // like :current, :any and :global.
+
+    auto activityToWhereClause = transformed(&ResourceModel::activityToWhereClause, this);
+    auto agentToWhereClause = transformed(&ResourceModel::agentToWhereClause, this);
+
+    // Generating the SQL WHERE part by concatenating the generated clauses.
+    // The generated query will be in the form of '0 OR clause1 OR clause2 ...'
+
+    const QString whereActivity = accumulate(activities | activityToWhereClause, QStringLiteral("0"));
+
+    const QString whereAgent = accumulate(agents | agentToWhereClause, QStringLiteral("0"));
+
+    // qDebug() << "This is the filter: " << '(' + whereActivity + ") AND (" + whereAgent + ')';
+
+    return '(' + whereActivity + ") AND (" + whereAgent + ')';
+}
+
+void ResourceModel::reloadData()
+{
+    m_sorting = m_config.readEntry(m_shownAgents.first(), QStringList());
+
+    if (!m_database.isValid())
+        return;
+    m_databaseModel->setFilter(whereClause(m_shownActivities, m_shownAgents));
+}
+
+void ResourceModel::onCurrentActivityChanged(const QString &activity)
+{
+    Q_UNUSED(activity);
+
+    if (m_shownActivities.contains(":current")) {
+        reloadData();
+    }
+}
+
+QVariant ResourceModel::data(const QModelIndex &proxyIndex, int role) const
+{
+    auto index = mapToSource(proxyIndex);
+
+    if (role == Qt::DisplayRole || role == DescriptionRole || role == Qt::DecorationRole) {
+        auto uri = dataForColumn(index, RESOURCE_COLUMN).toString();
+
+        // TODO: Will probably need some more special handling -
+        //       for application:/ and a few more
+
+        if (uri.startsWith('/')) {
+            uri = QLatin1String("file://") + uri;
+        }
+
+        KFileItem file(uri);
+        // clang-format off
+        if (file.mimetype() == "application/x-desktop") {
+            KDesktopFile desktop(file.localPath());
+
+            return role == Qt::DisplayRole    ? desktop.readGenericName() :
+                   role == DescriptionRole    ? desktop.readName() :
+                   role == Qt::DecorationRole ? desktop.readIcon() : QVariant();
+        }
+
+        return role == Qt::DisplayRole    ? file.name() :
+               role == Qt::DecorationRole ? file.iconName() : QVariant();
+    }
+
+    return dataForColumn(index,
+            role == ResourceRole ? RESOURCE_COLUMN :
+            role == AgentRole    ? AGENT_COLUMN :
+            role == ActivityRole ? ACTIVITY_COLUMN :
+                                   UNKNOWN_COLUMN
+        );
+    // clang-format on
+}
+
+void ResourceModel::linkResourceToActivity(const QString &resource, const QJSValue &callback) const
+{
+    linkResourceToActivity(resource, m_shownActivities.first(), callback);
+}
+
+void ResourceModel::linkResourceToActivity(const QString &resource, const QString &activity, const QJSValue &callback) const
+{
+    linkResourceToActivity(m_shownAgents.first(), resource, activity, callback);
+}
+
+void ResourceModel::linkResourceToActivity(const QString &agent, const QString &_resource, const QString &activity, const QJSValue &callback) const
+{
+    if (activity == ":any") {
+        qWarning() << ":any is not a valid activity specification for linking";
+        return;
+    }
+
+    auto resource = validateResource(_resource);
+
+    // qDebug() << "ResourceModel: Linking resource to activity: --------------------------------------------------\n"
+    //          << "ResourceModel:         Resource: " << resource << "\n"
+    //          << "ResourceModel:         Agents: " << agent << "\n"
+    //          << "ResourceModel:         Activities: " << activity << "\n";
+
+    kamd::utils::continue_with(DBusFuture::asyncCall<void>(m_linker.get(),
+                                                           QStringLiteral("LinkResourceToActivity"),
+                                                           agent,
+                                                           resource,
+                                                           activity == ":current"      ? m_service.currentActivity()
+                                                               : activity == ":global" ? ""
+                                                                                       : activity),
+                               callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QString &resource, const QJSValue &callback)
+{
+    unlinkResourceFromActivity(m_shownAgents, resource, m_shownActivities, callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QString &resource, const QString &activity, const QJSValue &callback)
+{
+    unlinkResourceFromActivity(m_shownAgents, resource, QStringList() << activity, callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QString &agent, const QString &resource, const QString &activity, const QJSValue &callback)
+{
+    unlinkResourceFromActivity(QStringList() << agent, resource, QStringList() << activity, callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QStringList &agents, const QString &_resource, const QStringList &activities, const QJSValue &callback)
+{
+    auto resource = validateResource(_resource);
+
+    // qDebug() << "ResourceModel: Unlinking resource from activity: ----------------------------------------------\n"
+    //          << "ResourceModel:         Resource: " << resource << "\n"
+    //          << "ResourceModel:         Agents: " << agents << "\n"
+    //          << "ResourceModel:         Activities: " << activities << "\n";
+
+    for (const auto &agent : agents) {
+        for (const auto &activity : activities) {
+            if (activity == ":any") {
+                qWarning() << ":any is not a valid activity specification for linking";
+                return;
+            }
+
+            // We might want to compose the continuations into one
+            // so that the callback gets called only once,
+            // but we don't care about that at the moment
+            kamd::utils::continue_with(DBusFuture::asyncCall<void>(m_linker.get(),
+                                                                   QStringLiteral("UnlinkResourceFromActivity"),
+                                                                   agent,
+                                                                   resource,
+                                                                   activity == ":current"      ? m_service.currentActivity()
+                                                                       : activity == ":global" ? ""
+                                                                                               : activity),
+                                       callback);
+        }
+    }
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QString &resource)
+{
+    return isResourceLinkedToActivity(m_shownAgents, resource, m_shownActivities);
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QString &resource, const QString &activity)
+{
+    return isResourceLinkedToActivity(m_shownAgents, resource, QStringList() << activity);
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QString &agent, const QString &resource, const QString &activity)
+{
+    return isResourceLinkedToActivity(QStringList() << agent, resource, QStringList() << activity);
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QStringList &agents, const QString &_resource, const QStringList &activities)
+{
+    if (!m_database.isValid())
+        return false;
+
+    auto resource = validateResource(_resource);
+
+    // qDebug() << "ResourceModel: Testing whether the resource is linked to activity: ----------------------------\n"
+    //          << "ResourceModel:         Resource: " << resource << "\n"
+    //          << "ResourceModel:         Agents: " << agents << "\n"
+    //          << "ResourceModel:         Activities: " << activities << "\n";
+
+    QSqlQuery query(m_database);
+    query.prepare(
+        "SELECT targettedResource "
+        "FROM ResourceLink "
+        "WHERE targettedResource=:resource AND "
+        + whereClause(activities, agents));
+    query.bindValue(":resource", resource);
+    query.exec();
+
+    auto result = query.next();
+
+    // qDebug() << "Query: " << query.lastQuery();
+    //
+    // if (query.lastError().isValid()) {
+    //     qDebug() << "Error: " << query.lastError();
+    // }
+    //
+    // qDebug() << "Result: " << result;
+
+    return result;
+}
+
+void ResourceModel::onResourceLinkedToActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity)
+{
+    Q_UNUSED(targettedResource);
+
+    if (!loadDatabase())
+        return;
+
+    auto matchingActivity = boost::find_if(m_shownActivities, [&](const QString &shownActivity) {
+        return
+            // If the activity is not important
+            shownActivity == ":any" ||
+            // or we are listening for the changes for the current activity
+            (shownActivity == ":current" && usedActivity == m_service.currentActivity()) ||
+            // or we want the globally linked resources
+            (shownActivity == ":global" && usedActivity.isEmpty()) ||
+            // or we have a specific activity in mind
+            shownActivity == usedActivity;
+    });
+
+    auto matchingAgent = boost::find_if(m_shownAgents, [&](const QString &shownAgent) {
+        return
+            // If the agent is not important
+            shownAgent == ":any" ||
+            // or we are listening for the changes for the current agent
+            (shownAgent == ":current" && initiatingAgent == QCoreApplication::applicationName()) ||
+            // or for links that are global, and not related to a specific agent
+            (shownAgent == ":global" && initiatingAgent.isEmpty()) ||
+            // or we have a specific agent to listen for
+            shownAgent == initiatingAgent;
+    });
+
+    if (matchingActivity != m_shownActivities.end() && matchingAgent != m_shownAgents.end()) {
+        // TODO: This might be smarter possibly, but might collide
+        //       with the SQL model. Implement a custom model with internal
+        //       cache instead of basing it on QSqlModel.
+        reloadData();
+    }
+}
+
+void ResourceModel::onResourceUnlinkedFromActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity)
+{
+    // These are the same at the moment
+    onResourceLinkedToActivity(initiatingAgent, targettedResource, usedActivity);
+}
+
+void ResourceModel::setOrder(const QStringList &resources)
+{
+    m_sorting = resources;
+    m_config.writeEntry(m_shownAgents.first(), m_sorting);
+    m_config.sync();
+    invalidate();
+}
+
+void ResourceModel::move(int sourceItem, int destinationItem)
+{
+    QStringList resources;
+    const int rows = rowCount();
+
+    for (int row = 0; row < rows; row++) {
+        resources << resourceAt(row);
+    }
+
+    if (sourceItem < 0 || sourceItem >= rows || destinationItem < 0 || destinationItem >= rows) {
+        return;
+    }
+
+    // Moving one item from the source item's location to the location
+    // after the destination item
+    std::rotate(resources.begin() + sourceItem, resources.begin() + sourceItem + 1, resources.begin() + destinationItem + 1);
+
+    setOrder(resources);
+}
+
+void ResourceModel::sortItems(Qt::SortOrder sortOrder)
+{
+    typedef QPair<QString, QString> Resource;
+    QList<Resource> resources;
+    const int rows = rowCount();
+
+    for (int row = 0; row < rows; ++row) {
+        resources << qMakePair(resourceAt(row), displayAt(row));
+    }
+
+    std::sort(resources.begin(), resources.end(), [sortOrder](const Resource &left, const Resource &right) {
+        return sortOrder == Qt::AscendingOrder ? left.second < right.second : right.second < left.second;
+    });
+
+    QStringList result;
+
+    for (const auto &resource : std::as_const(resources)) {
+        result << resource.first;
+    }
+
+    setOrder(result);
+}
+
+KConfigGroup ResourceModel::config() const
+{
+    return KSharedConfig::openConfig("kactivitymanagerd-resourcelinkingrc")->group("Order");
+}
+
+int ResourceModel::count() const
+{
+    return QSortFilterProxyModel::rowCount();
+}
+
+QString ResourceModel::displayAt(int row) const
+{
+    return data(index(row, 0), Qt::DisplayRole).toString();
+}
+
+QString ResourceModel::resourceAt(int row) const
+{
+    return validateResource(data(index(row, 0), ResourceRole).toString());
+}
+
+void ResourceModel::loadDefaultsIfNeeded() const
+{
+    // Did we get a request to actually do anything?
+    if (m_defaultItemsConfig.isEmpty())
+        return;
+    if (m_shownAgents.size() == 0)
+        return;
+
+    // If we have already loaded the items, just exit
+    if (m_defaultItemsLoaded)
+        return;
+    m_defaultItemsLoaded = true;
+
+    // If there are items in the model, no need to load the defaults
+    if (count() != 0)
+        return;
+
+    // Did we already load the defaults for this agent?
+    QStringList alreadyInitialized = m_config.readEntry("defaultItemsProcessedFor", QStringList());
+    if (alreadyInitialized.contains(m_shownAgents.first()))
+        return;
+    alreadyInitialized << m_shownAgents.first();
+    m_config.writeEntry("defaultItemsProcessedFor", alreadyInitialized);
+    m_config.sync();
+
+    QStringList args = m_defaultItemsConfig.split("/");
+    QString configField = args.takeLast();
+    QString configGroup = args.takeLast();
+    QString configFile = args.join("/");
+
+    // qDebug() << "Config"
+    //          << configFile << " "
+    //          << configGroup << " "
+    //          << configField << " ";
+
+    QStringList items = KSharedConfig::openConfig(configFile)->group(configGroup).readEntry(configField, QStringList());
+
+    for (const auto &item : items) {
+        // qDebug() << "Adding: " << item;
+        linkResourceToActivity(item, ":global", QJSValue());
+    }
+}
+
+QString ResourceModel::validateResource(const QString &resource) const
+{
+    return resource.startsWith(QLatin1String("file://")) ? QUrl(resource).toLocalFile() : resource;
+}
+
+} // namespace Imports
+} // namespace KActivities
+
+// #include "resourcemodel.moc"
diff --git a/src/imports/resourcemodel.h b/src/imports/resourcemodel.h
new file mode 100644 (file)
index 0000000..69402f8
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_IMPORTS_RESOURCE_MODEL_H
+#define KACTIVITIES_IMPORTS_RESOURCE_MODEL_H
+
+// Qt
+#include <QJSValue>
+#include <QObject>
+#include <QSortFilterProxyModel>
+#include <QSqlDatabase>
+#include <QSqlTableModel>
+
+// KDE
+#include <KConfigGroup>
+
+// STL and Boost
+#include <memory>
+
+// Local
+#include <lib/consumer.h>
+#include <lib/controller.h>
+#include <lib/info.h>
+
+class QModelIndex;
+class QDBusPendingCallWatcher;
+
+namespace KActivities
+{
+namespace Imports
+{
+/**
+ * ResourceModel
+ */
+
+class ResourceModel : public QSortFilterProxyModel
+{
+    Q_OBJECT
+
+    /**
+     * Sets for which activities should the resources be shown for.
+     * Coma-separated values.
+     * Special values are:
+     *  - ":current" for the current activity
+     *  - ":any" show resources that are linked to any activity, including "global"
+     *  - ":global" show resources that are globally linked
+     */
+    Q_PROPERTY(QString shownActivities READ shownActivities WRITE setShownActivities NOTIFY shownActivitiesChanged)
+
+    /**
+     * Sets for which agents should the resources be shown for.
+     * Coma-separated values.
+     * Special values are:
+     *  - ":current" for the current application
+     *  - ":any" show resources that are linked to any agent, including "global"
+     *  - ":global" show resources that are globally linked
+     */
+    Q_PROPERTY(QString shownAgents READ shownAgents WRITE setShownAgents NOTIFY shownAgentsChanged)
+
+    /**
+     * If the model is empty, use this config file to read the default items.
+     * The default items are automatically linked globally, not per-activity.
+     * It needs to have the following format: 'config-namerc/ConfigGroup/ConfigEntry'.
+     * The config entry needs to be a list of strings.
+     */
+    Q_PROPERTY(QString defaultItemsConfig READ defaultItemsConfig WRITE setDefaultItemsConfig)
+
+public:
+    explicit ResourceModel(QObject *parent = nullptr);
+    ~ResourceModel() override;
+
+    enum Roles {
+        ResourceRole = Qt::UserRole,
+        ActivityRole = Qt::UserRole + 1,
+        AgentRole = Qt::UserRole + 2,
+        DescriptionRole = Qt::UserRole + 3,
+    };
+
+    QHash<int, QByteArray> roleNames() const override;
+
+    virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override;
+
+public Q_SLOTS:
+    // Resource linking control methods
+    void linkResourceToActivity(const QString &resource, const QJSValue &callback) const;
+    void linkResourceToActivity(const QString &resource, const QString &activity, const QJSValue &callback) const;
+    void linkResourceToActivity(const QString &agent, const QString &resource, const QString &activity, const QJSValue &callback) const;
+
+    void unlinkResourceFromActivity(const QString &resource, const QJSValue &callback);
+    void unlinkResourceFromActivity(const QString &resource, const QString &activity, const QJSValue &callback);
+    void unlinkResourceFromActivity(const QString &agent, const QString &resource, const QString &activity, const QJSValue &callback);
+    void unlinkResourceFromActivity(const QStringList &agents, const QString &resource, const QStringList &activities, const QJSValue &callback);
+
+    bool isResourceLinkedToActivity(const QString &resource);
+    bool isResourceLinkedToActivity(const QString &resource, const QString &activity);
+    bool isResourceLinkedToActivity(const QString &agent, const QString &resource, const QString &activity);
+    bool isResourceLinkedToActivity(const QStringList &agents, const QString &resource, const QStringList &activities);
+
+    // Model property getters and setters
+    void setShownActivities(const QString &activities);
+    QString shownActivities() const;
+
+    void setShownAgents(const QString &agents);
+    QString shownAgents() const;
+
+    QString defaultItemsConfig() const;
+    void setDefaultItemsConfig(const QString &defaultItemsConfig);
+
+    void setOrder(const QStringList &resources);
+    void move(int sourceItem, int destinationItem);
+    void sortItems(Qt::SortOrder sortOrder);
+
+    KConfigGroup config() const;
+
+    int count() const;
+    QString displayAt(int row) const;
+    QString resourceAt(int row) const;
+
+protected:
+    bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
+
+Q_SIGNALS:
+    void shownActivitiesChanged();
+    void shownAgentsChanged();
+
+private Q_SLOTS:
+    void onCurrentActivityChanged(const QString &activity);
+
+    void onResourceLinkedToActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity);
+    void onResourceUnlinkedFromActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity);
+
+private:
+    KActivities::Consumer m_service;
+
+    inline QVariant dataForColumn(const QModelIndex &index, int column) const;
+
+    QString activityToWhereClause(const QString &activity) const;
+    QString agentToWhereClause(const QString &agent) const;
+    QString whereClause(const QStringList &activities, const QStringList &agents) const;
+
+    void loadDefaultsIfNeeded() const;
+
+    bool loadDatabase();
+    QString m_databaseFile;
+    QSqlDatabase m_database;
+    QSqlTableModel *m_databaseModel;
+
+    QStringList m_shownActivities;
+    QStringList m_shownAgents;
+    QStringList m_sorting;
+
+    QString m_defaultItemsConfig;
+    mutable bool m_defaultItemsLoaded;
+
+    void reloadData();
+    QString validateResource(const QString &resource) const;
+
+    class LinkerService;
+    std::shared_ptr<LinkerService> m_linker;
+
+    mutable KConfigGroup m_config;
+};
+
+} // namespace Imports
+} // namespace KActivities
+
+#endif // KACTIVITIES_IMPORTS_RESOURCE_MODEL_H
diff --git a/src/kactivities-features.h.cmake b/src/kactivities-features.h.cmake
new file mode 100644 (file)
index 0000000..30aa01b
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef CONFIG_FEATURES_H_
+#define CONFIG_FEATURES_H_
+
+#cmakedefine KAMD_DATA_DIR "@KAMD_DATA_DIR@"
+
+#cmakedefine KAMD_PLUGIN_DIR "@KAMD_PLUGIN_DIR@"
+#cmakedefine KAMD_FULL_PLUGIN_DIR "@KAMD_FULL_PLUGIN_DIR@"
+
+#cmakedefine KAMD_INSTALL_PREFIX "@KAMD_INSTALL_PREFIX@"
+
+#cmakedefine01 HAVE_CXX11_AUTO
+#cmakedefine01 HAVE_CXX11_NULLPTR
+#cmakedefine01 HAVE_CXX11_LAMBDA
+#cmakedefine01 HAVE_CXX11_OVERRIDE
+#cmakedefine01 HAVE_CXX_OVERRIDE_ATTR
+
+#endif
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..abb21e9
--- /dev/null
@@ -0,0 +1,190 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+# =======================================================
+# Now that we finished with the boilerplate, start
+# with the library definition
+
+set (
+   KActivities_LIB_SRCS
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.cpp
+
+   consumer.cpp
+   controller.cpp
+   info.cpp
+   resourceinstance.cpp
+   activitiesmodel.cpp
+
+   mainthreadexecutor_p.cpp
+   manager_p.cpp
+   activitiescache_p.cpp
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/utils/dbusfuture_p.cpp
+
+   version.cpp
+   )
+
+set_source_files_properties (
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml
+   PROPERTIES
+   INCLUDE ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.h
+   )
+
+qt_add_dbus_interface (
+   KActivities_LIB_SRCS
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml
+   activities_interface
+   )
+
+qt_add_dbus_interface (
+   KActivities_LIB_SRCS
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Resources.xml
+   resources_interface
+   )
+
+qt_add_dbus_interface (
+   KActivities_LIB_SRCS
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Features.xml
+   features_interface
+   )
+
+qt_add_dbus_interface (
+   KActivities_LIB_SRCS
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml
+   resources_linking_interface
+   )
+
+qt_add_dbus_interface (
+   KActivities_LIB_SRCS
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Application.xml
+   application_interface
+   )
+
+ecm_qt_declare_logging_category(KActivities_LIB_SRCS
+    HEADER debug_p.h
+    IDENTIFIER KAMD_CORELIB
+    CATEGORY_NAME kf.activities
+    OLD_CATEGORY_NAMES org.kde.kactivities.lib.core
+    DEFAULT_SEVERITY Warning
+    DESCRIPTION "kactivities core lib"
+    EXPORT KACTIVITIES
+)
+
+
+add_library (
+   KF5Activities SHARED
+   ${KActivities_LIB_SRCS}
+   )
+add_library (KF5::Activities ALIAS KF5Activities)
+
+set(KACTIVITIES_BUILD_INCLUDE_DIRS
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src
+   ${CMAKE_BINARY_DIR}/
+   )
+include_directories (${KACTIVITIES_BUILD_INCLUDE_DIRS})
+
+set_target_properties (
+   KF5Activities
+   PROPERTIES
+   VERSION ${KACTIVITIES_VERSION}
+   SOVERSION ${KACTIVITIES_SOVERSION}
+   EXPORT_NAME Activities
+   )
+
+target_link_libraries (
+   KF5Activities
+   PUBLIC
+      Qt${QT_MAJOR_VERSION}::Core
+   PRIVATE
+      Qt${QT_MAJOR_VERSION}::DBus
+   )
+
+target_include_directories (
+   KF5Activities
+   INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KActivities>"
+   )
+
+# install
+generate_export_header (KF5Activities BASE_NAME KActivities)
+
+ecm_generate_headers (
+   KActivities_CamelCase_HEADERS
+   HEADER_NAMES
+   Consumer
+   Controller
+   Info
+   ResourceInstance
+   ActivitiesModel
+   Version
+   PREFIX KActivities
+   REQUIRED_HEADERS KActivities_HEADERS
+   )
+install (
+   FILES ${KActivities_CamelCase_HEADERS}
+   DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities/KActivities
+   COMPONENT Devel
+   )
+
+install (
+   FILES ${KActivities_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kactivities_export.h
+   DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities/kactivities
+   COMPONENT Devel
+   )
+
+install (
+   TARGETS KF5Activities
+   EXPORT KF5ActivitiesLibraryTargets
+   ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}
+   )
+
+if(BUILD_QCH)
+    ecm_add_qch(
+        KF5Activities_QCH
+        NAME KActivities
+        BASE_NAME KF5Activities
+        VERSION ${KF_VERSION}
+        ORG_DOMAIN org.kde
+        SOURCES # using only public headers, to cover only public API
+            ${KActivities_HEADERS}
+        MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
+        LINK_QCHS
+            Qt5Core_QCH
+        INCLUDE_DIRS
+            ${KACTIVITIES_BUILD_INCLUDE_DIRS}
+        BLANK_MACROS
+            KACTIVITIES_EXPORT
+            KACTIVITIES_DEPRECATED
+            KACTIVITIES_DEPRECATED_EXPORT
+        TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
+        QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
+        COMPONENT Devel
+    )
+endif()
+
+
+if (NOT WIN32)
+    ecm_generate_pkgconfig_file(BASE_NAME libKActivities
+      LIB_NAME KF5Activities
+      INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities
+      DEPS Qt${QT_MAJOR_VERSION}Core
+      DESCRIPTION "libKActivities is a C++ library for using KDE activities"
+      INSTALL
+    )
+endif ()
+
+include (ECMGeneratePriFile)
+ecm_generate_pri_file (
+   BASE_NAME KActivities
+   LIB_NAME KF5Activities
+   FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities
+   )
+install (
+   FILES ${PRI_FILENAME}
+   DESTINATION ${ECM_MKSPECS_INSTALL_DIR}
+   )
+
diff --git a/src/lib/activitiescache_p.cpp b/src/lib/activitiescache_p.cpp
new file mode 100644 (file)
index 0000000..343678b
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+    SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "activitiescache_p.h"
+#include "manager_p.h"
+
+#include <mutex>
+
+#include <QString>
+
+#include "mainthreadexecutor_p.h"
+
+namespace KActivities
+{
+static QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000");
+
+using kamd::utils::Mutable;
+
+std::shared_ptr<ActivitiesCache> ActivitiesCache::self()
+{
+    static std::weak_ptr<ActivitiesCache> s_instance;
+    static std::mutex singleton;
+    std::lock_guard<std::mutex> singleton_lock(singleton);
+
+    auto result = s_instance.lock();
+
+    if (s_instance.expired()) {
+        runInMainThread([&result] {
+            result.reset(new ActivitiesCache());
+            s_instance = result;
+        });
+    }
+
+    return result;
+}
+
+ActivitiesCache::ActivitiesCache()
+    : m_status(Consumer::NotRunning)
+{
+    // qDebug() << "ActivitiesCache: Creating a new instance";
+    using org::kde::ActivityManager::Activities;
+
+    auto activities = Manager::self()->activities();
+
+    connect(activities, &Activities::ActivityAdded, this, &ActivitiesCache::updateActivity);
+    connect(activities, &Activities::ActivityChanged, this, &ActivitiesCache::updateActivity);
+    connect(activities, &Activities::ActivityRemoved, this, &ActivitiesCache::removeActivity);
+
+    connect(activities, &Activities::ActivityStateChanged, this, &ActivitiesCache::updateActivityState);
+    connect(activities, &Activities::ActivityNameChanged, this, &ActivitiesCache::setActivityName);
+    connect(activities, &Activities::ActivityDescriptionChanged, this, &ActivitiesCache::setActivityDescription);
+    connect(activities, &Activities::ActivityIconChanged, this, &ActivitiesCache::setActivityIcon);
+
+    connect(activities, &Activities::CurrentActivityChanged, this, &ActivitiesCache::setCurrentActivity);
+
+    connect(Manager::self(), &Manager::serviceStatusChanged, this, &ActivitiesCache::setServiceStatus);
+
+    // These are covered by ActivityStateChanged
+    // signal void org.kde.ActivityManager.Activities.ActivityStarted(QString activity)
+    // signal void org.kde.ActivityManager.Activities.ActivityStopped(QString activity)
+
+    setServiceStatus(Manager::self()->isServiceRunning());
+}
+
+void ActivitiesCache::setServiceStatus(bool status)
+{
+    // qDebug() << "Setting service status to:" << status;
+    loadOfflineDefaults();
+
+    if (status) {
+        updateAllActivities();
+    }
+}
+
+void ActivitiesCache::loadOfflineDefaults()
+{
+    m_status = Consumer::NotRunning;
+
+    m_activities.clear();
+    m_activities << ActivityInfo(nulluuid, QString(), QString(), QString(), Info::Running);
+    m_currentActivity = nulluuid;
+
+    Q_EMIT serviceStatusChanged(m_status);
+    Q_EMIT activityListChanged();
+}
+
+ActivitiesCache::~ActivitiesCache()
+{
+    // qDebug() << "ActivitiesCache: Destroying the instance";
+}
+
+void ActivitiesCache::removeActivity(const QString &id)
+{
+    // qDebug() << "Removing the activity";
+
+    // Since we are sorting the activities by name now,
+    // we can not use lower_bound to search for an activity
+    // with a specified id
+    const auto where = find(id);
+
+    if (where != m_activities.end() && where->id == id) {
+        m_activities.erase(where);
+        Q_EMIT activityRemoved(id);
+        Q_EMIT activityListChanged();
+
+    } else {
+        // qFatal("Requested to delete an non-existent activity");
+    }
+}
+
+void ActivitiesCache::updateAllActivities()
+{
+    // qDebug() << "Updating all";
+    m_status = Consumer::Unknown;
+    Q_EMIT serviceStatusChanged(m_status);
+
+    // Loading the current activity
+    auto call = Manager::self()->activities()->asyncCall(QStringLiteral("CurrentActivity"));
+
+    onCallFinished(call, SLOT(setCurrentActivityFromReply(QDBusPendingCallWatcher *)));
+
+    // Loading all the activities
+    call = Manager::self()->activities()->asyncCall(QStringLiteral("ListActivitiesWithInformation"));
+
+    onCallFinished(call, SLOT(setAllActivitiesFromReply(QDBusPendingCallWatcher *)));
+}
+
+void ActivitiesCache::updateActivity(const QString &id)
+{
+    // qDebug() << "Updating activity" << id;
+
+    auto call = Manager::self()->activities()->asyncCall(QStringLiteral("ActivityInformation"), id);
+
+    onCallFinished(call, SLOT(setActivityInfoFromReply(QDBusPendingCallWatcher *)));
+}
+
+void ActivitiesCache::updateActivityState(const QString &id, int state)
+{
+    auto where = getInfo<Mutable>(id);
+
+    if (where && where->state != state) {
+        auto isInvalid = [](int state) {
+            return state == Info::Invalid || state == Info::Unknown;
+        };
+        auto isStopped = [](int state) {
+            return state == Info::Stopped || state == Info::Starting;
+        };
+        auto isRunning = [](int state) {
+            return state == Info::Running || state == Info::Stopping;
+        };
+
+        const bool runningStateChanged =
+            (isInvalid(state) || isInvalid(where->state) || (isStopped(state) && isRunning(where->state)) || (isRunning(state) && isStopped(where->state)));
+
+        where->state = state;
+
+        if (runningStateChanged) {
+            Q_EMIT runningActivityListChanged();
+        }
+
+        Q_EMIT activityStateChanged(id, state);
+
+    } else {
+        // qFatal("Requested to update the state of an non-existent activity");
+    }
+}
+
+template<typename _Result, typename _Functor>
+void ActivitiesCache::passInfoFromReply(QDBusPendingCallWatcher *watcher, _Functor f)
+{
+    QDBusPendingReply<_Result> reply = *watcher;
+
+    if (!reply.isError()) {
+        auto replyValue = reply.template argumentAt<0>();
+        // qDebug() << "Got some reply" << replyValue;
+
+        ((*this).*f)(replyValue);
+    }
+
+    watcher->deleteLater();
+}
+
+void ActivitiesCache::setActivityInfoFromReply(QDBusPendingCallWatcher *watcher)
+{
+    // qDebug() << "reply...";
+    passInfoFromReply<ActivityInfo>(watcher, &ActivitiesCache::setActivityInfo);
+}
+
+void ActivitiesCache::setAllActivitiesFromReply(QDBusPendingCallWatcher *watcher)
+{
+    // qDebug() << "reply...";
+    passInfoFromReply<ActivityInfoList>(watcher, &ActivitiesCache::setAllActivities);
+}
+
+void ActivitiesCache::setCurrentActivityFromReply(QDBusPendingCallWatcher *watcher)
+{
+    // qDebug() << "reply...";
+    passInfoFromReply<QString>(watcher, &ActivitiesCache::setCurrentActivity);
+}
+
+void ActivitiesCache::setActivityInfo(const ActivityInfo &info)
+{
+    // qDebug() << "Setting activity info" << info.id;
+
+    // Are we updating an existing activity, or adding a new one?
+    const auto iter = find(info.id);
+    const auto present = iter != m_activities.end();
+    bool runningChanged = true;
+    // If there is an activity with the specified id,
+    // we are going to remove it, temporarily.
+    if (present) {
+        runningChanged = (*iter).state != info.state;
+        m_activities.erase(iter);
+    }
+
+    // Now, we need to find where to insert the activity
+    // and keep the cache sorted by name
+    const auto where = lower_bound(info);
+
+    m_activities.insert(where, info);
+
+    if (present) {
+        Q_EMIT activityChanged(info.id);
+    } else {
+        Q_EMIT activityAdded(info.id);
+        Q_EMIT activityListChanged();
+        if (runningChanged) {
+            Q_EMIT runningActivityListChanged();
+        }
+    }
+}
+// clang-format off
+#define CREATE_SETTER(WHAT, What)                                              \
+    void ActivitiesCache::setActivity##WHAT(const QString &id,                 \
+                                            const QString &value)              \
+    {                                                                          \
+        auto where = getInfo<Mutable>(id);                                     \
+                                                                               \
+        if (where) {                                                           \
+            where->What = value;                                               \
+            Q_EMIT activity##WHAT##Changed(id, value);                           \
+        }                                                                      \
+    }
+// clang-format on
+
+CREATE_SETTER(Name, name)
+CREATE_SETTER(Description, description)
+CREATE_SETTER(Icon, icon)
+
+#undef CREATE_SETTER
+
+void ActivitiesCache::setAllActivities(const ActivityInfoList &_activities)
+{
+    // qDebug() << "Setting all activities";
+
+    m_activities.clear();
+
+    const ActivityInfoList activities = _activities;
+
+    for (const ActivityInfo &info : activities) {
+        m_activities << info;
+    }
+
+    std::sort(m_activities.begin(), m_activities.end(), &infoLessThan);
+
+    m_status = Consumer::Running;
+    Q_EMIT serviceStatusChanged(m_status);
+    Q_EMIT activityListChanged();
+}
+
+void ActivitiesCache::setCurrentActivity(const QString &activity)
+{
+    // qDebug() << "Setting current activity to" << activity;
+
+    if (m_currentActivity == activity) {
+        return;
+    }
+
+    m_currentActivity = activity;
+
+    Q_EMIT currentActivityChanged(activity);
+}
+
+} // namespace KActivities
diff --git a/src/lib/activitiescache_p.h b/src/lib/activitiescache_p.h
new file mode 100644 (file)
index 0000000..d091f4c
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+    SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CACHE_P_H
+#define ACTIVITIES_CACHE_P_H
+
+#include <memory>
+
+#include <QObject>
+
+#include <common/dbus/org.kde.ActivityManager.Activities.h>
+#include <utils/ptr_to.h>
+
+#include "activities_interface.h"
+#include "consumer.h"
+
+namespace KActivities
+{
+class ActivitiesCache : public QObject
+{
+    Q_OBJECT
+
+public:
+    static std::shared_ptr<ActivitiesCache> self();
+
+    ~ActivitiesCache() override;
+
+Q_SIGNALS:
+    void activityAdded(const QString &id);
+    void activityChanged(const QString &id);
+    void activityRemoved(const QString &id);
+
+    void activityStateChanged(const QString &id, int state);
+    void activityNameChanged(const QString &id, const QString &name);
+    void activityDescriptionChanged(const QString &id, const QString &description);
+    void activityIconChanged(const QString &id, const QString &icon);
+
+    void currentActivityChanged(const QString &id);
+    void serviceStatusChanged(Consumer::ServiceStatus status);
+    void activityListChanged();
+    void runningActivityListChanged();
+
+private Q_SLOTS:
+    void updateAllActivities();
+    void loadOfflineDefaults();
+
+    void updateActivity(const QString &id);
+    void updateActivityState(const QString &id, int state);
+    void removeActivity(const QString &id);
+
+    void setActivityInfoFromReply(QDBusPendingCallWatcher *watcher);
+    void setAllActivitiesFromReply(QDBusPendingCallWatcher *watcher);
+    void setCurrentActivityFromReply(QDBusPendingCallWatcher *watcher);
+
+    void setActivityName(const QString &id, const QString &name);
+    void setActivityDescription(const QString &id, const QString &description);
+    void setActivityIcon(const QString &id, const QString &icon);
+
+    void setActivityInfo(const ActivityInfo &info);
+    void setAllActivities(const ActivityInfoList &activities);
+    void setCurrentActivity(const QString &activity);
+
+    void setServiceStatus(bool status);
+
+public:
+    template<typename _Result, typename _Functor>
+    void passInfoFromReply(QDBusPendingCallWatcher *watcher, _Functor f);
+
+    static bool infoLessThan(const ActivityInfo &info, const ActivityInfo &other)
+    {
+        const auto comp = QString::compare(info.name, other.name, Qt::CaseInsensitive);
+        return comp < 0 || (comp == 0 && info.id < other.id);
+    }
+
+    ActivityInfoList::iterator find(const QString &id)
+    {
+        return std::find_if(m_activities.begin(), m_activities.end(), [&id](const ActivityInfo &info) {
+            return info.id == id;
+        });
+    }
+
+    ActivityInfoList::iterator lower_bound(const ActivityInfo &info)
+    {
+        return std::lower_bound(m_activities.begin(), m_activities.end(), info, &infoLessThan);
+    }
+
+    template<int Policy = kamd::utils::Const>
+    inline typename kamd::utils::ptr_to<ActivityInfo, Policy>::type getInfo(const QString &id)
+    {
+        const auto where = find(id);
+
+        if (where != m_activities.end()) {
+            return &(*where);
+        }
+
+        return nullptr;
+    }
+
+    template<typename TargetSlot>
+    void onCallFinished(QDBusPendingCall &call, TargetSlot slot)
+    {
+        auto watcher = new QDBusPendingCallWatcher(call, this);
+
+        connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), this, slot);
+    }
+
+    ActivitiesCache();
+
+    QList<ActivityInfo> m_activities;
+    QString m_currentActivity;
+    Consumer::ServiceStatus m_status;
+};
+
+} // namespace KActivities
+
+#endif /* ACTIVITIES_CACHE_P_H */
diff --git a/src/lib/activitiesmodel.cpp b/src/lib/activitiesmodel.cpp
new file mode 100644 (file)
index 0000000..b1547ab
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+    SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "activitiesmodel.h"
+#include "activitiesmodel_p.h"
+
+// Qt
+#include <QByteArray>
+#include <QDBusPendingCall>
+#include <QDBusPendingCallWatcher>
+#include <QDebug>
+#include <QFutureWatcher>
+#include <QHash>
+#include <QModelIndex>
+
+// Local
+#include "utils/remove_if.h"
+
+namespace KActivities
+{
+namespace Private
+{
+template<typename _Container>
+struct ActivityPosition {
+    ActivityPosition()
+        : isValid(false)
+        , index(0)
+        , iterator()
+    {
+    }
+
+    ActivityPosition(unsigned int index, typename _Container::const_iterator iterator)
+        : isValid(true)
+        , index(index)
+        , iterator(iterator)
+    {
+    }
+
+    operator bool() const
+    {
+        return isValid;
+    }
+
+    const bool isValid;
+    const unsigned int index;
+    const typename _Container::const_iterator iterator;
+
+    typedef typename _Container::value_type ContainerElement;
+};
+
+/**
+ * Returns whether the activity has a desired state.
+ * If the state is 0, returns true
+ */
+template<typename T>
+inline bool matchingState(ActivitiesModelPrivate::InfoPtr activity, const T &states)
+{
+    return states.empty() || states.contains(activity->state());
+}
+
+/**
+ * Searches for the activity.
+ * Returns an option(index, iterator) for the found activity.
+ */
+template<typename _Container>
+inline ActivityPosition<_Container> activityPosition(const _Container &container, const QString &activityId)
+{
+    auto position = std::find_if(container.begin(), container.end(), [&](const typename ActivityPosition<_Container>::ContainerElement &activity) {
+        return activity->id() == activityId;
+    });
+
+    return (position != container.end()) ? ActivityPosition<_Container>(position - container.begin(), position) : ActivityPosition<_Container>();
+}
+
+/**
+ * Notifies the model that an activity was updated
+ */
+template<typename _Model, typename _Container>
+inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role)
+{
+    auto position = Private::activityPosition(container, activity);
+
+    if (position) {
+        Q_EMIT model->q->dataChanged(model->q->index(position.index),
+                                     model->q->index(position.index),
+                                     role == Qt::DecorationRole ? QVector<int>{role, ActivitiesModel::ActivityIconSource} : QVector<int>{role});
+    }
+}
+
+/**
+ * Notifies the model that an activity was updated
+ */
+template<typename _Model, typename _Container>
+inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role)
+{
+    const auto activity = static_cast<Info *>(activityInfo);
+    emitActivityUpdated(model, container, activity->id(), role);
+}
+
+}
+
+ActivitiesModelPrivate::ActivitiesModelPrivate(ActivitiesModel *parent)
+    : q(parent)
+{
+}
+
+ActivitiesModel::ActivitiesModel(QObject *parent)
+    : QAbstractListModel(parent)
+    , d(new ActivitiesModelPrivate(this))
+{
+    // Initializing role names for qml
+    connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) {
+        d->setServiceStatus(status);
+    });
+
+    connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) {
+        d->onActivityAdded(activity);
+    });
+    connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) {
+        d->onActivityRemoved(activity);
+    });
+    connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) {
+        d->onCurrentActivityChanged(activity);
+    });
+
+    d->setServiceStatus(d->activities.serviceStatus());
+}
+
+ActivitiesModel::ActivitiesModel(QVector<Info::State> shownStates, QObject *parent)
+    : QAbstractListModel(parent)
+    , d(new ActivitiesModelPrivate(this))
+{
+    d->shownStates = shownStates;
+
+    // Initializing role names for qml
+    connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) {
+        d->setServiceStatus(status);
+    });
+
+    connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) {
+        d->onActivityAdded(activity);
+    });
+    connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) {
+        d->onActivityRemoved(activity);
+    });
+    connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) {
+        d->onCurrentActivityChanged(activity);
+    });
+
+    d->setServiceStatus(d->activities.serviceStatus());
+}
+
+ActivitiesModel::~ActivitiesModel()
+{
+    delete d;
+}
+
+QHash<int, QByteArray> ActivitiesModel::roleNames() const
+{
+    return {{ActivityName, "name"},
+            {ActivityState, "state"},
+            {ActivityId, "id"},
+            {ActivityIconSource, "iconSource"},
+            {ActivityDescription, "description"},
+            {ActivityBackground, "background"},
+            {ActivityIsCurrent, "isCurrent"}};
+}
+
+void ActivitiesModelPrivate::setServiceStatus(Consumer::ServiceStatus)
+{
+    replaceActivities(activities.activities());
+}
+
+void ActivitiesModelPrivate::replaceActivities(const QStringList &activities)
+{
+    q->beginResetModel();
+
+    knownActivities.clear();
+    shownActivities.clear();
+
+    for (const QString &activity : activities) {
+        onActivityAdded(activity, false);
+    }
+
+    q->endResetModel();
+}
+
+void ActivitiesModelPrivate::onActivityAdded(const QString &id, bool notifyClients)
+{
+    auto info = registerActivity(id);
+
+    showActivity(info, notifyClients);
+}
+
+void ActivitiesModelPrivate::onActivityRemoved(const QString &id)
+{
+    hideActivity(id);
+    unregisterActivity(id);
+}
+
+void ActivitiesModelPrivate::onCurrentActivityChanged(const QString &id)
+{
+    Q_UNUSED(id);
+
+    for (const auto &activity : shownActivities) {
+        Private::emitActivityUpdated(this, shownActivities, activity->id(), ActivitiesModel::ActivityIsCurrent);
+    }
+}
+
+ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::registerActivity(const QString &id)
+{
+    auto position = Private::activityPosition(knownActivities, id);
+
+    if (position) {
+        return *(position.iterator);
+
+    } else {
+        auto activityInfo = std::make_shared<Info>(id);
+
+        auto ptr = activityInfo.get();
+
+        connect(ptr, &Info::nameChanged, this, &ActivitiesModelPrivate::onActivityNameChanged);
+        connect(ptr, &Info::descriptionChanged, this, &ActivitiesModelPrivate::onActivityDescriptionChanged);
+        connect(ptr, &Info::iconChanged, this, &ActivitiesModelPrivate::onActivityIconChanged);
+        connect(ptr, &Info::stateChanged, this, &ActivitiesModelPrivate::onActivityStateChanged);
+
+        knownActivities.insert(InfoPtr(activityInfo));
+
+        return activityInfo;
+    }
+}
+
+void ActivitiesModelPrivate::unregisterActivity(const QString &id)
+{
+    auto position = Private::activityPosition(knownActivities, id);
+
+    if (position) {
+        if (auto shown = Private::activityPosition(shownActivities, id)) {
+            q->beginRemoveRows(QModelIndex(), shown.index, shown.index);
+            shownActivities.removeAt(shown.index);
+            q->endRemoveRows();
+        }
+
+        knownActivities.removeAt(position.index);
+    }
+}
+
+void ActivitiesModelPrivate::showActivity(InfoPtr activityInfo, bool notifyClients)
+{
+    // Should it really be shown?
+    if (!Private::matchingState(activityInfo, shownStates)) {
+        return;
+    }
+
+    // Is it already shown?
+    if (std::binary_search(shownActivities.cbegin(), shownActivities.cend(), activityInfo, InfoPtrComparator())) {
+        return;
+    }
+
+    auto registeredPosition = Private::activityPosition(knownActivities, activityInfo->id());
+
+    if (!registeredPosition) {
+        qDebug() << "Got a request to show an unknown activity, ignoring";
+        return;
+    }
+
+    const auto activityInfoPtr = *(registeredPosition.iterator);
+
+    // In C++17, this would be:
+    // const auto [iterator, index, found] = shownActivities.insert(...);
+    const auto _result = shownActivities.insert(activityInfoPtr);
+    // const auto iterator = std::get<0>(_result);
+    const auto index = std::get<1>(_result);
+
+    if (notifyClients) {
+        q->beginInsertRows(QModelIndex(), index, index);
+        q->endInsertRows();
+    }
+}
+
+void ActivitiesModelPrivate::hideActivity(const QString &id)
+{
+    auto position = Private::activityPosition(shownActivities, id);
+
+    if (position) {
+        q->beginRemoveRows(QModelIndex(), position.index, position.index);
+        shownActivities.removeAt(position.index);
+        q->endRemoveRows();
+    }
+}
+
+// clang-format off
+#define CREATE_SIGNAL_EMITTER(What,Role)                                      \
+    void ActivitiesModelPrivate::onActivity##What##Changed(const QString &)    \
+    {                                                                          \
+        Private::emitActivityUpdated(this, shownActivities, sender(), Role);   \
+    }
+// clang-format on
+
+CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole)
+CREATE_SIGNAL_EMITTER(Description, ActivitiesModel::ActivityDescription)
+CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole)
+
+#undef CREATE_SIGNAL_EMITTER
+
+void ActivitiesModelPrivate::onActivityStateChanged(Info::State state)
+{
+    if (shownStates.empty()) {
+        Private::emitActivityUpdated(this, shownActivities, sender(), ActivitiesModel::ActivityState);
+
+    } else {
+        auto info = findActivity(sender());
+
+        if (!info) {
+            return;
+        }
+
+        if (shownStates.contains(state)) {
+            showActivity(info, true);
+        } else {
+            hideActivity(info->id());
+        }
+    }
+}
+
+void ActivitiesModel::setShownStates(const QVector<Info::State> &states)
+{
+    d->shownStates = states;
+
+    d->replaceActivities(d->activities.activities());
+
+    Q_EMIT shownStatesChanged(states);
+}
+
+QVector<Info::State> ActivitiesModel::shownStates() const
+{
+    return d->shownStates;
+}
+
+int ActivitiesModel::rowCount(const QModelIndex &parent) const
+{
+    if (parent.isValid()) {
+        return 0;
+    }
+
+    return d->shownActivities.size();
+}
+
+QVariant ActivitiesModel::data(const QModelIndex &index, int role) const
+{
+    const int row = index.row();
+    const auto &item = d->shownActivities.at(row);
+
+    switch (role) {
+    case Qt::DisplayRole:
+    case ActivityName:
+        return item->name();
+
+    case ActivityId:
+        return item->id();
+
+    case ActivityState:
+        return item->state();
+
+    case Qt::DecorationRole:
+    case ActivityIconSource: {
+        const QString &icon = item->icon();
+
+        // We need a default icon for activities
+        return icon.isEmpty() ? QStringLiteral("activities") : icon;
+    }
+
+    case ActivityDescription:
+        return item->description();
+
+    case ActivityIsCurrent:
+        return d->activities.currentActivity() == item->id();
+
+    default:
+        return QVariant();
+    }
+}
+
+QVariant ActivitiesModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    Q_UNUSED(section);
+    Q_UNUSED(orientation);
+    Q_UNUSED(role);
+
+    return QVariant();
+}
+
+ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::findActivity(QObject *ptr) const
+{
+    auto info = std::find_if(knownActivities.cbegin(), knownActivities.cend(), [ptr](const InfoPtr &info) {
+        return ptr == info.get();
+    });
+
+    if (info == knownActivities.end()) {
+        return nullptr;
+    } else {
+        return *info;
+    }
+}
+
+} // namespace KActivities
+
+// #include "activitiesmodel.moc"
diff --git a/src/lib/activitiesmodel.h b/src/lib/activitiesmodel.h
new file mode 100644 (file)
index 0000000..5f5e3f3
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+    SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef ACTIVITIES_ACTIVITIESMODEL_H
+#define ACTIVITIES_ACTIVITIESMODEL_H
+
+// Qt
+#include <QAbstractListModel>
+#include <QObject>
+
+// STL
+#include <memory>
+
+// Local
+#include "info.h"
+
+class QModelIndex;
+class QDBusPendingCallWatcher;
+
+namespace KActivities
+{
+class ActivitiesModelPrivate;
+
+/**
+ * Data model that shows existing activities
+ */
+class KACTIVITIES_EXPORT ActivitiesModel : public QAbstractListModel
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QVector<Info::State> shownStates READ shownStates WRITE setShownStates NOTIFY shownStatesChanged)
+
+public:
+    explicit ActivitiesModel(QObject *parent = nullptr);
+
+    /**
+     * Constructs the model and sets the shownStates
+     */
+    ActivitiesModel(QVector<Info::State> shownStates, QObject *parent = nullptr);
+    ~ActivitiesModel() override;
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+    QHash<int, QByteArray> roleNames() const override;
+
+    enum Roles {
+        ActivityId = Qt::UserRole, ///< UUID of the activity
+        ActivityName = Qt::UserRole + 1, ///< Activity name
+        ActivityDescription = Qt::UserRole + 2, ///< Activity description
+        ActivityIconSource = Qt::UserRole + 3, ///< Activity icon source name
+        ActivityState = Qt::UserRole + 4, ///< The current state of the activity @see Info::State
+        ActivityBackground = Qt::UserRole + 5, ///< Activity wallpaper (currently unsupported)
+        ActivityIsCurrent = Qt::UserRole + 6, ///< Is this activity the current one current
+
+        UserRole = Qt::UserRole + 32, ///< To be used by models that inherit this one
+    };
+
+public Q_SLOTS:
+    /**
+     * The model can filter the list of activities based on their state.
+     * This method sets which states should be shown.
+     */
+    void setShownStates(const QVector<Info::State> &shownStates);
+
+    /**
+     * The model can filter the list of activities based on their state.
+     * This method returns which states are currently shown.
+     */
+    QVector<Info::State> shownStates() const;
+
+Q_SIGNALS:
+    void shownStatesChanged(const QVector<Info::State> &state);
+
+private:
+    friend class ActivitiesModelPrivate;
+    ActivitiesModelPrivate *const d;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_ACTIVITIESMODEL_H
diff --git a/src/lib/activitiesmodel_p.h b/src/lib/activitiesmodel_p.h
new file mode 100644 (file)
index 0000000..8a3a9db
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+    SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_ACTIVITIESMODEL_P_H
+#define ACTIVITIES_ACTIVITIESMODEL_P_H
+
+#include "activitiesmodel.h"
+
+#include "consumer.h"
+
+#include "utils/qflatset.h"
+
+#include <QCollator>
+
+namespace KActivities
+{
+class ActivitiesModelPrivate : public QObject
+{
+    Q_OBJECT
+public:
+    ActivitiesModelPrivate(ActivitiesModel *parent);
+
+public Q_SLOTS:
+    void onActivityNameChanged(const QString &name);
+    void onActivityDescriptionChanged(const QString &description);
+    void onActivityIconChanged(const QString &icon);
+    void onActivityStateChanged(KActivities::Info::State state);
+
+    void replaceActivities(const QStringList &activities);
+    void onActivityAdded(const QString &id, bool notifyClients = true);
+    void onActivityRemoved(const QString &id);
+    void onCurrentActivityChanged(const QString &id);
+
+    void setServiceStatus(KActivities::Consumer::ServiceStatus status);
+
+public:
+    KActivities::Consumer activities;
+    QVector<Info::State> shownStates;
+
+    typedef std::shared_ptr<Info> InfoPtr;
+
+    struct InfoPtrComparator {
+        bool operator()(const InfoPtr &left, const InfoPtr &right) const
+        {
+            QCollator c;
+            c.setCaseSensitivity(Qt::CaseInsensitive);
+            c.setNumericMode(true);
+            int rc = c.compare(left->name(), right->name());
+            if (rc == 0) {
+                return left->id() < right->id();
+            }
+            return rc < 0;
+        }
+    };
+
+    QFlatSet<InfoPtr, InfoPtrComparator> knownActivities;
+    QFlatSet<InfoPtr, InfoPtrComparator> shownActivities;
+
+    InfoPtr registerActivity(const QString &id);
+    void unregisterActivity(const QString &id);
+    void showActivity(InfoPtr activityInfo, bool notifyClients);
+    void hideActivity(const QString &id);
+    void backgroundsUpdated(const QStringList &activities);
+
+    InfoPtr findActivity(QObject *ptr) const;
+
+    ActivitiesModel *const q;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_ACTIVITIESMODEL_P_H
diff --git a/src/lib/consumer.cpp b/src/lib/consumer.cpp
new file mode 100644 (file)
index 0000000..2891562
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "consumer.h"
+#include "consumer_p.h"
+#include "manager_p.h"
+
+#include "debug_p.h"
+
+namespace KActivities
+{
+ConsumerPrivate::ConsumerPrivate()
+    : cache(ActivitiesCache::self())
+{
+}
+
+void ConsumerPrivate::setServiceStatus(Consumer::ServiceStatus status)
+{
+    Q_EMIT serviceStatusChanged(status);
+}
+
+Consumer::Consumer(QObject *parent)
+    : QObject(parent)
+    , d(new ConsumerPrivate())
+{
+    connect(d->cache.get(), &KActivities::ActivitiesCache::currentActivityChanged, this, &Consumer::currentActivityChanged);
+    connect(d->cache.get(), &KActivities::ActivitiesCache::activityAdded, this, &Consumer::activityAdded);
+    connect(d->cache.get(), &KActivities::ActivitiesCache::activityRemoved, this, &Consumer::activityRemoved);
+    connect(d->cache.get(), &KActivities::ActivitiesCache::serviceStatusChanged, this, &Consumer::serviceStatusChanged);
+
+    connect(d->cache.get(), &ActivitiesCache::activityListChanged, this, [=]() {
+        Q_EMIT activitiesChanged(activities());
+    });
+    connect(d->cache.get(), &ActivitiesCache::runningActivityListChanged, this, [=]() {
+        Q_EMIT runningActivitiesChanged(runningActivities());
+    });
+
+    // connect(d->cache.get(), SIGNAL(activityStateChanged(QString,int)),
+    //         this, SIGNAL(activityStateChanged(QString,int)));
+}
+
+Consumer::~Consumer()
+{
+    qCDebug(KAMD_CORELIB) << "Killing the consumer";
+}
+
+QString Consumer::currentActivity() const
+{
+    return d->cache->m_currentActivity;
+}
+
+QStringList Consumer::activities(Info::State state) const
+{
+    QStringList result;
+
+    result.reserve(d->cache->m_activities.size());
+
+    for (const auto &info : std::as_const(d->cache->m_activities)) {
+        if (info.state == state) {
+            result << info.id;
+        }
+    }
+
+    return result;
+}
+
+QStringList Consumer::activities() const
+{
+    QStringList result;
+
+    result.reserve(d->cache->m_activities.size());
+
+    for (const auto &info : std::as_const(d->cache->m_activities)) {
+        result << info.id;
+    }
+
+    return result;
+}
+
+QStringList Consumer::runningActivities() const
+{
+    QStringList result;
+
+    result.reserve(d->cache->m_activities.size());
+
+    for (const auto &info : std::as_const(d->cache->m_activities)) {
+        if (info.state == Info::Running || info.state == Info::Stopping) {
+            result << info.id;
+        }
+    }
+
+    return result;
+}
+
+Consumer::ServiceStatus Consumer::serviceStatus()
+{
+    return d->cache->m_status;
+}
+
+} // namespace KActivities
diff --git a/src/lib/consumer.h b/src/lib/consumer.h
new file mode 100644 (file)
index 0000000..8268258
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CONSUMER_H
+#define ACTIVITIES_CONSUMER_H
+
+#include <QObject>
+#include <QScopedPointer>
+#include <QString>
+#include <QStringList>
+
+#include "info.h"
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class ConsumerPrivate;
+
+/**
+ * Contextual information can be, from the user's point of view, divided
+ * into three aspects - "who am I?", "where am I?" (what are my surroundings?)
+ * and "what am I doing?".
+ *
+ * Activities deal with the last one - "what am I doing?". The current activity
+ * refers to what the user is doing at the moment, while the other activities
+ * represent things that he/she was doing before, and probably will be doing
+ * again.
+ *
+ * Activity is an abstract concept whose meaning can differ from one user to
+ * another. Typical examples of activities are "developing a KDE project",
+ * "studying the 19th century art", "composing music", "lazing on a Sunday
+ * afternoon" etc.
+ *
+ * Consumer provides read-only information about activities.
+ *
+ * Before relying on the values retrieved by the class, make sure that the
+ * serviceStatus is set to Running. Otherwise, you can get invalid data either
+ * because the service is not functioning properly (or at all) or because
+ * the class did not have enough time to synchronize the data with it.
+ *
+ * For example, if this is the only existing instance of the Consumer class,
+ * the listActivities method will return an empty list.
+ *
+ * @code
+ * void someMethod() {
+ *     // Do not copy. This approach is not a good one!
+ *     Consumer c;
+ *     doSomethingWith(c.listActivities());
+ * }
+ * @endcode
+ *
+ * Instances of the Consumer class should be long-lived. For example, members
+ * of the classes that use them, and you should listen for the changes in the
+ * provided properties.
+ *
+ * @since 4.5
+ */
+class KACTIVITIES_EXPORT Consumer : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QString currentActivity READ currentActivity NOTIFY currentActivityChanged)
+    Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged)
+    Q_PROPERTY(QStringList runningActivities READ runningActivities NOTIFY runningActivitiesChanged)
+    Q_PROPERTY(ServiceStatus serviceStatus READ serviceStatus NOTIFY serviceStatusChanged)
+
+public:
+    /**
+     * Different states of the activities service
+     */
+    enum ServiceStatus {
+        NotRunning, ///< Service is not running
+        Unknown, ///< Unable to determine the status of the service
+        Running, ///< Service is running properly
+    };
+
+    explicit Consumer(QObject *parent = nullptr);
+
+    ~Consumer() override;
+
+    /**
+     * @returns the id of the current activity
+     * @note Activity ID is a UUID-formatted string. If the serviceStatus
+     *       is not Running, a null UUID is returned. The ID can also be an empty
+     *       string in the case there is no current activity.
+     */
+    QString currentActivity() const;
+
+    /**
+     * @returns the list of activities filtered by state
+     * @param state state of the activity
+     * @note If the serviceStatus is not Running, only a null activity will be
+     *       returned.
+     */
+    QStringList activities(Info::State state) const;
+
+    /**
+     * @returns a list of running activities
+     * This is a convenience method that returns Running and Stopping activities
+     */
+    QStringList runningActivities() const;
+
+    /**
+     * @returns the list of all existing activities
+     * @note If the serviceStatus is not Running, only a null activity will be
+     *       returned.
+     */
+    QStringList activities() const;
+
+    /**
+     * @returns status of the activities service
+     */
+    ServiceStatus serviceStatus();
+
+Q_SIGNALS:
+    /**
+     * This signal is emitted when the current activity is changed
+     * @param id id of the new current activity
+     */
+    void currentActivityChanged(const QString &id);
+
+    /**
+     * This signal is emitted when the activity service goes online or offline,
+     * or when the class manages to synchronize the data with the service.
+     * @param status new status of the service
+     */
+    void serviceStatusChanged(Consumer::ServiceStatus status);
+
+    /**
+     * This signal is emitted when a new activity is added
+     * @param id id of the new activity
+     */
+    void activityAdded(const QString &id);
+
+    /**
+     * This signal is emitted when an activity has been removed
+     * @param id id of the removed activity
+     */
+    void activityRemoved(const QString &id);
+
+    /**
+     * This signal is emitted when the activity list changes
+     * @param activities list of activities
+     */
+    void activitiesChanged(const QStringList &activities);
+
+    /**
+     * This signal is emitted when the list of running activities changes
+     * @param runningActivities list of running activities
+     */
+    void runningActivitiesChanged(const QStringList &runningActivities);
+
+private:
+    const QScopedPointer<ConsumerPrivate> d;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_CONSUMER_H
diff --git a/src/lib/consumer_p.h b/src/lib/consumer_p.h
new file mode 100644 (file)
index 0000000..77d2a41
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CONSUMER_P_H
+#define ACTIVITIES_CONSUMER_P_H
+
+#include "consumer.h"
+
+#include <memory>
+
+#include "activitiescache_p.h"
+
+namespace KActivities
+{
+class ConsumerPrivate : public QObject
+{
+    Q_OBJECT
+
+public:
+    ConsumerPrivate();
+
+    std::shared_ptr<ActivitiesCache> cache;
+
+public Q_SLOTS:
+    void setServiceStatus(Consumer::ServiceStatus status);
+
+Q_SIGNALS:
+    void serviceStatusChanged(Consumer::ServiceStatus status);
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_CONSUMER_P_H
diff --git a/src/lib/controller.cpp b/src/lib/controller.cpp
new file mode 100644 (file)
index 0000000..6d2a254
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "controller.h"
+#include "consumer_p.h"
+#include "manager_p.h"
+
+#include "utils/dbusfuture_p.h"
+
+namespace KActivities
+{
+Controller::Controller(QObject *parent)
+    : Consumer(parent)
+{
+}
+
+Controller::~Controller()
+{
+}
+
+// clang-format off
+#define CREATE_SETTER(What)                                                    \
+    QFuture<void> Controller::setActivity##What(const QString &id,             \
+                                                const QString &value)          \
+    {                                                                          \
+        return Manager::isServiceRunning()                                     \
+                   ? DBusFuture::asyncCall<void>(                              \
+                         Manager::activities(),                                \
+                         QString::fromLatin1("SetActivity" #What), id, value)  \
+                   : DBusFuture::fromVoid();                                   \
+    }
+// clang-format on
+
+CREATE_SETTER(Name)
+CREATE_SETTER(Description)
+CREATE_SETTER(Icon)
+
+#undef CREATE_SETTER
+
+QFuture<bool> Controller::setCurrentActivity(const QString &id)
+{
+    // Q_ASSERT_X(activities().contains(id), "Controller::setCurrentActivity",
+    //            "You can not set an non-existent activity to be the current");
+
+    // return Manager::activities()->SetCurrentActivity(id);
+    return Manager::isServiceRunning() ? DBusFuture::asyncCall<bool>(Manager::activities(), QStringLiteral("SetCurrentActivity"), id)
+                                       : DBusFuture::fromValue(false);
+}
+
+QFuture<QString> Controller::addActivity(const QString &name)
+{
+    Q_ASSERT_X(!name.isEmpty(), "Controller::addActivity", "The activity name can not be an empty string");
+
+    // return Manager::activities()->AddActivity(name);
+    return Manager::isServiceRunning() ? DBusFuture::asyncCall<QString>(Manager::activities(), QStringLiteral("AddActivity"), name)
+                                       : DBusFuture::fromValue(QString());
+}
+
+QFuture<void> Controller::removeActivity(const QString &id)
+{
+    // Q_ASSERT_X(activities().contains(id), "Controller::removeActivity",
+    //            "You can not remove an non-existent activity");
+
+    // Manager::activities()->RemoveActivity(id);
+    return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("RemoveActivity"), id) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::stopActivity(const QString &id)
+{
+    // Q_ASSERT_X(activities().contains(id), "Controller::stopActivity",
+    //            "You can not stop an non-existent activity");
+
+    // Manager::activities()->StopActivity(id);
+    return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("StopActivity"), id) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::startActivity(const QString &id)
+{
+    // Q_ASSERT_X(activities().contains(id), "Controller::startActivity",
+    //            "You can not start an non-existent activity");
+
+    // Manager::activities()->StartActivity(id);
+    return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("StartActivity"), id) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::previousActivity()
+{
+    return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("PreviousActivity")) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::nextActivity()
+{
+    return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("NextActivity")) : DBusFuture::fromVoid();
+}
+
+} // namespace KActivities
+
+// #include "controller.moc"
diff --git a/src/lib/controller.h b/src/lib/controller.h
new file mode 100644 (file)
index 0000000..64d3527
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CONTROLLER_H
+#define ACTIVITIES_CONTROLLER_H
+
+#include <QFuture>
+#include <QObject>
+#include <QString>
+
+#include "consumer.h"
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class ControllerPrivate;
+
+/**
+ * This class provides methods for controlling and managing
+ * the activities.
+ *
+ * @note The QFuture objects returned by these methods are not thread-based,
+ * you can not call synchronous methods like waitForFinished, cancel, pause on
+ * them. You need either to register watchers to check when those have finished,
+ * or to check whether they are ready from time to time manually.
+ *
+ * @see Consumer for info about activities
+ *
+ * @since 5.0
+ */
+class KACTIVITIES_EXPORT Controller : public Consumer
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QString currentActivity READ currentActivity WRITE setCurrentActivity)
+
+public:
+    explicit Controller(QObject *parent = nullptr);
+
+    ~Controller() override;
+
+    /**
+     * Sets the name of the specified activity
+     * @param id id of the activity
+     * @param name name to be set
+     */
+    QFuture<void> setActivityName(const QString &id, const QString &name);
+
+    /**
+     * Sets the description of the specified activity
+     * @param id id of the activity
+     * @param description description to be set
+     */
+    QFuture<void> setActivityDescription(const QString &id, const QString &description);
+
+    /**
+     * Sets the icon of the specified activity
+     * @param id id of the activity
+     * @param icon icon to be set - freedesktop.org name or file path
+     */
+    QFuture<void> setActivityIcon(const QString &id, const QString &icon);
+
+    /**
+     * Sets the current activity
+     * @param id id of the activity to make current
+     * @returns true if successful
+     */
+    QFuture<bool> setCurrentActivity(const QString &id);
+
+    /**
+     * Adds a new activity
+     * @param name name of the activity
+     * @returns id of the newly created activity
+     */
+    QFuture<QString> addActivity(const QString &name);
+
+    /**
+     * Removes the specified activity
+     * @param id id of the activity to delete
+     */
+    QFuture<void> removeActivity(const QString &id);
+
+    /**
+     * Stops the activity
+     * @param id id of the activity to stop
+     */
+    QFuture<void> stopActivity(const QString &id);
+
+    /**
+     * Starts the activity
+     * @param id id of the activity to start
+     */
+    QFuture<void> startActivity(const QString &id);
+
+    /**
+     * Switches to the previous activity
+     */
+    QFuture<void> previousActivity();
+
+    /**
+     * Switches to the next activity
+     */
+    QFuture<void> nextActivity();
+
+private:
+    // const QScopedPointer<ControllerPrivate> d;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_CONTROLLER_H
diff --git a/src/lib/info.cpp b/src/lib/info.cpp
new file mode 100644 (file)
index 0000000..8f2d38b
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "info.h"
+#include "info_p.h"
+#include "manager_p.h"
+
+#include "utils/dbusfuture_p.h"
+
+#include <QFileSystemWatcher>
+
+namespace KActivities
+{
+// InfoPrivate
+
+InfoPrivate::InfoPrivate(Info *info, const QString &activity)
+    : q(info)
+    , cache(ActivitiesCache::self())
+    , id(activity)
+{
+}
+
+// clang-format off
+// Filters out signals for only this activity
+#define IMPLEMENT_SIGNAL_HANDLER(INTERNAL)                                     \
+    void InfoPrivate::INTERNAL(const QString &_id) const                       \
+    {   if (id == _id) Q_EMIT q->INTERNAL(); }
+
+IMPLEMENT_SIGNAL_HANDLER(added)
+IMPLEMENT_SIGNAL_HANDLER(removed)
+IMPLEMENT_SIGNAL_HANDLER(started)
+IMPLEMENT_SIGNAL_HANDLER(stopped)
+IMPLEMENT_SIGNAL_HANDLER(infoChanged)
+
+#undef IMPLEMENT_SIGNAL_HANDLER
+
+#define IMPLEMENT_SIGNAL_HANDLER(INTERNAL)                                     \
+    void InfoPrivate::INTERNAL##Changed(const QString &_id,                    \
+                                        const QString &val) const              \
+    {                                                                          \
+        if (id == _id) {                                                       \
+            Q_EMIT q->INTERNAL##Changed(val);                                    \
+        }                                                                      \
+    }
+
+IMPLEMENT_SIGNAL_HANDLER(name)
+IMPLEMENT_SIGNAL_HANDLER(description)
+IMPLEMENT_SIGNAL_HANDLER(icon)
+
+#undef IMPLEMENT_SIGNAL_HANDLER
+
+void InfoPrivate::activityStateChanged(const QString &idChanged,
+                                       int newState) const
+{
+    if (idChanged == id) {
+        auto state = static_cast<Info::State>(newState);
+        Q_EMIT q->stateChanged(state);
+
+        if (state == KActivities::Info::Stopped) {
+            Q_EMIT q->stopped();
+        } else if (state == KActivities::Info::Running) {
+            Q_EMIT q->started();
+        }
+    }
+}
+
+void InfoPrivate::setCurrentActivity(const QString &currentActivity)
+{
+    if (isCurrent) {
+        if (currentActivity != id) {
+            // We are no longer the current activity
+            isCurrent = false;
+            Q_EMIT q->isCurrentChanged(false);
+        }
+    } else {
+        if (currentActivity == id) {
+            // We are the current activity
+            isCurrent = true;
+            Q_EMIT q->isCurrentChanged(true);
+        }
+    }
+}
+
+// Info
+Info::Info(const QString &activity, QObject *parent)
+    : QObject(parent)
+    , d(new InfoPrivate(this, activity))
+{
+    // qDebug() << "Created an instance of Info: " << (void*)this;
+#define PASS_SIGNAL_HANDLER(SIGNAL_NAME,SLOT_NAME)                            \
+    connect(d->cache.get(),  SIGNAL(SIGNAL_NAME(QString)),                     \
+            this,            SLOT(SLOT_NAME(QString)));
+
+    PASS_SIGNAL_HANDLER(activityAdded,added)
+    PASS_SIGNAL_HANDLER(activityRemoved,removed)
+    // PASS_SIGNAL_HANDLER(started)
+    // PASS_SIGNAL_HANDLER(stopped)
+    PASS_SIGNAL_HANDLER(activityChanged,infoChanged)
+#undef PASS_SIGNAL_HANDLER
+
+#define PASS_SIGNAL_HANDLER(SIGNAL_NAME,SLOT_NAME,TYPE)                      \
+    connect(d->cache.get(),  SIGNAL(SIGNAL_NAME(QString,TYPE)),               \
+            this,            SLOT(SLOT_NAME(QString,TYPE)));                  \
+
+    PASS_SIGNAL_HANDLER(activityStateChanged,activityStateChanged,int);
+    PASS_SIGNAL_HANDLER(activityNameChanged,nameChanged,QString);
+    PASS_SIGNAL_HANDLER(activityDescriptionChanged,descriptionChanged,QString);
+    PASS_SIGNAL_HANDLER(activityIconChanged,iconChanged,QString);
+// clang-format on
+#undef PASS_SIGNAL_HANDLER
+    connect(d->cache.get(), SIGNAL(currentActivityChanged(QString)), this, SLOT(setCurrentActivity(QString)));
+
+    d->isCurrent = (d->cache.get()->m_currentActivity == activity);
+}
+
+Info::~Info()
+{
+    // qDebug() << "Deleted an instance of Info: " << (void*)this;
+}
+
+bool Info::isValid() const
+{
+    auto currentState = state();
+    return (currentState != Invalid && currentState != Unknown);
+}
+
+QString Info::uri() const
+{
+    return QStringLiteral("activities://") + d->id;
+}
+
+QString Info::id() const
+{
+    return d->id;
+}
+
+bool Info::isCurrent() const
+{
+    return d->isCurrent;
+}
+
+Info::State Info::state() const
+{
+    if (d->cache->m_status == Consumer::Unknown) {
+        return Info::Unknown;
+    }
+
+    auto info = d->cache->getInfo(d->id);
+
+    if (!info) {
+        return Info::Invalid;
+    }
+
+    return static_cast<Info::State>(info->state);
+}
+
+void InfoPrivate::setServiceStatus(Consumer::ServiceStatus status) const
+{
+    switch (status) {
+    case Consumer::NotRunning:
+    case Consumer::Unknown:
+        activityStateChanged(id, Info::Unknown);
+        break;
+
+    default:
+        activityStateChanged(id, q->state());
+        break;
+    }
+}
+
+Info::Availability Info::availability() const
+{
+    Availability result = Nothing;
+
+    if (!Manager::isServiceRunning()) {
+        return result;
+    }
+
+    if (Manager::activities()->ListActivities().value().contains(d->id)) {
+        result = BasicInfo;
+
+        if (Manager::features()->IsFeatureOperational(QStringLiteral("resources/linking"))) {
+            result = Everything;
+        }
+    }
+
+    return result;
+}
+
+// clang-format off
+#define CREATE_GETTER(What)                                                    \
+    QString Info::What() const                                                 \
+    {                                                                          \
+        auto info = d->cache->getInfo(d->id);                                  \
+        return info ? info->What : QString();                                  \
+    }
+// clang-format on
+
+CREATE_GETTER(name)
+CREATE_GETTER(description)
+CREATE_GETTER(icon)
+
+#undef CREATE_GETTER
+
+} // namespace KActivities
+
+#include "moc_info.cpp"
+// #include "moc_info_p.cpp"
diff --git a/src/lib/info.h b/src/lib/info.h
new file mode 100644 (file)
index 0000000..1638218
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_INFO_H
+#define ACTIVITIES_INFO_H
+
+#include <QFuture>
+#include <QObject>
+#include <QString>
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class InfoPrivate;
+
+/**
+ * This class provides info about an activity. Most methods in it require a
+ * semantic backend running to function properly.
+ *
+ * This class is not thread-safe.
+ *
+ * @see Consumer for info about activities
+ *
+ * The API of the class is synchronous, but the most used properties
+ * are pre-fetched and cached. This means that, in order to get the least
+ * amount of d-bus related locks, you should declare long-lived instances
+ * of this class.
+ *
+ * Before relying on the values retrieved by the class, make sure that the
+ * state is not Info::Unknown. You can get invalid data either because the
+ * service is not functioning properly (or at all) or because the class did
+ * not have enough time to synchronize the data with it.
+ *
+ * For example, if this is the only existing instance of the Info class, the
+ * name method will return an empty string.
+ *
+ * For example, this is wrong (works, but blocks):
+ * @code
+ * void someMethod(const QString & activity) {
+ *     // Do not copy. This approach is not a good one!
+ *     Info info(activity);
+ *     doSomethingWith(info.name());
+ * }
+ * @endcode
+ *
+ * Instances of the Info class should be long-lived. For example, members
+ * of the classes that use them, and you should listen for the changes in the
+ * provided properties.
+ *
+ * @since 4.5
+ */
+class KACTIVITIES_EXPORT Info : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QString id READ id)
+    Q_PROPERTY(QString name READ name NOTIFY nameChanged)
+    Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
+    Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
+    Q_PROPERTY(bool isCurrent READ isCurrent NOTIFY isCurrentChanged)
+    Q_PROPERTY(Info::State state READ state NOTIFY stateChanged)
+
+public:
+    explicit Info(const QString &activity, QObject *parent = nullptr);
+    ~Info() override;
+
+    /**
+     * @return true if the activity represented by this object exists and is valid
+     */
+    bool isValid() const;
+
+    /**
+     * Specifies which parts of this class are functional
+     */
+    enum Availability {
+        Nothing = 0, ///< No activity info provided (isValid is false)
+        BasicInfo = 1, ///< Basic info is provided
+        Everything = 2, ///< Everything is available
+    };
+
+    /**
+     * State of the activity
+     */
+    enum State {
+        Invalid = 0, ///< This activity does not exist
+        Unknown = 1, ///< Information is not yet retrieved from the service
+        Running = 2, ///< Activity is running
+        Starting = 3, ///< Activity is begin started
+        Stopped = 4, ///< Activity is stopped
+        Stopping = 5, ///< Activity is begin started
+    };
+
+    /**
+     * @returns what info is provided by this instance of Info
+     */
+    Availability availability() const;
+
+    /**
+     * @returns the URI of this activity. The same URI is used by activities
+     * KIO slave.
+     */
+    QString uri() const;
+
+    /**
+     * @returns the id of the activity
+     */
+    QString id() const;
+
+    /**
+     * @returns whether this activity is the current one
+     */
+    bool isCurrent() const;
+
+    /**
+     * @returns the name of the activity
+     */
+    QString name() const;
+
+    /**
+     * @returns the description of the activity
+     */
+    QString description() const;
+
+    /**
+     * @returns the icon of the activity. Icon can be a freedesktop.org name or
+     * a file path. Or empty if no icon is set.
+     */
+    QString icon() const;
+
+    /**
+     * @returns the state of the activity
+     */
+    State state() const;
+
+    /**
+     * Links the specified resource to the activity
+     * @param resourceUri resource URI
+     * @note This method is <b>asynchronous</b>. It will return before the
+     * resource is actually linked to the activity.
+     */
+    // QFuture<void> linkResource(const QString &resourceUri);
+
+    /**
+     * Unlinks the specified resource from the activity
+     * @param resourceUri resource URI
+     * @note This method is <b>asynchronous</b>. It will return before the
+     * resource is actually unlinked from the activity.
+     */
+    // QFuture<void> unlinkResource(const QString &resourceUri);
+
+    /**
+     * @returns whether a resource is linked to this activity
+     * @note This QFuture is not thread-based, you can not call synchronous
+     * methods like waitForFinished, cancel, pause on it.
+     * @since 5.0
+     */
+    // QFuture<bool> isResourceLinked(const QString &resourceUri);
+
+Q_SIGNALS:
+    /**
+     * Emitted when the activity's name, icon or some custom property is changed
+     */
+    void infoChanged();
+
+    /**
+     * Emitted when the name is changed
+     */
+    void nameChanged(const QString &name);
+
+    /**
+     * Emitted when the activity becomes the current one, or when it stops
+     * being the current one
+     */
+    void isCurrentChanged(bool current);
+
+    /**
+     * Emitted when the description is changed
+     */
+    void descriptionChanged(const QString &description);
+
+    /**
+     * Emitted when the icon was changed
+     */
+    void iconChanged(const QString &icon);
+
+    /**
+     * Emitted when the activity is added
+     */
+    void added();
+
+    /**
+     * Emitted when the activity is removed
+     */
+    void removed();
+
+    /**
+     * Emitted when the activity is started
+     */
+    void started();
+
+    /**
+     * Emitted when the activity is stopped
+     */
+    void stopped();
+
+    /**
+     * Emitted when the activity changes state
+     * @param state new state of the activity
+     */
+    void stateChanged(KActivities::Info::State state);
+
+private:
+    const QScopedPointer<InfoPrivate> d;
+
+    Q_PRIVATE_SLOT(d, void activityStateChanged(const QString &, int))
+    Q_PRIVATE_SLOT(d, void added(const QString &))
+    Q_PRIVATE_SLOT(d, void removed(const QString &))
+    Q_PRIVATE_SLOT(d, void started(const QString &))
+    Q_PRIVATE_SLOT(d, void stopped(const QString &))
+    Q_PRIVATE_SLOT(d, void infoChanged(const QString &))
+    Q_PRIVATE_SLOT(d, void nameChanged(const QString &, const QString &))
+    Q_PRIVATE_SLOT(d, void descriptionChanged(const QString &, const QString &))
+    Q_PRIVATE_SLOT(d, void iconChanged(const QString &, const QString &))
+    Q_PRIVATE_SLOT(d, void setServiceStatus(Consumer::ServiceStatus))
+    Q_PRIVATE_SLOT(d, void setCurrentActivity(const QString &))
+
+    friend class InfoPrivate;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_INFO_H
diff --git a/src/lib/info_p.h b/src/lib/info_p.h
new file mode 100644 (file)
index 0000000..7c92d0b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KACTIVITIESINFO_P_H
+#define KACTIVITIESINFO_P_H
+
+#include "info.h"
+#include <memory>
+
+#include "activitiescache_p.h"
+
+namespace KActivities
+{
+class InfoPrivate
+{
+public:
+    InfoPrivate(Info *info, const QString &activity);
+
+    void activityStateChanged(const QString &, int) const;
+
+    void added(const QString &) const;
+    void removed(const QString &) const;
+    void started(const QString &) const;
+    void stopped(const QString &) const;
+    void infoChanged(const QString &) const;
+    void nameChanged(const QString &, const QString &) const;
+    void descriptionChanged(const QString &, const QString &) const;
+    void iconChanged(const QString &, const QString &) const;
+    void setServiceStatus(Consumer::ServiceStatus status) const;
+    void setCurrentActivity(const QString &currentActivity);
+
+    Info *const q;
+    std::shared_ptr<ActivitiesCache> cache;
+    bool isCurrent;
+
+    const QString id;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_INFO_P_H
diff --git a/src/lib/mainthreadexecutor_p.cpp b/src/lib/mainthreadexecutor_p.cpp
new file mode 100644 (file)
index 0000000..e3aea1a
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    SPDX-FileCopyrightText: 2014-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "mainthreadexecutor_p.h"
+
+#include <mutex>
+
+#include <QCoreApplication>
+#include <QMetaObject>
+#include <QThread>
+
+namespace KActivities
+{
+namespace detail
+{
+MainThreadExecutor::MainThreadExecutor(std::function<void()> &&f)
+    : m_function(std::forward<std::function<void()>>(f))
+{
+}
+
+void MainThreadExecutor::start()
+{
+    m_function();
+    deleteLater();
+}
+
+} // namespace detail
+
+void runInMainThread(std::function<void()> &&f)
+{
+    static auto mainThread = QCoreApplication::instance()->thread();
+
+    if (QThread::currentThread() == mainThread) {
+        f();
+
+    } else {
+        auto executor = new detail::MainThreadExecutor(std::forward<std::function<void()>>(f));
+
+        executor->moveToThread(mainThread);
+
+        QMetaObject::invokeMethod(executor, "start", Qt::BlockingQueuedConnection);
+    }
+}
+
+} // namespace KActivities
diff --git a/src/lib/mainthreadexecutor_p.h b/src/lib/mainthreadexecutor_p.h
new file mode 100644 (file)
index 0000000..72601ca
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    SPDX-FileCopyrightText: 2014-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_MAINTHREADEXECUTOR_P
+#define ACTIVITIES_MAINTHREADEXECUTOR_P
+
+#include <functional>
+
+#include <QObject>
+
+namespace KActivities
+{
+namespace detail
+{
+class MainThreadExecutor : public QObject
+{
+    Q_OBJECT
+
+public:
+    MainThreadExecutor(std::function<void()> &&f);
+
+    Q_INVOKABLE void start();
+
+private:
+    std::function<void()> m_function;
+};
+} // namespace detail
+
+void runInMainThread(std::function<void()> &&f);
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_MAINTHREADEXECUTOR_P
diff --git a/src/lib/manager_p.cpp b/src/lib/manager_p.cpp
new file mode 100644 (file)
index 0000000..a49e9ea
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "manager_p.h"
+
+#include <mutex>
+
+#include <QCoreApplication>
+#include <QDBusConnection>
+#include <QFutureWatcher>
+#include <QFutureWatcherBase>
+
+#include "debug_p.h"
+#include "mainthreadexecutor_p.h"
+
+#include "common/dbus/common.h"
+#include "utils/continue_with.h"
+#include "utils/dbusfuture_p.h"
+#include "version.h"
+
+namespace KActivities
+{
+Manager *Manager::s_instance = nullptr;
+
+Manager::Manager()
+    : QObject()
+    , m_watcher(KAMD_DBUS_SERVICE, QDBusConnection::sessionBus())
+    , m_service(new KAMD_DBUS_CLASS_INTERFACE("/", Application, this))
+    , m_activities(new KAMD_DBUS_CLASS_INTERFACE("Activities", Activities, this))
+    , m_resources(new KAMD_DBUS_CLASS_INTERFACE("Resources", Resources, this))
+    , m_resourcesLinking(new KAMD_DBUS_CLASS_INTERFACE("Resources/Linking", ResourcesLinking, this))
+    , m_features(new KAMD_DBUS_CLASS_INTERFACE("Features", Features, this))
+    , m_serviceRunning(false)
+{
+    connect(&m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &Manager::serviceOwnerChanged);
+
+    if (isServiceRunning()) {
+        serviceOwnerChanged(KAMD_DBUS_SERVICE, QString(), KAMD_DBUS_SERVICE);
+    }
+}
+
+Manager *Manager::self()
+{
+    static std::mutex singleton;
+    std::lock_guard<std::mutex> singleton_lock(singleton);
+#if defined(QT_DEBUG)
+    QLoggingCategory::setFilterRules(QStringLiteral("kf.activities.debug=true"));
+#endif
+
+    if (!s_instance) {
+        runInMainThread([]() {
+            // check if the activity manager is already running
+            if (!Manager::isServiceRunning()) {
+                bool disableAutolaunch = QCoreApplication::instance()->property("org.kde.KActivities.core.disableAutostart").toBool();
+
+                qCDebug(KAMD_CORELIB) << "Should we start the daemon?";
+                // start only if not disabled and we have a dbus connection at all
+                if (!disableAutolaunch && QDBusConnection::sessionBus().interface()) {
+                    qCDebug(KAMD_CORELIB) << "Starting the activity manager daemon";
+                    auto busInterface = QDBusConnection::sessionBus().interface();
+                    busInterface->asyncCall(QStringLiteral("StartServiceByName"), KAMD_DBUS_SERVICE, uint(0));
+                }
+            }
+
+            // creating a new instance of the class
+            Manager::s_instance = new Manager();
+        });
+    }
+
+    return s_instance;
+}
+
+bool Manager::isServiceRunning()
+{
+    return (s_instance ? s_instance->m_serviceRunning : true) && QDBusConnection::sessionBus().interface()
+        && QDBusConnection::sessionBus().interface()->isServiceRegistered(KAMD_DBUS_SERVICE);
+}
+
+void Manager::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
+{
+    Q_UNUSED(oldOwner);
+
+    if (serviceName == KAMD_DBUS_SERVICE) {
+        m_serviceRunning = !newOwner.isEmpty();
+        Q_EMIT serviceStatusChanged(m_serviceRunning);
+
+        if (m_serviceRunning) {
+            using namespace kamd::utils;
+
+            continue_with(DBusFuture::fromReply(m_service->serviceVersion()), [this](const optional_view<QString> &serviceVersion) {
+                // Test whether the service is older than the library.
+                // If it is, we need to end this
+
+                if (!serviceVersion.is_initialized()) {
+                    qWarning() << "KActivities: FATAL ERROR: Failed to contact the activity manager daemon";
+                    m_serviceRunning = false;
+                    return;
+                }
+
+                auto split = serviceVersion->split(QLatin1Char('.'));
+                QList<int> version;
+
+                // We require kactivitymanagerd version to be at least the
+                // one before the repository split
+                const int requiredVersion[] = {6, 2, 0};
+
+                std::transform(split.cbegin(), split.cend(), std::back_inserter(version), [](const QString &component) {
+                    return component.toInt();
+                });
+
+                // if required version is greater than the current version
+                if (std::lexicographical_compare(version.cbegin(), version.cend(), std::begin(requiredVersion), std::end(requiredVersion))) {
+                    QString libraryVersion = QString::number(requiredVersion[0]) + QLatin1Char('.') + QString::number(requiredVersion[1]) + QLatin1Char('.')
+                        + QString::number(requiredVersion[2]);
+
+                    qDebug() << "KActivities service version: " << serviceVersion.get();
+                    qDebug() << "KActivities library version: " << libraryVersion;
+                    qFatal("KActivities: FATAL ERROR: The service is older than the library");
+                }
+            });
+        }
+    }
+}
+
+Service::Activities *Manager::activities()
+{
+    return self()->m_activities;
+}
+
+Service::Resources *Manager::resources()
+{
+    return self()->m_resources;
+}
+
+Service::ResourcesLinking *Manager::resourcesLinking()
+{
+    return self()->m_resourcesLinking;
+}
+
+Service::Features *Manager::features()
+{
+    return self()->m_features;
+}
+
+} // namespace KActivities
diff --git a/src/lib/manager_p.h b/src/lib/manager_p.h
new file mode 100644 (file)
index 0000000..3472a0d
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+    SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_MANAGER_P
+#define ACTIVITIES_MANAGER_P
+
+#include <common/dbus/org.kde.ActivityManager.Activities.h>
+
+#include "activities_interface.h"
+#include "application_interface.h"
+#include "features_interface.h"
+#include "resources_interface.h"
+#include "resources_linking_interface.h"
+
+#include <QDBusServiceWatcher>
+
+namespace Service = org::kde::ActivityManager;
+
+namespace KActivities
+{
+class Manager : public QObject
+{
+    Q_OBJECT
+
+public:
+    static Manager *self();
+
+    static bool isServiceRunning();
+
+    static Service::Activities *activities();
+    static Service::Resources *resources();
+    static Service::ResourcesLinking *resourcesLinking();
+    static Service::Features *features();
+
+public Q_SLOTS:
+    void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner);
+
+Q_SIGNALS:
+    void serviceStatusChanged(bool status);
+
+private:
+    Manager();
+
+    QDBusServiceWatcher m_watcher;
+
+    static Manager *s_instance;
+
+    Service::Application *const m_service;
+    Service::Activities *const m_activities;
+    Service::Resources *const m_resources;
+    Service::ResourcesLinking *const m_resourcesLinking;
+    Service::Features *const m_features;
+    bool m_serviceRunning;
+
+    friend class ManagerInstantiator;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_MANAGER_P
diff --git a/src/lib/resourceinstance.cpp b/src/lib/resourceinstance.cpp
new file mode 100644 (file)
index 0000000..f66a820
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+    SPDX-FileCopyrightText: 2011-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "resourceinstance.h"
+#include "manager_p.h"
+
+#include "debug_p.h"
+#include <QCoreApplication>
+
+namespace KActivities
+{
+class ResourceInstancePrivate
+{
+public:
+    quintptr wid;
+    QUrl uri;
+    QString mimetype;
+    QString title;
+    QString application;
+
+    void closeResource();
+    void openResource();
+
+    enum Type {
+        Accessed = 0,
+        Opened = 1,
+        Modified = 2,
+        Closed = 3,
+        FocusedIn = 4,
+        FocusedOut = 5,
+    };
+
+    static void registerResourceEvent(const QString &application, quintptr wid, const QUrl &uri, Type event)
+    {
+        Q_ASSERT_X(!application.isEmpty(), "ResourceInstance::event", "The application id must not be empty");
+
+        if (uri.isEmpty()) {
+            return;
+        }
+
+        Manager::resources()->RegisterResourceEvent(application, wid, uri.toString(), uint(event));
+    }
+};
+
+void ResourceInstancePrivate::closeResource()
+{
+    registerResourceEvent(application, wid, uri, Closed);
+}
+
+void ResourceInstancePrivate::openResource()
+{
+    registerResourceEvent(application, wid, uri, Opened);
+}
+
+ResourceInstance::ResourceInstance(quintptr wid, QObject *parent)
+    : QObject(parent)
+    , d(new ResourceInstancePrivate())
+{
+    qCDebug(KAMD_CORELIB) << "Creating ResourceInstance: empty for now";
+    d->wid = wid;
+    d->application = QCoreApplication::instance()->applicationName();
+}
+
+ResourceInstance::ResourceInstance(quintptr wid, const QString &application, QObject *parent)
+    : QObject(parent)
+    , d(new ResourceInstancePrivate())
+{
+    qCDebug(KAMD_CORELIB) << "Creating ResourceInstance: empty for now";
+    d->wid = wid;
+    d->application = application.isEmpty() ? QCoreApplication::instance()->applicationName() : application;
+}
+
+ResourceInstance::ResourceInstance(quintptr wid, QUrl resourceUri, const QString &mimetype, const QString &title, const QString &application, QObject *parent)
+    : QObject(parent)
+    , d(new ResourceInstancePrivate())
+{
+    qCDebug(KAMD_CORELIB) << "Creating ResourceInstance:" << resourceUri;
+    d->wid = wid;
+    d->uri = resourceUri.adjusted(QUrl::StripTrailingSlash);
+    d->application = application.isEmpty() ? QCoreApplication::instance()->applicationName() : application;
+
+    d->openResource();
+
+    setTitle(title);
+    setMimetype(mimetype);
+}
+
+ResourceInstance::~ResourceInstance()
+{
+    d->closeResource();
+}
+
+void ResourceInstance::notifyModified()
+{
+    d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::Modified);
+}
+
+void ResourceInstance::notifyFocusedIn()
+{
+    d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::FocusedIn);
+}
+
+void ResourceInstance::notifyFocusedOut()
+{
+    d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::FocusedOut);
+}
+
+void ResourceInstance::setUri(const QUrl &newUri)
+{
+    if (d->uri == newUri) {
+        return;
+    }
+
+    if (!d->uri.isEmpty()) {
+        d->closeResource();
+    }
+
+    d->uri = newUri.adjusted(QUrl::StripTrailingSlash);
+
+    d->openResource();
+}
+
+void ResourceInstance::setMimetype(const QString &mimetype)
+{
+    if (mimetype.isEmpty()) {
+        return;
+    }
+
+    d->mimetype = mimetype;
+    // TODO: update the service info
+    Manager::resources()->RegisterResourceMimetype(d->uri.toString(), mimetype);
+}
+
+void ResourceInstance::setTitle(const QString &title)
+{
+    qCDebug(KAMD_CORELIB) << "Setting the title:" << title;
+    if (title.isEmpty()) {
+        return;
+    }
+
+    d->title = title;
+    // TODO: update the service info
+    Manager::resources()->RegisterResourceTitle(d->uri.toString(), title);
+}
+
+QUrl ResourceInstance::uri() const
+{
+    return d->uri;
+}
+
+QString ResourceInstance::mimetype() const
+{
+    return d->mimetype;
+}
+
+QString ResourceInstance::title() const
+{
+    return d->title;
+}
+
+quintptr ResourceInstance::winId() const
+{
+    return d->wid;
+}
+
+void ResourceInstance::notifyAccessed(const QUrl &uri, const QString &application)
+{
+    ResourceInstancePrivate::registerResourceEvent(application.isEmpty() ? QCoreApplication::instance()->applicationName() : application,
+                                                   0,
+                                                   uri,
+                                                   ResourceInstancePrivate::Accessed);
+}
+
+} // namespace KActivities
diff --git a/src/lib/resourceinstance.h b/src/lib/resourceinstance.h
new file mode 100644 (file)
index 0000000..13b8d78
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+    SPDX-FileCopyrightText: 2011-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_RESOURCEINSTANCE_H
+#define ACTIVITIES_RESOURCEINSTANCE_H
+
+#include <QObject>
+#include <QUrl>
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class ResourceInstancePrivate;
+
+/**
+ * This class is used to notify the system that a file, web page
+ * or some other resource has been accessed.
+ *
+ * It provides methods to notify the system when the resource was
+ * opened, modified and closed, along with in what window the
+ * resource is shown.
+ *
+ * You should create an instance of this class for every resource
+ * you open.
+ *
+ * "The system" in this case can be the backend for tracking
+ * and automatically scoring files that are being accessed, the
+ * system to show the open files per window in the taskbar,
+ * the share-like-connect, etc.
+ *
+ * The user of this class shouldn't care about the backend
+ * systems - everything is done under-the-hood automatically.
+ *
+ */
+class KACTIVITIES_EXPORT ResourceInstance : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QUrl uri READ uri WRITE setUri)
+    Q_PROPERTY(QString mimetype READ mimetype WRITE setMimetype)
+    Q_PROPERTY(QString title READ title WRITE setTitle)
+    Q_PROPERTY(quintptr winId READ winId)
+
+public:
+    /**
+     * Creates a new resource instance
+     * @param wid id of the window that will show the resource
+     * @param parent pointer to the parent object
+     * @since 4.10
+     */
+    explicit ResourceInstance(quintptr wid, QObject *parent = nullptr);
+
+    /**
+     * Creates a new resource instance
+     * @param wid id of the window that will show the resource
+     * @param application application's name (the name used for the .desktop file).
+     *        If not specified, QCoreApplication::applicationName is used
+     * @param parent pointer to the parent object
+     */
+    explicit ResourceInstance(quintptr wid, const QString &application, QObject *parent = nullptr);
+
+    /**
+     * Creates a new resource instance and automatically
+     * notifies the system that it was opened.
+     *
+     * In some special cases, where the URI of the resource is
+     * being constantly changed (for example, in the world globe,
+     * street map applications) you have two options:
+     *  - to pass an empty resourceUri while passing the mimetype
+     *  - to update the uri from time to time (in the example of
+     *    the world map - to send URIs for major objects - cities
+     *    or main streets.
+     * and in both cases reimplementing the currentUri() method
+     * which will return the exact URI shown at that specific moment.
+     *
+     * @param wid window id in which the resource is shown
+     * @param resourceUri URI of the resource that is shown
+     * @param mimetype the mime type of the resource
+     * @param title the title of the resource
+     * @param application application's name (the name used for the .desktop file).
+     *        If not specified, QCoreApplication::applicationName is used
+     * @param parent pointer to the parent object
+     */
+    ResourceInstance(quintptr wid,
+                     QUrl resourceUri,
+                     const QString &mimetype = QString(),
+                     const QString &title = QString(),
+                     const QString &application = QString(),
+                     QObject *parent = nullptr);
+
+    /**
+     * Destroys the ResourceInstance and notifies the system
+     * that the resource has been closed
+     */
+    ~ResourceInstance() override;
+
+public Q_SLOTS:
+    /**
+     * Call this method to notify the system that you modified
+     * (the contents of) the resource
+     */
+    void notifyModified();
+
+    /**
+     * Call this method to notify the system that the resource
+     * has the focus in your application
+     * @note You only need to call this in MDI applications
+     */
+    void notifyFocusedIn();
+
+    /**
+     * Call this method to notify the system that the resource
+     * lost the focus in your application
+     * @note You only need to call this in MDI applications
+     */
+    void notifyFocusedOut();
+
+    /**
+     * This is a convenience method that sets the new URI.
+     * This is usually handled by sending the close event for
+     * the previous URI, and an open event for the new one.
+     */
+    void setUri(const QUrl &newUri);
+
+    /**
+     * Sets the mimetype for this resource
+     */
+    void setMimetype(const QString &mimetype);
+
+    /**
+     * Sets the title for this resource
+     */
+    void setTitle(const QString &title);
+
+Q_SIGNALS:
+    /**
+     * Emitted when the system wants to show the resource
+     * represented by this ResourceInstance.
+     *
+     * You should listen to this signal if you have multiple
+     * resources shown in one window (MDI). On catching it, show
+     * the resource and give it focus.
+     */
+    void requestsFocus();
+
+public:
+    /**
+     * @returns the current uri
+     * The default implementation returns the URI that was passed
+     * to the constructor.
+     * You need to reimplement it only for the applications with
+     * frequently updated URIs.
+     */
+    virtual QUrl uri() const;
+
+    /**
+     * @returns mimetype of the resource
+     */
+    QString mimetype() const;
+
+    /**
+     * @returns title of the resource
+     */
+    QString title() const;
+
+    /**
+     * @returns the window id
+     */
+    quintptr winId() const;
+
+    /**
+     * If there's no way to tell for how long an application is keeping
+     * the resource open, you can just call this static method - it
+     * will notify the system that the application has accessed the
+     * resource
+     * @param uri URI of the resource
+     * @param application application's name (the name used for the .desktop file).
+     *        If not specified, QCoreApplication::applicationName is used
+     *
+     */
+    static void notifyAccessed(const QUrl &uri, const QString &application = QString());
+
+private:
+    const QScopedPointer<ResourceInstancePrivate> d;
+};
+}
+
+#endif // ACTIVITIES_RESOURCEINSTANCE_H
diff --git a/src/lib/version.cpp b/src/lib/version.cpp
new file mode 100644 (file)
index 0000000..1674ee6
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    SPDX-FileCopyrightText: 2008 Aaron Seigo <aseigo@kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "version.h"
+
+namespace KActivities
+{
+unsigned int version()
+{
+    return KACTIVITIES_VERSION;
+}
+
+unsigned int versionMajor()
+{
+    return KACTIVITIES_VERSION_MAJOR;
+}
+
+unsigned int versionMinor()
+{
+    return KACTIVITIES_VERSION_MINOR;
+}
+
+unsigned int versionRelease()
+{
+    return KACTIVITIES_VERSION_RELEASE;
+}
+
+const char *versionString()
+{
+    return KACTIVITIES_VERSION_STRING;
+}
+
+} // KActivities namespace
diff --git a/src/lib/version.h b/src/lib/version.h
new file mode 100644 (file)
index 0000000..ec849c7
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+    SPDX-FileCopyrightText: 2008-2016 Aaron Seigo <aseigo@kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_VERSION_BIN_H
+#define KACTIVITIES_VERSION_BIN_H
+
+/** @file version.h <KActivities/Version> */
+
+#include "kactivities_export.h"
+#include <kactivities_version.h>
+
+#define KACTIVITIES_VERSION_RELEASE KACTIVITIES_VERSION_PATCH
+
+/**
+ * Namespace for everything in libkactivities
+ */
+namespace KActivities
+{
+/**
+ * The runtime version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int version();
+
+/**
+ * The runtime major version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int versionMajor();
+
+/**
+ * The runtime major version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int versionMinor();
+
+/**
+ * The runtime major version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int versionRelease();
+
+/**
+ * The runtime version string of libkactivities
+ */
+KACTIVITIES_EXPORT const char *versionString();
+
+} // KActivities namespace
+
+#endif // multiple inclusion guard
diff --git a/src/utils/continue_with.h b/src/utils/continue_with.h
new file mode 100644 (file)
index 0000000..a69c85a
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+    SPDX-FileCopyrightText: 2014-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef UTILS_CONTINUE_WITH_H
+#define UTILS_CONTINUE_WITH_H
+
+#include <QDebug>
+#include <QFuture>
+#include <QFutureWatcher>
+
+#include "utils/optional_view.h"
+// #include <boost/optional.hpp>
+
+#ifdef ENABLE_QJSVALUE_CONTINUATION
+#include <QJSValue>
+#endif
+
+namespace kamd
+{
+namespace utils
+{
+namespace detail
+{ //_
+#ifdef ENABLE_QJSVALUE_CONTINUATION
+inline void test_continuation(const QJSValue &continuation)
+{
+    if (!continuation.isCallable()) {
+        qWarning() << "Passed handler is not callable: " << continuation.toString();
+    }
+}
+
+template<typename _ReturnType>
+inline void pass_value(const QFuture<_ReturnType> &future, QJSValue continuation)
+{
+    auto result = continuation.call({future.result()});
+    if (result.isError()) {
+        qWarning() << "Handler returned this error: " << result.toString();
+    }
+}
+
+inline void pass_value(const QFuture<void> &future, QJSValue continuation)
+{
+    Q_UNUSED(future);
+    auto result = continuation.call({});
+    if (result.isError()) {
+        qWarning() << "Handler returned this error: " << result.toString();
+    }
+}
+#endif
+
+template<typename _Continuation>
+inline void test_continuation(_Continuation &&continuation)
+{
+    Q_UNUSED(continuation);
+}
+
+template<typename _ReturnType, typename _Continuation>
+inline void pass_value(const QFuture<_ReturnType> &future, _Continuation &&continuation)
+{
+    using namespace kamd::utils;
+    continuation(future.resultCount() > 0 ? make_optional_view(future.result()) : none());
+}
+
+template<typename _Continuation>
+inline void pass_value(_Continuation &&continuation)
+{
+    continuation();
+}
+
+} //^ namespace detail
+
+template<typename _ReturnType, typename _Continuation>
+inline void continue_with(const QFuture<_ReturnType> &future, _Continuation &&continuation)
+{
+    detail::test_continuation(continuation);
+
+    auto watcher = new QFutureWatcher<_ReturnType>();
+    QObject::connect(watcher, &QFutureWatcherBase::finished, [=]() mutable {
+        detail::pass_value(future, continuation);
+    });
+
+    watcher->setFuture(future);
+}
+
+} // namespace utils
+} // namespace kamd
+
+#endif /* !UTILS_CONTINUE_WITH_H */
diff --git a/src/utils/dbusfuture_p.cpp b/src/utils/dbusfuture_p.cpp
new file mode 100644 (file)
index 0000000..96109d2
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "dbusfuture_p.h"
+
+namespace DBusFuture
+{
+namespace detail
+{ //_
+
+template<>
+void DBusCallFutureInterface<void>::callFinished()
+{
+    deleteLater();
+
+    // qDebug() << "This is call end";
+
+    this->reportFinished();
+}
+
+ValueFutureInterface<void>::ValueFutureInterface()
+{
+}
+
+QFuture<void> ValueFutureInterface<void>::start()
+{
+    auto future = this->future();
+
+    this->reportFinished();
+
+    deleteLater();
+
+    return future;
+}
+
+} //^ namespace detail
+
+QFuture<void> fromVoid()
+{
+    using namespace detail;
+
+    auto valueFutureInterface = new ValueFutureInterface<void>();
+
+    return valueFutureInterface->start();
+}
+
+} // namespace DBusFuture
diff --git a/src/utils/dbusfuture_p.h b/src/utils/dbusfuture_p.h
new file mode 100644 (file)
index 0000000..a32d91c
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+    SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_DBUSFUTURE_P_H
+#define ACTIVITIES_DBUSFUTURE_P_H
+
+#include <QDBusAbstractInterface>
+#include <QDBusPendingCallWatcher>
+#include <QDBusPendingReply>
+#include <QDBusServiceWatcher>
+#include <QFuture>
+#include <QFutureInterface>
+#include <QFutureWatcherBase>
+
+#include "debug_p.h"
+
+namespace DBusFuture
+{
+namespace detail
+{ //_
+
+template<typename _Result>
+class DBusCallFutureInterface : public QObject, public QFutureInterface<_Result>
+{
+public:
+    DBusCallFutureInterface(QDBusPendingReply<_Result> reply)
+        : reply(reply)
+        , replyWatcher(nullptr)
+    {
+    }
+
+    ~DBusCallFutureInterface() override
+    {
+        delete replyWatcher;
+    }
+
+    void callFinished();
+
+    QFuture<_Result> start()
+    {
+        replyWatcher = new QDBusPendingCallWatcher(reply);
+
+        QObject::connect(replyWatcher, &QDBusPendingCallWatcher::finished, [this]() {
+            callFinished();
+        });
+
+        this->reportStarted();
+
+        if (reply.isFinished()) {
+            this->callFinished();
+        }
+
+        return this->future();
+    }
+
+private:
+    QDBusPendingReply<_Result> reply;
+    QDBusPendingCallWatcher *replyWatcher;
+};
+
+template<typename _Result>
+void DBusCallFutureInterface<_Result>::callFinished()
+{
+    deleteLater();
+
+    if (!reply.isError()) {
+        this->reportResult(reply.value());
+    }
+
+    this->reportFinished();
+}
+
+template<>
+void DBusCallFutureInterface<void>::callFinished();
+
+template<typename _Result>
+class ValueFutureInterface : public QObject, QFutureInterface<_Result>
+{
+public:
+    ValueFutureInterface(const _Result &value)
+        : value(value)
+    {
+    }
+
+    QFuture<_Result> start()
+    {
+        auto future = this->future();
+
+        this->reportResult(value);
+        this->reportFinished();
+
+        deleteLater();
+
+        return future;
+    }
+
+private:
+    _Result value;
+};
+
+template<>
+class ValueFutureInterface<void> : public QObject, QFutureInterface<void>
+{
+public:
+    ValueFutureInterface();
+
+    QFuture<void> start();
+    // {
+    //     auto future = this->future();
+    //     this->reportFinished();
+    //     deleteLater();
+    //     return future;
+    // }
+};
+
+} //^ namespace detail
+
+template<typename _Result>
+QFuture<_Result> asyncCall(QDBusAbstractInterface *interface,
+                           const QString &method,
+                           const QVariant &arg1 = QVariant(),
+                           const QVariant &arg2 = QVariant(),
+                           const QVariant &arg3 = QVariant(),
+                           const QVariant &arg4 = QVariant(),
+                           const QVariant &arg5 = QVariant(),
+                           const QVariant &arg6 = QVariant(),
+                           const QVariant &arg7 = QVariant(),
+                           const QVariant &arg8 = QVariant())
+{
+    using namespace detail;
+
+    auto callFutureInterface = new DBusCallFutureInterface<_Result>(interface->asyncCall(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8));
+
+    return callFutureInterface->start();
+}
+
+template<typename _Result>
+QFuture<_Result> fromValue(const _Result &value)
+{
+    using namespace detail;
+
+    auto valueFutureInterface = new ValueFutureInterface<_Result>(value);
+
+    return valueFutureInterface->start();
+}
+
+template<typename _Result>
+QFuture<_Result> fromReply(const QDBusPendingReply<_Result> &reply)
+{
+    using namespace detail;
+
+    auto callFutureInterface = new DBusCallFutureInterface<_Result>(reply);
+
+    return callFutureInterface->start();
+}
+
+QFuture<void> fromVoid();
+
+} // namespace DBusFuture
+
+#endif /* DBUSFUTURE_P_H */
diff --git a/src/utils/model_updaters.h b/src/utils/model_updaters.h
new file mode 100644 (file)
index 0000000..1ab731a
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_MODEL_UPDATERS_H
+#define KACTIVITIES_MODEL_UPDATERS_H
+
+// -----------------------------------------
+// RAII classes for model updates ----------
+// -----------------------------------------
+
+// clang-format off
+#define DECLARE_RAII_MODEL_UPDATERS(Class)                                     \
+    template <typename T> class _model_reset {                                 \
+        T *model;                                                              \
+                                                                               \
+    public:                                                                    \
+        _model_reset(T *m) : model(m)                                          \
+        {                                                                      \
+            model->beginResetModel();                                          \
+        }                                                                      \
+        ~_model_reset()                                                        \
+        {                                                                      \
+            model->endResetModel();                                            \
+        }                                                                      \
+    };                                                                         \
+    template <typename T> class _model_insert {                                \
+        T *model;                                                              \
+                                                                               \
+    public:                                                                    \
+        _model_insert(T *m, const QModelIndex &parent, int first, int last)    \
+            : model(m)                                                         \
+        {                                                                      \
+            model->beginInsertRows(parent, first, last);                       \
+        }                                                                      \
+        ~_model_insert()                                                       \
+        {                                                                      \
+            model->endInsertRows();                                            \
+        }                                                                      \
+    };                                                                         \
+    template <typename T> class _model_remove {                                \
+        T *model;                                                              \
+                                                                               \
+    public:                                                                    \
+        _model_remove(T *m, const QModelIndex &parent, int first, int last)    \
+            : model(m)                                                         \
+        {                                                                      \
+            model->beginRemoveRows(parent, first, last);                       \
+        }                                                                      \
+        ~_model_remove()                                                       \
+        {                                                                      \
+            model->endRemoveRows();                                            \
+        }                                                                      \
+    };                                                                         \
+    typedef _model_reset<Class> model_reset;                                   \
+    typedef _model_remove<Class> model_remove;                                 \
+    typedef _model_insert<Class> model_insert;
+
+// -----------------------------------------
+
+#endif // KACTIVITIES_MODEL_UPDATERS_H
+
diff --git a/src/utils/optional_view.h b/src/utils/optional_view.h
new file mode 100644 (file)
index 0000000..f7f8682
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+    SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef UTILS_OPTIONAL_VIEW_H
+#define UTILS_OPTIONAL_VIEW_H
+
+namespace kamd
+{
+namespace utils
+{
+struct none_t {
+};
+inline const none_t none()
+{
+    return none_t();
+}
+
+// A simple implementation of the optional class
+// until we can rely on std::optional.
+// It is not going to come close in the supported
+// features to the std one.
+// (we need it in the core library, so we don't
+// want to use boost.optional)
+template<typename T>
+class optional_view
+{
+public:
+    explicit optional_view(const T &value)
+        : m_value(&value)
+    {
+    }
+
+    optional_view(const none_t &)
+        : m_value(nullptr)
+    {
+    }
+
+    bool is_initialized() const
+    {
+        return m_value != nullptr;
+    }
+
+    const T &get() const
+    {
+        return *m_value;
+    }
+
+    const T *operator->() const
+    {
+        return m_value;
+    }
+
+private:
+    const T *const m_value;
+};
+
+template<typename T>
+optional_view<T> make_optional_view(const T &value)
+{
+    return optional_view<T>(value);
+}
+
+} // namespace utils
+} // namespace kamd
+
+#endif // UTILS_OPTIONAL_VIEW_H
diff --git a/src/utils/ptr_to.h b/src/utils/ptr_to.h
new file mode 100644 (file)
index 0000000..e13ef23
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+    SPDX-FileCopyrightText: 2015-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef PTR_TO_H
+#define PTR_TO_H
+
+namespace kamd
+{
+namespace utils
+{
+enum {
+    Const = 0,
+    Mutable = 1,
+};
+
+template<typename T, int Policy = Const>
+struct ptr_to {
+    typedef const T *const type;
+};
+
+template<typename T>
+struct ptr_to<T, Mutable> {
+    typedef T *const type;
+};
+
+} // namespace utils
+} // namespace kamd
+
+#endif // PTR_TO_H
diff --git a/src/utils/qflatset.h b/src/utils/qflatset.h
new file mode 100644 (file)
index 0000000..5d235cd
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+    SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KACTIVITIES_STATS_QFLATSET_H
+#define KACTIVITIES_STATS_QFLATSET_H
+
+#include <QPair>
+#include <QVector>
+
+namespace KActivities
+{
+template<typename T, typename LessThan>
+class QFlatSet : public QVector<T>
+{
+public:
+    QFlatSet()
+    {
+    }
+
+    inline
+        // QPair<typename QVector<T>::iterator, bool> insert(const T &value)
+        std::tuple<typename QVector<T>::iterator, int, bool>
+        insert(const T &value)
+    {
+        auto lessThan = LessThan();
+        auto begin = this->begin();
+        auto end = this->end();
+
+        if (begin == end) {
+            QVector<T>::insert(0, value);
+
+            return std::make_tuple(QVector<T>::begin(), 0, true);
+
+        } else {
+            auto iterator = std::lower_bound(begin, end, value, lessThan);
+
+            if (iterator != end) {
+                if (!lessThan(value, *iterator)) {
+                    // Already present
+                    return std::make_tuple(iterator, iterator - begin, false);
+                }
+            }
+
+            QVector<T>::insert(iterator, value);
+
+            return std::make_tuple(iterator, iterator - begin, true);
+        }
+    }
+
+private:
+    QFlatSet(const QFlatSet &original); // = delete
+};
+
+} // namespace KActivities
+
+#endif // KACTIVITIES_STATS_QFLATSET_H
diff --git a/src/utils/range.h b/src/utils/range.h
new file mode 100644 (file)
index 0000000..0208b09
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef UTILS_RANGE_H
+#define UTILS_RANGE_H
+
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+#include <boost/range/algorithm/copy.hpp>
+
+/********************************************************************
+ *  Syntactic sugar for converting ranges to collections            *
+ ********************************************************************/
+
+namespace kamd
+{
+namespace utils
+{
+template<typename Collection, typename Range>
+__inline Collection as_collection(Range range)
+{
+    Collection result;
+
+    boost::copy(range, std::back_inserter(result));
+
+    return result;
+}
+
+template<typename Member, typename... Args>
+__inline auto transformed(Member member, Args... args) -> decltype(boost::adaptors::transformed(std::bind(member, args..., std::placeholders::_1)))
+{
+    return boost::adaptors::transformed(std::bind(member, args..., std::placeholders::_1));
+}
+
+template<typename Member, typename... Args>
+__inline auto filtered(Member member, Args... args) -> decltype(boost::adaptors::filtered(std::bind(member, args..., std::placeholders::_1)))
+{
+    return boost::adaptors::filtered(std::bind(member, args..., std::placeholders::_1));
+}
+
+template<typename Class, typename Member>
+__inline auto filtered(Class *const self, Member member) -> decltype(boost::adaptors::filtered(std::bind(member, self, std::placeholders::_1)))
+{
+    return boost::adaptors::filtered(std::bind(member, self, std::placeholders::_1));
+}
+
+} // namespace utils
+} // namespace kamd
+
+#endif // UTILS_RANGE_H
diff --git a/src/utils/remove_if.h b/src/utils/remove_if.h
new file mode 100644 (file)
index 0000000..0aa2700
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+    SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef UTILS_REMOVE_IF_H
+#define UTILS_REMOVE_IF_H
+
+#include <algorithm>
+
+/********************************************************************
+ *  Syntactic sugar for the erase-remove idiom                      *
+ ********************************************************************/
+
+namespace kamd
+{
+namespace utils
+{
+template<typename Collection, typename Filter>
+__inline void remove_if(Collection &collection, Filter filter)
+{
+    collection.erase(std::remove_if(collection.begin(), collection.end(), filter), collection.end());
+}
+
+} // namespace utils
+} // namespace kamd
+
+#endif // UTILS_REMOVE_IF_H
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9d268e8
--- /dev/null
@@ -0,0 +1,6 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+# add_subdirectory(slc-interface)
+add_subdirectory(activities-model)
+
+
diff --git a/tests/activities-model/CMakeLists.txt b/tests/activities-model/CMakeLists.txt
new file mode 100644 (file)
index 0000000..cded02a
--- /dev/null
@@ -0,0 +1,40 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesModelTestApp)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+find_package (KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS WindowSystem)
+
+include_directories (
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+   )
+
+set (
+   KActivitiesModelTestApp_SRCS
+   window.cpp
+   main.cpp
+   )
+
+qt_wrap_ui(
+   KActivitiesModelTestApp_SRCS
+   window.ui
+   )
+
+if (NOT WIN32)
+
+   add_executable (
+      KActivitiesModelTestApp
+      ${KActivitiesModelTestApp_SRCS}
+      )
+
+   target_link_libraries (
+      KActivitiesModelTestApp
+      Qt${QT_MAJOR_VERSION}::Core
+      Qt${QT_MAJOR_VERSION}::Gui
+      Qt${QT_MAJOR_VERSION}::Widgets
+      Qt${QT_MAJOR_VERSION}::DBus
+      KF5::Activities
+      KF5::WindowSystem
+      )
+
+endif ()
diff --git a/tests/activities-model/main.cpp b/tests/activities-model/main.cpp
new file mode 100644 (file)
index 0000000..a471c4c
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+    SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+
+    Window w;
+    w.show();
+
+    return app.exec();
+}
diff --git a/tests/activities-model/window.cpp b/tests/activities-model/window.cpp
new file mode 100644 (file)
index 0000000..944aab6
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+    SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+
+#include "ui_window.h"
+
+#include <QItemDelegate>
+#include <QPainter>
+
+#include <KWindowSystem>
+
+class Delegate : public QItemDelegate
+{
+public:
+    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
+    {
+        painter->save();
+
+        const QString title = index.data().toString();
+
+        QRect titleRect = painter->fontMetrics().boundingRect(title);
+        // unused int lineHeight = titleRect.height();
+
+        // Header background
+        auto rect = option.rect;
+        rect.setHeight(64);
+        titleRect.moveTop(option.rect.top());
+        titleRect.setWidth(option.rect.width());
+
+        if (index.data(KActivities::ActivitiesModel::ActivityIsCurrent).toBool()) {
+            painter->fillRect(rect, QColor(64, 64, 64));
+        } else {
+            painter->fillRect(rect, QColor(32, 32, 32));
+        }
+
+        // Painting the title
+        painter->setPen(QColor(255, 255, 255));
+
+        titleRect.moveTop(titleRect.top() + 8);
+        titleRect.setLeft(64 + 8);
+        titleRect.setWidth(titleRect.width() - 64 - 8);
+        painter->drawText(titleRect, title);
+
+        titleRect.moveTop(titleRect.bottom() + 16);
+
+        const QString description = index.data(KActivities::ActivitiesModel::ActivityDescription).toString();
+
+        if (!description.isEmpty()) {
+            painter->drawText(titleRect, index.data(KActivities::ActivitiesModel::ActivityDescription).toString());
+        } else {
+            painter->setPen(QColor(128, 128, 128));
+            painter->drawText(titleRect, index.data(KActivities::ActivitiesModel::ActivityId).toString());
+        }
+
+        const QString iconName = index.data(KActivities::ActivitiesModel::ActivityIconSource).toString();
+
+        if (!iconName.isEmpty()) {
+            painter->drawPixmap(option.rect.x(), option.rect.y(), QIcon::fromTheme(iconName).pixmap(64, 64));
+        }
+
+        painter->restore();
+    }
+
+    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
+    {
+        Q_UNUSED(option);
+        Q_UNUSED(index);
+        return QSize(0, 70);
+    }
+};
+
+Window::Window()
+    : ui(new Ui::MainWindow())
+    , activities(new KActivities::Consumer(this))
+    , modelRunningActivities(new KActivities::ActivitiesModel({KActivities::Info::Running, KActivities::Info::Stopping}, this))
+    , modelStoppedActivities(new KActivities::ActivitiesModel({KActivities::Info::Stopped, KActivities::Info::Starting}, this))
+{
+    ui->setupUi(this);
+
+    modelRunningActivities->setObjectName(QStringLiteral("RUNNING"));
+    ui->listRunningActivities->setModel(modelRunningActivities);
+    ui->listRunningActivities->setItemDelegate(new Delegate());
+
+    modelStoppedActivities->setObjectName(QStringLiteral("STOPPED"));
+    ui->listStoppedActivities->setModel(modelStoppedActivities);
+    ui->listStoppedActivities->setItemDelegate(new Delegate());
+
+    qDebug() << connect(activities, &KActivities::Consumer::runningActivitiesChanged, this, [](const QStringList &running) {
+        qDebug() << running;
+    });
+}
+
+void Window::showEvent(QShowEvent *event)
+{
+    Q_UNUSED(event);
+    KWindowSystem::self()->setOnActivities(effectiveWinId(), QStringList());
+    KWindowSystem::self()->setOnAllDesktops(effectiveWinId(), true);
+}
+
+Window::~Window()
+{
+    delete ui;
+}
diff --git a/tests/activities-model/window.h b/tests/activities-model/window.h
new file mode 100644 (file)
index 0000000..10a9589
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+    SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <QMainWindow>
+
+#include <activitiesmodel.h>
+#include <consumer.h>
+
+namespace Ui
+{
+class MainWindow;
+}
+
+class Window : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    Window();
+    ~Window() override;
+
+protected:
+    void showEvent(QShowEvent *event) override;
+
+private:
+    Ui::MainWindow *ui;
+    KActivities::Consumer *activities;
+    KActivities::ActivitiesModel *modelRunningActivities;
+    KActivities::ActivitiesModel *modelStoppedActivities;
+};
diff --git a/tests/activities-model/window.ui b/tests/activities-model/window.ui
new file mode 100644 (file)
index 0000000..6e566b1
--- /dev/null
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>406</width>
+    <height>869</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout_3" stretch="2,1,0">
+    <item>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <item>
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Running</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QListView" name="listRunningActivities"/>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Stopped</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QListView" name="listStoppedActivities"/>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <widget class="QPushButton" name="buttonClose">
+      <property name="minimumSize">
+       <size>
+        <width>0</width>
+        <height>48</height>
+       </size>
+      </property>
+      <property name="text">
+       <string>Close</string>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonClose</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>332</x>
+     <y>842</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>341</x>
+     <y>878</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/tests/imports/activities.qml b/tests/imports/activities.qml
new file mode 100644 (file)
index 0000000..eca5b18
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+    SPDX-FileCopyrightText: 2013 Heena Mahour <heena393@gmail.com>
+    SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick 2.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+import org.kde.activities 0.1 as Activities
+
+ListView {
+    id: main
+
+    property int minimumWidth: 32
+    property int minimumHeight: 32 // theme.mSize(theme.defaultFont).height * 14
+    property int implicitWidth: minimumWidth * 1.5
+    property int implicitHeight: minimumHeight * 1.5
+
+    property int formFactor: plasmoid.formFactor
+
+
+
+    model: modelMain
+
+    Activities.ActivityModel {
+        id: modelMain
+    }
+
+    add: Transition {
+        NumberAnimation { properties: "x"; from: 300; duration: 1000 }
+    }
+
+    addDisplaced: Transition {
+       NumberAnimation { properties: "x,y"; duration: 1000 }
+    }
+
+    remove: Transition {
+        NumberAnimation { properties: "x"; to: 300; duration: 1000 }
+    }
+
+    removeDisplaced: Transition {
+        NumberAnimation { properties: "x,y"; duration: 1000 }
+    }
+
+    ListModel {
+        id: modelDummy
+
+        ListElement {
+            name: "Bill Smith"
+            number: "555 3264"
+        }
+        ListElement {
+            name: "John Brown"
+            number: "555 8426"
+        }
+        ListElement {
+            name: "Sam Wise"
+            number: "555 0473"
+        }
+    }
+
+    delegate: Column {
+        height: 32
+        Text {
+            text: name
+            height: 16
+            font.bold: true
+        }
+        Text {
+            text: "   id: " + id
+            height: 16
+        }
+
+    }
+}
diff --git a/tests/imports/org.kde.listactivitiestest/contents/ui/main.qml b/tests/imports/org.kde.listactivitiestest/contents/ui/main.qml
new file mode 100644 (file)
index 0000000..eca5b18
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+    SPDX-FileCopyrightText: 2013 Heena Mahour <heena393@gmail.com>
+    SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick 2.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+import org.kde.activities 0.1 as Activities
+
+ListView {
+    id: main
+
+    property int minimumWidth: 32
+    property int minimumHeight: 32 // theme.mSize(theme.defaultFont).height * 14
+    property int implicitWidth: minimumWidth * 1.5
+    property int implicitHeight: minimumHeight * 1.5
+
+    property int formFactor: plasmoid.formFactor
+
+
+
+    model: modelMain
+
+    Activities.ActivityModel {
+        id: modelMain
+    }
+
+    add: Transition {
+        NumberAnimation { properties: "x"; from: 300; duration: 1000 }
+    }
+
+    addDisplaced: Transition {
+       NumberAnimation { properties: "x,y"; duration: 1000 }
+    }
+
+    remove: Transition {
+        NumberAnimation { properties: "x"; to: 300; duration: 1000 }
+    }
+
+    removeDisplaced: Transition {
+        NumberAnimation { properties: "x,y"; duration: 1000 }
+    }
+
+    ListModel {
+        id: modelDummy
+
+        ListElement {
+            name: "Bill Smith"
+            number: "555 3264"
+        }
+        ListElement {
+            name: "John Brown"
+            number: "555 8426"
+        }
+        ListElement {
+            name: "Sam Wise"
+            number: "555 0473"
+        }
+    }
+
+    delegate: Column {
+        height: 32
+        Text {
+            text: name
+            height: 16
+            font.bold: true
+        }
+        Text {
+            text: "   id: " + id
+            height: 16
+        }
+
+    }
+}
diff --git a/tests/imports/org.kde.listactivitiestest/metadata.desktop b/tests/imports/org.kde.listactivitiestest/metadata.desktop
new file mode 100644 (file)
index 0000000..dae15d6
--- /dev/null
@@ -0,0 +1,62 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=Activities testing
+Name[ar]=اختبار الأنشطة
+Name[ast]=Prueba d'actividaes
+Name[ca]=Prova de les activitats
+Name[ca@valencia]=Prova de les activitats
+Name[cs]=Testování aktivit
+Name[da]=Test af aktiviteter
+Name[de]=Aktivitätentest
+Name[el]=Δοκιμές δραστηριοτήτων
+Name[en_GB]=Activities testing
+Name[es]=Prueba de actividades
+Name[fi]=Aktiviteettitesti
+Name[fr]=Test des activités
+Name[gd]=Deuchainn nan gnìomhachdan
+Name[gl]=Probas de actividades
+Name[he]=בדיקת פעילויות
+Name[hu]=Aktivitásteszt
+Name[ia]=Essayante Activitates
+Name[is]=Prófun á virknisviðum
+Name[it]=Prova delle attività
+Name[ko]=활동 테스트
+Name[lt]=Veiklų bandymas
+Name[mr]=कार्यपध्दती चाचणी
+Name[nb]=Aktiviteter
+Name[nds]=Aktiviteten-Tests
+Name[nl]=Testen van activiteiten
+Name[nn]=Aktivitetstesting
+Name[pa]=ਐਕਟਵਿਟੀ ਟੈਸਟਿੰਗ
+Name[pl]=Próba działań
+Name[pt]=Teste das actividades
+Name[pt_BR]=Teste de atividades
+Name[ro]=Testare activități
+Name[ru]=Тестирование комнат
+Name[sk]=Testovanie aktivít
+Name[sl]=Preizkušanje dejavnosti
+Name[sr]=Испробавање активности
+Name[sr@ijekavian]=Испробавање активности
+Name[sr@ijekavianlatin]=Isprobavanje aktivnosti
+Name[sr@latin]=Isprobavanje aktivnosti
+Name[sv]=Aktivitetstestning
+Name[tr]=Etkinlik sınaması
+Name[uk]=Тестування просторів дій
+Name[x-test]=xxActivities testingxx
+Name[zh_CN]=活动测试
+Name[zh_TW]=活動測試
+Icon=preferences-system-time
+Type=Service
+X-KDE-ParentApp=
+X-KDE-PluginInfo-Author=Ivan
+X-KDE-PluginInfo-Email=
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-Name=org.kde.listactivitiestest
+X-KDE-PluginInfo-Version=1.0
+X-KDE-PluginInfo-Website=plasma.kde.org
+X-KDE-ServiceTypes=Plasma/Applet
+X-Plasma-API=declarativeappletscript
+X-Plasma-DefaultSize=640,400
+X-Plasma-MainScript=ui/main.qml
+X-Plasma-RemoteLocation=
+X-KDE-PluginInfo-Category=
diff --git a/tests/imports/plasma-applet-org.kde.listactivitiestest.desktop b/tests/imports/plasma-applet-org.kde.listactivitiestest.desktop
new file mode 100644 (file)
index 0000000..b089b47
--- /dev/null
@@ -0,0 +1,104 @@
+[Desktop Entry]
+Name=List activities test
+Name[ar]=اختبار سرد الأنشطة
+Name[ca]=Prova de la llista d'activitats
+Name[ca@valencia]=Prova de la llista d'activitats
+Name[cs]=Test seznamu aktivit
+Name[da]=Oplist test af aktiviteter
+Name[de]=Aktivitätentest auflisten
+Name[el]=Λίστα δοκιμών δραστηριοτήτων
+Name[en_GB]=List activities test
+Name[es]=Prueba de listado de actividades
+Name[fi]=Aktiviteettien listaamistesti
+Name[fr]=Test de la liste d'activités
+Name[gd]=Seall deuchainn air na gnìomhachdan
+Name[gl]=Proba da lista de actividades
+Name[he]=בדיקת רשימת פעילויות
+Name[hu]=Aktivitáslista tesztelése
+Name[ia]=Lista essayos de activitates
+Name[is]=Gera lista með prófunum á virknisviðum
+Name[it]=Prova dell'elenco delle attività
+Name[ko]=활동 목록 테스트
+Name[lt]=Veiklų rikiavimo bandymas
+Name[mr]=कार्यपध्दती चाचणी यादी करा
+Name[nb]=List aktivitetsstester
+Name[nds]=Aktiviteten-Oplisttest
+Name[nl]=Lijst maken van activiteitentest
+Name[nn]=Vis oversikt over aktivitetstestar
+Name[pa]=ਐਕਟੀਵਿਟੀ ਟੈਸਟ ਸੂਚੀ
+Name[pl]=Próba wyszczególniania działań
+Name[pt]=Teste da listagem de actividades
+Name[pt_BR]=Teste da lista de atividades
+Name[ru]=Тестирование списка комнат
+Name[sk]=Test zoznamu aktivít
+Name[sl]=Seznam preizkusov dejavnosti
+Name[sr]=Проба набрајања активности
+Name[sr@ijekavian]=Проба набрајања активности
+Name[sr@ijekavianlatin]=Proba nabrajanja aktivnosti
+Name[sr@latin]=Proba nabrajanja aktivnosti
+Name[sv]=Lista aktivitetstester
+Name[tr]=Etkinilk sınamasını listele
+Name[uk]=Тестування списку просторів дій
+Name[x-test]=xxList activities testxx
+Name[zh_CN]=显示活动测试项
+Name[zh_TW]=列出活動測試
+Comment=Strange, but not a Clock
+Comment[ast]=Estrañu, pero nun ye un reló
+Comment[ca]=És estrany, però no és cap rellotge
+Comment[ca@valencia]=És estrany, però no és cap rellotge
+Comment[da]=Mærkelig, men ikke et ur
+Comment[de]=Seltsam, aber keine Uhr
+Comment[el]=Παράξενο, αλλά δεν είναι ρολόι
+Comment[en_GB]=Strange, but not a Clock
+Comment[es]=Es extraño, pero no es un reloj
+Comment[fi]=Outo, mutta ei kello
+Comment[fr]=Étranges, mais ceci n'est pas une horloge
+Comment[gd]=Neònach ach chan e uaireadair a th' ann
+Comment[gl]=Estraño, pero non é un reloxo
+Comment[he]=מוזר, אבל לא שעון
+Comment[hu]=Furcsa, de nem egy óra
+Comment[ia]=Stranie, ma non un horologio
+Comment[is]=Skrýtið, en ekki klukka
+Comment[it]=Stranamente, non un orologio
+Comment[ko]=수상하지만 시계는 아님
+Comment[lt]=Keistas, bet ne laikrodis
+Comment[nb]=Underlig, men ingen klokke
+Comment[nds]=Snaaksch, man keen Klock
+Comment[nl]=Vreemd, maar geen klok
+Comment[nn]=Merkeleg, men ikkje ei klokke
+Comment[pa]=ਅਜੀਬ, ਪਰ ਇਹ ਘੜੀ ਨਹੀਂ
+Comment[pl]=Dziwne, ale nie Zegar
+Comment[pt]=Estranho, Mas Não é um Relógio
+Comment[pt_BR]=Estranho, mas não é um relógio
+Comment[ro]=Straniu, dar nu e ceas
+Comment[ru]=Странное, но не часы
+Comment[sk]=Zvláštne, ale nie hodiny
+Comment[sl]=Nenavadno, ni pa ura
+Comment[sr]=Чудно, али није сат
+Comment[sr@ijekavian]=Чудно, али није сат
+Comment[sr@ijekavianlatin]=Čudno, ali nije sat
+Comment[sr@latin]=Čudno, ali nije sat
+Comment[sv]=Underlig, men inte en klocka
+Comment[tr]=Garip, ancak Saat değil
+Comment[uk]=Дивно, але не годинник
+Comment[x-test]=xxStrange, but not a Clockxx
+Comment[zh_CN]=奇怪,但不是一个时钟
+Comment[zh_TW]=Strange, but not a Clock(這是什麼奇怪的 comment?)
+
+Icon=preferences-system-time
+Type=Service
+X-KDE-ServiceTypes=Plasma/Applet
+
+X-Plasma-API=declarativeappletscript
+X-Plasma-MainScript=ui/main.qml
+X-Plasma-DefaultSize=250,250
+
+X-KDE-PluginInfo-Author=Ivan
+X-KDE-PluginInfo-Email=
+X-KDE-PluginInfo-Name=org.kde.listactivitiestest
+X-KDE-PluginInfo-Version=1.0
+X-KDE-PluginInfo-Website=https://userbase.kde.org/Plasma/Clocks
+X-KDE-PluginInfo-Category=Date and Time
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-EnabledByDefault=true
+
diff --git a/tests/imports/resources.qml b/tests/imports/resources.qml
new file mode 100644 (file)
index 0000000..924bd2d
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+    SPDX-FileCopyrightText: 2013 Heena Mahour <heena393@gmail.com>
+    SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick 2.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+import org.kde.activities 0.1 as Activities
+
+Item {
+    id: main
+
+    width: 320
+    height: 320
+
+    Row {
+        id: buttons
+
+        height: 32
+
+        anchors {
+            left:   parent.left
+            right:  parent.right
+            top:    parent.top
+        }
+
+        PlasmaComponents.Button {
+            id: buttonAdd
+            text: "Add"
+
+            onClicked: {
+                modelMain.linkResourceToActivity("/tmp", function () {});
+            }
+
+            width: 64
+        }
+
+        PlasmaComponents.Button {
+            id: buttonRemove
+            text: "Del"
+
+            onClicked: {
+                modelMain.unlinkResourceFromActivity("/tmp", function () {});
+            }
+
+            width: 64
+        }
+
+        PlasmaComponents.Button {
+            id: buttonSortAZ
+            text: "A-Z"
+
+            onClicked: {
+
+                var items = [];
+
+                for (var i = 0; i < modelMain.count(); i++) {
+                    items.push([
+                        modelMain.displayAt(i),
+                        modelMain.resourceAt(i)
+                    ]);
+                }
+
+                items = items.sort(function(left, right) {
+                    return (left[0] < right[0]) ? -1 :
+                           (left[0] > right[0]) ?  1 :
+                                                   0
+                });
+
+                items = items.map(function(item) { return item[1]; });
+
+                modelMain.setOrder(items);
+            }
+
+            width: 64
+        }
+
+        PlasmaComponents.Button {
+            id: buttonSortZA
+            text: "Z-A"
+
+            onClicked: {
+
+                var items = [];
+
+                for (var i = 0; i < modelMain.count(); i++) {
+                    items.push([
+                        modelMain.displayAt(i),
+                        modelMain.resourceAt(i)
+                    ]);
+                }
+
+                items = items.sort(function(left, right) {
+                    return (left[0] < right[0]) ?  1 :
+                           (left[0] > right[0]) ? -1 :
+                                                   0
+                });
+
+                items = items.map(function(item) { return item[1]; });
+
+                modelMain.setOrder(items);
+            }
+
+            width: 64
+        }
+    }
+
+    ListView {
+        id: list
+
+        anchors {
+            left: parent.left
+            right: parent.right
+            bottom: parent.bottom
+
+            top: buttons.bottom
+
+        }
+
+        property int minimumWidth: 320
+        property int minimumHeight: 320 // theme.mSize(theme.defaultFont).height * 14
+        property int implicitWidth: minimumWidth * 1.5
+        property int implicitHeight: minimumHeight * 1.5
+
+        model: modelMain
+
+        Activities.ResourceModel {
+            id: modelMain
+            shownAgents: "org.kde.plasma.kickoff"
+            shownActivities: ":global,:current"
+        }
+
+        add: Transition {
+            NumberAnimation { properties: "x"; from: 300; duration: 1000 }
+        }
+
+        addDisplaced: Transition {
+           NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+
+        remove: Transition {
+            NumberAnimation { properties: "x"; to: 300; duration: 1000 }
+        }
+
+        removeDisplaced: Transition {
+            NumberAnimation { properties: "x,y"; duration: 1000 }
+        }
+
+        ListModel {
+            id: modelDummy
+
+            ListElement {
+                name: "Bill Smith"
+                number: "555 3264"
+            }
+            ListElement {
+                name: "John Brown"
+                number: "555 8426"
+            }
+            ListElement {
+                name: "Sam Wise"
+                number: "555 0473"
+            }
+        }
+
+        delegate: Column {
+            height: 48
+            Text {
+                text: display
+                height: 16
+                font.bold: true
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        if (display != "tmp") {
+                            modelMain.linkResourceToActivity("/tmp", function () {});
+                        } else {
+                            modelMain.unlinkResourceFromActivity("/tmp", function () {});
+                        }
+                    }
+                }
+
+            }
+            Text {
+                text: "   icon: " + decoration
+                height: 16
+            }
+            Text {
+                text: "   application: " + agent
+                height: 16
+            }
+
+        }
+    }
+}
diff --git a/tests/slc-interface/CMakeLists.txt b/tests/slc-interface/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7d68f59
--- /dev/null
@@ -0,0 +1,45 @@
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesSLCTestApp)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+
+include_directories (
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+   )
+
+set (
+   KActivitiesSLCTestApp_SRCS
+   window.cpp
+   main.cpp
+   )
+
+qt_add_dbus_interface (
+   KActivitiesSLCTestApp_SRCS
+
+   ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/service/plugins/slc/org.kde.ActivityManager.SLC.xml
+   slc_interface
+   )
+
+qt_wrap_ui(
+   KActivitiesSLCTestApp_SRCS
+   window.ui
+   )
+
+if (NOT WIN32)
+
+   add_executable (
+      KActivitiesSLCTestApp
+      ${KActivitiesSLCTestApp_SRCS}
+      )
+
+   target_link_libraries (
+      KActivitiesSLCTestApp
+      Qt${QT_MAJOR_VERSION}::Core
+      Qt${QT_MAJOR_VERSION}::Gui
+      Qt${QT_MAJOR_VERSION}::Widgets
+      Qt${QT_MAJOR_VERSION}::DBus
+      KF5::Activities
+      )
+
+endif ()
diff --git a/tests/slc-interface/main.cpp b/tests/slc-interface/main.cpp
new file mode 100644 (file)
index 0000000..12e3061
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+
+    Window w;
+    w.show();
+
+    // ResultSet results(UsedResources | Agent{"gvim"});
+    //
+    // int count = 20;
+    // for (const auto& result: results) {
+    //     qDebug() << "Result:" << result.title << result.resource;
+    //     if (count -- == 0) break;
+    // }
+    //
+    // ResultModel model(UsedResources | Agent{"gvim"});
+    // model.setItemCountLimit(50);
+    //
+    // QListView view;
+    // view.setModel(&model);
+    //
+    // view.show();
+
+    return app.exec();
+}
diff --git a/tests/slc-interface/window.cpp b/tests/slc-interface/window.cpp
new file mode 100644 (file)
index 0000000..58f0a9c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+    SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+
+#include "ui_window.h"
+
+#include <QDBusConnection>
+
+Window::Window()
+    : ui(new Ui::MainWindow())
+    , slc(new org::kde::ActivityManager::SLC("org.kde.ActivityManager", "/SLC", QDBusConnection::sessionBus(), this))
+{
+    ui->setupUi(this);
+
+    connect(slc, &org::kde::ActivityManager::SLC::focusChanged, this, &Window::focusChanged);
+}
+
+Window::~Window()
+{
+    delete ui;
+}
+
+void Window::focusChanged(const QString &uri, const QString &mimetype, const QString &title)
+{
+    Q_UNUSED(mimetype);
+    Q_UNUSED(title);
+    ui->textCurrentResource->setText(uri);
+}
diff --git a/tests/slc-interface/window.h b/tests/slc-interface/window.h
new file mode 100644 (file)
index 0000000..9c6e2c7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#pragma once
+
+#include "slc_interface.h"
+#include <QMainWindow>
+
+namespace Ui
+{
+class MainWindow;
+}
+
+class Window : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    Window();
+    ~Window();
+
+private Q_SLOTS:
+    void focusChanged(const QString &uri, const QString &mimetype, const QString &title);
+
+private:
+    Ui::MainWindow *ui;
+    org::kde::ActivityManager::SLC *slc;
+};
diff --git a/tests/slc-interface/window.ui b/tests/slc-interface/window.ui
new file mode 100644 (file)
index 0000000..35ddc9d
--- /dev/null
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>474</width>
+    <height>74</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QHBoxLayout" name="horizontalLayout">
+    <item>
+     <widget class="QLabel" name="icon">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="minimumSize">
+       <size>
+        <width>64</width>
+        <height>64</height>
+       </size>
+      </property>
+      <property name="text">
+       <string/>
+      </property>
+     </widget>
+    </item>
+    <item>
+     <layout class="QFormLayout" name="formLayout">
+      <item row="0" column="0">
+       <widget class="QLabel" name="labelCurrentResource">
+        <property name="text">
+         <string>Resource</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QLabel" name="textCurrentResource">
+        <property name="text">
+         <string>...</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="labelCurrentResourceTitle">
+        <property name="text">
+         <string>Title</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QLabel" name="textCurrentResourceTitle">
+        <property name="text">
+         <string>...</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="labelCurrentResourceMimetype">
+        <property name="text">
+         <string>Mimetype</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QLabel" name="textCurrentResourceMimetype">
+        <property name="text">
+         <string>...</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/vim-extrarc b/vim-extrarc
new file mode 100644 (file)
index 0000000..ea92437
--- /dev/null
@@ -0,0 +1,12 @@
+
+set makeprg=OBJ_REPLACEMENT='s=src=build-clang='\ makeobj
+
+imap <f8> <esc>:SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher<cr>
+map <f8> <esc>:SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher<cr>
+
+let g:ctrlpswitcher_project_sources = expand('<sfile>:p:h')."/src"
+let g:ctrlpswitcher_mode = 1
+
+set foldmethod=marker
+set foldmarker=//_,//^
+