diff --git a/Makefile b/Makefile index 8951fbc..9538339 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build tests install +.PHONY: build tests install i18n build: meson setup build @@ -13,3 +13,7 @@ install: tests check: cppcheck --language=c++ --enable=all -q src \ --suppress=missingInclude + +i18n: + lupdate src -ts i18n/pix_en.ts + lupdate src -ts i18n/pix_fr.ts diff --git a/i18n/pix_en.ts b/i18n/pix_en.ts index f1c0645..25d7ea1 100644 --- a/i18n/pix_en.ts +++ b/i18n/pix_en.ts @@ -4,17 +4,56 @@ QAction - + + new-project + New Project + + + + close-project + Close Project + + + quit Quit + + QDialog + + + new-project + New Project + + + + QLineEdit + + + new-project-name + New project name + + QMenu - + file File + + QPushButton + + + new-project + New Project + + + + cancel-new-project + Cancel + + diff --git a/i18n/pix_fr.ts b/i18n/pix_fr.ts index 78ce21c..c7d7719 100644 --- a/i18n/pix_fr.ts +++ b/i18n/pix_fr.ts @@ -4,17 +4,56 @@ QAction - + + new-project + Nouveau Projet + + + + close-project + Fermer Projet + + + quit Quitter + + QDialog + + + new-project + Nouveau Projet + + + + QLineEdit + + + new-project-name + Nom du nouveau projet + + QMenu - + file Fichier + + QPushButton + + + new-project + Nouveau Projet + + + + cancel-new-project + Annuler + + diff --git a/meson.build b/meson.build index 5e7668e..175e063 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'pixtool', 'cpp', - version: '0.0.0', + version: '0.0', default_options: [ 'cpp_std=c++17', 'warning_level=3' @@ -45,6 +45,7 @@ pixtool = static_library( # model 'src/model/PixTool.cpp', + 'src/model/Project.cpp', 'src/model/Command.cpp', 'src/model/Shortcut.cpp', 'src/model/CommandRunner.cpp', diff --git a/src/Presenter.cpp b/src/Presenter.cpp index 16e4d8f..92c744e 100644 --- a/src/Presenter.cpp +++ b/src/Presenter.cpp @@ -1,5 +1,6 @@ #include "Presenter.hpp" #include "src/model/Event.hpp" +#include "model/Observable.hpp" 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) { @@ -24,5 +60,15 @@ namespace pt { 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(); + } } } diff --git a/src/Presenter.hpp b/src/Presenter.hpp index 0847422..f050e29 100644 --- a/src/Presenter.hpp +++ b/src/Presenter.hpp @@ -15,7 +15,10 @@ namespace pt explicit Presenter(gui::Window& window, model::PixTool& pixtool); 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: gui::Window& m_window; model::PixTool& m_pixtool; diff --git a/src/gui/Window.cpp b/src/gui/Window.cpp index 0f3c5c8..6e669a9 100644 --- a/src/gui/Window.cpp +++ b/src/gui/Window.cpp @@ -1,11 +1,21 @@ -#include #include "../model/Shortcut.hpp" +#include "qboxlayout.h" +#include "src/model/Event.hpp" #include "Window.hpp" #include "conf.hpp" + #include "qmainwindow.h" #include "qnamespace.h" -#include "src/model/Event.hpp" +#include "qtabwidget.h" + +#include #include +#include +#include +#include +#include +#include +#include namespace pt { @@ -14,14 +24,34 @@ namespace pt /*explicit*/ Window::Window(QWidget* parent) : QMainWindow(parent) { - setWindowTitle(QString::fromStdString("PixTool v" + PT_VERSION)); + setWindowTitle(QString::fromStdString(m_base_title)); 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")}; 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")}; file->addAction(quit); @@ -68,5 +98,81 @@ namespace pt 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); + } } } diff --git a/src/gui/Window.hpp b/src/gui/Window.hpp index 264efd3..87ceaa6 100644 --- a/src/gui/Window.hpp +++ b/src/gui/Window.hpp @@ -17,9 +17,23 @@ namespace pt explicit Window(QWidget* parent=nullptr); virtual ~Window(); + // Events + // ====== 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: + std::string const m_base_title = "PIXTOOL " + PT_VERSION; + QAction* m_close_project; + }; } } diff --git a/src/main.cpp b/src/main.cpp index 7bef5a2..728a689 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ int main(int argc, char** argv) pt::model::PixTool pixtool; auto presenter = std::make_shared(window, pixtool); + pixtool.add_observer(presenter); window.add_observer(presenter); pixtool.ready(); diff --git a/src/model/Event.hpp b/src/model/Event.hpp index 9480cbd..b99633b 100644 --- a/src/model/Event.hpp +++ b/src/model/Event.hpp @@ -4,8 +4,10 @@ #include "../commons.hpp" #include "Shortcut.hpp" -#define EVENT_TYPE(G) \ - G(EVENT_KEYPRESSED), \ +#define EVENT_TYPE(G) \ + G(EVENT_NEW_PROJECT), G(EVENT_CLOSE_PROJECT), \ + G(EVENT_QUERY_NEW_PROJECT), G(EVENT_QUERY_CLOSE_PROJECT), \ + G(EVENT_KEYPRESSED), \ G(EVENT_QUIT) namespace pt @@ -19,12 +21,19 @@ namespace pt KeyMod keymod {""}; }; + struct EventProject + { + std::string name; + std::filesystem::path dir; + }; + struct Event { explicit Event(EventType ty) : type { ty } {} EventType type; EventKeyPressed key_pressed; + EventProject project; }; } } diff --git a/src/model/Observable.cpp b/src/model/Observable.cpp index 1553cc9..7474f79 100644 --- a/src/model/Observable.cpp +++ b/src/model/Observable.cpp @@ -23,9 +23,15 @@ namespace pt { if (auto ob = observer.lock(); ob) { - ob->update(event); + ob->update(*this, event); } } } + + void Observable::notify(EventType type) + { + Event e { type }; + notify(e); + } } } diff --git a/src/model/Observable.hpp b/src/model/Observable.hpp index 73fe487..c9f8ef1 100644 --- a/src/model/Observable.hpp +++ b/src/model/Observable.hpp @@ -18,6 +18,7 @@ namespace pt void add_observer(std::shared_ptr observer); void notify(Event& event); + void notify(EventType type); private: std::vector> m_observers; diff --git a/src/model/Observer.hpp b/src/model/Observer.hpp index a219eea..900cdd1 100644 --- a/src/model/Observer.hpp +++ b/src/model/Observer.hpp @@ -8,13 +8,15 @@ namespace pt { namespace model { + class Observable; + class Observer { public: explicit Observer(); virtual ~Observer(); - virtual void update(Event& event) = 0; + virtual void update(Observable& source, Event& event) = 0; private: }; } diff --git a/src/model/PixTool.cpp b/src/model/PixTool.cpp index a6fb139..5629ef9 100644 --- a/src/model/PixTool.cpp +++ b/src/model/PixTool.cpp @@ -2,6 +2,8 @@ #include "PixTool.hpp" #include "CommandRunner.hpp" #include "cmds/Quit.hpp" +#include "cmds/Project.hpp" +#include "src/model/Event.hpp" namespace pt { @@ -13,6 +15,12 @@ namespace pt m_cmd_runner->bind(Shortcut {{KeyMod {"X", {PT_CONTROL}}, KeyMod {"C", {PT_CONTROL}}}}, std::make_shared()); + + m_cmd_runner->bind(Shortcut {{KeyMod {"N", {PT_CONTROL}}}}, + std::make_shared()); + + m_cmd_runner->bind(Shortcut {{KeyMod {"W", {PT_CONTROL}}}}, + std::make_shared()); } /*virtual*/ PixTool::~PixTool() @@ -34,5 +42,29 @@ namespace pt spdlog::info("done"); exit(0); } + + void PixTool::new_project(std::string const& name, + std::filesystem::path const& dir) + { + m_project = std::make_unique(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 + "'"); + } + } } } diff --git a/src/model/PixTool.hpp b/src/model/PixTool.hpp index 9e7c98c..e15a3e7 100644 --- a/src/model/PixTool.hpp +++ b/src/model/PixTool.hpp @@ -3,6 +3,8 @@ #include "../commons.hpp" #include "Shortcut.hpp" +#include "Project.hpp" +#include "Observable.hpp" namespace pt { @@ -10,7 +12,7 @@ namespace pt { class CommandRunner; - class PixTool + class PixTool: public Observable { public: explicit PixTool(); @@ -20,8 +22,15 @@ namespace pt void update(KeyMod const& km); 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: std::unique_ptr m_cmd_runner; + std::unique_ptr m_project; }; } } diff --git a/src/model/Project.cpp b/src/model/Project.cpp new file mode 100644 index 0000000..5430d32 --- /dev/null +++ b/src/model/Project.cpp @@ -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() + { + } + } +} diff --git a/src/model/Project.hpp b/src/model/Project.hpp new file mode 100644 index 0000000..5d09c92 --- /dev/null +++ b/src/model/Project.hpp @@ -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 diff --git a/src/model/cmds/Project.hpp b/src/model/cmds/Project.hpp new file mode 100644 index 0000000..a2955f9 --- /dev/null +++ b/src/model/cmds/Project.hpp @@ -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