Implemented project manager, app data, welcome window

This commit is contained in:
Tiberiu Chibici 2018-08-07 17:51:29 +03:00
parent 49a4a17407
commit 7e355c1981
15 changed files with 561 additions and 27 deletions

View File

@ -31,7 +31,8 @@ SOURCES += \
model/project.cpp \ model/project.cpp \
model/projectitem.cpp \ model/projectitem.cpp \
storage/appdatastorage.cpp \ storage/appdatastorage.cpp \
business/projectmanager.cpp business/projectmanager.cpp \
ui/welcome/recentprojectwidget.cpp
HEADERS += \ HEADERS += \
ui/welcome/welcomedialog.h \ ui/welcome/welcomedialog.h \
@ -41,11 +42,13 @@ HEADERS += \
model/projectitem.h \ model/projectitem.h \
properties/config.h \ properties/config.h \
storage/appdatastorage.h \ storage/appdatastorage.h \
business/projectmanager.h business/projectmanager.h \
ui/welcome/recentprojectwidget.h
FORMS += \ FORMS += \
ui/welcome/welcomedialog.ui \ ui/welcome/welcomedialog.ui \
ui/dialogs/newprojectdialog.ui ui/dialogs/newprojectdialog.ui \
ui/welcome/recentprojectwidget.ui
RESOURCES += \ RESOURCES += \
resources/appresources.qrc resources/appresources.qrc
@ -53,3 +56,5 @@ RESOURCES += \
LIBS += -lboost_filesystem LIBS += -lboost_filesystem
LIBS += -lboost_system LIBS += -lboost_system
LIBS += -lpugixml LIBS += -lpugixml
CONFIG += c++14

View File

@ -1,11 +1,63 @@
#include "projectmanager.h" #include "projectmanager.h"
#include <storage/appdatastorage.h>
namespace Ember namespace Ember
{ {
ProjectManager::ProjectManager() ProjectManager::ProjectManager()
: m_currentProject(nullptr),
m_recentProjects(),
m_recentProjectsLoaded(false)
{ {
} }
const ProjectManager::RecentProjectMap &ProjectManager::recentProjects()
{
if (!m_recentProjectsLoaded)
{
std::vector<RecentProject> projects;
AppDataStorage::readRecentProjects(projects);
for (RecentProject& project : projects)
m_recentProjects.emplace(project.path, project);
m_recentProjectsLoaded = true;
}
return m_recentProjects;
}
void ProjectManager::pinRecentProject(boost::filesystem::path path, bool pinned)
{
auto it = m_recentProjects.find(path);
if (it != m_recentProjects.end())
{
if (it->second.pinned != pinned)
{
it->second.pinned = pinned;
saveRecentProjects();
}
}
}
void ProjectManager::deleteRecentProject(boost::filesystem::path path)
{
auto it = m_recentProjects.find(path);
if (it != m_recentProjects.end())
{
m_recentProjects.erase(it);
saveRecentProjects();
}
}
void ProjectManager::saveRecentProjects()
{
std::vector<RecentProject> projects;
for (auto pair : m_recentProjects)
projects.push_back(pair.second);
AppDataStorage::storeRecentProjects(projects);
}
} }

View File

@ -1,13 +1,49 @@
#ifndef PROJECTMANAGER_H #ifndef PROJECTMANAGER_H
#define PROJECTMANAGER_H #define PROJECTMANAGER_H
#include <vector>
#include <map>
#include <string>
#include <model/project.h>
namespace Ember namespace Ember
{ {
class ProjectManager class ProjectManager
{ {
public: public:
typedef std::map<boost::filesystem::path, RecentProject> RecentProjectMap;
/**
* @brief Constructor
*/
ProjectManager(); ProjectManager();
/**
* @brief Gets a list of recent projects. First time reads from disk.
* @return Map of recent projects.
*/
const RecentProjectMap& recentProjects();
/**
* @brief Pins or unpins a recent project. Updates storage as well.
* @param path Path
* @param pinned True to pin, false to unpin
*/
void pinRecentProject(boost::filesystem::path path, bool pinned = true);
/**
* @brief Deletes a recent project.
* @param path
*/
void deleteRecentProject(boost::filesystem::path path);
private:
void saveRecentProjects();
Project* m_currentProject;
RecentProjectMap m_recentProjects;
bool m_recentProjectsLoaded;
}; };
} }

View File

@ -1,5 +1,7 @@
#include <QApplication> #include <QApplication>
#include "ui/mainwindow.h" #include <storage/appdatastorage.h>
#include <business/projectmanager.h>
#include <ui/mainwindow.h>
#include <properties/config.h> #include <properties/config.h>
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@ -9,7 +11,11 @@ int main(int argc, char *argv[])
a.setApplicationDisplayName(APP_NAME); a.setApplicationDisplayName(APP_NAME);
a.setApplicationVersion(APP_VERSION); a.setApplicationVersion(APP_VERSION);
MainWindow w; Ember::AppDataStorage::initialize();
Ember::ProjectManager projectManager;
Ember::MainWindow w(&projectManager);
w.show(); w.show();
return a.exec(); return a.exec();

View File

@ -19,6 +19,11 @@ ProjectItem::ProjectItem(boost::filesystem::path path, ProjectItem* parent, Proj
{ {
} }
ProjectItem::~ProjectItem()
{
}
std::string ProjectItem::name() const std::string ProjectItem::name() const
{ {
return m_path.filename().string(); return m_path.filename().string();

View File

@ -33,6 +33,11 @@ public:
*/ */
ProjectItem(boost::filesystem::path path, ProjectItem* parent, Project* project); ProjectItem(boost::filesystem::path path, ProjectItem* parent, Project* project);
/**
* @brief Destructor
*/
virtual ~ProjectItem();
// Getters // Getters
/** /**
* @brief Gets the name of the item * @brief Gets the name of the item

View File

@ -8,20 +8,23 @@
namespace Ember namespace Ember
{ {
AppDataStorage::AppDataStorage() boost::filesystem::path AppDataStorage::s_appData;
boost::filesystem::path AppDataStorage::s_recentProjects;
void AppDataStorage::initialize()
{ {
QString appData = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QString appData = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
m_appData = appData.toStdString(); s_appData = appData.toStdString();
m_recentProjects = m_appData; s_recentProjects = s_appData;
m_recentProjects += RECENT_PROJECTS_FILENAME; s_recentProjects += RECENT_PROJECTS_FILENAME;
} }
void AppDataStorage::readRecentProjects(std::vector<RecentProject> &projects) void AppDataStorage::readRecentProjects(std::vector<RecentProject> &projects)
{ {
if (boost::filesystem::exists(m_recentProjects)) if (boost::filesystem::exists(s_recentProjects))
{ {
pugi::xml_document doc; pugi::xml_document doc;
doc.load_file(m_recentProjects.c_str()); doc.load_file(s_recentProjects.c_str());
for (auto& node : doc.document_element()) for (auto& node : doc.document_element())
{ {
@ -54,8 +57,8 @@ void AppDataStorage::storeRecentProjects(const std::vector<RecentProject> &proje
} }
// Save file, ensure directory exists // Save file, ensure directory exists
boost::filesystem::create_directories(m_appData); boost::filesystem::create_directories(s_appData);
doc.save_file(m_recentProjects.string().c_str()); doc.save_file(s_recentProjects.string().c_str());
} }
} }

View File

@ -12,23 +12,23 @@ namespace Ember
class AppDataStorage class AppDataStorage
{ {
public: public:
AppDataStorage(); static void initialize();
/** /**
* @brief Reads recent projects * @brief Reads recent projects
* @param projects List will be saved to given vector. * @param projects List will be saved to given vector.
*/ */
void readRecentProjects(std::vector<RecentProject>& projects); static void readRecentProjects(std::vector<RecentProject>& projects);
/** /**
* @brief Stores recent projects * @brief Stores recent projects
* @param projects List of projects. * @param projects List of projects.
*/ */
void storeRecentProjects(const std::vector<RecentProject>& projects); static void storeRecentProjects(const std::vector<RecentProject>& projects);
private: private:
boost::filesystem::path m_appData; static boost::filesystem::path s_appData;
boost::filesystem::path m_recentProjects; static boost::filesystem::path s_recentProjects;
}; };
} }

View File

@ -1,11 +1,60 @@
#include <QTimer>
#include <ui/welcome/welcomedialog.h>
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : namespace Ember
QMainWindow(parent)
{ {
MainWindow::MainWindow(ProjectManager* projectManager, QWidget *parent) :
QMainWindow(parent),
m_projectManager(projectManager)
{
setupUi();
setupActions();
QTimer::singleShot(0, this, &MainWindow::showWelcomeDialog);
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
} }
void MainWindow::setupUi()
{
QWidget* central = new QWidget();
setCentralWidget(central);
}
void MainWindow::setupActions()
{
}
void MainWindow::showWelcomeDialog()
{
WelcomeDialog dialog(m_projectManager, this);
if (dialog.exec() == QDialog::Accepted)
{
switch(dialog.resultAction())
{
case WelcomeDialog::NEW_PROJECT:
newProject();
break;
case WelcomeDialog::OPEN_PROJECT:
openProject(dialog.openPath());
break;
}
}
}
void MainWindow::newProject()
{
}
void MainWindow::openProject(boost::filesystem::path path)
{
}
}

View File

@ -1,18 +1,33 @@
#ifndef MAINWINDOW_H #ifndef MAINWINDOW_H
#define MAINWINDOW_H #define MAINWINDOW_H
#include <boost/filesystem.hpp>
#include <QMainWindow> #include <QMainWindow>
#include <business/projectmanager.h>
namespace Ember
{
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit MainWindow(QWidget *parent = 0); explicit MainWindow(ProjectManager* projectManager, QWidget *parent = 0);
~MainWindow(); virtual ~MainWindow();
private slots:
void showWelcomeDialog();
void newProject();
void openProject(boost::filesystem::path path);
private: private:
void setupUi();
void setupActions();
ProjectManager* m_projectManager;
}; };
}
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

View File

@ -0,0 +1,63 @@
#include "recentprojectwidget.h"
#include "ui_recentprojectwidget.h"
namespace Ember {
RecentProjectWidget::RecentProjectWidget(RecentProject project, QWidget *parent) :
QWidget(parent),
ui(new Ui::RecentProjectWidget),
m_project(project),
m_selected(false)
{
ui->setupUi(this);
setupActions();
setSelected(false);
}
RecentProjectWidget::~RecentProjectWidget()
{
delete ui;
}
void RecentProjectWidget::setupUi()
{
ui->text->setText(QString::fromStdString(m_project.name));
ui->buttonPin->setChecked(m_project.pinned);
}
void RecentProjectWidget::setupActions()
{
connect(ui->buttonDelete, &QToolButton::clicked, this, &RecentProjectWidget::onDeleteClicked);
connect(ui->buttonPin, &QToolButton::toggled, this, &RecentProjectWidget::onPinToggled);
}
const RecentProject& RecentProjectWidget::project() const
{
return m_project;
}
void RecentProjectWidget::setSelected(bool selected)
{
m_selected = selected;
ui->buttonPin->setVisible(selected);
ui->buttonDelete->setVisible(selected);
ui->textPin->setVisible(!selected && ui->buttonPin->isChecked());
}
void RecentProjectWidget::onDeleteClicked()
{
emit deleted(m_project);
}
void RecentProjectWidget::onPinToggled(bool pinned)
{
ui->textPin->setVisible(!m_selected && pinned);
if (m_project.pinned != pinned)
{
m_project.pinned = pinned;
emit pinToggled(m_project, pinned);
}
}
}

View File

@ -0,0 +1,43 @@
#ifndef RECENTPROJECTWIDGET_H
#define RECENTPROJECTWIDGET_H
#include <QWidget>
#include <model/project.h>
namespace Ui {
class RecentProjectWidget;
}
namespace Ember {
class RecentProjectWidget : public QWidget
{
Q_OBJECT
public:
explicit RecentProjectWidget(RecentProject project, QWidget *parent = 0);
virtual ~RecentProjectWidget();
const RecentProject& project() const;
void setSelected(bool selected);
signals:
void deleted(const RecentProject& project);
void pinToggled(const RecentProject& project, bool pinned);
private slots:
void onDeleteClicked();
void onPinToggled(bool pinned);
private:
void setupUi();
void setupActions();
Ui::RecentProjectWidget *ui;
RecentProject m_project;
bool m_selected;
};
}
#endif // RECENTPROJECTWIDGET_H

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RecentProjectWidget</class>
<widget class="QWidget" name="RecentProjectWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>663</width>
<height>46</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QLabel" name="text">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonPin">
<property name="font">
<font>
<family>Font Awesome 5 Free</family>
</font>
</property>
<property name="text">
<string></string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonDelete">
<property name="font">
<font>
<family>Font Awesome 5 Free</family>
</font>
</property>
<property name="text">
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="textPin">
<property name="font">
<font>
<family>Font Awesome 5 Free</family>
</font>
</property>
<property name="text">
<string></string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,14 +1,157 @@
#include <QFileDialog>
#include <properties/config.h>
#include "welcomedialog.h" #include "welcomedialog.h"
#include "ui_welcomedialog.h" #include "ui_welcomedialog.h"
#include "recentprojectwidget.h"
WelcomeDialog::WelcomeDialog(QWidget *parent) : namespace Ember {
WelcomeDialog::WelcomeDialog(ProjectManager* projectManager, QWidget *parent) :
QDialog(parent), QDialog(parent),
ui(new Ui::WelcomeDialog) ui(new Ui::WelcomeDialog),
m_projectManager(projectManager),
m_originalPicture()
{ {
ui->setupUi(this); ui->setupUi(this);
m_originalPicture = QPixmap(*ui->picture->pixmap());
setResult(Rejected);
setupActions();
populateProjects();
} }
WelcomeDialog::~WelcomeDialog() WelcomeDialog::~WelcomeDialog()
{ {
delete ui; delete ui;
} }
void WelcomeDialog::setupActions()
{
connect(ui->buttonNewProject, &QCommandLinkButton::clicked, this, &WelcomeDialog::onNewProjectClicked);
connect(ui->buttonOpenProject, &QCommandLinkButton::clicked, this, &WelcomeDialog::onOpenProjectClicked);
connect(ui->listRecentProjects, &QListWidget::currentItemChanged, this, &WelcomeDialog::onProjectCurrentChanged);
connect(ui->listRecentProjects, &QListWidget::itemActivated, this, &WelcomeDialog::onProjectActivated);
}
void WelcomeDialog::populateProjects()
{
std::vector<RecentProject> projects;
for (auto pair : m_projectManager->recentProjects())
projects.push_back(pair.second);
// Sort by pinned and access date
std::sort(projects.begin(), projects.end(), [](const RecentProject& a, const RecentProject& b) -> bool
{
if (a.pinned == b.pinned)
return a.access > b.access;
return a.pinned > b.pinned;
});
// Add items
for (auto& project : projects)
{
RecentProjectWidget* itemWidget = new RecentProjectWidget(project);
connect(itemWidget, &RecentProjectWidget::pinToggled, this, &WelcomeDialog::onProjectPinned);
connect(itemWidget, &RecentProjectWidget::deleted, this, &WelcomeDialog::onProjectDeleted);
QListWidgetItem* item = new QListWidgetItem();
item->setSizeHint(itemWidget->sizeHint());
ui->listRecentProjects->addItem(item);
ui->listRecentProjects->setItemWidget(item, itemWidget);
}
}
void WelcomeDialog::resizeEvent(QResizeEvent*)
{
QSize pictSize = ui->picture->size();
QPixmap pictScaled = m_originalPicture.scaled(pictSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
ui->picture->setPixmap(pictScaled);
}
void WelcomeDialog::onNewProjectClicked()
{
m_resultAction = NEW_PROJECT;
accept();
}
void WelcomeDialog::onOpenProjectClicked()
{
QString path = QFileDialog::getOpenFileName(this, tr("Open project"), "", buildFilterString());
if (!path.isEmpty())
{
QByteArray str = path.toUtf8();
m_openPath.assign(str.begin(), str.end());
m_resultAction = OPEN_PROJECT;
accept();
}
}
void WelcomeDialog::onProjectPinned(const RecentProject& project, bool pinned)
{
m_projectManager->pinRecentProject(project.path, pinned);
}
void WelcomeDialog::onProjectDeleted(const RecentProject& project)
{
m_projectManager->deleteRecentProject(project.path);
delete ui->listRecentProjects->takeItem(ui->listRecentProjects->currentRow());
}
void WelcomeDialog::onProjectCurrentChanged(QListWidgetItem *current, QListWidgetItem *previous)
{
if (previous)
{
RecentProjectWidget* itemWidget = (RecentProjectWidget*) ui->listRecentProjects->itemWidget(previous);
if (itemWidget)
itemWidget->setSelected(false);
}
if (current)
{
RecentProjectWidget* itemWidget = (RecentProjectWidget*) ui->listRecentProjects->itemWidget(current);
if (itemWidget)
itemWidget->setSelected(true);
}
}
void WelcomeDialog::onProjectActivated(QListWidgetItem *item)
{
RecentProjectWidget* itemWidget = (RecentProjectWidget*) ui->listRecentProjects->itemWidget(item);
if (itemWidget)
{
m_resultAction = OPEN_PROJECT;
m_openPath = itemWidget->project().path;
accept();
}
}
QString WelcomeDialog::buildFilterString()
{
static QString str;
if (str.isEmpty())
{
str = QString("%1 (*%2);;%3 (*.*)").arg(
tr("Project files"),
EMBER_PROJECT_EXTENSION,
tr("All files"));
}
return str;
}
boost::filesystem::path WelcomeDialog::openPath() const
{
return m_openPath;
}
WelcomeDialog::ResultAction WelcomeDialog::resultAction() const
{
return m_resultAction;
}
}

View File

@ -2,21 +2,54 @@
#define WELCOMEDIALOG_H #define WELCOMEDIALOG_H
#include <QDialog> #include <QDialog>
#include <QListWidget>
#include <business/projectmanager.h>
namespace Ui { namespace Ui {
class WelcomeDialog; class WelcomeDialog;
} }
namespace Ember {
class WelcomeDialog : public QDialog class WelcomeDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit WelcomeDialog(QWidget *parent = 0); enum ResultAction
~WelcomeDialog(); {
NEW_PROJECT,
OPEN_PROJECT
};
explicit WelcomeDialog(ProjectManager* projectManager, QWidget *parent = 0);
virtual ~WelcomeDialog();
ResultAction resultAction() const;
boost::filesystem::path openPath() const;
protected:
virtual void resizeEvent(QResizeEvent*) override;
private slots:
void onNewProjectClicked();
void onOpenProjectClicked();
void onProjectPinned(const RecentProject& project, bool pinned);
void onProjectDeleted(const RecentProject& project);
void onProjectCurrentChanged(QListWidgetItem* current, QListWidgetItem* previous);
void onProjectActivated(QListWidgetItem* item);
private: private:
void setupActions();
void populateProjects();
static QString buildFilterString();
Ui::WelcomeDialog *ui; Ui::WelcomeDialog *ui;
ProjectManager* m_projectManager;
QPixmap m_originalPicture;
ResultAction m_resultAction;
boost::filesystem::path m_openPath;
}; };
}
#endif // WELCOMEDIALOG_H #endif // WELCOMEDIALOG_H