create and close projects.

main
bog 2023-12-27 14:38:10 +01:00
parent 6e4b01972a
commit ba34df088e
18 changed files with 414 additions and 17 deletions

View File

@ -1,4 +1,4 @@
.PHONY: build tests install .PHONY: build tests install i18n
build: build:
meson setup build meson setup build
@ -13,3 +13,7 @@ install: tests
check: check:
cppcheck --language=c++ --enable=all -q src \ cppcheck --language=c++ --enable=all -q src \
--suppress=missingInclude --suppress=missingInclude
i18n:
lupdate src -ts i18n/pix_en.ts
lupdate src -ts i18n/pix_fr.ts

View File

@ -4,17 +4,56 @@
<context> <context>
<name>QAction</name> <name>QAction</name>
<message> <message>
<location filename="../src/gui/Window.cpp" line="25"/> <location filename="../src/gui/Window.cpp" line="46"/>
<source>new-project</source>
<translation>New Project</translation>
</message>
<message>
<location filename="../src/gui/Window.cpp" line="50"/>
<source>close-project</source>
<translation>Close Project</translation>
</message>
<message>
<location filename="../src/gui/Window.cpp" line="55"/>
<source>quit</source> <source>quit</source>
<translation>Quit</translation> <translation>Quit</translation>
</message> </message>
</context> </context>
<context>
<name>QDialog</name>
<message>
<location filename="../src/gui/Window.cpp" line="119"/>
<source>new-project</source>
<translation>New Project</translation>
</message>
</context>
<context>
<name>QLineEdit</name>
<message>
<location filename="../src/gui/Window.cpp" line="124"/>
<source>new-project-name</source>
<translation>New project name</translation>
</message>
</context>
<context> <context>
<name>QMenu</name> <name>QMenu</name>
<message> <message>
<location filename="../src/gui/Window.cpp" line="22"/> <location filename="../src/gui/Window.cpp" line="43"/>
<source>file</source> <source>file</source>
<translation>File</translation> <translation>File</translation>
</message> </message>
</context> </context>
<context>
<name>QPushButton</name>
<message>
<location filename="../src/gui/Window.cpp" line="154"/>
<source>new-project</source>
<translation>New Project</translation>
</message>
<message>
<location filename="../src/gui/Window.cpp" line="158"/>
<source>cancel-new-project</source>
<translation>Cancel</translation>
</message>
</context>
</TS> </TS>

View File

@ -4,17 +4,56 @@
<context> <context>
<name>QAction</name> <name>QAction</name>
<message> <message>
<location filename="../src/gui/Window.cpp" line="25"/> <location filename="../src/gui/Window.cpp" line="46"/>
<source>new-project</source>
<translation>Nouveau Projet</translation>
</message>
<message>
<location filename="../src/gui/Window.cpp" line="50"/>
<source>close-project</source>
<translation>Fermer Projet</translation>
</message>
<message>
<location filename="../src/gui/Window.cpp" line="55"/>
<source>quit</source> <source>quit</source>
<translation>Quitter</translation> <translation>Quitter</translation>
</message> </message>
</context> </context>
<context>
<name>QDialog</name>
<message>
<location filename="../src/gui/Window.cpp" line="119"/>
<source>new-project</source>
<translation>Nouveau Projet</translation>
</message>
</context>
<context>
<name>QLineEdit</name>
<message>
<location filename="../src/gui/Window.cpp" line="124"/>
<source>new-project-name</source>
<translation>Nom du nouveau projet</translation>
</message>
</context>
<context> <context>
<name>QMenu</name> <name>QMenu</name>
<message> <message>
<location filename="../src/gui/Window.cpp" line="22"/> <location filename="../src/gui/Window.cpp" line="43"/>
<source>file</source> <source>file</source>
<translation>Fichier</translation> <translation>Fichier</translation>
</message> </message>
</context> </context>
<context>
<name>QPushButton</name>
<message>
<location filename="../src/gui/Window.cpp" line="154"/>
<source>new-project</source>
<translation>Nouveau Projet</translation>
</message>
<message>
<location filename="../src/gui/Window.cpp" line="158"/>
<source>cancel-new-project</source>
<translation>Annuler</translation>
</message>
</context>
</TS> </TS>

View File

@ -1,7 +1,7 @@
project( project(
'pixtool', 'pixtool',
'cpp', 'cpp',
version: '0.0.0', version: '0.0',
default_options: [ default_options: [
'cpp_std=c++17', 'cpp_std=c++17',
'warning_level=3' 'warning_level=3'
@ -45,6 +45,7 @@ pixtool = static_library(
# model # model
'src/model/PixTool.cpp', 'src/model/PixTool.cpp',
'src/model/Project.cpp',
'src/model/Command.cpp', 'src/model/Command.cpp',
'src/model/Shortcut.cpp', 'src/model/Shortcut.cpp',
'src/model/CommandRunner.cpp', 'src/model/CommandRunner.cpp',

View File

@ -1,5 +1,6 @@
#include "Presenter.hpp" #include "Presenter.hpp"
#include "src/model/Event.hpp" #include "src/model/Event.hpp"
#include "model/Observable.hpp"
namespace pt namespace pt
{ {
@ -13,7 +14,42 @@ namespace pt
{ {
} }
void Presenter::update(model::Event& event) /*override*/ void Presenter::update(model::Observable& source, model::Event& event) /*override*/
{
if (&source == &m_pixtool)
{
update_pixtool(event);
}
else if (&source == &m_window)
{
update_window(event);
}
}
void Presenter::update_pixtool(model::Event const& event)
{
if (event.type == model::EVENT_NEW_PROJECT)
{
m_window.show_project(event.project.name, event.project.dir);
}
if (event.type == model::EVENT_QUERY_NEW_PROJECT)
{
m_window.on_new_project();
}
if (event.type == model::EVENT_CLOSE_PROJECT)
{
m_window.hide_project();
}
if (event.type == model::EVENT_QUERY_CLOSE_PROJECT)
{
m_window.on_close_project();
}
}
void Presenter::update_window(model::Event const& event)
{ {
if (event.type == model::EVENT_KEYPRESSED) if (event.type == model::EVENT_KEYPRESSED)
{ {
@ -24,5 +60,15 @@ namespace pt
{ {
m_pixtool.quit(); m_pixtool.quit();
} }
if (event.type == model::EVENT_NEW_PROJECT)
{
m_pixtool.new_project(event.project.name, event.project.dir);
}
if (event.type == model::EVENT_CLOSE_PROJECT)
{
m_pixtool.close_project();
}
} }
} }

View File

@ -15,7 +15,10 @@ namespace pt
explicit Presenter(gui::Window& window, model::PixTool& pixtool); explicit Presenter(gui::Window& window, model::PixTool& pixtool);
virtual ~Presenter(); virtual ~Presenter();
void update(model::Event& event) override; void update(model::Observable& source, model::Event& event) override;
void update_pixtool(model::Event const& event);
void update_window(model::Event const& event);
private: private:
gui::Window& m_window; gui::Window& m_window;
model::PixTool& m_pixtool; model::PixTool& m_pixtool;

View File

@ -1,11 +1,21 @@
#include <QKeyEvent>
#include "../model/Shortcut.hpp" #include "../model/Shortcut.hpp"
#include "qboxlayout.h"
#include "src/model/Event.hpp"
#include "Window.hpp" #include "Window.hpp"
#include "conf.hpp" #include "conf.hpp"
#include "qmainwindow.h" #include "qmainwindow.h"
#include "qnamespace.h" #include "qnamespace.h"
#include "src/model/Event.hpp" #include "qtabwidget.h"
#include <QKeyEvent>
#include <QMenuBar> #include <QMenuBar>
#include <QLabel>
#include <QVBoxLayout>
#include <QFileDialog>
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>
namespace pt namespace pt
{ {
@ -14,14 +24,34 @@ namespace pt
/*explicit*/ Window::Window(QWidget* parent) /*explicit*/ Window::Window(QWidget* parent)
: QMainWindow(parent) : QMainWindow(parent)
{ {
setWindowTitle(QString::fromStdString("PixTool v" + PT_VERSION)); setWindowTitle(QString::fromStdString(m_base_title));
setMinimumSize(QSize {640, 480}); setMinimumSize(QSize {640, 480});
QMenuBar* bar = new QMenuBar {this}; // Window layout
// =============
QWidget* central = new QWidget {};
setCentralWidget(central);
QVBoxLayout* vbox = new QVBoxLayout {central};
// Menu
// ====
QMenuBar* bar = new QMenuBar {};
vbox->addWidget(bar);
QMenu* file = new QMenu {QMenu::tr("file")}; QMenu* file = new QMenu {QMenu::tr("file")};
bar->addMenu(file); bar->addMenu(file);
QAction* new_project = new QAction {QAction::tr("new-project")};
file->addAction(new_project);
connect(new_project, &QAction::triggered, this, &Window::on_new_project);
m_close_project = new QAction {QAction::tr("close-project")};
file->addAction(m_close_project);
connect(m_close_project, &QAction::triggered, this, &Window::on_close_project);
m_close_project->setEnabled(false);
QAction* quit = new QAction {QAction::tr("quit")}; QAction* quit = new QAction {QAction::tr("quit")};
file->addAction(quit); file->addAction(quit);
@ -68,5 +98,81 @@ namespace pt
notify(e); notify(e);
} }
} }
void Window::show_project(std::string const& name,
std::filesystem::path const& dir)
{
setWindowTitle(QString::fromStdString(m_base_title + " [" + name
+ "]: " + dir.string()));
m_close_project->setEnabled(true);
}
void Window::hide_project()
{
setWindowTitle(QString::fromStdString(m_base_title));
m_close_project->setEnabled(false);
}
void Window::on_new_project()
{
QDialog* dialog = new QDialog {this};
dialog->setWindowTitle(QDialog::tr("new-project"));
auto* layout = new QVBoxLayout {dialog};
QLineEdit* name_field = new QLineEdit {dialog};
name_field->setPlaceholderText(QLineEdit::tr("new-project-name"));
layout->addWidget(name_field);
std::filesystem::path project_dir = std::filesystem::current_path();
{
auto* path_btn = new QPushButton {QString::fromStdString(project_dir.string())};
connect(path_btn, &QPushButton::pressed, [&](){
auto* file_diag = new QFileDialog {};
file_diag->setFileMode(QFileDialog::Directory);
file_diag->setOption(QFileDialog::ShowDirsOnly);
file_diag->setAcceptMode(QFileDialog::AcceptOpen);
file_diag->setViewMode(QFileDialog::ViewMode::List);
if (file_diag->exec())
{
auto dir = file_diag->directory();
project_dir = dir.absolutePath().toStdString();
path_btn->setText(QString::fromStdString(project_dir.string()));
}
});
layout->addWidget(path_btn);
}
{
auto* btn_layout = new QHBoxLayout {};
layout->addLayout(btn_layout);
auto ok_btn = new QPushButton {QPushButton::tr("new-project"), dialog};
btn_layout->addWidget(ok_btn);
connect(ok_btn, &QPushButton::pressed, [dialog](){ dialog->done(true); });
auto cancel_btn = new QPushButton {QPushButton::tr("cancel-new-project"), dialog};
btn_layout->addWidget(cancel_btn);
connect(cancel_btn, &QPushButton::pressed, [dialog](){ dialog->done(false); });
if (dialog->exec())
{
std::string project_name = name_field->text().toStdString();
model::Event e {model::EVENT_NEW_PROJECT};
e.project.name = project_name;
e.project.dir = project_dir;
notify(e);
}
}
}
void Window::on_close_project()
{
notify(model::EVENT_CLOSE_PROJECT);
}
} }
} }

View File

@ -17,9 +17,23 @@ namespace pt
explicit Window(QWidget* parent=nullptr); explicit Window(QWidget* parent=nullptr);
virtual ~Window(); virtual ~Window();
// Events
// ======
void keyPressEvent(QKeyEvent* event) override; void keyPressEvent(QKeyEvent* event) override;
// Project
// =======
void show_project(std::string const& name,
std::filesystem::path const& dir);
void hide_project();
void on_new_project();
void on_close_project();
private: private:
std::string const m_base_title = "PIXTOOL " + PT_VERSION;
QAction* m_close_project;
}; };
} }
} }

View File

@ -23,6 +23,7 @@ int main(int argc, char** argv)
pt::model::PixTool pixtool; pt::model::PixTool pixtool;
auto presenter = std::make_shared<pt::Presenter>(window, pixtool); auto presenter = std::make_shared<pt::Presenter>(window, pixtool);
pixtool.add_observer(presenter);
window.add_observer(presenter); window.add_observer(presenter);
pixtool.ready(); pixtool.ready();

View File

@ -4,8 +4,10 @@
#include "../commons.hpp" #include "../commons.hpp"
#include "Shortcut.hpp" #include "Shortcut.hpp"
#define EVENT_TYPE(G) \ #define EVENT_TYPE(G) \
G(EVENT_KEYPRESSED), \ G(EVENT_NEW_PROJECT), G(EVENT_CLOSE_PROJECT), \
G(EVENT_QUERY_NEW_PROJECT), G(EVENT_QUERY_CLOSE_PROJECT), \
G(EVENT_KEYPRESSED), \
G(EVENT_QUIT) G(EVENT_QUIT)
namespace pt namespace pt
@ -19,12 +21,19 @@ namespace pt
KeyMod keymod {""}; KeyMod keymod {""};
}; };
struct EventProject
{
std::string name;
std::filesystem::path dir;
};
struct Event struct Event
{ {
explicit Event(EventType ty) : type { ty } {} explicit Event(EventType ty) : type { ty } {}
EventType type; EventType type;
EventKeyPressed key_pressed; EventKeyPressed key_pressed;
EventProject project;
}; };
} }
} }

View File

@ -23,9 +23,15 @@ namespace pt
{ {
if (auto ob = observer.lock(); ob) if (auto ob = observer.lock(); ob)
{ {
ob->update(event); ob->update(*this, event);
} }
} }
} }
void Observable::notify(EventType type)
{
Event e { type };
notify(e);
}
} }
} }

View File

@ -18,6 +18,7 @@ namespace pt
void add_observer(std::shared_ptr<Observer> observer); void add_observer(std::shared_ptr<Observer> observer);
void notify(Event& event); void notify(Event& event);
void notify(EventType type);
private: private:
std::vector<std::weak_ptr<Observer>> m_observers; std::vector<std::weak_ptr<Observer>> m_observers;

View File

@ -8,13 +8,15 @@ namespace pt
{ {
namespace model namespace model
{ {
class Observable;
class Observer class Observer
{ {
public: public:
explicit Observer(); explicit Observer();
virtual ~Observer(); virtual ~Observer();
virtual void update(Event& event) = 0; virtual void update(Observable& source, Event& event) = 0;
private: private:
}; };
} }

View File

@ -2,6 +2,8 @@
#include "PixTool.hpp" #include "PixTool.hpp"
#include "CommandRunner.hpp" #include "CommandRunner.hpp"
#include "cmds/Quit.hpp" #include "cmds/Quit.hpp"
#include "cmds/Project.hpp"
#include "src/model/Event.hpp"
namespace pt namespace pt
{ {
@ -13,6 +15,12 @@ namespace pt
m_cmd_runner->bind(Shortcut {{KeyMod {"X", {PT_CONTROL}}, m_cmd_runner->bind(Shortcut {{KeyMod {"X", {PT_CONTROL}},
KeyMod {"C", {PT_CONTROL}}}}, KeyMod {"C", {PT_CONTROL}}}},
std::make_shared<cmds::Quit>()); std::make_shared<cmds::Quit>());
m_cmd_runner->bind(Shortcut {{KeyMod {"N", {PT_CONTROL}}}},
std::make_shared<cmds::QueryNewProject>());
m_cmd_runner->bind(Shortcut {{KeyMod {"W", {PT_CONTROL}}}},
std::make_shared<cmds::QueryCloseProject>());
} }
/*virtual*/ PixTool::~PixTool() /*virtual*/ PixTool::~PixTool()
@ -34,5 +42,29 @@ namespace pt
spdlog::info("done"); spdlog::info("done");
exit(0); exit(0);
} }
void PixTool::new_project(std::string const& name,
std::filesystem::path const& dir)
{
m_project = std::make_unique<Project>(name, dir);
Event e {EVENT_NEW_PROJECT};
e.project.name = name;
e.project.dir = dir;
notify(e);
spdlog::info("new project '" + name + "'");
}
void PixTool::close_project()
{
if (m_project)
{
std::string name = m_project->name();
m_project.reset();
notify(EVENT_CLOSE_PROJECT);
spdlog::info("close project '" + name + "'");
}
}
} }
} }

View File

@ -3,6 +3,8 @@
#include "../commons.hpp" #include "../commons.hpp"
#include "Shortcut.hpp" #include "Shortcut.hpp"
#include "Project.hpp"
#include "Observable.hpp"
namespace pt namespace pt
{ {
@ -10,7 +12,7 @@ namespace pt
{ {
class CommandRunner; class CommandRunner;
class PixTool class PixTool: public Observable
{ {
public: public:
explicit PixTool(); explicit PixTool();
@ -20,8 +22,15 @@ namespace pt
void update(KeyMod const& km); void update(KeyMod const& km);
void quit(); void quit();
// Project
// =======
void new_project(std::string const& name, std::filesystem::path const& dir);
bool has_project() const { return m_project != nullptr; }
void close_project();
private: private:
std::unique_ptr<CommandRunner> m_cmd_runner; std::unique_ptr<CommandRunner> m_cmd_runner;
std::unique_ptr<Project> m_project;
}; };
} }
} }

18
src/model/Project.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "Project.hpp"
namespace pt
{
namespace model
{
/*explicit*/ Project::Project(std::string const& name,
std::filesystem::path const& dir)
: m_name { name }
, m_dir { dir }
{
}
/*virtual*/ Project::~Project()
{
}
}
}

27
src/model/Project.hpp Normal file
View File

@ -0,0 +1,27 @@
#ifndef pt_model_PROJECT_HPP
#define pt_model_PROJECT_HPP
#include "../commons.hpp"
namespace pt
{
namespace model
{
class Project
{
public:
explicit Project(std::string const& name,
std::filesystem::path const& dir);
virtual ~Project();
inline std::string name() const { return m_name; }
inline std::filesystem::path dir() const { return m_dir; }
private:
std::string m_name;
std::filesystem::path m_dir;
};
}
}
#endif

View File

@ -0,0 +1,40 @@
#ifndef pt_cmds_PROJECT_HPP
#define pt_cmds_PROJECT_HPP
#include "../Command.hpp"
#include "src/model/Event.hpp"
namespace pt
{
namespace cmds
{
struct QueryNewProject: public model::Command
{
std::string name;
std::filesystem::path dir;
explicit QueryNewProject()
: model::Command("query-new-project")
{}
void execute(model::PixTool& pixtool) override
{
pixtool.notify(model::EVENT_QUERY_NEW_PROJECT);
}
};
struct QueryCloseProject: public model::Command
{
explicit QueryCloseProject()
: model::Command("query-close-project") {}
void execute(model::PixTool& pixtool) override
{
pixtool.notify(model::EVENT_QUERY_CLOSE_PROJECT);
}
};
}
}
#endif