diff --git a/src/core/Bar.qml b/src/core/Bar.qml index 191f85030..4c179c918 100644 --- a/src/core/Bar.qml +++ b/src/core/Bar.qml @@ -1,477 +1,493 @@ /* GCompris - Bar.qml * * Copyright (C) 2014-2016 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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 . */ import QtQuick 2.2 import GCompris 1.0 import "qrc:/gcompris/src/core/core.js" as Core /** * A QML component for GCompris' navigation bar. * @ingroup components * * The Bar is visible in all activities and the main menu screen. It can be * hidden by clicking on the 'toggle' region. * * It can consist of a couple of child-elements, mostly buttons, defined * in the BarEnumContent container. An activity can define which elements its bar should * present using the content property. In most cases it contains at least * BarEnumContent.help, BarEnumContent.home and BarEnumContent.level. * * Cf. the BarEnumContent container for a full list of available Bar elements. * * Cf. the Bar object used in Template.qml as an example of how a minimal * Bar implementation should look like. * * @sa BarButton, BarEnumContent * @inherit QtQuick.Item */ Item { id: bar /** * type: real * Minimum size for BarZoom */ readonly property real minWidth: (parent.width - 20 - 10 * ApplicationInfo.ratio) / (totalWidth + textLength) /** * type: real * Maximum size for barZoom */ readonly property real maxWidth: 1.2 * ApplicationInfo.ratio /** * type: real * Length of level String */ property real textLength /** * type: real * The maximum size allowed for the bar */ readonly property real maxBarWidth: (totalWidth+textLength) * maxWidth + (numberOfButtons - 1) * 5 + 10 * ApplicationInfo.ratio /** * type: real * Font size for text level */ readonly property real textSize: (parent.width9) textLength = fullButton else textLength = halfButton } } } Component { id: next BarButton { source: "qrc:/gcompris/src/core/resource/bar_next.svg"; sourceSize.width: halfButton * barZoom onClicked: bar.nextLevelClicked() } } Component { id: repeat BarButton { source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: fullButton * barZoom onClicked: bar.repeatClicked() } } Component { id: reload BarButton { source: "qrc:/gcompris/src/core/resource/bar_reload.svg"; sourceSize.width: fullButton * barZoom onClicked: bar.reloadClicked() } } Component { id: config BarButton { source: "qrc:/gcompris/src/core/resource/bar_config.svg"; sourceSize.width: fullButton * barZoom onClicked: bar.configClicked() } } Component { id: home BarButton { source: "qrc:/gcompris/src/core/resource/bar_home.svg"; sourceSize.width: fullButton * barZoom onClicked: bar.homeClicked() } } Component { id: downloadImage AnimatedImage { source: "qrc:/gcompris/src/core/resource/loader.gif" MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true onClicked: { var downloadDialog = Core.showDownloadDialog(bar.parent, {}); } } } } /// @endcond } diff --git a/src/core/BarEnumContent.qml b/src/core/BarEnumContent.qml index eb539d82a..bcf8bbf53 100644 --- a/src/core/BarEnumContent.qml +++ b/src/core/BarEnumContent.qml @@ -1,108 +1,110 @@ /* GCompris - BarEnumContent.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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 . */ import QtQuick 2.2 /** * QML container for defining all visible child elements of a Bar. * @ingroup components * * Used in the Bar.content property. * * The @p value property must be set to a logically OR-ed combination of * the other properties each representing one Bar element. */ QtObject { /** * type:int * Bitmask defininig all visible elements of a bar. */ property int value: 0 /** * type:int * Representation of the help button. * * Used for opening a help screen. */ readonly property int help: 1 /** * type:int * Representation of the about button. * * Used on the menu screen. */ readonly property int about: 2 /** * type:int * Representation of the exit button. * * Used for exiting the application or returning to the menu. */ readonly property int exit: 4 /** * type:int * Representation of the home button. * * Used for returning from an acitivity to the main menu. */ readonly property int home: 8 /** * type:int * Representation of the config button. * * Used for switching to a config screen. */ readonly property int config: 16 /** * type:int * Representation of the current level and next and previous buttons. */ readonly property int level: 32 /** * type:int * Representation of the reload button. * * Used for restarting the current level of an activity. */ readonly property int reload: 64 /** * type:int * Representation of the repeat button. * * Used for repeating audio voices. */ readonly property int repeat: 128 /** * type:int * Representation of a currently running download. * * Shown automatically during running download. */ readonly property int download: 256 + + readonly property int previousView: 512 } diff --git a/src/core/DataStreamConverter.h b/src/core/DataStreamConverter.h index f90e3ed00..f31cdfc63 100644 --- a/src/core/DataStreamConverter.h +++ b/src/core/DataStreamConverter.h @@ -1,102 +1,102 @@ /* GCompris - DataStreamConverter.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #ifndef DATASTREAM_CONVERTER_H #define DATASTREAM_CONVERTER_H #include "Messages.h" #include -QDataStream& operator<<(QDataStream &dataStream, const Identifier &id) +inline QDataStream& operator<<(QDataStream &dataStream, const Identifier &id) { dataStream << int(id._id); return dataStream; }; -QDataStream& operator>>(QDataStream &dataStream, Identifier &id) +inline QDataStream& operator>>(QDataStream &dataStream, Identifier &id) { int identifier; dataStream >> identifier; id._id = (MessageIdentifier)identifier; return dataStream; }; -QDataStream& operator<<(QDataStream &dataStream, const Login &login) +inline QDataStream& operator<<(QDataStream &dataStream, const Login &login) { dataStream << login._name; return dataStream; }; -QDataStream& operator>>(QDataStream &dataStream, Login &login) +inline QDataStream& operator>>(QDataStream &dataStream, Login &login) { dataStream >> login._name; return dataStream; }; -QDataStream& operator<<(QDataStream &dataStream, const AvailableLogins &logins) +inline QDataStream& operator<<(QDataStream &dataStream, const AvailableLogins &logins) { dataStream << logins._logins; return dataStream; }; -QDataStream& operator>>(QDataStream &dataStream, AvailableLogins &logins) +inline QDataStream& operator>>(QDataStream &dataStream, AvailableLogins &logins) { dataStream >> logins._logins; return dataStream; }; -QDataStream& operator<<(QDataStream &dataStream, const DisplayedActivities &act) +inline QDataStream& operator<<(QDataStream &dataStream, const DisplayedActivities &act) { dataStream << act.activitiesToDisplay; return dataStream; }; -QDataStream& operator>>(QDataStream &dataStream, DisplayedActivities &act) +inline QDataStream& operator>>(QDataStream &dataStream, DisplayedActivities &act) { dataStream >> act.activitiesToDisplay; return dataStream; }; -QDataStream& operator<<(QDataStream &dataStream, const ActivityRawData &act) +inline QDataStream& operator<<(QDataStream &dataStream, const ActivityRawData &act) { dataStream << act.activityName << act.username << act.date << act.data; return dataStream; }; -QDataStream& operator>>(QDataStream &dataStream, ActivityRawData &act) +inline QDataStream& operator>>(QDataStream &dataStream, ActivityRawData &act) { dataStream >> act.activityName >> act.username >> act.date >> act.data; return dataStream; }; -QDataStream& operator<<(QDataStream &dataStream, const ActivityConfiguration &act) +inline QDataStream& operator<<(QDataStream &dataStream, const ActivityConfiguration &act) { dataStream << act.activityName << act.data; return dataStream; }; -QDataStream& operator>>(QDataStream &dataStream, ActivityConfiguration &act) +inline QDataStream& operator>>(QDataStream &dataStream, ActivityConfiguration &act) { dataStream >> act.activityName >> act.data; return dataStream; }; #endif diff --git a/src/server/ActivityData.cpp b/src/server/ActivityData.cpp index ec8e1b774..6b3ee5f86 100644 --- a/src/server/ActivityData.cpp +++ b/src/server/ActivityData.cpp @@ -1,82 +1,70 @@ /* GCompris - ActivityData.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #include "Messages.h" #include "ActivityData.h" -#include - +#include ActivityData::ActivityData() { + } ActivityData::ActivityData(const ActivityData &act) { } ActivityData::~ActivityData() { } -void ActivityData::operator=(const ActivityData &act) +const QList ActivityData::returnData() { - m_data = act.m_data; + return m_dataList; } -void ActivityData::push_back(const ActivityRawData &rawData) +void ActivityData::push_back(const ActivityRawData& rawData) { - ActivityData_d *data = new ActivityData_d; - data->setDate(rawData.date); - data->setData(rawData.data); - m_data.push_back(*data); - m_qmlData.push_back(data); - emit newData(); -} + // create a new activity_d object + ActivityData_d* actData_d = new ActivityData_d(rawData.date, rawData.data); + this->m_dataList.push_back(actData_d); -ActivityData_d::ActivityData_d() -{ } -ActivityData_d::ActivityData_d(const ActivityData_d &d) +ActivityData_d::ActivityData_d(const QDateTime &date, const QVariantMap &data): + m_date(date), + m_data(data) { - setDate(d.m_date); - setData(d.m_data); -} +} ActivityData_d::~ActivityData_d() { -} -void ActivityData_d::operator=(const ActivityData_d &d) -{ - setDate(d.m_date); - setData(d.m_data); } - -void ActivityData_d::setDate(const QDateTime &date) +QDateTime ActivityData_d::getDate() { - m_date = date; - emit newDate(); + qDebug() << m_date; + return m_date; } - -void ActivityData_d::setData(const QVariantMap &data) +QVariantMap ActivityData_d::getData() { - m_data = data; - emit newData(); + qDebug() << m_data; + return m_data; } + diff --git a/src/server/ActivityData.h b/src/server/ActivityData.h index c9d8e9361..5b978e020 100644 --- a/src/server/ActivityData.h +++ b/src/server/ActivityData.h @@ -1,80 +1,67 @@ /* GCompris - ActivityData.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #ifndef ACTIVITYDATA_H #define ACTIVITYDATA_H #include -#include -#include #include +#include -class QTcpSocket; struct ActivityRawData; +/** + * @class ActivityData_d + * @short contains specific data of an activity + */ -class ActivityData_d : public QObject { - Q_OBJECT - - Q_PROPERTY(QDateTime date MEMBER m_date NOTIFY newDate) - Q_PROPERTY(QVariantMap data MEMBER m_data NOTIFY newData) - +class ActivityData_d:public QObject +{ public: - ActivityData_d(); - ActivityData_d(const ActivityData_d &d); + ActivityData_d(const QDateTime& date, const QVariantMap& data); ~ActivityData_d(); - void operator=(const ActivityData_d &d); - - void setDate(const QDateTime &date); - void setData(const QVariantMap &data); - + QDateTime getDate(); + QVariantMap getData(); private: QDateTime m_date; QVariantMap m_data; -signals: - void newDate(); - void newData(); - }; + +/** + * @class ActivityData + * @short maintains list of ActivityData_d class + */ class ActivityData : public QObject { Q_OBJECT - Q_PROPERTY(QList data MEMBER m_qmlData NOTIFY newData) - public: ActivityData(); - ActivityData(const ActivityData &activityData); + ActivityData(const ActivityData& act); ~ActivityData(); - void operator=(const ActivityData &); + void push_back(const ActivityRawData& rawData); - void push_back(const ActivityRawData &rawData); - - QList m_qmlData; + const QList returnData(); private: - QList m_data; - -signals: - void newData(); + QList m_dataList; }; Q_DECLARE_METATYPE(ActivityData) - #endif diff --git a/src/server/Database.cpp b/src/server/Database.cpp index 00b5ed644..ca2a55264 100644 --- a/src/server/Database.cpp +++ b/src/server/Database.cpp @@ -1,255 +1,365 @@ /* GCompris - Database.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #include #include #include #include #include #include #include +#include +#include +#include +#include + #include "UserData.h" #include "Database.h" #include "GroupData.h" +#include "Messages.h" +#include "DataStreamConverter.h" #define CREATE_TABLE_USERS \ "CREATE TABLE users (user_name TEXT PRIMARY KEY NOT NULL, avatar TEXT); " #define CREATE_TABLE_GROUPS \ "CREATE TABLE groups (group_name TEXT PRIMARY KEY NOT NULL, description TEXT); " #define CREATE_TABLE_USERGROUP \ "CREATE TABLE group_users(user_name TEXT NOT NULL, group_name TEXT NOT NULL)" +#define CREATE_TABLE_ACTIVITY_DATA \ + "CREATE TABLE activity_data(user_name TEXT NOT NULL, activity_name TEXT NOT NULL, " \ + "date TEXT NOT NULL,data TEXT NOT NULL,PRIMARY KEY(user_name,activity_name,date))" + + Database* Database::_instance = 0; Database::Database() { } Database::~Database() { } Database* Database::getInstance() { if (!_instance) _instance = new Database; return _instance; } bool Database::addGroup(const QString &groupName, const QString& description, const QStringList& users) { bool groupAdded = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); // add group to db only if it has not been added before query.prepare("SELECT group_name FROM groups WHERE group_name=:groupName"); query.bindValue(":groupName", groupName); query.exec(); if(query.next()) { qDebug()<< "group "<< groupName << " already exists"; return false; } // since the group does not exist ,create the new group and add description and users to it query.prepare("INSERT INTO groups (group_name, description) VALUES (:groupName,:description)"); query.bindValue(":groupName", groupName); query.bindValue(":description",description); groupAdded = query.exec(); if(groupAdded) { //add users to the group for(const auto &user: users) { addUserToGroup(groupName, user); } } else qDebug()<<"group could not be added " << query.lastError(); return groupAdded; } bool Database::deleteGroup(const QString &groupName) { bool groupDeleted = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("DELETE FROM groups WHERE group_name=:gname"); query.bindValue(":gname",groupName); if(query.exec()) { query.prepare("DELETE FROM group_users WHERE group_name=:gname"); query.bindValue(":gname",groupName); if(query.exec()) groupDeleted = true; } return groupDeleted; } bool Database::addUserToGroup(const QString& group, const QString& user) { // insert in table group_users // add (user, group) to db only if they don't exist bool userAdded = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT * FROM group_users WHERE user_name=:user and group_name=:group"); query.bindValue(":user",user); query.bindValue(":group",group); query.exec(); if(query.next()) { qDebug() << "user " << user << "already exists in group " << group; return false; } query.prepare("INSERT INTO group_users (user_name, group_name) values(:user,:group)"); query.bindValue(":user",user); query.bindValue(":group",group); userAdded = query.exec(); if(!userAdded) { qDebug() << "user could not be added "<< query.lastError(); } return userAdded; } +bool Database::addDataToDatabase(const ActivityRawData &rawData) +{ + + + bool dataAdded = false; + QByteArray data; + QDataStream out(&data, QIODevice::WriteOnly); + out << ACTIVITY_DATA << rawData.data; + + QString activityData = QTextCodec::codecForMib(106)->toUnicode(data); + + QSqlDatabase dbConnection = QSqlDatabase::database(); + QSqlQuery query(dbConnection); + + // get the data from database and then prepend/append new data + query.prepare("SELECT * FROM activity_data where user_name=:userName and activity_name=:activityName and date=:date"); + query.bindValue(":userName",rawData.username); + query.bindValue(":activityName",rawData.activityName); + query.bindValue(":date", rawData.date.toString("dd/MM/yyyy")); + query.exec(); + if(query.next()) { + // update the data + QString oldData; + const int dataIndex = query.record().indexOf("data"); + oldData = query.value(dataIndex).toString(); + oldData.prepend(activityData); + + query.prepare("UPDATE activity_data SET data = :oldData where user_name=:userName and activity_name=:activityName " + "and date=:date"); + query.bindValue(":oldData",oldData); + query.bindValue(":userName",rawData.username); + query.bindValue(":activityName",rawData.activityName); + query.bindValue(":date",rawData.date.toString("dd/MM/yyyy")); + if(!query.exec()) { + qDebug() << "could not update the data " << query.lastError(); + } + else + dataAdded = true; + } + else { + //insert the data + query.prepare("INSERT INTO activity_data (user_name, activity_name, date, data) " + "VALUES(:userName, :activityName, :date, :data)"); + + query.bindValue(":userName", rawData.username); + query.bindValue(":activityName", rawData.activityName); + query.bindValue(":date",rawData.date.toString("dd/MM/yyyy")); + query.bindValue(":data", activityData); + if(!query.exec()) + qDebug() << "could not insert the data " << query.lastError(); + else + dataAdded = true; + } + + return dataAdded; +} + bool Database::addUser(const QString& name, const QString &avatar, const QStringList& groups) { // check whether user already exists before adding to database bool userAdded = false; QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT user_name FROM users WHERE user_name=:name"); query.bindValue(":name", name); query.exec(); if(query.next()) { qDebug() << "user " << name << "already exists"; return false; } query.prepare("INSERT INTO users (user_name, avatar) VALUES(:name,:avatar)"); query.bindValue(":name", name); query.bindValue(":avatar", avatar); userAdded = query.exec(); if(userAdded) { for (const auto &group:groups) { addUserToGroup(group, name); } } else qDebug()<< query.lastError(); return userAdded; } +void Database::retrieveActivityData(UserData* user) +{ + + QSqlDatabase dbConnection = QSqlDatabase::database(); + QSqlQuery query(dbConnection); + + query.prepare("SELECT * FROM activity_data WHERE user_name=:userName"); + query.bindValue(":userName",user->getName()); + query.exec(); + const int dataIndex = query.record().indexOf("data"); + const int dateIndex = query.record().indexOf("date"); + const int userIndex = query.record().indexOf("user_name"); + const int activityIndex = query.record().indexOf("activity_name"); + while(query.next()) { + + QString activityData = query.value(dataIndex).toString(); + QByteArray data = activityData.toUtf8(); + QDataStream in(&data, QIODevice::ReadOnly); + + while(!in.atEnd()) { + ActivityRawData rawData; + Identifier id; + QVariantMap mapData; + in >> id; + in >> mapData; + + rawData.activityName = query.value(activityIndex).toString(); + rawData.username = query.value(userIndex).toString(); + rawData.date = QDateTime::fromString(query.value(dateIndex).toString(), "dd/MM/yyyy"); + rawData.data = mapData; + user->addData(rawData); + } + } + +} + void Database::retrieveAllExistingGroups(QList &allGroups) { QSqlDatabase dbConnection = QSqlDatabase::database(); // Don't add twice the same login QSqlQuery query(dbConnection); query.prepare("SELECT * FROM groups"); query.exec(); const int nameIndex = query.record().indexOf("group_name"); const int descriptionIndex = query.record().indexOf("description"); while(query.next()) { GroupData *g = new GroupData(); g->setName(query.value(nameIndex).toString()); g->setDescription(query.value(descriptionIndex).toString()); allGroups.push_back(g); } } QMultiMap Database::retrieveGroupUsers() { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT * FROM group_users"); query.exec(); int userIndex = query.record().indexOf("user_name"); int groupIndex = query.record().indexOf("group_name"); QMultiMap groupUsers; while(query.next()) { groupUsers.insert(query.value(groupIndex).toString(),query.value(userIndex).toString()); } return groupUsers; } void Database::retrieveAllExistingUsers(QList &allUsers) { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); query.prepare("SELECT * FROM users"); query.exec(); const int nameIndex = query.record().indexOf("user_name"); const int avatarIndex = query.record().indexOf("avatar"); while(query.next()) { UserData *u = new UserData(); u->setName(query.value(nameIndex).toString()); u->setAvatar(query.value(avatarIndex).toString()); + retrieveActivityData(u); allUsers.push_back(u); } } void createDatabase(const QString &path) { QSqlDatabase dbConnection = QSqlDatabase::database(); QSqlQuery query(dbConnection); if(query.exec(CREATE_TABLE_USERS)) qDebug()<< "created table users"; else qDebug() << query.lastError(); if(query.exec(CREATE_TABLE_GROUPS)) qDebug()<< "created table groups"; else qDebug() << query.lastError(); if(query.exec(CREATE_TABLE_USERGROUP)) qDebug()<< "created table group_users"; else qDebug() << query.lastError(); + if(query.exec(CREATE_TABLE_ACTIVITY_DATA)) + qDebug() << "created table activity_data"; + else + qDebug() << query.lastError(); } void Database::init() { QDir databasePath; QString path = databasePath.currentPath()+"/gcompris-qt.db"; // todo set cache/data path instead of current folder QSqlDatabase dbConnection = QSqlDatabase::addDatabase("QSQLITE"); dbConnection.setDatabaseName(path); QFileInfo fileInfo(path); if(!fileInfo.exists()) { if (!dbConnection.open()) { qDebug() << "Error: connection with database fail"; } createDatabase(path); } if (!dbConnection.open()) { qDebug() << "Error: connection with database fail"; } } diff --git a/src/server/Database.h b/src/server/Database.h index b93245f4d..2bfce7758 100644 --- a/src/server/Database.h +++ b/src/server/Database.h @@ -1,64 +1,67 @@ /* GCompris - Database.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #ifndef DATABASE_H #define DATABASE_H #include #include #include #include class UserData; class GroupData; class QSqlError; +struct ActivityRawData; class Database : public QObject { Q_OBJECT private: Database(); static Database* _instance; // singleton instance public: /** * Registers Database singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static Database* getInstance(); ~Database(); bool addGroup(const QString &groupName, const QString& description = QString(), const QStringList& users=QStringList()); bool deleteGroup(const QString& groupName); void retrieveAllExistingUsers(QList &allUsers); void retrieveAllExistingGroups(QList &allGroups); bool addUser(const QString &name, const QString &avatar = "", const QStringList& groups=QStringList()); bool addUserToGroup(const QString& group, const QString& user); QMultiMap retrieveGroupUsers(); + bool addDataToDatabase(const ActivityRawData &rawData); + void retrieveActivityData(UserData* user); }; #endif diff --git a/src/server/MessageHandler.cpp b/src/server/MessageHandler.cpp index a6db29d57..6aa5488e5 100644 --- a/src/server/MessageHandler.cpp +++ b/src/server/MessageHandler.cpp @@ -1,312 +1,324 @@ /* GComprisServer - MessageHandler.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #include #include "Server.h" #include "Database.h" #include "MessageHandler.h" #include #include MessageHandler* MessageHandler::_instance = 0; MessageHandler::MessageHandler() { Server &server = *Server::getInstance(); connect(&server, &Server::loginReceived, this, &MessageHandler::onLoginReceived); connect(&server, &Server::activityDataReceived, this, &MessageHandler::onActivityDataReceived); connect(&server, &Server::newClientReceived, this, &MessageHandler::onNewClientReceived); connect(&server, &Server::clientDisconnected, this, &MessageHandler::onClientDisconnected); // retrieve all the existing users and groups QList users; Database::getInstance()->retrieveAllExistingUsers(users); for(auto it=users.begin(); it!= users.end(); it++) { m_users.push_back((QObject*)(*it)); } QList groups; Database::getInstance()->retrieveAllExistingGroups(groups); for(auto it=groups.begin(); it!= groups.end(); it++) { m_groups.push_back((QObject*)(*it)); } // add users to their respective groups QMultiMap groupUsers = Database::getInstance()->retrieveGroupUsers(); QMapIterator itr(groupUsers); while(itr.hasNext()) { itr.next(); GroupData* grp = getGroup(itr.key()); QList values = groupUsers.values(itr.key()); for(const auto &user: values) { UserData* usr = getUser(user); if(grp && usr) { grp->addUser(usr); usr->addGroup(grp); } } } } MessageHandler* MessageHandler::getInstance() { if (!_instance) _instance = new MessageHandler; return _instance; } QObject *MessageHandler::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return getInstance(); } void MessageHandler::init() { getInstance(); qmlRegisterType(); qmlRegisterType(); qmlRegisterSingletonType("GCompris", 1, 0, "MessageHandler", systeminfoProvider); } GroupData *MessageHandler::createGroup(const QString &newGroup,const QString &description, const QStringList& users) { //1. add group to database //2. make a new a group and add it to m_groups; if(Database::getInstance()->addGroup(newGroup, description,users)) { GroupData *c = new GroupData(); c->setName(newGroup); c->setDescription(description); m_groups.push_back((QObject*)c); for(const auto &user: users) { UserData* usr = getUser(user); if(usr) { c->addUser(usr); usr->addGroup(c); } } qDebug() << "size of the list after adding the new group is " << m_groups.length(); emit newGroups(); return c; } return nullptr; } void MessageHandler::deleteGroup(const QString &groupName) { //delete from database - if(Database::getInstance()->deleteGroup(groupName)){ + if(Database::getInstance()->deleteGroup(groupName)) { GroupData *c = getGroup(groupName); qDebug() << c; m_groups.removeAll(c); delete c; emit newGroups(); } - else{ + else { qDebug() << "could not delete the group from database"; } } UserData *MessageHandler::createUser(const QString &newUser, const QString &avatar, const QStringList &groups) { -// Add the user in the database + // Add the user in the database if(Database::getInstance()->addUser(newUser, avatar, groups)) { qDebug() << "createUser '" << newUser << "' in groups " << groups; UserData *u = new UserData(); u->setName(newUser); u->setAvatar(avatar); for(const QString &aGroup: groups) { GroupData *group = getGroup(aGroup); if(group) { group->addUser(u); u->addGroup(group); } } m_users.push_back((QObject*)u); emit newUsers(); return u; } else { qDebug() << "Error while creating user " << newUser; } return nullptr; } void MessageHandler::addUserToGroup(const QStringList &groups, const QStringList &users) { // 1. add user to group -In database; // 2. associate users with groups; for(const auto &user: users) { UserData* usr = getUser(user); for (const auto &group: groups) { if(Database::getInstance()->addUserToGroup(group,user)) { GroupData* grp = getGroup(group); if(grp && usr) { grp->addUser(usr); usr->addGroup(grp); } } } } } UserData *MessageHandler::updateUser(const QString &oldUser, const QString &newUser, const QString &avatar, const QStringList &groups) { UserData *user = getUser(oldUser); if (user) { user->setName(newUser); // for each group, remove the user if not in the new groups and add it in the new ones removeUserFromAllGroups(user); for(const QString &aGroup: groups) { GroupData *group = getGroup(aGroup); group->addUser(user); } emit newUsers(); } return user; } UserData *MessageHandler::getUser(const QString &userName) { for (QObject *oUser: m_users) { UserData *user = (UserData *) oUser; if (user->getName() == userName) { return user; } } return nullptr; } GroupData *MessageHandler::getGroup(const QString &groupName) { for (QObject *oGroup: m_groups) { GroupData *group = (GroupData *) oGroup; if (group->getName() == groupName) { return group; } } return nullptr; } void MessageHandler::removeUserFromAllGroups(UserData *user) { for (QObject *oGroup: m_groups) { GroupData *group = (GroupData *) oGroup; group->removeUser(user); } } void MessageHandler::deleteUser(const QString &userName) { for (QObject *oUser: m_users) { UserData *user = (UserData *) oUser; if (user->getName() == userName) { m_users.removeAll(user); removeUserFromAllGroups(user); delete user; emit newUsers(); } } } void MessageHandler::onLoginReceived(QTcpSocket *socket, const Login &data) { qDebug() << "Login received '" << data._name << "'"; ClientData *c = getClientData(socket); for(QObject *oClient: m_clients ) { ClientData *c = (ClientData*)oClient; if(c->getUserData() && c->getUserData()->getName() == data._name) { // found a client with the same user name.(i.e someone chose the wrong login) qDebug() << "a client with the same user name already exists"; return; //todo: // return an error message to client and inform that you have chosen the wrong login } } for(QObject *oUser: m_users) { UserData *user = (UserData*)oUser; qDebug() << "recieved login " << data._name << " " << c->getSocket(); if(user->getName() == data._name) { c->setUser(user); return; } } // Should not happen when login will be done properly... todo display an error message qDebug() << "Error: login " << data._name << " received, but no user found"; } -void MessageHandler::onActivityDataReceived(const ClientData &who, const ActivityRawData &act) +void MessageHandler::onActivityDataReceived(QTcpSocket* socket, const ActivityRawData &act) { + + qDebug() << "Activity: " << act.activityName << ", date: " << act.date << ", data:" << act.data << ", user: " << act.username; - UserData *u = getUser(act.username); - u->addData(act); + + if(Database::getInstance()) { + Database::getInstance()->addDataToDatabase(act); + } + + ClientData* client = getClientData(socket); + if(client) { + if(client->getUserData()) { + client->getUserData()->addData(act); + } + } + emit newActivityData(); } -void MessageHandler::onNewClientReceived(const ClientData &client) +void MessageHandler::onNewClientReceived(QTcpSocket* socket) { qDebug() << "New client"; - ClientData *c = new ClientData(client); - + ClientData *c = new ClientData; + c->setSocket(socket); m_clients.push_back((QObject*)c); emit newClients(); } void MessageHandler::onClientDisconnected(QTcpSocket* socket) { qDebug() << "client disconnected"; ClientData *c = getClientData(socket); c->setUser(nullptr); m_clients.removeAll(c); delete c; emit newClients(); } ClientData *MessageHandler::getClientData(QTcpSocket* socket) { for (QObject *oc: m_clients) { ClientData *c = (ClientData *) oc; if (c->getSocket() == socket) { return c; } } } diff --git a/src/server/MessageHandler.h b/src/server/MessageHandler.h index f86efccc6..06c49558e 100644 --- a/src/server/MessageHandler.h +++ b/src/server/MessageHandler.h @@ -1,124 +1,132 @@ /* GComprisServer - MessageHandler.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #ifndef MESSAGEHANDLER_H #define MESSAGEHANDLER_H #include "Messages.h" #include "ClientData.h" #include "GroupData.h" #include "UserData.h" #include #include /** * @class MessageHandler * @short Handles all the messages received by the socket * * JOB: * -Handle messages received by the socket * -Create,delete, Update information about: * --users * --groups * -Linking Users,Clients and groups with each other * * * @sa UserData * @sa GroupData * @sa ClientData */ class MessageHandler: public QObject { Q_OBJECT Q_PROPERTY(QList clients MEMBER m_clients NOTIFY newClients) Q_PROPERTY(QList groups MEMBER m_groups NOTIFY newGroups) Q_PROPERTY(QList users MEMBER m_users NOTIFY newUsers) private: MessageHandler(); // prohibit external creation, we are a singleton! static MessageHandler* _instance; // singleton instance public: /** * Registers MessageHandler singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static MessageHandler* getInstance(); Q_INVOKABLE GroupData *createGroup(const QString &groupName,const QString &description=QString(), const QStringList& users=QStringList()); Q_INVOKABLE void deleteGroup(const QString &groupName); Q_INVOKABLE UserData *createUser(const QString &userName, const QString &avatar = QString(), const QStringList &groups = QStringList()); Q_INVOKABLE UserData *updateUser(const QString &oldUser, const QString &newUser, const QString &avatar = QString(), const QStringList &groups = QStringList()); Q_INVOKABLE void deleteUser(const QString &userName); Q_INVOKABLE void addUserToGroup(const QStringList& groups, const QStringList& users); UserData *getUser(const QString &userName); GroupData *getGroup(const QString &groupName); Q_INVOKABLE QList returnUserGroups(const QString& user) { UserData* usr = getUser(user); if(usr) { return usr->getGroups(); } } Q_INVOKABLE QList returnGroupUsers(const QString& group){ GroupData* g = getGroup(group); if(g){ return g->getUsers(); } } + QList returnUsers() { + return m_users; + } + + /*QList returnClients(){ + return m_clients; + }*/ public slots: - void onActivityDataReceived(const ClientData &who, const ActivityRawData &act); void onLoginReceived(QTcpSocket* socket, const Login &data); - void onNewClientReceived(const ClientData &client); + void onActivityDataReceived(QTcpSocket* socket, const ActivityRawData &act); + void onNewClientReceived(QTcpSocket* socket); void onClientDisconnected(QTcpSocket* socket); signals: void newClients(); void newGroups(); void newUsers(); + void newActivityData(); private: ClientData *getClientData(QTcpSocket* socket); void removeUserFromAllGroups(UserData *user); // ClientData* QList m_clients; // GroupData* QList m_groups; // UserData* QList m_users; }; #endif diff --git a/src/server/Server.cpp b/src/server/Server.cpp index 8f96fdea4..46d8c423f 100644 --- a/src/server/Server.cpp +++ b/src/server/Server.cpp @@ -1,216 +1,215 @@ /* GCompris - Server.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #include #include "Messages.h" #include "DataStreamConverter.h" #include "ClientData.h" #include "GroupData.h" #include "Server.h" #include "MessageHandler.h" #include Server* Server::_instance = 0; Server::Server(QObject *parent) : QObject(parent) , tcpServer(Q_NULLPTR) , networkSession(0) { udpSocket = new QUdpSocket(this); messageNo = 1; tcpServer = new QTcpServer(this); connect(tcpServer, &QTcpServer::newConnection, this, &Server::newTcpConnection); if (!tcpServer->listen(QHostAddress::Any, 5678)) { qDebug() << tr("Unable to start the server: %1.") .arg(tcpServer->errorString()); } } Server* Server::getInstance() { if (!_instance) _instance = new Server; return _instance; } QObject *Server::systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return getInstance(); } void Server::init() { qmlRegisterSingletonType("GCompris", 1, 0, "Server", systeminfoProvider); } void Server::newTcpConnection() { QTcpSocket *clientConnection = tcpServer->nextPendingConnection(); qDebug() << clientConnection; connect(clientConnection, &QAbstractSocket::disconnected, this, &Server::disconnected); connect(clientConnection, &QAbstractSocket::readyRead, this, &Server::slotReadyRead); list.push_back(clientConnection); qDebug() << clientConnection->peerAddress().toString(); - ClientData newClient; - newClient.setSocket(clientConnection); qDebug("New tcp connection"); - emit newClientReceived(newClient); + emit newClientReceived(clientConnection); } void Server::broadcastDatagram() { qDebug()<< QHostInfo::localHostName(); QByteArray datagram; // todo use a real message structure in Messages.h QDataStream out(&datagram, QIODevice::WriteOnly); out << MessageIdentifier::REQUEST_CONTROL << QHostInfo::localHostName(); qint64 data = udpSocket->writeDatagram(datagram.data(),datagram.size(),QHostAddress::Broadcast, 5678); qDebug()<< " size of data :" << data; } void Server::slotReadyRead() { QTcpSocket* clientConnection = qobject_cast(sender()); QByteArray data = clientConnection->readAll(); QDataStream in(&data, QIODevice::ReadOnly); in.setVersion(QDataStream::Qt_4_0); - ClientData client; - client.setSocket(clientConnection); - while(!in.atEnd()) { Identifier messageId; in >> messageId; switch(messageId._id) { case MessageIdentifier::LOGIN: { Login log; in >> log; emit loginReceived(clientConnection, log); break; } case MessageIdentifier::ACTIVITY_DATA: { ActivityRawData act; in >> act; - emit activityDataReceived(client, act); + emit activityDataReceived(clientConnection, act); break; } default: qDebug() << messageId._id << " received but not handled"; } } } void Server::disconnected() { QTcpSocket* clientConnection = qobject_cast(sender()); if(!clientConnection) return; qDebug() << "Removing " << clientConnection; list.removeAll(clientConnection); emit clientDisconnected(clientConnection); clientConnection->deleteLater(); } void Server::sendActivities() { DisplayedActivities activities; activities.activitiesToDisplay << "geography/Geography.qml" << "erase/Erase.qml" << "reversecount/Reversecount.qml"; QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out << DISPLAYED_ACTIVITIES << activities; for(auto sock: list) { qDebug() << "Sending " << block << " to " << sock; sock->write(block); } } -void Server::sendLoginList(QObject *g) +void Server::sendLoginList() { - GroupData *group = (GroupData *) g; // Get all the clients from MessageHandler // For each client, if it does not have a name yet, send the message QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); + // remove the sockets not the names +// QStringList usedLogins; +// for(const QObject* oClient: MessageHandler::getInstance()->returnClients()) { +// ClientData* c = (ClientData*)(oClient); +// if(c->getUserData()){ +// usedLogins << c->getUserData()->getName(); +// } +// } + AvailableLogins act; - for(const QObject *oC: group->getUsers()) { - act._logins << ((const UserData*)oC)->getName(); + for(const QObject *oC: MessageHandler::getInstance()->returnUsers()) { + act._logins << ((const UserData*)oC)->getName(); } - // todo remove already used logins out << LOGINS_LIST << act; - for(auto sock: list) { qDebug() << "Sending " << block << " to " << sock; sock->write(block); } - //qDebug() << "Sending " << block << " to " << client->getSocket(); - //((QTcpSocket*)client->getSocket())->write(block); } void Server::sendConfiguration(/*QObject *c*//*, const ConfigurationData &config*/) { //ClientData *client = (ClientData*)c; QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); ActivityConfiguration act; act.activityName = "reversecount/Reversecount.qml"; act.data["dataset"] = "[{\"maxNumber\": 1, \"minNumber\": 1, \"numberOfFish\": 1}," " {\"maxNumber\": 2, \"minNumber\": 2, \"numberOfFish\": 4}]"; out << ACTIVITY_CONFIGURATION << act; for(auto sock: list) { qDebug() << "Sending " << block << " to " << sock; sock->write(block); } //qDebug() << "Sending " << block << " to " << client->getSocket(); //((QTcpSocket*)client->getSocket())->write(block); } diff --git a/src/server/Server.h b/src/server/Server.h index e95711c78..eadee5b11 100644 --- a/src/server/Server.h +++ b/src/server/Server.h @@ -1,96 +1,96 @@ /* GCompris - Server.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #ifndef SERVER_H #define SERVER_H class QTcpServer; class QTcpSocket; class QNetworkSession; class QUdpSocket; struct Login; struct ActivityRawData; struct GroupData; struct ClientData; #include #include /** * @class Server * @short Receive and send messages to the gcompris client instances * * Contains the tcp socket that sends and receives the different messages to * the clients. * Sends the following messages: * * LoginList: list of all the logins the client can choose * * DisplayedActivities: activities to display on the target client * * ActivityConfiguration: for one activity, send a specific configuration (dataset) * * Receives the following ones: * * Login: allows to identify a client with a name * * ActivityData: contains the data for one result activity send by a client * * @sa MessageHandler */ class Server : public QObject { Q_OBJECT private: explicit Server(QObject *parent = Q_NULLPTR); static Server* _instance; // singleton instance public: /** * Registers Server singleton in the QML engine. */ /// @cond INTERNAL_DOCS static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static Server* getInstance(); Q_INVOKABLE void broadcastDatagram(); Q_INVOKABLE void sendConfiguration(); Q_INVOKABLE void sendActivities(); - Q_INVOKABLE void sendLoginList(QObject *g); + Q_INVOKABLE void sendLoginList(); private slots: void newTcpConnection(); void slotReadyRead(); void disconnected(); private: QTcpServer *tcpServer; QNetworkSession *networkSession; QList list; QUdpSocket *udpSocket; int messageNo; signals: - void newClientReceived(const ClientData &client); - void activityDataReceived(const ClientData &who, const ActivityRawData &data); + void newClientReceived(QTcpSocket* socket); void clientDisconnected(QTcpSocket* socket); void loginReceived(QTcpSocket* socket, const Login &log); + void activityDataReceived(QTcpSocket* socket, const ActivityRawData &data); }; #endif diff --git a/src/server/UserData.cpp b/src/server/UserData.cpp index b10429cb1..3f5113723 100644 --- a/src/server/UserData.cpp +++ b/src/server/UserData.cpp @@ -1,82 +1,102 @@ /* GCompris - UserData.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #include +#include +#include + #include "Messages.h" #include "UserData.h" #include "GroupData.h" +#include "ActivityData.h" + UserData::UserData() : m_name(""), m_avatar("") { } UserData::UserData(const UserData &user) { m_name = user.m_name; m_avatar = user.m_avatar; } UserData::~UserData() { } void UserData::setName(const QString &name) { m_name = name; emit newName(); } QList UserData::getGroups() { return m_groups; } void UserData::setAvatar(const QString &avatar) { m_avatar = avatar; emit newAvatar(); } void UserData::addGroup(GroupData *group) { if(!m_groups.contains((QObject*)group)) m_groups << (QObject*)group; } const QString &UserData::getName() const { return m_name; } void UserData::addData(const ActivityRawData &rawData) { - ActivityData &act = m_activityData[rawData.activityName]; - act.push_back(rawData); - - ActivityData *act2 = m_variantData[rawData.activityName].value(); - if(!act2) act2 = new ActivityData; - act2->push_back(rawData); - m_variantData[rawData.activityName] = QVariant::fromValue(act2); - emit newActivityData(); + ActivityData* act = m_activityData.value(rawData.activityName , NULL); + if(!act){ + act = new ActivityData(); + m_activityData.insert(rawData.activityName, act); + qDebug() << act; + } + act->push_back(rawData); } -const QList UserData::getActivityData(const QString &activity) +const QVariantMap UserData::getActivityData(const QString &activity) { - return m_activityData[activity].m_qmlData; + qDebug() << activity << " "<< this->m_name; + QMap result; + QList dataList; + QVariant var; + ActivityData* act = m_activityData.value(activity); + if(act){ + const QList &data = act->returnData(); + for(auto it = data.begin(); it!= data.end(); it++) { + var = result.value((*it)->getDate().toString("dd/MM/yyyy"),QVariant()); + dataList = var.value>(); + dataList.push_back((*it)->getData()); + var.setValue(dataList); + result.insert((*it)->getDate().toString("dd/MM/yyyy"),var); + } + } + + return result; } diff --git a/src/server/UserData.h b/src/server/UserData.h index e7126e46e..c5e031c06 100644 --- a/src/server/UserData.h +++ b/src/server/UserData.h @@ -1,76 +1,79 @@ /* GCompris - UserData.h * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #ifndef USERDATA_H #define USERDATA_H #include #include -#include "ActivityData.h" +#include +#include + class GroupData; struct ActivityRawData; +class ActivityData; + /** * @class UserData * @short Contains all the data relative to a user * * A user has a name and a map of all its results per activity * */ class UserData : public QObject { Q_OBJECT Q_PROPERTY(QString avatar MEMBER m_avatar NOTIFY newAvatar) Q_PROPERTY(QString name MEMBER m_name NOTIFY newName) - Q_PROPERTY(QVariantMap activityData MEMBER m_variantData NOTIFY newActivityData) - public: UserData(); UserData(const UserData &user); ~UserData(); void setName(const QString &name); void setAvatar(const QString &avatar); void addGroup(GroupData* group); void addData(const ActivityRawData &rawData); QList getGroups(); const QString &getName() const; + Q_INVOKABLE const QVariantMap getActivityData(const QString &activity); + - Q_INVOKABLE const QList getActivityData(const QString &activity); private: QList m_groups; QString m_avatar; QString m_name; - QMap m_activityData; + QMap m_activityData; QVariantMap m_variantData; signals: void newAvatar(); void newName(); void newActivityData(); }; Q_DECLARE_METATYPE(UserData) #endif diff --git a/src/server/main.cpp b/src/server/main.cpp index e4c0e920a..f3f10c178 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -1,92 +1,90 @@ /* GCompris - main.cpp * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ #include #include #include #include #include #include "Server.h" #include "Database.h" #include "MessageHandler.h" #include "ApplicationInfo.h" #include "ApplicationSettings.h" #include "ActivityInfoTree.h" #include "File.h" #include "DownloadManager.h" #include #include #define GCOMPRIS_SERVER_APPLICATION_NAME "gcompris-server" int main(int argc, char *argv[]) { // Disable it because we already support HDPI display natively qunsetenv("QT_DEVICE_PIXEL_RATIO"); QApplication app(argc, argv); app.setOrganizationName("KDE"); app.setApplicationName(GCOMPRIS_SERVER_APPLICATION_NAME); app.setOrganizationDomain("kde.org"); #if defined(Q_OS_MAC) // Sandboxing on MacOSX as documented in: // http://doc.qt.io/qt-5/osx-deployment.html QDir dir(QGuiApplication::applicationDirPath()); dir.cdUp(); dir.cd("Plugins"); QGuiApplication::setLibraryPaths(QStringList(dir.absolutePath())); #endif ActivityInfoTree::init(); ApplicationInfo::init(); ApplicationSettings::init(); File::init(); DownloadManager::init(); Server::init(); Database::init(); MessageHandler::init(); - MessageHandler::getInstance(); // the instance seems to be created only when we enter a qml items that have a loader using a MessageHandler attribute as model - if(!QResource::registerResource(ApplicationInfo::getFilePath("core.rcc"))) qDebug() << "Failed to load the resource file " << ApplicationInfo::getFilePath("core.rcc"); if(!QResource::registerResource(ApplicationInfo::getFilePath("server.rcc"))) qDebug() << "Failed to load the resource file " << ApplicationInfo::getFilePath("server.rcc"); QQmlApplicationEngine engine(QUrl("qrc:/gcompris/src/server/main.qml")); // add import path for shipped qml modules: engine.addImportPath(QStringLiteral("%1/../lib/qml") .arg(QCoreApplication::applicationDirPath())); QObject *topLevel = engine.rootObjects().value(0); QQuickWindow *window = qobject_cast(topLevel); if (!window) { qWarning("Error: Your root item has to be a Window."); return -1; } window->setIcon(QIcon(QPixmap(QString::fromUtf8(":/gcompris/src/core/resource/gcompris-icon.png")))); window->show(); return app.exec(); } diff --git a/src/server/views/Clients.qml b/src/server/views/Clients.qml index 7ff0038d1..54592a60b 100644 --- a/src/server/views/Clients.qml +++ b/src/server/views/Clients.qml @@ -1,96 +1,87 @@ /* GCompris - Clients.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ import QtQuick 2.1 import GCompris 1.0 import "../../core" ActivityBase { id: activity activityInfo: QtObject { property bool demo: false } function loadActivity() { activityLoader.item.loading = loading activityLoader.item.menu = activity pageView.push(activityLoader.item) } Loader { id: activityLoader asynchronous: true onStatusChanged: { if (status == Loader.Loading) { loading.start(); } else if (status == Loader.Ready) { loading.stop(); loadActivity(); } else if (status == Loader.Error) loading.stop(); } } Loading { id: loading } pageComponent: Item { anchors.fill: parent - Grid { + GridView { id: clients - spacing: 10 - columns: 2 - Repeater { - model: MessageHandler.clients - Rectangle { - width: 200 - height: 200 - color: "red" - GCText { - text: modelData.user ? modelData.user.name : qsTr("unnamed") - } - MouseArea { - id: mouse - anchors.fill: parent - onClicked: { - //Server.sendConfiguration(modelData) - var activity = "reversecount/Reversecount.qml"; - activityLoader.setSource("qrc:/gcompris/src/server/views/activities/" + activity, - { - 'user': modelData.user, - 'act': activity - }) - } // todo what do we do? display configuration - } + width: parent.width + height: parent.height - (bar.height * 2) + cellWidth: parent.width/10 + cellHeight: cellWidth + property string currentClient: "" + model: MessageHandler.clients + delegate: Rectangle { + id: delegate + width: clients.cellWidth/1.25 + height: width + color: "red" + GCText { + text: modelData.user ? modelData.user.name : "unnamed" } + } + } Bar { id: bar content: BarEnumContent { value: home } onHomeClicked: activity.home() } } } diff --git a/src/server/views/Groups.qml b/src/server/views/Groups.qml index 3387bf8d1..9f0869169 100644 --- a/src/server/views/Groups.qml +++ b/src/server/views/Groups.qml @@ -1,507 +1,507 @@ /* GCompris - Groups.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ import QtQuick 2.1 import GCompris 1.0 import QtQuick.Controls 1.0 import QtQuick.Controls.Styles 1.0 import "../../core" ActivityBase { id: activity activityInfo: QtObject { property bool demo: false } pageComponent: Item { id: mainItem anchors.fill: parent property string groupName: groupText.text property var users: [] property string groupDescription: descriptionText.text property string mode:"" GridView { id: clients width: activity.width height: activity.height cellWidth: 210 cellHeight: cellWidth model: MessageHandler.groups property string currentGroup: "" highlight: Rectangle { color: "lightsteelblue"; radius: 5 } delegate: Rectangle { id: itemDelegate width: 200 height: 200 color: "red" property string name: modelData.name GCText { text: modelData.name } MouseArea { id: mouse anchors.fill: parent onClicked: { clients.currentIndex = index; clients.currentGroup = name print(modelData.name) groupConfig.visible = true configView.visible = true cancelButton.visible = true } } } } Grid { rows: 1 anchors.bottom: bar.top Button { id: createGroupButton text: qsTr("Create a Group") style: GCButtonStyle {} onClicked: { createGroupItem.visible = true; } } } - Item{ + Item { id: groupConfig visible: false width: parent.width/2.5 height: parent.height/1.75 anchors.centerIn: parent - Rectangle{ + Rectangle { id: baseRect anchors.fill: parent color: "grey" opacity: 0.6 } - ListView{ + ListView { id: configView anchors.fill: parent visible: true property var list : [ { "shortName": "addUsers", "text": qsTr("add new users to %1").arg(clients.currentGroup) }, { "shortName": "showUsers", "text": qsTr("show users belonging to %1").arg(clients.currentGroup) }, { "shortName": "description", "text": qsTr("update description about the group") }, { "shortName" : "deleteUsers", "text" : qsTr("delete users from the group %1").arg(clients.currentGroup) }, { "shortName" : "deleteGroup", "text": qsTr("delete this group ") }, { "shortName" : "changeName", "text" : qsTr("change name of this group ") } ] model:list spacing: 3 - delegate: Button{ + delegate: Button { id: actions Text { text: modelData.text anchors.centerIn: parent color: "black" font.pixelSize: 22 } width: parent.width height: 70 - style: GCButtonStyle{} + style: GCButtonStyle {} // anchors.horizontalCenter: parent.horizontalCenter - MouseArea{ + MouseArea { anchors.fill: parent onClicked: { cancelButton.visible = false configView.visible = false if(modelData.shortName === "addUsers" ) { usersView.visible = true usersView.model = MessageHandler.users addNewUsersButton.visible = true goBackButton.visible = true } - else if(modelData.shortName === "showUsers"){ + else if(modelData.shortName === "showUsers") { usersView.model = MessageHandler.returnGroupUsers(clients.currentGroup) addNewUsersButton.visible = false goBackButton.visible = true usersView.visible = true } - else if(modelData.shortName === "deleteGroup"){ + else if(modelData.shortName === "deleteGroup") { groupConfig.visible = false confirmationBox.visible = true mainItem.mode = "deleteGroup" } } } } } - ListView{ + ListView { id: usersView anchors.fill: parent visible: false model: undefined spacing: 50 delegate: checkBoxDelegate } - Button{ + Button { id: addNewUsersButton width:parent.width/2 visible: false height: parent.height/6 - style: GCButtonStyle{} + style: GCButtonStyle {} anchors.bottom: parent.bottom text: qsTr("Add") - MouseArea{ + MouseArea { anchors.fill: parent onClicked: { mainItem.mode = "addUsers"; groupConfig.visible = false addNewUsersButton.visible = false goBackButton.visible = false usersView.visible = false confirmationBox.visible = true } } } - Button{ + Button { id: goBackButton width:addNewUsersButton.visible ? parent.width/2: parent.width visible: false height: parent.height/6 - style: GCButtonStyle{} + style: GCButtonStyle {} anchors.bottom: parent.bottom anchors.right: parent.right // anchors.left: addNewUsersButton.visible? addNewUsersButton.right : undefined text: qsTr("Back") - MouseArea{ + MouseArea { anchors.fill: parent - onClicked:{ + onClicked: { addNewUsersButton.visible = false goBackButton.visible = false usersView.visible = false configView.visible = true cancelButton.visible = true } } } } - GCButtonCancel{ + GCButtonCancel { id: cancelButton visible: false anchors.right: undefined anchors.top: undefined anchors.bottom: groupConfig.top anchors.left: groupConfig.right anchors.margins: 0 - onClose:{ + onClose: { cancelButton.visible = false groupConfig.visible = false } } - Item{ + Item { id: createGroupItem visible: false width:parent.width/1.75 height: parent.height/1.5 anchors.centerIn: parent - Rectangle{ + Rectangle { id: createGroupRect anchors.fill: parent color: "grey" opacity: 0.6 - Button{ + Button { id: groupName anchors.left: parent.left height: parent.height/8 width: parent.width/4 text: qsTr("Group Name") - style: GCButtonStyle{} + style: GCButtonStyle {} } - TextField{ + TextField { id: groupText anchors.left: groupName.right anchors.leftMargin: 30 anchors.right: parent.right height: groupName.height text: "" placeholderText: qsTr("Enter the group Name") } - Button{ + Button { id: description anchors.left: parent.left anchors.top: groupName.bottom anchors.topMargin: 50 height: parent.height/8 // anchors.verticalCenter: parent.verticalCenter width: parent.width/4 text: qsTr("Description") - style: GCButtonStyle{} + style: GCButtonStyle {} } - TextField{ + TextField { id: descriptionText anchors.left: description.right anchors.leftMargin: 30 anchors.top: groupText.bottom anchors.topMargin: 50 anchors.right: parent.right height: description.height text: "" placeholderText: qsTr("Add description about the group") } - Button{ + Button { id: addUsers height: parent.height/8 anchors.left: parent.left anchors.topMargin: 50 anchors.top: description.bottom width: parent.width/4 text: qsTr("Add Users") - style: GCButtonStyle{} + style: GCButtonStyle {} } - ListView{ + ListView { id: chooseUsers anchors.left: addUsers.right anchors.right: parent.right anchors.top: descriptionText.bottom anchors.topMargin: 50 anchors.leftMargin: 30 height: parent.height/4 model: MessageHandler.users spacing: 50 - Component{ + Component { id: checkBoxDelegate - GCDialogCheckBox{ + GCDialogCheckBox { id: checkbox width: parent.width text:modelData.name - onCheckedChanged:{ - if(checkbox.checked){ + onCheckedChanged: { + if(checkbox.checked) { console.log("checked ",modelData.name) mainItem.users.push(modelData.name) } - else{ + else { mainItem.users.pop(modelData.name) console.log("unchecked ", modelData.name) } } style: CheckBoxStyle { spacing: 10 indicator: Image { sourceSize.height: 50 property string suffix: control.enabled ? ".svg" : "_disabled.svg" source: control.checked ? "qrc:/gcompris/src/core/resource/apply" + suffix : "qrc:/gcompris/src/core/resource/cancel" + suffix } label: GCText { fontSize: mediumSize text: control.text wrapMode: Text.WordWrap width: parent.parent.width - 50 * ApplicationInfo.ratio - 10 * 2 } } } } delegate: checkBoxDelegate } } - Button{ + Button { id: createButton anchors.bottom: parent.bottom height: parent.height/6 width: parent.width/2 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("Create") - MouseArea{ + MouseArea { anchors.fill: parent - onClicked:{ + onClicked: { //show the confirmation box mainItem.mode = "create" createGroupItem.visible = false confirmationBox.visible = true } } } - Button{ + Button { id: cancel anchors.bottom: parent.bottom height: parent.height/6 anchors.left: createButton.right width: parent.width/2 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("Cancel") - MouseArea{ + MouseArea { anchors.fill: parent - onClicked:{ + onClicked: { createGroupItem.visible = false } } } } - Rectangle{ + Rectangle { id: confirmationBox width: parent.width/2 height: parent.height/4.5 anchors.centerIn: parent color: "grey" opacity: 0.6 visible: false - GCText{ + GCText { text: { if (mainItem.mode === "create") return "Are you sure you want to create this group?" else if (mainItem.mode === "addUsers") return "Are you sure you want to add these users to this group?" else if(mainItem.mode === "deleteGroup") return "Are you sure you want to delete the group " + clients.currentGroup else return "" } wrapMode: Text.Wrap font.pixelSize: 25 anchors.centerIn: parent } - Button{ + Button { id: yes anchors.bottom: parent.bottom height: parent.height/6 width: parent.width/2 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("YES") - MouseArea{ + MouseArea { anchors.fill: parent onClicked: { if (mainItem.mode === "create") { console.log(mainItem.groupName," ",mainItem.groupDescription, " ", mainItem.users) MessageHandler.createGroup(mainItem.groupName, mainItem.groupDescription, mainItem.users) } if (mainItem.mode === "addUsers") { console.log("add new users to group ",clients.currentGroup, mainItem.users) MessageHandler.addUserToGroup(clients.currentGroup,mainItem.users) } - if(mainItem.mode === "deleteGroup"){ + if(mainItem.mode === "deleteGroup") { console.log("deleting group ",clients.currentGroup) MessageHandler.deleteGroup(clients.currentGroup) } groupText.text = "" descriptionText.text = "" mainItem.users = [] confirmationBox.visible = false } } } - Button{ + Button { id: no anchors.bottom: parent.bottom height: parent.height/6 anchors.left: yes.right width: parent.width/2 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("NO") - MouseArea{ + MouseArea { anchors.fill: parent - onClicked:{ + onClicked: { // set all the values to "" groupText.text = "" descriptionText.text = "" mainItem.users = [] confirmationBox.visible = false } } } } Bar { id: bar content: BarEnumContent { value: home } onHomeClicked: activity.home() } } } diff --git a/src/server/views/Menu.qml b/src/server/views/Menu.qml index 186ec8b3a..34f13d271 100644 --- a/src/server/views/Menu.qml +++ b/src/server/views/Menu.qml @@ -1,257 +1,398 @@ /* GCompris - Menu.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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 . */ import QtQuick 2.2 import "../../core" import GCompris 1.0 import "qrc:/gcompris/src/core/core.js" as Core import QtGraphicalEffects 1.0 import QtQuick.Controls 1.2 /** * GCompris' top level menu screen. * * Displays a grid of available activities divided subdivided in activity * categories/sections. * * The visibility of the section row is toggled by the setting * ApplicationSettings.sectionVisible. * * The list of available activities depends on the following settings: * * * ApplicationSettings.showLockedActivities * * ApplicationSettings.filterLevelMin * * ApplicationSettings.filterLevelMax * * @inherit QtQuick.Item */ ActivityBase { id: activity focus: true activityInfo: ActivityInfoTree.rootMenu onBack: { pageView.pop(to); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } onHome: { if(pageView.depth === 1) { Core.quit(main); } else { pageView.pop(); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } } onDisplayDialog: pageView.push(dialog) onDisplayDialogs: { var toPush = new Array(); for (var i = 0; i < dialogs.length; i++) { toPush.push({item: dialogs[i]}); } pageView.push(toPush); } // @cond INTERNAL_DOCS property string url: "qrc:/gcompris/src/activities/menu/resource/" property variant actions: [ { view: "Broadcast", text: qsTr("Broadcast") }, { view: "Clients", text: qsTr("Connected clients") }, { view: "Configurations", text: qsTr("Configurations") }, { view: "ServerConfiguration", text: qsTr("Server Configuration") }, { view: "Groups", text: qsTr("Groups") }, { view: "Users", text: qsTr("Users") }, { view:"ActivityConfiguration", - text:qsTr("Send Dataset") + text: qsTr("Send Dataset") + }, + { + view: "LoginList", + text: qsTr("Send Login List") + + }, + { + view: "Results", + text: qsTr("Activity Results") } ] pageComponent: Image { id: background source: activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) height: main.height fillMode: Image.PreserveAspectCrop function loadActivity() { // @TODO init of item would be better in setsource but it crashes on Qt5.6 // https://bugreports.qt.io/browse/QTBUG-49793 activityLoader.item.loading = loading activityLoader.item.menu = activity pageView.push(activityLoader.item) } Loader { id: activityLoader asynchronous: true onStatusChanged: { if (status == Loader.Loading) { loading.start(); } else if (status == Loader.Ready) { loading.stop(); loadActivity(); } else if (status == Loader.Error) loading.stop(); } } Loading { id: loading } // Filters property bool horizontal: main.width > main.height property bool keyboardMode: false Keys.onPressed: { if(event.key === Qt.Key_Space && actionGrid.currentItem) { actionGrid.currentItem.selectCurrentItem() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onEnterPressed: actionGrid.currentItem.selectCurrentItem(); Keys.onReturnPressed: actionGrid.currentItem.selectCurrentItem(); Keys.onRightPressed: actionGrid.moveCurrentIndexRight(); Keys.onLeftPressed: actionGrid.moveCurrentIndexLeft(); Keys.onDownPressed: if(!actionGrid.atYEnd) actionGrid.moveCurrentIndexDown(); Keys.onUpPressed: if(!actionGrid.atYBeginning) actionGrid.moveCurrentIndexUp(); GridView { id: actionGrid model: actions width: main.width height: main.height interactive: false keyNavigationWraps: true property int initialX: 4 property int initialY: 4 cellWidth: width cellHeight: 80 Component { id: actionDelegate Button { id: backgroundAction style: GCButtonStyle {} onClicked: { selectCurrentItem() } text: modelData.text function selectCurrentItem() { if(modelData.view === "Broadcast") { print("sending broadcast to find clients") Server.broadcastDatagram() } else if(modelData.view === "ActivityConfiguration") { Server.sendConfiguration() } + else if(modelData.view === "LoginList") { + login.visible = true + + } else { activityLoader.setSource("qrc:/gcompris/src/server/views/" + modelData.view + ".qml", {}) print("select: " + text + " - load " + activityLoader.source) if (activityLoader.status == Loader.Ready) loadActivity() } } } } delegate: actionDelegate highlight: Item { width: actionGrid.cellWidth height: actionGrid.cellHeight Rectangle { anchors.fill: parent color: "#99FFFFFF" } Image { source: "qrc:/gcompris/src/core/resource/button.svg" anchors.fill: parent } Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } } Bar { id: bar // No exit button on mobile, UI Guidelines prohibits it content: BarEnumContent { value: help | about | (ApplicationInfo.isMobile ? 0 : exit) } anchors.bottom: parent.bottom onAboutClicked: { displayDialog(dialogAbout) } onHelpClicked: { displayDialog(dialogHelp) } onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.loader.item.loadFromConfig() displayDialog(dialogActivityConfig) } } } DialogAbout { id: dialogAbout onClose: home() } DialogHelp { id: dialogHelp onClose: home() activityInfo: ActivityInfoTree.rootMenu } + Item { + id: login + visible: false + width: parent.width/3 + height: parent.height/2 + anchors.centerIn: parent + property string mode: "" + Rectangle { + id: loginRect + anchors.fill: parent + color: "black" + opacity: 0.4 + Button { + id: chooseGroups + width: parent.width/1.5 + style: GCButtonStyle { + theme: "highContrast" + } + text: qsTr("Choose Groups") + height: parent.height/4 + anchors.top: parent.top + anchors.topMargin: height + anchors.horizontalCenter: parent.horizontalCenter + + } + Button { + id: sendToAll + width: parent.width/1.5 + height: parent.height/4 + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Send to all") + anchors.top: chooseGroups.bottom + anchors.topMargin: height/2 + style: GCButtonStyle { + theme: "highContrast" + } + MouseArea { + anchors.fill: parent + onClicked:{ + // show the confirmation box + confirmationBox.visible = true + login.visible = false + login.mode = "sendtoall" + } + } + } + } + GCButtonCancel { + id: cancel + anchors.left: login.right + anchors.right: undefined + anchors.top: undefined + visible: login.visible + anchors.bottom: login.top + onClose: { + login.visible = false + } + } + + } + Rectangle { + id: confirmationBox + width: parent.width/2 + height: parent.height/4 + anchors.centerIn: parent + color: "black" + opacity: 0.4 + visible: false + GCText { + text: { + if (login.mode === "sendtoall") + return "Are you sure you want to send login list to all the clients ?" + + else + return "" + } + color: "black" + font.bold: true + width: parent.width + font.weight: Font.ExtraBold + wrapMode: Text.Wrap + font.pixelSize: 25 + anchors.centerIn: parent + } + + Button { + id: yes + anchors.bottom: parent.bottom + height: parent.height/5 + width: parent.width/2 + style: GCButtonStyle { + theme: "highContrast" + } + text: qsTr("YES") + MouseArea { + anchors.fill: parent + onClicked: { + + if (login.mode === "sendtoall") + { + Server.sendLoginList() + } + confirmationBox.visible = false + + } + } + } + Button { + id: no + anchors.bottom: parent.bottom + height: parent.height/5 + anchors.left: yes.right + width: parent.width/2 + style: GCButtonStyle { + theme: "highContrast" + } + + text: qsTr("NO") + MouseArea { + anchors.fill: parent + onClicked:{ + login.visible = true + confirmationBox.visible = false + } + } + } + } + } diff --git a/src/server/views/Results.qml b/src/server/views/Results.qml new file mode 100644 index 000000000..c70924e20 --- /dev/null +++ b/src/server/views/Results.qml @@ -0,0 +1,239 @@ +import QtQuick 2.1 +import GCompris 1.0 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 + +import "../../core" + +ActivityBase { + id: activity + + activityInfo: QtObject { + property bool demo: false + } + + property string url: "qrc:/gcompris/src/activities/menu/resource/" + pageComponent: Image { + id: background + source: activity.url + "background.svg" + sourceSize.width: Math.max(parent.width, parent.height) + height: parent.height + fillMode: Image.PreserveAspectCrop + + property int iconWidth: 120 * ApplicationInfo.ratio + property int iconHeight: 120 * ApplicationInfo.ratio + property int activityCellWidth: background.width / Math.floor(background.width / iconWidth) + property int activityCellHeight: iconHeight * 1.7 + property bool activitiesVisible: true + property bool usersVisible: false + property bool resultsVisible: false + property string currentActivity: "" + property var result: null + + GridView { + id: activitiesGrid + width: background.width + height: background.height - (bar.height*2) + cellWidth: activityCellWidth + cellHeight: activityCellHeight + visible: background.activitiesVisible + property var menuTree: ActivityInfoTree.fullTree() + property int spacing: 10 + model:ActivityInfoTree.menuTree + + delegate: Item { + id: activityDelegate + width: activityCellWidth - activitiesGrid.spacing + height: activityCellHeight - activitiesGrid.spacing + Rectangle { + id: activityBackground + width: parent.width + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + color: "white" + opacity: 0.5 + } + Image { + source: "qrc:/gcompris/src/activities/" + icon; + anchors.top: activityBackground.top + anchors.horizontalCenter: parent.horizontalCenter + sourceSize.height: iconHeight + anchors.margins: 5 + GCText { + id: title + anchors.top: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + width: activityBackground.width + fontSizeMode: Text.Fit + minimumPointSize: 7 + fontSize: regularSize + elide: Text.ElideRight + maximumLineCount: 2 + wrapMode: Text.WordWrap + text: modelData.title + } + } + MouseArea { + anchors.fill: parent + onClicked:{ + background.activitiesVisible = false + background.usersVisible = true + background.currentActivity = modelData.name + } + } + + } + + } + Item { + id: users + anchors.fill:parent + visible: background.usersVisible + GridView { + id: userView + width: parent.width + height: parent.height - (bar.height*2) + cellWidth: parent.width/10 + cellHeight: cellWidth + model: MessageHandler.users + property var currentUser: null + delegate: Rectangle { + id: userDelegate + width: userView.cellWidth/1.25 + height: width + color: "red" + GCText { + text: modelData.name + fontSize: mediumSize + + } + MouseArea { + anchors.fill: parent + // get the activity data + onClicked:{ + userView.currentUser = modelData + background.result = modelData.getActivityData(background.currentActivity) + background.usersVisible = false + background.resultsVisible = true + results.storeData() + } + } + + } + + Connections { + target: MessageHandler + onNewActivityData: { + console.log("new data received") + background.result = userView.currentUser.getActivityData(background.currentActivity) + results.storeData() + } + } + } + } + Item { + id: results + anchors.fill:parent + visible: background.resultsVisible + property var datalist: [] + function storeData(){ + results.datalist = new Array; + var m_date="" + var m_data="" + for(var date in background.result){ + m_date=date + var list = background.result[date] + for(var i=0; i * * Authors: * Johnny Jazeix * * 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 . */ import QtQuick 2.1 import GCompris 1.0 import QtQuick.Controls 1.0 import QtQuick.Controls.Styles 1.0 import "../../core" ActivityBase { id: activity activityInfo: QtObject { property bool demo: false } pageComponent: Item { id: mainItem anchors.fill: parent property string userName: userText.text property var groups: [] property string avatar: avatarText.text property string mode: "" GridView { id: users width: parent.width height: parent.height - (bar.height * 2) cellWidth: parent.width/10 cellHeight: cellWidth property string currentUser: "" model: MessageHandler.users delegate: Rectangle { id: delegate width: users.cellWidth/1.25 height: width color: "red" GCText { text: modelData.name } MouseArea { anchors.fill: parent onClicked: { users.currentUser = modelData.name userConfig.visible = true configView.visible = true cancelButton.visible = true } } } } Grid { rows: 1 anchors.bottom: bar.top Button { id: createUserButton text: qsTr("Create a new user") - style: GCButtonStyle{} + style: GCButtonStyle {} onClicked: { createUserItem.visible = true } } } Item { id: userConfig visible: false width: parent.width/2.5 height: parent.height/1.75 anchors.centerIn: parent Rectangle { id: baseRect anchors.fill: parent color: "grey" opacity: 0.6 } ListView { id: configView anchors.fill: parent visible: true property var options : [ { "shortName": "addGroups", "text": qsTr("add %1 to groups").arg(users.currentUser) }, { "shortName": "showGroups", "text": qsTr("show groups belonging to %1").arg(users.currentUser) }, { "shortName" : "deleteGroups", "text" : qsTr("remove %1 from the groups").arg(users.currentUser) }, { "shortName" : "deleteUser", "text": qsTr("delete this User ") }, { "shortName" : "activityData", "text": qsTr("see the data of different activities of user %1").arg(users.currentUser) } ] model: options spacing: 3 delegate: Button { id: actions Text { text: modelData.text anchors.centerIn: parent color: "black" font.pixelSize: 22 } width: parent.width height: 70 - style: GCButtonStyle{} + style: GCButtonStyle {} MouseArea { anchors.fill: parent onClicked: { cancelButton.visible = false configView.visible = false if(modelData.shortName === "addGroups" ) { groupsView.visible = true groupsView.model = MessageHandler.groups addNewGroupsButton.visible = true goBackButton.visible = true mainItem.mode = "addGroups"; } if(modelData.shortName === "showGroups") { groupsView.model = MessageHandler.returnUserGroups(users.currentUser) groupsView.visible = true goBackButton.visible = true } } } } } ListView { id: groupsView anchors.fill: parent visible: false model: undefined spacing: 50 delegate: checkBoxDelegate } Button { id: addNewGroupsButton width: parent.width/2 visible: false height: parent.height/6 - style: GCButtonStyle{} + style: GCButtonStyle {} anchors.bottom: parent.bottom text: qsTr("Add") MouseArea { anchors.fill: parent onClicked: { addNewGroupsButton.visible = false goBackButton.visible = false groupsView.visible = false userConfig.visible = false confirmationBox.visible = true } } } Button { id: goBackButton width: addNewGroupsButton.visible ? parent.width/2: parent.width visible: false height: parent.height/6 - style: GCButtonStyle{} + style: GCButtonStyle {} anchors.bottom: parent.bottom anchors.right: parent.right text: qsTr("Back") MouseArea { anchors.fill: parent onClicked:{ addNewGroupsButton.visible = false goBackButton.visible = false groupsView.visible = false configView.visible = true cancelButton.visible = true } } } } Item { id: createUserItem visible: false width:parent.width/1.75 height: parent.height/1.5 anchors.centerIn: parent Rectangle { id: createUserRect anchors.fill: parent color: "grey" opacity: 0.6 Button { id: userName anchors.left: parent.left height: parent.height/8 width: parent.width/4 text: qsTr("User Name") - style: GCButtonStyle{} + style: GCButtonStyle {} } TextField { id: userText anchors.left: userName.right anchors.leftMargin: 30 anchors.right: parent.right height: userName.height text: "" placeholderText: qsTr("Enter the name of the user") } Button { id: avatar anchors.left: parent.left anchors.top: userName.bottom anchors.topMargin: 50 height: parent.height/8 width: parent.width/4 text: qsTr("Avatar") - style: GCButtonStyle{} + style: GCButtonStyle {} } TextField { id: avatarText anchors.left: avatar.right anchors.leftMargin: 30 anchors.top: userText.bottom anchors.topMargin: 50 anchors.right: parent.right height: avatar.height text: "" placeholderText: qsTr("Add avatar for the user") } Button { id: addGroups height: parent.height/8 anchors.left: parent.left anchors.topMargin: 50 anchors.top: avatar.bottom width: parent.width/4 text: qsTr("Add Groups") - style: GCButtonStyle{} + style: GCButtonStyle {} } ListView { id: chooseGroups anchors.left: addGroups.right anchors.right: parent.right anchors.top: avatarText.bottom anchors.topMargin: 50 anchors.leftMargin: 30 height: parent.height/4 model: MessageHandler.groups spacing: 50 Component { id: checkBoxDelegate GCDialogCheckBox { id: checkbox width: parent.width text:modelData.name onCheckedChanged: { if(checkbox.checked) { console.log("checked ",modelData.name) mainItem.groups.push(modelData.name) } else { mainItem.groups.pop(modelData.name) console.log("unchecked ", modelData.name) } } style: CheckBoxStyle { spacing: 10 indicator: Image { sourceSize.height: 50 property string suffix: control.enabled ? ".svg" : "_disabled.svg" source: control.checked ? "qrc:/gcompris/src/core/resource/apply" + suffix : "qrc:/gcompris/src/core/resource/cancel" + suffix } label: GCText { fontSize: mediumSize text: control.text wrapMode: Text.WordWrap width: parent.parent.width - 50 * ApplicationInfo.ratio - 10 * 2 } } } } delegate: checkBoxDelegate } Button { id: create width: parent.width/2 anchors.bottom: parent.bottom height: parent.height/6 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("Create") MouseArea { anchors.fill: parent onClicked: { // display the confirmation box mainItem.mode = "create" confirmationBox.visible = true createUserItem.visible = false } } } Button { id: cancel anchors.left: create.right anchors.right: parent.right anchors.bottom: parent.bottom height: parent.height/6 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("Cancel") MouseArea { anchors.fill: parent onClicked: { createUserItem.visible = false } } } } } Rectangle { id: confirmationBox width: parent.width/2 height: parent.height/4.5 anchors.centerIn: parent color: "grey" opacity: 0.6 visible: false GCText { text: { if (mainItem.mode === "create") return "Are you sure you want to create this user?" else if(mainItem.mode === "addGroups") return "Are you sure you want to add " + users.currentUser + " to the slected groups ?" else return "" } wrapMode: Text.Wrap font.pixelSize: 25 anchors.centerIn: parent } Button { id: yes anchors.bottom: parent.bottom height: parent.height/6 width: parent.width/2 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("YES") MouseArea { anchors.fill: parent onClicked: { if (mainItem.mode === "create") { console.log(mainItem.userName," ",mainItem.avatar, " ", mainItem.groups) MessageHandler.createUser(mainItem.userName, mainItem.avatar, mainItem.groups) } if(mainItem.mode === "addGroups") { console.log(users.currentUser, mainItem.groups) MessageHandler.addUserToGroup(mainItem.groups, users.currentUser) } userText.text = "" avatarText.text = "" mainItem.groups = [] confirmationBox.visible = false } } } Button { id: no anchors.bottom: parent.bottom height: parent.height/6 anchors.left: yes.right width: parent.width/2 - style: GCButtonStyle{} + style: GCButtonStyle {} text: qsTr("NO") MouseArea { anchors.fill: parent onClicked: { // set all the values to "" userText.text = "" avatarText.text = "" mainItem.groups = [] confirmationBox.visible = false } } } } GCButtonCancel { id: cancelButton visible: false anchors.right: undefined anchors.top: undefined anchors.bottom: userConfig.top anchors.left: userConfig.right anchors.margins: 0 onClose: { cancelButton.visible = false userConfig.visible = false } } Bar { id: bar content: BarEnumContent { value: home } onHomeClicked: activity.home() } } }