Merge lp:~uriboni/messaging-app/stickers into lp:messaging-app

Proposed by Ugo Riboni
Status: Merged
Merged at revision: 491
Proposed branch: lp:~uriboni/messaging-app/stickers
Merge into: lp:messaging-app
Diff against target: 1937 lines (+1685/-33)
22 files modified
.bzrignore (+37/-0)
debian/control (+1/-0)
debian/messaging-app.install (+1/-0)
src/CMakeLists.txt (+3/-1)
src/messagingapplication.cpp (+17/-0)
src/qml/CMakeLists.txt (+1/-0)
src/qml/Messages.qml (+55/-2)
src/qml/Stickers/CMakeLists.txt (+4/-0)
src/qml/Stickers/HistoryButton.qml (+35/-0)
src/qml/Stickers/StickerDelegate.qml (+32/-0)
src/qml/Stickers/StickerPackDelegate.qml (+53/-0)
src/qml/Stickers/StickerPackModel.qml (+27/-0)
src/qml/Stickers/StickerPacksModel.qml (+25/-0)
src/qml/Stickers/StickersPicker.qml (+112/-0)
src/qml/assets/face-smile-big-symbolic-2.svg (+182/-0)
src/qml/assets/history.svg (+173/-0)
src/qml/assets/input-keyboard-symbolic.svg (+221/-0)
src/stickers-history-model.cpp (+307/-0)
src/stickers-history-model.h (+91/-0)
tests/qml/CMakeLists.txt (+42/-30)
tests/qml/tst_QmlTests.cpp (+78/-0)
tests/qml/tst_StickersHistoryModel.qml (+188/-0)
To merge this branch: bzr merge lp:~uriboni/messaging-app/stickers
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Ubuntu Phablet Team Pending
Review via email: mp+277446@code.launchpad.net

Commit message

Prototype implementation for stickers support.

Description of the change

Prototype implementation for stickers support.
At the moment only the sticker picker component is implemented (including the most frequently used stickers panel).
Actually sending the sticker chosen by the user is not implemented by this branch yet.

It is also unclear what stickers will be preinstalled, so at the moment the app reads stickers from the user data directory: ~/.local/share/com.ubuntu.messaging-app/MessagingApp/stickers

Each subdirectory is considered a "sticker pack" and the pack can contain any number of png, webm or gif files, preferrably with transparency.

Test sticke packs: http://people.canonical.com/~bfiller/stickers.tgz

To post a comment you must log in.
lp:~uriboni/messaging-app/stickers updated
475. By Ugo Riboni

Remove debug code

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:475
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~uriboni/messaging-app/stickers/+merge/277446/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/messaging-app-ci/700/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/5144/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/messaging-app-vivid-i386-ci/213/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/5158/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/messaging-app-ci/700/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~uriboni/messaging-app/stickers updated
476. By Ugo Riboni

Add stickers directory to cmake

477. By Ugo Riboni

The model is now a chronological list of the most recently used stickers

478. By Ugo Riboni

Add a limit property to the model to set the maximum size of the history

479. By Ugo Riboni

Fix .bzrignore to properly ignore test binaries, and remove mistakely added test binary

480. By Ugo Riboni

Add stickers qml dir to the list of installed files in debian

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~uriboni/messaging-app/stickers updated
481. By Ugo Riboni

Fix interactions between OSK, stickers picker and the rest of the UI

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~uriboni/messaging-app/stickers updated
482. By Ugo Riboni

Add missing debian dependecy

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2015-11-16 14:15:20 +0000
@@ -0,0 +1,37 @@
1*.qmlproject.user
2CMakeCache.txt
3CMakeFiles/
4CMakeLists.txt.user
5cmake_install.cmake
6cmake_uninstall.cmake
7Makefile
8install_manifest.txt
9CTestTestfile.cmake
10
11*.cbp
12*.moc
13moc_*.cpp
14*_automoc.cpp
15
16Testing
17RE:tests/qml/tst_\w+Tests$
18po/*.gmo
19po/src
20
21config.h
22src/messaging-app
23src/messaging-app.desktop*
24
25obj-*
26debian/usr.bin.webbrowser-app
27debian/files
28debian/tmp/
29debian/qtdeclarative5-ubuntu-ui-extras-browser-plugin/
30debian/qtdeclarative5-ubuntu-web-plugin/
31debian/qtdeclarative5-ubuntu-web-plugin-doc/
32debian/messaging-app/
33debian/messaging-app-autopilot/
34debian/*.debhelper
35debian/*.debhelper.log
36debian/*.substvars
37debian/stamp-*
038
=== modified file 'debian/control'
--- debian/control 2015-09-11 14:25:57 +0000
+++ debian/control 2015-11-16 14:15:20 +0000
@@ -23,6 +23,7 @@
23 qtdeclarative5-ubuntu-addressbook0.1,23 qtdeclarative5-ubuntu-addressbook0.1,
24 qtdeclarative5-qtcontacts-plugin,24 qtdeclarative5-qtcontacts-plugin,
25 qml-module-qt-labs-settings,25 qml-module-qt-labs-settings,
26 qtdeclarative5-folderlistmodel-plugin,
26 qtpim5-dev,27 qtpim5-dev,
27 xvfb,28 xvfb,
28Standards-Version: 3.9.429Standards-Version: 3.9.4
2930
=== modified file 'debian/messaging-app.install'
--- debian/messaging-app.install 2014-08-11 22:22:59 +0000
+++ debian/messaging-app.install 2015-11-16 14:15:20 +0000
@@ -8,4 +8,5 @@
8usr/share/messaging-app/3rd_party8usr/share/messaging-app/3rd_party
9usr/share/messaging-app/MMS/*.qml9usr/share/messaging-app/MMS/*.qml
10usr/share/messaging-app/Dialogs/*.qml10usr/share/messaging-app/Dialogs/*.qml
11usr/share/messaging-app/Stickers/*.qml
11usr/bin/*messaging-app*12usr/bin/*messaging-app*
1213
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2015-02-24 13:33:31 +0000
+++ src/CMakeLists.txt 2015-11-16 14:15:20 +0000
@@ -2,17 +2,19 @@
22
3set(messaging_app_HDRS3set(messaging_app_HDRS
4 messagingapplication.h4 messagingapplication.h
5 stickers-history-model.h
5 )6 )
67
7set(messaging_app_SRCS8set(messaging_app_SRCS
8 messagingapplication.cpp9 messagingapplication.cpp
9 main.cpp10 main.cpp
11 stickers-history-model.cpp
10 )12 )
1113
12add_executable(${MESSAGING_APP}14add_executable(${MESSAGING_APP}
13 ${messaging_app_SRCS}15 ${messaging_app_SRCS}
14 )16 )
15qt5_use_modules(${MESSAGING_APP} Core DBus Gui Qml Quick Versit)17qt5_use_modules(${MESSAGING_APP} Core DBus Gui Qml Quick Versit Sql)
1618
17include_directories(19include_directories(
18 ${CMAKE_CURRENT_BINARY_DIR}20 ${CMAKE_CURRENT_BINARY_DIR}
1921
=== modified file 'src/messagingapplication.cpp'
--- src/messagingapplication.cpp 2015-07-07 16:31:26 +0000
+++ src/messagingapplication.cpp 2015-11-16 14:15:20 +0000
@@ -17,6 +17,7 @@
17 */17 */
1818
19#include "messagingapplication.h"19#include "messagingapplication.h"
20#include "stickers-history-model.h"
2021
21#include <libnotify/notify.h>22#include <libnotify/notify.h>
2223
@@ -24,6 +25,7 @@
24#include <QUrl>25#include <QUrl>
25#include <QUrlQuery>26#include <QUrlQuery>
26#include <QDebug>27#include <QDebug>
28#include <QDir>
27#include <QStringList>29#include <QStringList>
28#include <QQuickItem>30#include <QQuickItem>
29#include <QQmlComponent>31#include <QQmlComponent>
@@ -36,6 +38,7 @@
36#include "config.h"38#include "config.h"
37#include <QQmlEngine>39#include <QQmlEngine>
38#include <QMimeDatabase>40#include <QMimeDatabase>
41#include <QStandardPaths>
39#include <QVersitReader>42#include <QVersitReader>
4043
41using namespace QtVersit;44using namespace QtVersit;
@@ -71,6 +74,13 @@
71 setApplicationName("MessagingApp");74 setApplicationName("MessagingApp");
72}75}
7376
77static QObject* StickersHistoryModel_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine)
78{
79 Q_UNUSED(engine);
80 Q_UNUSED(scriptEngine);
81 return new StickersHistoryModel();
82}
83
74bool MessagingApplication::setup()84bool MessagingApplication::setup()
75{85{
76 installIconPath();86 installIconPath();
@@ -149,6 +159,13 @@
149 m_view->rootContext()->setContextProperty("QTCONTACTS_MANAGER_OVERRIDE", contactsBackend);159 m_view->rootContext()->setContextProperty("QTCONTACTS_MANAGER_OVERRIDE", contactsBackend);
150 }160 }
151161
162 QDir dataLocation(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
163 m_view->rootContext()->setContextProperty("dataLocation", dataLocation.absolutePath());
164 dataLocation.mkpath("stickers");
165 const char* uri = "messagingapp.private";
166 qmlRegisterSingletonType<StickersHistoryModel>(uri, 0, 1, "StickersHistoryModel", StickersHistoryModel_singleton_factory);
167
168
152 // used by autopilot tests to load vcards during tests169 // used by autopilot tests to load vcards during tests
153 QByteArray testData = qgetenv("QTCONTACTS_PRELOAD_VCARD");170 QByteArray testData = qgetenv("QTCONTACTS_PRELOAD_VCARD");
154 m_view->rootContext()->setContextProperty("QTCONTACTS_PRELOAD_VCARD", testData);171 m_view->rootContext()->setContextProperty("QTCONTACTS_PRELOAD_VCARD", testData);
155172
=== modified file 'src/qml/CMakeLists.txt'
--- src/qml/CMakeLists.txt 2014-08-11 22:22:59 +0000
+++ src/qml/CMakeLists.txt 2015-11-16 14:15:20 +0000
@@ -17,3 +17,4 @@
1717
18add_subdirectory(MMS)18add_subdirectory(MMS)
19add_subdirectory(Dialogs)19add_subdirectory(Dialogs)
20add_subdirectory(Stickers)
2021
=== modified file 'src/qml/Messages.qml'
--- src/qml/Messages.qml 2015-11-12 17:33:30 +0000
+++ src/qml/Messages.qml 2015-11-16 14:15:20 +0000
@@ -26,6 +26,7 @@
26import Ubuntu.Telephony 0.126import Ubuntu.Telephony 0.1
27import Ubuntu.Contacts 0.127import Ubuntu.Contacts 0.1
2828
29import "Stickers"
29import "dateUtils.js" as DateUtils30import "dateUtils.js" as DateUtils
3031
31Page {32Page {
@@ -783,9 +784,26 @@
783 }784 }
784 }785 }
785786
787 StickersPicker {
788 id: stickersPicker
789 anchors.left: parent.left
790 anchors.right: parent.right
791 anchors.bottom: parent.bottom
792 height: keyboard.maximumHeight
793 visible: false
794
795 onStickerSelected: console.log(">>>>>>>>>>>> send:", path)
796
797 Connections {
798 target: Qt.inputMethod
799 onVisibleChanged: if (Qt.inputMethod.visible) stickersPicker.visible = false
800 }
801 }
802
786 Item {803 Item {
787 id: bottomPanel804 id: bottomPanel
788 anchors.bottom: isSearching ? parent.bottom : keyboard.top805 anchors.bottom: isSearching ? parent.bottom :
806 (Qt.inputMethod.visible ? keyboard.top : stickersPicker.top)
789 anchors.left: parent.left807 anchors.left: parent.left
790 anchors.right: parent.right808 anchors.right: parent.right
791 height: selectionMode || (participants.length > 0 && !contactWatcher.interactive) ? 0 : textEntry.height + units.gu(2)809 height: selectionMode || (participants.length > 0 && !contactWatcher.interactive) ? 0 : textEntry.height + units.gu(2)
@@ -826,6 +844,32 @@
826 }844 }
827 }845 }
828846
847 Icon {
848 id: stickersButton
849 objectName: "stickersButton"
850 anchors.left: attachButton.right
851 anchors.leftMargin: units.gu(2)
852 anchors.verticalCenter: sendButton.verticalCenter
853 height: units.gu(3)
854 width: units.gu(3)
855 color: "gray"
856 visible: messageTextArea.activeFocus || stickersPicker.visible
857 source: Qt.resolvedUrl("./assets/" + (Qt.inputMethod.visible ?
858 "face-smile-big-symbolic-2.svg" :
859 "input-keyboard-symbolic.svg"))
860 MouseArea {
861 anchors.fill: parent
862 onClicked: {
863 if (Qt.inputMethod.visible) {
864 messageTextArea.focus = false
865 stickersPicker.visible = true
866 } else {
867 messageTextArea.forceActiveFocus()
868 }
869 }
870 }
871 }
872
829 StyledItem {873 StyledItem {
830 id: textEntry874 id: textEntry
831 property alias text: messageTextArea.text875 property alias text: messageTextArea.text
@@ -834,7 +878,7 @@
834 style: Theme.createStyleComponent("TextAreaStyle.qml", textEntry)878 style: Theme.createStyleComponent("TextAreaStyle.qml", textEntry)
835 anchors.bottomMargin: units.gu(1)879 anchors.bottomMargin: units.gu(1)
836 anchors.bottom: parent.bottom880 anchors.bottom: parent.bottom
837 anchors.left: attachButton.right881 anchors.left: stickersButton.visible ? stickersButton.right : attachButton.right
838 anchors.leftMargin: units.gu(1)882 anchors.leftMargin: units.gu(1)
839 anchors.right: sendButton.left883 anchors.right: sendButton.left
840 anchors.rightMargin: units.gu(1)884 anchors.rightMargin: units.gu(1)
@@ -1045,6 +1089,7 @@
1045 font.pixelSize: FontUtils.sizeToPixels("medium")1089 font.pixelSize: FontUtils.sizeToPixels("medium")
1046 color: "#5d5d5d"1090 color: "#5d5d5d"
1047 text: messages.text1091 text: messages.text
1092 onActiveFocusChanged: if (activeFocus) stickersPicker.visible = false
1048 }1093 }
10491094
1050 /*InverseMouseArea {1095 /*InverseMouseArea {
@@ -1125,6 +1170,14 @@
11251170
1126 KeyboardRectangle {1171 KeyboardRectangle {
1127 id: keyboard1172 id: keyboard
1173
1174 // Workaround for not being able to know the keyboard rectangle size
1175 // before it has shown at least once: keep matching its height until it
1176 // has reached its maximum.
1177 property int maximumHeight: 0
1178 onHeightChanged: {
1179 if (height > maximumHeight) maximumHeight = height
1180 }
1128 }1181 }
11291182
1130 MessageInfoDialog {1183 MessageInfoDialog {
11311184
=== added directory 'src/qml/Stickers'
=== added file 'src/qml/Stickers/CMakeLists.txt'
--- src/qml/Stickers/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ src/qml/Stickers/CMakeLists.txt 2015-11-16 14:15:20 +0000
@@ -0,0 +1,4 @@
1file(GLOB STICKERS_QML_FILES *.qml)
2
3add_custom_target(messaging_app_Stickers_QMlFiles ALL SOURCES ${STICKERS_QML_FILES})
4install(FILES ${STICKERS_QML_FILES} DESTINATION ${MESSAGING_APP_DIR}/Stickers)
05
=== added file 'src/qml/Stickers/HistoryButton.qml'
--- src/qml/Stickers/HistoryButton.qml 1970-01-01 00:00:00 +0000
+++ src/qml/Stickers/HistoryButton.qml 2015-11-16 14:15:20 +0000
@@ -0,0 +1,35 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.3
20import Ubuntu.Components 1.3
21
22AbstractButton {
23 property bool selected
24
25 Rectangle {
26 anchors.fill: parent
27 color: selected ? "#f5f5f5" : "transparent"
28 }
29
30 Icon {
31 name: "history"
32 anchors.fill: parent
33 anchors.margins: units.gu(1.5)
34 }
35}
036
=== added file 'src/qml/Stickers/StickerDelegate.qml'
--- src/qml/Stickers/StickerDelegate.qml 1970-01-01 00:00:00 +0000
+++ src/qml/Stickers/StickerDelegate.qml 2015-11-16 14:15:20 +0000
@@ -0,0 +1,32 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.3
20import Ubuntu.Components 1.3
21
22AbstractButton {
23 property alias stickerSource: image.source
24
25 Image {
26 id: image
27 anchors.fill: parent
28 anchors.margins: units.gu(0.5)
29 fillMode: Image.PreserveAspectFit
30 smooth: true
31 }
32}
033
=== added file 'src/qml/Stickers/StickerPackDelegate.qml'
--- src/qml/Stickers/StickerPackDelegate.qml 1970-01-01 00:00:00 +0000
+++ src/qml/Stickers/StickerPackDelegate.qml 2015-11-16 14:15:20 +0000
@@ -0,0 +1,53 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.3
20import Qt.labs.folderlistmodel 2.1
21import Ubuntu.Components 1.3
22
23AbstractButton {
24 property alias path: stickers.folder
25 property string name
26 property bool selected
27
28 Rectangle {
29 anchors.fill: parent
30 color: selected ? "#f5f5f5" : "transparent"
31 }
32
33 Icon {
34 anchors.fill: parent
35 visible: stickers.count === 0
36 name: "cancel"
37 }
38
39 Image {
40 visible: stickers.count > 0
41 anchors.fill: parent
42 anchors.margins: units.gu(0.5)
43 fillMode: Image.PreserveAspectFit
44 smooth: true
45 source: visible ? stickers.get(0, "filePath") : ""
46 }
47
48 FolderListModel {
49 id: stickers
50 showDirs: false
51 nameFilters: ["*.png", "*.webm", "*.gif"]
52 }
53}
054
=== added file 'src/qml/Stickers/StickerPackModel.qml'
--- src/qml/Stickers/StickerPackModel.qml 1970-01-01 00:00:00 +0000
+++ src/qml/Stickers/StickerPackModel.qml 2015-11-16 14:15:20 +0000
@@ -0,0 +1,27 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.3
20import Qt.labs.folderlistmodel 2.1
21
22FolderListModel {
23 property string packName
24 folder: "%1/stickers/%2".arg(dataLocation).arg(packName)
25 showDirs: false
26 nameFilters: ["*.png", "*.webm", "*.gif"]
27}
028
=== added file 'src/qml/Stickers/StickerPacksModel.qml'
--- src/qml/Stickers/StickerPacksModel.qml 1970-01-01 00:00:00 +0000
+++ src/qml/Stickers/StickerPacksModel.qml 2015-11-16 14:15:20 +0000
@@ -0,0 +1,25 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.3
20import Qt.labs.folderlistmodel 2.1
21
22FolderListModel {
23 folder: dataLocation + "/stickers/"
24 showFiles: false
25}
026
=== added file 'src/qml/Stickers/StickersPicker.qml'
--- src/qml/Stickers/StickersPicker.qml 1970-01-01 00:00:00 +0000
+++ src/qml/Stickers/StickersPicker.qml 2015-11-16 14:15:20 +0000
@@ -0,0 +1,112 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.3
20import Ubuntu.Components 1.3
21import messagingapp.private 0.1
22
23FocusScope {
24 id: pickerRoot
25 signal stickerSelected(string path)
26
27 Component.onCompleted: {
28 StickersHistoryModel.databasePath = dataLocation + "/stickers/stickers.sqlite"
29 StickersHistoryModel.limit = 10
30 }
31
32 ListView {
33 id: setsList
34 model: StickerPacksModel {}
35 orientation: ListView.Horizontal
36 anchors.left: parent.left
37 anchors.right: parent.right
38 anchors.top: parent.top
39 height: units.gu(6)
40
41 header: HistoryButton {
42 height: units.gu(6)
43 width: height
44
45 onTriggered: stickersGrid.model.packName = ""
46 selected: stickersGrid.model.packName === ""
47 }
48 delegate: StickerPackDelegate {
49 anchors.top: parent.top
50 anchors.bottom: parent.bottom
51 width: units.gu(6)
52
53 path: filePath
54 onTriggered: stickersGrid.model.packName = fileName
55 selected: stickersGrid.model.packName === fileName
56 }
57 }
58
59 Rectangle {
60 anchors.fill: stickersGrid
61 color: "#f5f5f5"
62 }
63
64 GridView {
65 id: stickersGrid
66 anchors.left: parent.left
67 anchors.right: parent.right
68 anchors.top: setsList.bottom
69 anchors.bottom: parent.bottom
70 clip: true
71 cellWidth: units.gu(10)
72 cellHeight: units.gu(10)
73 visible: stickersGrid.model.packName.length > 0
74
75 model: StickerPackModel { }
76 delegate: StickerDelegate {
77 stickerSource: filePath
78 width: stickersGrid.cellWidth
79 height: stickersGrid.cellHeight
80
81 onTriggered: {
82 StickersHistoryModel.add("%1/%2".arg(stickersGrid.model.packName).arg(fileName))
83 pickerRoot.stickerSelected(stickerSource)
84 }
85 }
86 }
87
88 GridView {
89 id: historyGrid
90 anchors.left: parent.left
91 anchors.right: parent.right
92 anchors.top: setsList.bottom
93 anchors.bottom: parent.bottom
94 clip: true
95 cellWidth: units.gu(10)
96 cellHeight: units.gu(10)
97 visible: stickersGrid.model.packName.length === 0
98
99 model: StickersHistoryModel
100
101 delegate: StickerDelegate {
102 stickerSource: "%1/stickers/%2".arg(dataLocation).arg(sticker)
103 width: stickersGrid.cellWidth
104 height: stickersGrid.cellHeight
105
106 onTriggered: {
107 StickersHistoryModel.add(sticker)
108 pickerRoot.stickerSelected(stickerSource)
109 }
110 }
111 }
112}
0113
=== added file 'src/qml/assets/face-smile-big-symbolic-2.svg'
--- src/qml/assets/face-smile-big-symbolic-2.svg 1970-01-01 00:00:00 +0000
+++ src/qml/assets/face-smile-big-symbolic-2.svg 2015-11-16 14:15:20 +0000
@@ -0,0 +1,182 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12 width="96"
13 height="96"
14 id="svg4874"
15 version="1.1"
16 inkscape:version="0.91+devel r"
17 viewBox="0 0 96 96.000001"
18 sodipodi:docname="face-smile-big-symbolic.svg">
19 <defs
20 id="defs4876" />
21 <sodipodi:namedview
22 id="base"
23 pagecolor="#ffffff"
24 bordercolor="#666666"
25 borderopacity="1.0"
26 inkscape:pageopacity="0.0"
27 inkscape:pageshadow="2"
28 inkscape:zoom="2.8774396"
29 inkscape:cx="-93.086241"
30 inkscape:cy="47.64648"
31 inkscape:document-units="px"
32 inkscape:current-layer="g4780"
33 showgrid="true"
34 showborder="true"
35 fit-margin-top="0"
36 fit-margin-left="0"
37 fit-margin-right="0"
38 fit-margin-bottom="0"
39 inkscape:snap-bbox="true"
40 inkscape:bbox-paths="true"
41 inkscape:bbox-nodes="true"
42 inkscape:snap-bbox-edge-midpoints="true"
43 inkscape:snap-bbox-midpoints="true"
44 inkscape:object-paths="true"
45 inkscape:snap-intersection-paths="true"
46 inkscape:object-nodes="true"
47 inkscape:snap-smooth-nodes="true"
48 inkscape:snap-midpoints="true"
49 inkscape:snap-object-midpoints="true"
50 inkscape:snap-center="true"
51 showguides="true"
52 inkscape:guide-bbox="true">
53 <inkscape:grid
54 type="xygrid"
55 id="grid5451"
56 empspacing="8" />
57 <sodipodi:guide
58 orientation="1,0"
59 position="8,-8.0000001"
60 id="guide4063" />
61 <sodipodi:guide
62 orientation="1,0"
63 position="4,-8.0000001"
64 id="guide4065" />
65 <sodipodi:guide
66 orientation="0,1"
67 position="-8,88.000001"
68 id="guide4067" />
69 <sodipodi:guide
70 orientation="0,1"
71 position="-8,92.000001"
72 id="guide4069" />
73 <sodipodi:guide
74 orientation="0,1"
75 position="104,4"
76 id="guide4071" />
77 <sodipodi:guide
78 orientation="0,1"
79 position="-5,8.0000001"
80 id="guide4073" />
81 <sodipodi:guide
82 orientation="1,0"
83 position="92,-8.0000001"
84 id="guide4075" />
85 <sodipodi:guide
86 orientation="1,0"
87 position="88,-8.0000001"
88 id="guide4077" />
89 <sodipodi:guide
90 orientation="0,1"
91 position="-8,84.000001"
92 id="guide4074" />
93 <sodipodi:guide
94 orientation="1,0"
95 position="12,-8.0000001"
96 id="guide4076" />
97 <sodipodi:guide
98 orientation="0,1"
99 position="-5,12"
100 id="guide4078" />
101 <sodipodi:guide
102 orientation="1,0"
103 position="84,-9.0000001"
104 id="guide4080" />
105 <sodipodi:guide
106 position="48,-8.0000001"
107 orientation="1,0"
108 id="guide4170" />
109 <sodipodi:guide
110 position="-8,48"
111 orientation="0,1"
112 id="guide4172" />
113 </sodipodi:namedview>
114 <metadata
115 id="metadata4879">
116 <rdf:RDF>
117 <cc:Work
118 rdf:about="">
119 <dc:format>image/svg+xml</dc:format>
120 <dc:type
121 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
122 <dc:title></dc:title>
123 </cc:Work>
124 </rdf:RDF>
125 </metadata>
126 <g
127 inkscape:label="Layer 1"
128 inkscape:groupmode="layer"
129 id="layer1"
130 transform="translate(67.857146,-78.50504)">
131 <g
132 transform="matrix(0,-1,-1,0,373.50506,516.50504)"
133 id="g4845"
134 style="display:inline">
135 <g
136 inkscape:export-ydpi="90"
137 inkscape:export-xdpi="90"
138 inkscape:export-filename="next01.png"
139 transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
140 id="g4778"
141 inkscape:label="Layer 1">
142 <g
143 transform="matrix(-1,0,0,1,575.99999,611)"
144 id="g4780"
145 style="display:inline">
146 <rect
147 style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
148 id="rect4782"
149 width="96.037987"
150 height="96"
151 x="-438.00244"
152 y="345.36221"
153 transform="scale(-1,1)" />
154 <path
155 style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
156 d="m 432,393.36133 c 0,23.17236 -18.83538,42 -42.01562,42 -23.18025,0 -42.01563,-18.82764 -42.01563,-42 0,-23.17236 18.83538,-42 42.01563,-42 23.18024,0 42.01562,18.82764 42.01562,42 z m -4.00195,0 c 0,-21.00932 -16.99476,-37.99805 -38.01367,-37.99805 -21.01892,0 -38.01563,16.98873 -38.01563,37.99805 0,21.00931 16.99671,37.99804 38.01563,37.99805 21.01891,0 38.01367,-16.98874 38.01367,-37.99805 z"
157 id="path4116"
158 inkscape:connector-curvature="0" />
159 <path
160 style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:64.01265717;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
161 d="m 404.99002,409.36281 1.50057,-8.00059 -13.50534,0 0,8.00059 z"
162 id="rect4175"
163 inkscape:connector-curvature="0"
164 sodipodi:nodetypes="ccccc" />
165 <path
166 sodipodi:nodetypes="ccccc"
167 inkscape:connector-curvature="0"
168 id="path4178"
169 d="m 404.99002,385.36222 1.50057,-7.99941 -13.50534,0 0,7.99941 z"
170 style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:64.01265717;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
171 <path
172 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.00019777px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
173 d="m 383.98169,414.37529 c -1.5268,-0.21155 -2.98748,-0.52576 -4.37873,-0.94375 -2.81692,-0.88674 -5.23617,-2.19676 -7.25967,-3.92985 -2.0236,-1.77325 -3.59002,-3.99099 -4.70116,-6.65098 -1.11104,-2.70036 -1.66756,-5.86393 -1.66756,-9.49118 0,-3.62725 0.55652,-6.77166 1.66756,-9.43164 1.11114,-2.70027 2.67756,-4.91499 4.70116,-6.64799 2.0235,-1.77334 4.44275,-3.0835 7.25967,-3.92986 1.39125,-0.4379 2.85193,-0.76678 4.37873,-0.98841 l 0,42.01366 z"
174 id="path4190"
175 inkscape:connector-curvature="0"
176 inkscape:transform-center-x="0.0068360001"
177 inkscape:transform-center-y="-9.0000001" />
178 </g>
179 </g>
180 </g>
181 </g>
182</svg>
0183
=== added file 'src/qml/assets/history.svg'
--- src/qml/assets/history.svg 1970-01-01 00:00:00 +0000
+++ src/qml/assets/history.svg 2015-11-16 14:15:20 +0000
@@ -0,0 +1,173 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12 width="96"
13 height="96"
14 id="svg4874"
15 version="1.1"
16 inkscape:version="0.91+devel r"
17 viewBox="0 0 96 96.000001"
18 sodipodi:docname="history.svg">
19 <defs
20 id="defs4876" />
21 <sodipodi:namedview
22 id="base"
23 pagecolor="#ffffff"
24 bordercolor="#666666"
25 borderopacity="1.0"
26 inkscape:pageopacity="0.0"
27 inkscape:pageshadow="2"
28 inkscape:zoom="8.7812488"
29 inkscape:cx="12.412809"
30 inkscape:cy="56.142342"
31 inkscape:document-units="px"
32 inkscape:current-layer="g4780"
33 showgrid="true"
34 showborder="true"
35 fit-margin-top="0"
36 fit-margin-left="0"
37 fit-margin-right="0"
38 fit-margin-bottom="0"
39 inkscape:snap-bbox="true"
40 inkscape:bbox-paths="true"
41 inkscape:bbox-nodes="true"
42 inkscape:snap-bbox-edge-midpoints="true"
43 inkscape:snap-bbox-midpoints="true"
44 inkscape:object-paths="true"
45 inkscape:snap-intersection-paths="true"
46 inkscape:object-nodes="true"
47 inkscape:snap-smooth-nodes="true"
48 inkscape:snap-midpoints="true"
49 inkscape:snap-object-midpoints="true"
50 inkscape:snap-center="true"
51 showguides="true"
52 inkscape:guide-bbox="true">
53 <inkscape:grid
54 type="xygrid"
55 id="grid5451"
56 empspacing="8" />
57 <sodipodi:guide
58 orientation="1,0"
59 position="8,-8.0000001"
60 id="guide4063" />
61 <sodipodi:guide
62 orientation="1,0"
63 position="4,-8.0000001"
64 id="guide4065" />
65 <sodipodi:guide
66 orientation="0,1"
67 position="-8,88.000001"
68 id="guide4067" />
69 <sodipodi:guide
70 orientation="0,1"
71 position="-8,92.000001"
72 id="guide4069" />
73 <sodipodi:guide
74 orientation="0,1"
75 position="104,4"
76 id="guide4071" />
77 <sodipodi:guide
78 orientation="0,1"
79 position="-5,8.0000001"
80 id="guide4073" />
81 <sodipodi:guide
82 orientation="1,0"
83 position="92,-8.0000001"
84 id="guide4075" />
85 <sodipodi:guide
86 orientation="1,0"
87 position="88,-8.0000001"
88 id="guide4077" />
89 <sodipodi:guide
90 orientation="0,1"
91 position="-8,84.000001"
92 id="guide4074" />
93 <sodipodi:guide
94 orientation="1,0"
95 position="12,-8.0000001"
96 id="guide4076" />
97 <sodipodi:guide
98 orientation="0,1"
99 position="-5,12"
100 id="guide4078" />
101 <sodipodi:guide
102 orientation="1,0"
103 position="84,-9.0000001"
104 id="guide4080" />
105 <sodipodi:guide
106 position="48,-8.0000001"
107 orientation="1,0"
108 id="guide4170" />
109 <sodipodi:guide
110 position="-8,48"
111 orientation="0,1"
112 id="guide4172" />
113 </sodipodi:namedview>
114 <metadata
115 id="metadata4879">
116 <rdf:RDF>
117 <cc:Work
118 rdf:about="">
119 <dc:format>image/svg+xml</dc:format>
120 <dc:type
121 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
122 <dc:title></dc:title>
123 </cc:Work>
124 </rdf:RDF>
125 </metadata>
126 <g
127 inkscape:label="Layer 1"
128 inkscape:groupmode="layer"
129 id="layer1"
130 transform="translate(67.857146,-78.50504)">
131 <g
132 transform="matrix(0,-1,-1,0,373.50506,516.50504)"
133 id="g4845"
134 style="display:inline">
135 <g
136 inkscape:export-ydpi="90"
137 inkscape:export-xdpi="90"
138 inkscape:export-filename="next01.png"
139 transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
140 id="g4778"
141 inkscape:label="Layer 1">
142 <g
143 transform="matrix(-1,0,0,1,575.99999,611)"
144 id="g4780"
145 style="display:inline">
146 <rect
147 style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
148 id="rect4782"
149 width="96.037987"
150 height="96"
151 x="-438.00244"
152 y="345.36221"
153 transform="scale(-1,1)" />
154 <path
155 style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
156 d="m 420.87891,421.82617 c -13.02519,14.12639 -34.00962,17.61704 -50.90821,8.4668 -16.89858,-9.15024 -25.43667,-28.62704 -20.71484,-47.25 4.72183,-18.62296 21.51014,-31.68164 40.72852,-31.68164 l 0,4.00195 c -17.40844,0 -32.5749,11.79684 -36.85157,28.66406 -4.27667,16.86723 3.43743,34.45783 18.74414,42.7461 15.30671,8.28827 34.26279,5.13698 46.06055,-7.65821 l 2.94141,2.71094 z"
157 id="path4116"
158 inkscape:connector-curvature="0" />
159 <path
160 style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:8.00158199, 8.00158199;stroke-dashoffset:1.60031641;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
161 d="m 431.99414,394.00977 -0.0137,0.42968 -3.99805,-0.13281 0.0137,-0.42187 0,-0.008 -0.006,-1.35937 0,-0.0254 -0.0527,-1.31446 -0.002,-0.0254 -0.10352,-1.36328 -0.002,-0.01 -0.16016,-1.41016 -0.20312,-1.34961 -0.004,-0.0215 -0.041,-0.21875 3.93164,-0.74414 0.0449,0.24023 0.0117,0.0723 0.2207,1.45703 0.01,0.0723 0.16602,1.45899 0.008,0.0742 0.11133,1.46093 0.006,0.0723 0.0586,1.46094 0,0.0723 0.006,1.45899 z m -1.11914,9.00195 -0.0156,0.0703 -0.36133,1.4082 -0.0195,0.0684 -0.41211,1.39453 -0.0215,0.0703 -0.46289,1.3789 -0.0234,0.0703 -0.51367,1.36328 -0.0273,0.0684 -0.5625,1.3457 -0.0293,0.0664 -0.17969,0.38867 -3.63281,-1.67774 0.15039,-0.32226 0.0137,-0.0312 0.54493,-1.30274 0.004,-0.0117 0.48242,-1.28516 0.004,-0.0117 0.42773,-1.27734 0.40039,-1.35938 0.3418,-1.33789 0.0469,-0.2168 3.9082,0.85352 z m -5.52344,-23.56055 -0.25,-0.63476 -0.0117,-0.0312 -0.54492,-1.24805 -0.0156,-0.0312 -0.60352,-1.25586 -0.0137,-0.0254 -0.68359,-1.29297 -0.002,-0.004 -0.6914,-1.19531 -0.0176,-0.0293 -0.64453,-1.02343 3.38672,-2.12891 0.68359,1.08789 0.0391,0.0625 0.74414,1.28906 0.0371,0.0664 0.69922,1.32227 0.0332,0.0664 0.6543,1.35547 0.0312,0.0684 0.60547,1.38477 0.0273,0.0703 0.26172,0.66601 z m -1.52929,38.8086 -0.0449,0.0605 -0.90625,1.18164 -0.0469,0.0566 -0.95313,1.15235 -0.0488,0.0566 -1.00196,1.12304 -0.0527,0.0547 -1.04883,1.08789 -2.88086,-2.77539 1.02344,-1.06054 0.92773,-1.03711 0.0234,-0.0274 0.88477,-1.0664 0.0215,-0.0274 0.86719,-1.13086 0.0176,-0.0234 0.1875,-0.26563 3.26171,2.31445 z m -6.66993,-51.46875 -0.68554,-0.68946 -0.0215,-0.0215 -0.99219,-0.92774 -0.0195,-0.0176 -0.98828,-0.85938 -0.0117,-0.01 -1.11132,-0.89648 -0.008,-0.008 -1.04687,-0.78516 -0.0176,-0.0117 -0.94922,-0.66015 2.28125,-3.28711 0.9668,0.67187 0.0586,0.043 1.16406,0.87109 0.0566,0.0449 1.13086,0.91406 0.0566,0.0469 1.09766,0.95508 0.0547,0.0488 1.06445,0.99609 0.0527,0.0508 0.70507,0.71093 z m -12.49609,-8.47852 -0.86523,-0.35547 -0.0176,-0.006 -1.32813,-0.49023 -0.0254,-0.01 -1.29101,-0.41992 -1.30469,-0.375 -0.0156,-0.004 -1.39843,-0.3457 -0.0156,-0.004 -1.02734,-0.21289 0.81445,-3.91601 1.10156,0.22851 0.0703,0.0156 1.43164,0.35351 0.0703,0.0195 1.41211,0.40429 0.0703,0.0215 1.39258,0.45508 0.0703,0.0254 1.3711,0.5039 0.0684,0.0274 0.9336,0.38281 z"
162 id="ellipse4178"
163 inkscape:connector-curvature="0" />
164 <path
165 style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.5999999;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
166 d="m 387.98242,367.36133 0,26.83008 20.5957,20.58593 2.82813,-2.83007 -19.42187,-19.41407 0,-25.17187 -4.00196,0 z"
167 id="path4180"
168 inkscape:connector-curvature="0" />
169 </g>
170 </g>
171 </g>
172 </g>
173</svg>
0174
=== added file 'src/qml/assets/input-keyboard-symbolic.svg'
--- src/qml/assets/input-keyboard-symbolic.svg 1970-01-01 00:00:00 +0000
+++ src/qml/assets/input-keyboard-symbolic.svg 2015-11-16 14:15:20 +0000
@@ -0,0 +1,221 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12 width="96"
13 height="96"
14 id="svg4874"
15 version="1.1"
16 inkscape:version="0.91+devel r"
17 viewBox="0 0 96 96.000001"
18 sodipodi:docname="input-keyboard-symbolic.svg">
19 <defs
20 id="defs4876" />
21 <sodipodi:namedview
22 id="base"
23 pagecolor="#ffffff"
24 bordercolor="#666666"
25 borderopacity="1.0"
26 inkscape:pageopacity="0.0"
27 inkscape:pageshadow="2"
28 inkscape:zoom="7.0249992"
29 inkscape:cx="12.362986"
30 inkscape:cy="54.10675"
31 inkscape:document-units="px"
32 inkscape:current-layer="g4780"
33 showgrid="true"
34 showborder="true"
35 fit-margin-top="0"
36 fit-margin-left="0"
37 fit-margin-right="0"
38 fit-margin-bottom="0"
39 inkscape:snap-bbox="true"
40 inkscape:bbox-paths="true"
41 inkscape:bbox-nodes="true"
42 inkscape:snap-bbox-edge-midpoints="true"
43 inkscape:snap-bbox-midpoints="true"
44 inkscape:object-paths="true"
45 inkscape:snap-intersection-paths="true"
46 inkscape:object-nodes="true"
47 inkscape:snap-smooth-nodes="true"
48 inkscape:snap-midpoints="true"
49 inkscape:snap-object-midpoints="true"
50 inkscape:snap-center="true"
51 showguides="true"
52 inkscape:guide-bbox="true"
53 inkscape:snap-global="true">
54 <inkscape:grid
55 type="xygrid"
56 id="grid5451"
57 empspacing="8" />
58 <sodipodi:guide
59 orientation="1,0"
60 position="8,-8.0000001"
61 id="guide4063" />
62 <sodipodi:guide
63 orientation="1,0"
64 position="4,-8.0000001"
65 id="guide4065" />
66 <sodipodi:guide
67 orientation="0,1"
68 position="-8,88.000001"
69 id="guide4067" />
70 <sodipodi:guide
71 orientation="0,1"
72 position="-8,92.000001"
73 id="guide4069" />
74 <sodipodi:guide
75 orientation="0,1"
76 position="104,4"
77 id="guide4071" />
78 <sodipodi:guide
79 orientation="0,1"
80 position="-5,8.0000001"
81 id="guide4073" />
82 <sodipodi:guide
83 orientation="1,0"
84 position="88,-8.0000001"
85 id="guide4077" />
86 <sodipodi:guide
87 orientation="0,1"
88 position="-8,84.000001"
89 id="guide4074" />
90 <sodipodi:guide
91 orientation="1,0"
92 position="12,-8.0000001"
93 id="guide4076" />
94 <sodipodi:guide
95 orientation="1,0"
96 position="84,-8.0000001"
97 id="guide4080" />
98 <sodipodi:guide
99 position="48,-8.0000001"
100 orientation="1,0"
101 id="guide4170" />
102 <sodipodi:guide
103 position="-8,48"
104 orientation="0,1"
105 id="guide4172" />
106 <sodipodi:guide
107 position="92,-8.0000001"
108 orientation="1,0"
109 id="guide4760" />
110 </sodipodi:namedview>
111 <metadata
112 id="metadata4879">
113 <rdf:RDF>
114 <cc:Work
115 rdf:about="">
116 <dc:format>image/svg+xml</dc:format>
117 <dc:type
118 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
119 <dc:title></dc:title>
120 </cc:Work>
121 </rdf:RDF>
122 </metadata>
123 <g
124 inkscape:label="Layer 1"
125 inkscape:groupmode="layer"
126 id="layer1"
127 transform="translate(67.857146,-78.50504)">
128 <g
129 transform="matrix(0,-1,-1,0,373.50506,516.50504)"
130 id="g4845"
131 style="display:inline">
132 <g
133 inkscape:export-ydpi="90"
134 inkscape:export-xdpi="90"
135 inkscape:export-filename="next01.png"
136 transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
137 id="g4778"
138 inkscape:label="Layer 1">
139 <g
140 transform="matrix(-1,0,0,1,575.99999,611)"
141 id="g4780"
142 style="display:inline">
143 <rect
144 style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
145 id="rect4782"
146 width="96.037987"
147 height="96"
148 x="-438.00244"
149 y="345.36221"
150 transform="scale(-1,1)" />
151 <path
152 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none"
153 d="m 407.99059,425.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
154 id="path4321"
155 inkscape:connector-curvature="0" />
156 <path
157 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none"
158 d="m 379.97952,411.36222 0,-36 -8.00318,0 0,36 8.00318,0 z"
159 id="path4301"
160 inkscape:connector-curvature="0" />
161 <path
162 sodipodi:nodetypes="ccccccccc"
163 inkscape:connector-curvature="0"
164 id="path4297"
165 d="m 371.97588,349.36261 c -10.00396,0 -9.8873,3.91306 -10.00396,14 l 0,59.99961 c 0.11666,10.08694 0,14 10.00396,14 l 36.01466,0 c 10.00396,0 9.8873,-3.91306 10.00396,-14 l 0,-59.99961 c -0.11666,-10.08694 0,-14 -10.00396,-14 z"
166 style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
167 <path
168 inkscape:connector-curvature="0"
169 id="path4165"
170 d="m 407.99059,411.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
171 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none" />
172 <path
173 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none"
174 d="m 407.99059,397.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
175 id="path4167"
176 inkscape:connector-curvature="0" />
177 <path
178 inkscape:connector-curvature="0"
179 id="path4169"
180 d="m 407.99059,383.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
181 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none" />
182 <path
183 inkscape:connector-curvature="0"
184 id="path4171"
185 d="m 393.98503,418.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
186 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none" />
187 <path
188 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none"
189 d="m 393.98503,404.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
190 id="path4173"
191 inkscape:connector-curvature="0" />
192 <path
193 inkscape:connector-curvature="0"
194 id="path4175"
195 d="m 393.98503,390.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
196 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none" />
197 <path
198 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none"
199 d="m 393.98503,376.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
200 id="path4177"
201 inkscape:connector-curvature="0" />
202 <path
203 inkscape:connector-curvature="0"
204 id="path4179"
205 d="m 379.97951,425.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
206 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none" />
207 <path
208 inkscape:connector-curvature="0"
209 id="path4181"
210 d="m 379.97947,369.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
211 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none" />
212 <path
213 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none"
214 d="m 407.99059,369.36222 0,-8.00001 -8.00317,0 0,8.00001 8.00317,0 z"
215 id="path4185"
216 inkscape:connector-curvature="0" />
217 </g>
218 </g>
219 </g>
220 </g>
221</svg>
0222
=== added file 'src/stickers-history-model.cpp'
--- src/stickers-history-model.cpp 1970-01-01 00:00:00 +0000
+++ src/stickers-history-model.cpp 2015-11-16 14:15:20 +0000
@@ -0,0 +1,307 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "stickers-history-model.h"
20
21// Qt
22#include <QtCore/QDebug>
23#include <QtCore/QMutexLocker>
24#include <QtSql/QSqlQuery>
25#include <QtSql/QSqlError>
26
27#define CONNECTION_NAME "messaging-app-stickers-history"
28
29/*!
30 \class StickersHistoryModel
31 \brief List model that stores information about most recently used stickers
32
33 StickersHistoryModel is a list model that stores information about the most
34 recently used stickers.
35 Each sticker is simply identified by the sticker pack name plus the name of
36 the sticker image file itself.
37 Stickers are ordered by the time of last use, with the most recent first.
38 By default the model stores a rolling list of the 10 most recently used
39 stickers, though this number can be changed by setting the /a limit
40*/
41StickersHistoryModel::StickersHistoryModel(QObject* parent)
42 : QAbstractListModel(parent),
43 m_limit(10)
44{
45 m_database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), CONNECTION_NAME);
46}
47
48StickersHistoryModel::~StickersHistoryModel()
49{
50 m_database.close();
51 m_database = QSqlDatabase();
52 QSqlDatabase::removeDatabase(CONNECTION_NAME);
53}
54
55void StickersHistoryModel::resetDatabase(const QString& databaseName)
56{
57 beginResetModel();
58 m_entries.clear();
59 m_database.close();
60 m_database.setDatabaseName(databaseName);
61 m_database.open();
62 createOrAlterDatabaseSchema();
63 endResetModel();
64 populateFromDatabase();
65}
66
67void StickersHistoryModel::createOrAlterDatabaseSchema()
68{
69 QMutexLocker ml(&m_dbMutex);
70 QSqlQuery query(m_database);
71 QString statement = QLatin1String("CREATE TABLE IF NOT EXISTS history "
72 "(sticker VARCHAR, mostRecentUse DATETIME);");
73 query.prepare(statement);
74 if (!query.exec()) {
75 qWarning() << "Query failed" << query.lastError();
76 }
77}
78
79void StickersHistoryModel::populateFromDatabase()
80{
81 QSqlQuery query(m_database);
82 QString statement = QLatin1String("SELECT sticker, mostRecentUse "
83 "FROM history ORDER BY mostRecentUse DESC;");
84 query.prepare(statement);
85 if (!query.exec()) {
86 qWarning() << "Query failed" << query.lastError();
87 }
88
89 int count = 0;
90 while (query.next()) {
91 HistoryEntry entry;
92 entry.sticker = query.value(0).toString();
93 entry.mostRecentUse = QDateTime::fromTime_t(query.value(1).toInt());
94 beginInsertRows(QModelIndex(), count, count);
95 m_entries.append(entry);
96 endInsertRows();
97 ++count;
98 }
99}
100
101QHash<int, QByteArray> StickersHistoryModel::roleNames() const
102{
103 static QHash<int, QByteArray> roles;
104 if (roles.isEmpty()) {
105 roles[Sticker] = "sticker";
106 roles[MostRecentUse] = "mostRecentUse";
107 }
108 return roles;
109}
110
111int StickersHistoryModel::rowCount(const QModelIndex& parent) const
112{
113 Q_UNUSED(parent);
114 return m_entries.count();
115}
116
117QVariant StickersHistoryModel::data(const QModelIndex& index, int role) const
118{
119 if (!index.isValid()) {
120 return QVariant();
121 }
122 const HistoryEntry& entry = m_entries.at(index.row());
123 switch (role) {
124 case Sticker:
125 return entry.sticker;
126 case MostRecentUse:
127 return entry.mostRecentUse;
128 default:
129 return QVariant();
130 }
131}
132
133const QString StickersHistoryModel::databasePath() const
134{
135 return m_database.databaseName();
136}
137
138int StickersHistoryModel::limit() const
139{
140 return m_limit;
141}
142
143void StickersHistoryModel::setLimit(int limit)
144{
145 if (limit != m_limit) {
146 m_limit = limit;
147 Q_EMIT limitChanged();
148 removeExcessRows();
149 }
150}
151
152void StickersHistoryModel::setDatabasePath(const QString& path)
153{
154 if (path != databasePath()) {
155 if (path.isEmpty()) {
156 resetDatabase(":memory:");
157 } else {
158 resetDatabase(path);
159 }
160 Q_EMIT databasePathChanged();
161 }
162}
163
164int StickersHistoryModel::getEntryIndex(const QString& sticker) const
165{
166 for (int i = 0; i < m_entries.count(); ++i) {
167 if (m_entries.at(i).sticker == sticker) {
168 return i;
169 }
170 }
171 return -1;
172}
173
174/*!
175 Add an entry to the model.
176
177 If an entry for the same sticker already exists, it is updated and placed
178 first in the model. Otherwise a new entry is created and added at the
179 begining of the model.
180 If the new row count exceeds the limit, excess rows are purged.
181*/
182void StickersHistoryModel::add(const QString& sticker)
183{
184 if (sticker.isEmpty()) {
185 return;
186 }
187 QDateTime now = QDateTime::currentDateTime();
188 int index = getEntryIndex(sticker);
189
190 if (index == -1) {
191 HistoryEntry entry;
192 entry.sticker = sticker;
193 entry.mostRecentUse = now;
194 beginInsertRows(QModelIndex(), 0, 0);
195 m_entries.prepend(entry);
196 endInsertRows();
197 insertNewEntryInDatabase(entry);
198 Q_EMIT rowCountChanged();
199 removeExcessRows();
200 } else {
201 HistoryEntry entry;
202 if (index > 0) {
203 beginMoveRows(QModelIndex(), index, index, QModelIndex(), 0);
204 }
205 entry = m_entries.takeAt(index);
206 entry.mostRecentUse = now;
207 m_entries.prepend(entry);
208 if (index > 0) {
209 endMoveRows();
210 }
211 QVector<int> roles;
212 roles << MostRecentUse;
213 Q_EMIT dataChanged(this->index(0), this->index(0), roles);
214 updateExistingEntryInDatabase(m_entries.first());
215 }
216}
217
218void StickersHistoryModel::removeExcessRows()
219{
220 if (m_limit < rowCount()) {
221 beginRemoveRows(QModelIndex(), m_limit, rowCount() - 1);
222 for (int i = rowCount() - 1; i >= m_limit; i--) {
223 HistoryEntry item = m_entries.takeAt(i);
224 removeEntryFromDatabase(item.sticker);
225 }
226 endRemoveRows();
227 Q_EMIT rowCountChanged();
228 }
229}
230
231void StickersHistoryModel::insertNewEntryInDatabase(const HistoryEntry& entry)
232{
233 QMutexLocker ml(&m_dbMutex);
234 QSqlQuery query(m_database);
235 static QString statement = QLatin1String("INSERT INTO history (sticker, mostRecentUse) "
236 "VALUES (?, ?);");
237 query.prepare(statement);
238 query.addBindValue(entry.sticker);
239 query.addBindValue(entry.mostRecentUse.toTime_t());
240
241 if (!query.exec()) {
242 qWarning() << "Query failed" << query.lastError();
243 }
244}
245
246void StickersHistoryModel::updateExistingEntryInDatabase(const HistoryEntry& entry)
247{
248 QMutexLocker ml(&m_dbMutex);
249 QSqlQuery query(m_database);
250 static QString statement = QLatin1String("UPDATE history SET mostRecentUse=?"
251 " WHERE sticker=?;");
252 query.prepare(statement);
253 query.addBindValue(entry.mostRecentUse.toTime_t());
254 query.addBindValue(entry.sticker);
255 if (!query.exec()) {
256 qWarning() << "Query failed" << query.lastError();
257 }
258}
259
260void StickersHistoryModel::removeEntryFromDatabase(const QString& sticker)
261{
262 QMutexLocker ml(&m_dbMutex);
263 QSqlQuery query(m_database);
264 static QString statement = QLatin1String("DELETE FROM history WHERE sticker=?;");
265 query.prepare(statement);
266 query.addBindValue(sticker);
267 if (!query.exec()) {
268 qWarning() << "Query failed" << query.lastError();
269 }
270}
271
272void StickersHistoryModel::clearAll()
273{
274 if (!m_entries.isEmpty()) {
275 beginResetModel();
276 m_entries.clear();
277 endResetModel();
278 clearDatabase();
279 Q_EMIT rowCountChanged();
280 }
281}
282
283void StickersHistoryModel::clearDatabase()
284{
285 QMutexLocker ml(&m_dbMutex);
286 QSqlQuery query(m_database);
287 QString statement = QLatin1String("DELETE FROM history;");
288 query.prepare(statement);
289 if (!query.exec()) {
290 qWarning() << "Query failed" << query.lastError();
291 }
292}
293
294QVariantMap StickersHistoryModel::get(int i) const
295{
296 QVariantMap item;
297 QHash<int, QByteArray> roles = roleNames();
298
299 QModelIndex modelIndex = index(i, 0);
300 if (modelIndex.isValid()) {
301 Q_FOREACH(int role, roles.keys()) {
302 QString roleName = QString::fromUtf8(roles.value(role));
303 item.insert(roleName, data(modelIndex, role));
304 }
305 }
306 return item;
307}
0308
=== added file 'src/stickers-history-model.h'
--- src/stickers-history-model.h 1970-01-01 00:00:00 +0000
+++ src/stickers-history-model.h 2015-11-16 14:15:20 +0000
@@ -0,0 +1,91 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#ifndef __STICKERS_HISTORY_MODEL_H__
20#define __STICKERS_HISTORY_MODEL_H__
21
22// Qt
23#include <QtCore/QAbstractListModel>
24#include <QtCore/QDateTime>
25#include <QtCore/QList>
26#include <QtCore/QMutex>
27#include <QtCore/QString>
28#include <QtSql/QSqlDatabase>
29
30class StickersHistoryModel : public QAbstractListModel
31{
32 Q_OBJECT
33
34 Q_PROPERTY(QString databasePath READ databasePath WRITE setDatabasePath NOTIFY databasePathChanged)
35 Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged)
36 Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged)
37
38 Q_ENUMS(Roles)
39
40public:
41 StickersHistoryModel(QObject* parent=0);
42 ~StickersHistoryModel();
43
44 enum Roles {
45 Sticker = Qt::UserRole + 1,
46 MostRecentUse
47 };
48
49 // reimplemented from QAbstractListModel
50 QHash<int, QByteArray> roleNames() const;
51 int rowCount(const QModelIndex& parent=QModelIndex()) const;
52 QVariant data(const QModelIndex& index, int role) const;
53
54 const QString databasePath() const;
55 void setDatabasePath(const QString& path);
56 int limit() const;
57 void setLimit(int limit);
58
59 Q_INVOKABLE void add(const QString& sticker);
60 Q_INVOKABLE void clearAll();
61 Q_INVOKABLE QVariantMap get(int index) const;
62
63Q_SIGNALS:
64 void databasePathChanged() const;
65 void rowCountChanged() const;
66 void limitChanged() const;
67
68protected:
69 struct HistoryEntry {
70 QString sticker;
71 QDateTime mostRecentUse;
72 };
73 QList<HistoryEntry> m_entries;
74 int getEntryIndex(const QString& sticker) const;
75 void updateExistingEntryInDatabase(const HistoryEntry& entry);
76
77private:
78 QMutex m_dbMutex;
79 QSqlDatabase m_database;
80 int m_limit;
81
82 void resetDatabase(const QString& databaseName);
83 void createOrAlterDatabaseSchema();
84 void populateFromDatabase();
85 void insertNewEntryInDatabase(const HistoryEntry& entry);
86 void removeEntryFromDatabase(const QString& sticker);
87 void clearDatabase();
88 void removeExcessRows();
89};
90
91#endif // __STICKERS_HISTORY_MODEL_H__
092
=== modified file 'tests/qml/CMakeLists.txt'
--- tests/qml/CMakeLists.txt 2015-08-13 18:34:55 +0000
+++ tests/qml/CMakeLists.txt 2015-11-16 14:15:20 +0000
@@ -1,33 +1,45 @@
1find_program(QMLTESTRUNNER_BIN1find_package(Qt5Core REQUIRED)
2 NAMES qmltestrunner2find_package(Qt5Gui REQUIRED)
3 PATHS /usr/lib/*/qt5/bin3find_package(Qt5Network REQUIRED)
4 NO_DEFAULT_PATH4find_package(Qt5Qml REQUIRED)
5)5find_package(Qt5Quick REQUIRED)
66find_package(Qt5QuickTest REQUIRED)
7find_program(XVFB_RUN_BIN7find_package(Qt5Sql REQUIRED)
8 NAMES xvfb-run8
9)9set(XVFB_COMMAND)
1010find_program(XVFBRUN xvfb-run)
11macro(DECLARE_QML_TEST TST_NAME TST_QML_FILE)11if(XVFBRUN)
12 add_test(NAME ${TST_NAME}12 set(XVFB_COMMAND ${XVFBRUN} -s "-screen 0 1024x768x24" -a)
13 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
14 COMMAND ${XVFB_RUN_BIN} -a -s "-screen 0 1024x768x24" ${QMLTESTRUNNER_BIN} -import ${qml_BINARY_DIR} -input ${CMAKE_CURRENT_SOURCE_DIR}/${TST_QML_FILE}
15 )
16endmacro()
17
18if(QMLTESTRUNNER_BIN AND XVFB_RUN_BIN)
19 declare_qml_test("message_bubble" tst_MessageBubble.qml)
20 declare_qml_test("messages_view" tst_MessagesView.qml)
21else()13else()
22 if (NOT QMLTESTRUNNER_BIN)14 message(WARNING "Cannot find xvfb-run.")
23 message(WARNING "Qml tests disabled: qmltestrunner not found")
24 else()
25 message(WARNING "Qml tests disabled: xvfb-run not found")
26 endif()
27endif()15endif()
2816
29set(QML_TST_FILES17set(CMAKE_AUTOMOC ON)
30 tst_MessageBubble.qml18
31 tst_MessagesView.qml19set(TEST tst_QmlTests)
32)20set(SOURCES
33add_custom_target(tst_QmlFiles ALL SOURCES ${QML_TST_FILES})21 ${messaging-app_SOURCE_DIR}/src/stickers-history-model.cpp
22 tst_QmlTests.cpp
23)
24add_executable(${TEST} ${SOURCES})
25include_directories(
26 ${CMAKE_CURRENT_BINARY_DIR}
27 ${CMAKE_CURRENT_SOURCE_DIR}
28 ${messaging-app_SOURCE_DIR}/src/
29)
30target_link_libraries(${TEST}
31 Qt5::Core
32 Qt5::Gui
33 Qt5::Network
34 Qt5::Qml
35 Qt5::Quick
36 Qt5::QuickTest
37 Qt5::Sql
38)
39add_test(${TEST} ${XVFB_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/${TEST}
40 -input ${CMAKE_CURRENT_SOURCE_DIR}
41 -import ${CMAKE_BINARY_DIR}/src)
42
43# make qml files visible in QtCreator
44file(GLOB_RECURSE NON_COMPILED_FILES *.qml)
45add_custom_target(NON_COMPILED_TARGET ALL SOURCES ${NON_COMPILED_FILES})
3446
=== added file 'tests/qml/tst_QmlTests.cpp'
--- tests/qml/tst_QmlTests.cpp 1970-01-01 00:00:00 +0000
+++ tests/qml/tst_QmlTests.cpp 2015-11-16 14:15:20 +0000
@@ -0,0 +1,78 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19// Qt
20#include <QtCore/QDir>
21#include <QtCore/QTemporaryDir>
22#include <QtCore/QObject>
23#include <QtCore/QString>
24#include <QtCore/QTemporaryDir>
25#include <QtQml/QtQml>
26#include <QtQuickTest/QtQuickTest>
27
28// local
29#include "stickers-history-model.h"
30
31static QObject* StickersHistoryModel_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine)
32{
33 Q_UNUSED(engine);
34 Q_UNUSED(scriptEngine);
35 return new StickersHistoryModel();
36}
37
38class TestContext : public QObject
39{
40 Q_OBJECT
41
42 Q_PROPERTY(QString testDir READ testDir CONSTANT)
43
44public:
45 explicit TestContext(QObject* parent=0)
46 : QObject(parent)
47 {
48 QDir dir(m_temporary.path());
49 dir.mkpath("stickers");
50 }
51
52 QString testDir() const
53 {
54 return m_temporary.path();
55 }
56
57 QTemporaryDir m_temporary;
58};
59
60static QObject* TestContext_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine)
61{
62 Q_UNUSED(engine);
63 Q_UNUSED(scriptEngine);
64 return new TestContext();
65}
66
67int main(int argc, char** argv)
68{
69 const char* uri = "messagingapp.private";
70 qmlRegisterSingletonType<StickersHistoryModel>(uri, 0, 1, "StickersHistoryModel", StickersHistoryModel_singleton_factory);
71
72 const char* testUri = "messagingapptest.private";
73 qmlRegisterSingletonType<TestContext>(testUri, 0, 1, "TestContext", TestContext_singleton_factory);
74
75 return quick_test_main(argc, argv, "QmlTests", 0);
76}
77
78#include "tst_QmlTests.moc"
079
=== added file 'tests/qml/tst_StickersHistoryModel.qml'
--- tests/qml/tst_StickersHistoryModel.qml 1970-01-01 00:00:00 +0000
+++ tests/qml/tst_StickersHistoryModel.qml 2015-11-16 14:15:20 +0000
@@ -0,0 +1,188 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This file is part of messaging-app.
5 *
6 * messaging-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * messaging-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.2
20import QtTest 1.0
21import Ubuntu.Components 1.3
22import Ubuntu.Test 0.1
23import messagingapp.private 0.1
24import messagingapptest.private 0.1
25
26Item {
27 id: root
28 width: 1
29 height: 1
30
31 property var model: StickersHistoryModel
32
33 SignalSpy {
34 id: countSpy
35 target: model
36 signalName: "rowCountChanged"
37 }
38
39 SignalSpy {
40 id: rowsInsertedSpy
41 target: model
42 signalName: "rowsInserted"
43 }
44
45 SignalSpy {
46 id: rowsMovedSpy
47 target: model
48 signalName: "rowsMoved"
49 }
50
51 SignalSpy {
52 id: dataChangedSpy
53 target: model
54 signalName: "dataChanged"
55 }
56
57 SignalSpy {
58 id: rowsRemovedSpy
59 target: model
60 signalName: "rowsRemoved"
61 }
62
63 UbuntuTestCase {
64 id: test
65 name: 'stickersHistoryModelTestCase'
66 when: windowShown
67
68 function init() {
69 model.databasePath = ":memory:"
70 model.limit = 10
71 }
72
73 function cleanup() {
74 model.databasePath = ""
75 countSpy.clear()
76 rowsInsertedSpy.clear()
77 dataChangedSpy.clear()
78 rowsMovedSpy.clear()
79 rowsRemovedSpy.clear()
80 }
81
82 function test_initiallyEmpty() {
83 compare(model.count, 0)
84 }
85
86 function test_addingEmptyOrInvalidDoesNothing() {
87 model.add("")
88 compare(model.count, 0)
89 model.add(null)
90 compare(model.count, 0)
91 model.add(undefined)
92 compare(model.count, 0)
93 }
94
95 function test_add_new() {
96 model.add("foo")
97 compare(model.count, 1)
98 compare(countSpy.count, 1)
99 compare(rowsInsertedSpy.count, 1)
100 compare(rowsInsertedSpy.signalArguments[0][1], 0) // first
101 compare(rowsInsertedSpy.signalArguments[0][2], 0) // last
102
103 model.add("bar")
104 compare(model.count, 2)
105 compare(countSpy.count, 2)
106 compare(rowsInsertedSpy.count, 2)
107 compare(rowsInsertedSpy.signalArguments[0][1], 0) // first
108 compare(rowsInsertedSpy.signalArguments[0][2], 0) // last
109 }
110
111 function test_signals() {
112 model.limit = 2
113
114 model.add("a")
115 compare(model.count, 1)
116 compare(countSpy.count, 1)
117 compare(rowsInsertedSpy.count, 1)
118 compare(rowsInsertedSpy.signalArguments[0][1], 0) // first
119 compare(rowsInsertedSpy.signalArguments[0][2], 0) // last
120
121 model.add("a")
122 compare(dataChangedSpy.count, 1)
123
124 model.add("b")
125 compare(model.count, 2)
126 compare(countSpy.count, 2)
127 compare(rowsInsertedSpy.count, 2)
128 compare(rowsInsertedSpy.signalArguments[0][1], 0) // first
129 compare(rowsInsertedSpy.signalArguments[0][2], 0) // last
130
131 model.add("a")
132 compare(rowsMovedSpy.count, 1)
133 compare(rowsMovedSpy.signalArguments[0][1], 1) // from first
134 compare(rowsMovedSpy.signalArguments[0][2], 1) // from last
135 compare(rowsMovedSpy.signalArguments[0][4], 0) // to
136 compare(dataChangedSpy.count, 2)
137
138 model.add("c")
139 compare(model.count, 2)
140 compare(rowsRemovedSpy.count, 1)
141 compare(rowsRemovedSpy.signalArguments[0][1], 2) // from
142 compare(rowsRemovedSpy.signalArguments[0][2], 2) // to
143 }
144
145 function test_get_and_order() {
146 model.add("a")
147 // datetimes have only millisecond precision, and we want to prevent
148 // the two entries to have the same timestamp
149 wait(100)
150 model.add("b")
151
152 var a = model.get(1)
153 verify(a !== null)
154 compare(a.sticker, "a")
155
156 var b = model.get(0)
157 verify(b !== null)
158 compare(b.sticker, "b")
159
160 verify(b.mostRecentUse.toISOString() > a.mostRecentUse.toISOString())
161
162 wait(100)
163 model.add("a")
164
165 a = model.get(0)
166 verify(a !== null)
167 compare(a.sticker, "a")
168
169 b = model.get(1)
170 verify(b !== null)
171 compare(b.sticker, "b")
172
173 verify(a.mostRecentUse.toISOString() > b.mostRecentUse.toISOString())
174 }
175
176 function test_limit_change() {
177 model.add("d")
178 model.add("c")
179 model.add("b")
180 model.add("a")
181 model.limit = 2
182
183 compare(model.count, 2)
184 compare(model.get(0).sticker, "a")
185 compare(model.get(1).sticker, "b")
186 }
187 }
188}

Subscribers

People subscribed via source and target branches