Commit 5341ee65 authored by Martin Straka's avatar Martin Straka

First message search prototype

parent 1cb0e4cb
......@@ -60,6 +60,7 @@ ApplicationWindow {
property Component pageSettingsPin: PageSettingsPin {}
property Component pageSettingsStorage: PageSettingsStorage {}
property Component pageSettingsSync: PageSettingsSync {}
property Component pageMessageSearch: PageMessageSearch {}
// header background color
property string mainHeaderBgColor: "#00539b"
......
......@@ -87,6 +87,11 @@ Component {
image: "qrc:/ui/account-plus.svg"
}
ListElement {
index: 6
name: qsTr("Find message")
image: "qrc:/ui/magnify.svg"
}
ListElement {
// id:generalItem
index: 2
name: qsTr("General")
......@@ -188,6 +193,11 @@ Component {
"pageView": pageView,
"statusBar": statusBar
}, StackView.Immediate)
} else if (index == 6) {
pageView.replace(pageMessageSearch, {
"pageView": pageView,
"statusBar": statusBar
}, StackView.Immediate)
} else {
pageView.pop(StackView.Immediate)
}
......
......@@ -203,7 +203,7 @@ Component {
}
Text {
id: r1c2
text: rFromTo
text: (msgType == MessageType.TYPE_RECEIVED) ? rFrom : rTo
color: if (rReadLocally) {
datovkaPalette.dark
} else {
......
/*
* Copyright (C) 2014-2017 CZ.NIC
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations including
* the two.
*/
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Window 2.1
import QtQuick.Layouts 1.2
import QtQuick.Dialogs 1.2
import QtGraphicalEffects 1.0
import cz.nic.mobileDatovka.messages 1.0
/*
* Roles are defined in MessageListModel::roleNames() and are accessed directly
* via their names.
*/
Component {
id: msgSearchListComponent
Item {
id: msgSearchListPage
/* These properties must be set by caller. */
property var pageView
property var statusBar
Component.onCompleted: {
}
Rectangle {
id: header
anchors.top: parent.top
width: parent.width
height: headerHeight
color: datovkaPalette.highlight
Image {
id: backElement2
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: defaultMargin
sourceSize.height: navImgHeight
source: "qrc:/ui/back.svg"
}
Rectangle {
anchors.left: parent.left
width: parent.width * 0.5
height: parent.height
color: "transparent"
MouseArea {
anchors.fill: parent
onClicked: {
messages.clearMessagesModel()
statusBar.visible = false
pageView.pop(StackView.Immediate)
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
anchors.left: backElement2.right
anchors.leftMargin: defaultMargin
Text {
text: qsTr("Find message")
font.bold: true
color: datovkaPalette.text
}
}
}
Item {
id: searchPanel
anchors.top: header.bottom
width: parent.width
height: parent.height * 0.1
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: formItemVerticalSpacing
Row {
spacing: defaultMargin
TextField {
id: searchPhraseText
placeholderText: qsTr("Enter prase")
Menu {
id: searchPhraseTextMenu
implicitWidth: 800 // Chosen to be large enough
MenuItem {
text: qsTr("Clear")
enabled: searchPhraseText.text != ""
onTriggered: {
searchPhraseText.clear()
}
}
MenuItem {
text: qsTr("Copy")
enabled: searchPhraseText.text != ""
onTriggered: {
searchPhraseText.selectAll()
searchPhraseText.copy()
searchPhraseText.deselect()
}
}
MenuItem {
text: qsTr("Paste")
enabled: searchPhraseText.canPaste
onTriggered: {
searchPhraseText.paste()
}
}
}
onPressAndHold: {
if (settings.useExplicitClipboardOperations()) {
searchPhraseTextMenu.implicitWidth = computeMenuWidth(searchPhraseTextMenu)
searchPhraseTextMenu.x = mouse.x
searchPhraseTextMenu.y = mouse.y
searchPhraseTextMenu.open()
}
}
}
ComboBox {
id: searchOptionComboBox
currentIndex: 0
textRole: "key"
model: ListModel {
id: searchOptionComboBoxModel
// Don't change position of list elements, keys and values.
ListElement { key: qsTr("All envelope fields"); value: "all" }
ListElement { key: qsTr("Recieved messages"); value: "rmsg" }
ListElement { key: qsTr("Sent messages"); value: "smsg" }
}
}
Button {
id: searchButton
text: qsTr("Search")
onClicked: {
emptyList.visible = (messages.searchMsg(searchPhraseText.text, 0) <= 0)
}
}
}
}
}
Text {
id: emptyList
visible: false
color: datovkaPalette.text
anchors.centerIn: parent
text: qsTr("No message for the search phrase.")
}
ListView {
id: messageList
anchors.top: searchPanel.bottom
anchors.bottom: parent.bottom
clip: true
spacing: 1
opacity: 1
visible: true
width: parent.width
interactive: true
model: messageListModel
delegate:
Rectangle {
id: messageItem
height: listItemHeight
width: parent.width
color: if (rReadLocally) {
datovkaPalette.alternateBase
} else {
datovkaPalette.base
}
Rectangle {
id: msgEnvelope
anchors.left: parent.left
anchors.leftMargin: defaultMargin
height: parent.height
width: parent.width * 0.95
color: parent.color
GridLayout {
id: grid
columns: 2
rows: 3
rowSpacing: defaultMargin * 0.2
columnSpacing: defaultMargin
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: r1c1
width: defaultMargin * 3
Layout.fillHeight: true
color: "transparent"
Image {
id: r1c1p
anchors.centerIn: parent
sourceSize.height: parent.height * 0.8
source: if (rReadLocally) {
"qrc:/ui/email-open-outline.svg"
} else {
"qrc:/ui/email-outline.svg"
}
}
ColorOverlay {
anchors.fill: r1c1p
source: r1c1p
color: datovkaPalette.text
}
}
Text {
id: r1c2
text: (rMsgType == MessageType.TYPE_RECEIVED) ? rFrom : rTo
color: if (rReadLocally) {
datovkaPalette.dark
} else {
datovkaPalette.highlight
}
font.bold: true
}
Rectangle {
id: r2c1
width: defaultMargin * 3
Layout.fillHeight: true
color: "transparent"
Image {
id: r2c1p
anchors.centerIn: parent
sourceSize.height: parent.height * 0.8
source: if (rAttachmentsDownloaded) {
"qrc:/ui/paperclip.svg"
} else {
"qrc:/ui/datovka-msg-blank.png"
}
}
ColorOverlay {
anchors.fill: r2c1p
source: r2c1p
color: datovkaPalette.text
}
}
Text {
id: r2c2
text: rAnnotation
color: datovkaPalette.text
font.bold: true
}
Image {
id: r3c1
anchors.centerIn: parent
sourceSize.width: 1
fillMode: Image.PreserveAspectFit
source: "qrc:/ui/datovka-msg-blank.png"
}
Text {
id: r3c2
text: rMsgId + " " + rDelivTime + " " + rAcceptTime
color: datovkaPalette.mid
font.pointSize: textFontSizeSmall
renderType: Text.NativeRendering
font.bold: true
}
}
}
Rectangle {
id: next
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
height: parent.height
width: parent.width * 0.07
color: parent.color
Image {
id: nextImage
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: defaultMargin
sourceSize.height: navImgHeight
source: "qrc:/ui/next.svg"
}
ColorOverlay {
anchors.fill: nextImage
source: nextImage
color: datovkaPalette.text
}
}
MouseArea {
anchors.fill: parent
onClicked: {
statusBar.visible = false
pageView.push(pageMessageDetail, {
"pageView": pageView,
"statusBar": statusBar,
"fromLocalDb": true,
"acntName": rUserName,
"userName": rUserName,
"msgType": rMsgType,
"msgId": rMsgId
}, StackView.Immediate)
}
}
Rectangle {
anchors.top: parent.bottom
height: 1
width: parent.width
color: datovkaPalette.mid
}
}
}
}
}
......@@ -112,5 +112,6 @@
<file>../qml/pages/PageSettingsStorage.qml</file>
<file>../qml/pages/PageSettingsSync.qml</file>
<file>../qml/main.qml</file>
<file>../qml/pages/PageMessageSearch.qml</file>
</qresource>
</RCC>
......@@ -87,6 +87,7 @@ const struct QmlTypeEntry qmlPages[] = {
{ "PageSettingsPin", 1, 0 },
{ "PageSettingsStorage", 1, 0 },
{ "PageSettingsSync", 1, 0 },
{ "PageMessageSearch", 1, 0 },
{ NULL, 0, 0 }
};
......
......@@ -99,7 +99,7 @@ void Messages::fillMessageList(const QString &userName,
globMessagesModel.clearAll();
/* Translate into database enum type. */
msgDb->getMessageListDataFromDb(enumToDbRepr(msgType));
msgDb->getMessageListDataFromDb(userName, enumToDbRepr(msgType));
}
......@@ -523,3 +523,28 @@ bool Messages::relocateDatabases(const QString &newLocation,
return relocationSucceeded;
}
int Messages::searchMsg(const QString &phrase, int options)
{
qDebug("%s()", __func__);
int msgs = 0;
globMessagesModel.clearAll();
QStringList userNameList = AccountListModel::globAccounts.keys();
foreach (const QString &userName, userNameList) {
MessageDb *msgDb = Q_NULLPTR;
msgDb = globMessageDbsPtr->accessMessageDb(globSet.dbsLocation, userName,
AccountListModel::globAccounts[userName].storeToDisk());
if (msgDb == Q_NULLPTR) {
qDebug() << "ERROR: Message database cannot open!" << userName;
continue;
}
msgs += msgDb->searchAndAppendMsgs(userName, phrase, options);
}
return msgs;
}
......@@ -49,6 +49,9 @@ public:
/*!
* @brief Load messages from database and fill QML listview via model.
*
* @param[in] userName User name identifying account.
* @param[in] msgType Message orientation.
*/
Q_INVOKABLE void fillMessageList(const QString &userName,
enum MessageType msgType);
......@@ -99,6 +102,16 @@ public:
Q_INVOKABLE
bool moveOrCreateNewDbsToNewLocation(const QString &newLocation);
/*!
* @brief Search messages via all accounts.
*
* @param[in] phrase Search phrase.
* @param[in] options Search options.
* @return Number of messages in the search result.
*/
Q_INVOKABLE
int searchMsg(const QString &phrase, int options);
/*!
* @brief Delete messages from all message databases together with files
* where lifetime expired.
......
......@@ -29,25 +29,32 @@
MessageModelEntry::MessageModelEntry(const MessageModelEntry &mme)
: m_dmId(mme.m_dmId),
m_fromTo(mme.m_fromTo),
m_userName(mme.m_userName),
m_from(mme.m_from),
m_to(mme.m_to),
m_annotation(mme.m_annotation),
m_deliveryTime(mme.m_deliveryTime),
m_acceptanceTime(mme.m_acceptanceTime),
m_readLocally(mme.m_readLocally),
m_attachmentsDownloaded(mme.m_attachmentsDownloaded)
m_attachmentsDownloaded(mme.m_attachmentsDownloaded),
m_msgType(mme.m_msgType)
{
}
MessageModelEntry::MessageModelEntry(qint64 dmId, const QString &fromTo,
MessageModelEntry::MessageModelEntry(qint64 dmId, const QString &userName,
const QString &from, const QString &to,
const QString &annotation, const QString &dTime, const QString &aTime,
bool readLocally, bool attachmentsDownloaded)
bool readLocally, bool attachmentsDownloaded, int msgType)
: m_dmId(dmId),
m_fromTo(fromTo),
m_userName(userName),
m_from(from),
m_to(to),
m_annotation(annotation),
m_deliveryTime(dTime),
m_acceptanceTime(aTime),
m_readLocally(readLocally),
m_attachmentsDownloaded(attachmentsDownloaded)
m_attachmentsDownloaded(attachmentsDownloaded),
m_msgType(msgType)
{
}
......@@ -56,14 +63,34 @@ qint64 MessageModelEntry::dmId(void) const
return m_dmId;
}
QString MessageModelEntry::fromTo(void) const
QString MessageModelEntry::userName(void) const
{
return m_fromTo;
return m_userName;
}
void MessageModelEntry::setFromTo(const QString &fromTo)
void MessageModelEntry::setUserName(const QString &userName)
{
m_fromTo = fromTo;
m_userName = userName;
}
QString MessageModelEntry::from(void) const
{
return m_from;
}
void MessageModelEntry::setFrom(const QString &from)
{
m_from = from;
}
QString MessageModelEntry::to(void) const
{
return m_to;
}
void MessageModelEntry::setTo(const QString &to)
{
m_to = to;
}
QString MessageModelEntry::annotation(void) const
......@@ -116,6 +143,16 @@ void MessageModelEntry::setAttachmentsDownloaded(bool attachmentsDownloaded)
m_attachmentsDownloaded = attachmentsDownloaded;
}
int MessageModelEntry::msgType(void) const
{
return m_msgType;
}
void MessageModelEntry::setMsgType(int msgType)
{
m_msgType = msgType;
}
MessageListModel globMessagesModel;
MessageListModel::MessageListModel(QObject *parent)
......@@ -137,12 +174,15 @@ QHash<int, QByteArray> MessageListModel::roleNames(void) const
static QHash<int, QByteArray> roles;
if (roles.isEmpty()) {
roles[ROLE_MSG_ID] = "rMsgId";
roles[ROLE_FROM_TO] = "rFromTo";
roles[ROLE_USERNAME] = "rUserName";
roles[ROLE_FROM] = "rFrom";
roles[ROLE_TO] = "rTo";
roles[ROLE_ANNOTATION] = "rAnnotation";
roles[ROLE_DELIVERY_TIME] = "rDelivTime";
roles[ROLE_ACCEPTANCE_TIME] = "rAcceptTime";
roles[ROLE_READ_LOCALLY] = "rReadLocally";
roles[ROLE_ATTACHMENTS_DOWNLOADED] = "rAttachmentsDownloaded";
roles[ROLE_MSG_TYPE] = "rMsgType";
}
return roles;
}
......@@ -159,8 +199,14 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const
case ROLE_MSG_ID:
return message.dmId();
break;
case ROLE_FROM_TO:
return message.fromTo();
case ROLE_USERNAME:
return message.userName();
break;
case ROLE_FROM:
return message.from();
break;
case ROLE_TO:
return message.to();
break;
case ROLE_ANNOTATION:
return message.annotation();
......@@ -177,6 +223,9 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const
case ROLE_ATTACHMENTS_DOWNLOADED:
return message.attachmentsDownloaded();
break;
case ROLE_MSG_TYPE:
return message.msgType();
break;
default:
/* Do nothing. */
break;
......@@ -208,33 +257,44 @@ void MessageListModel::deleteMessage(qint64 dmId)
endRemoveRows();
}
void MessageListModel::setQuery(QSqlQuery &query)
int MessageListModel::setQuery(const QString &userName, QSqlQuery &query,
bool isAppend)
{
if (query.record().count() != 7) {
return;
int msgCnt = 0;
if (query.record().count() != 9) {
return msgCnt;
}
beginResetModel();
m_messages.clear();
if (!isAppend) {
m_messages.clear();
}
query.first();
while (query.isActive() && query.isValid()) {
m_messages.append(MessageModelEntry(
query.value(0).toLongLong(),
userName,
query.value(1).toString(),
query.value(2).toString(),
dateTimeStrFromDbFormat(query.value(3).toString(),
DATETIME_QML_FORMAT),
query.value(3).toString(),
dateTimeStrFromDbFormat(query.value(4).toString(),
DATETIME_QML_FORMAT),
query.value(5).toBool(),
query.value(6).toBool()));
dateTimeStrFromDbFormat(query.value(5).toString(),