Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/debug/bin/cpp_lab_project", // path to the executable
// "program": "${workspaceFolder}/build/debug/bin/cpp_lab_project", // path to the executable
"program": "${workspaceFolder}/build/debug/bin/mvc_ap", // path to the executable
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}", // working directory
Expand Down
10 changes: 10 additions & 0 deletions cmake/Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,13 @@ FetchContent_Declare(

# Make GoogleTest available
FetchContent_MakeAvailable(googletest)


# ----------------------------------------------------------------------------------------
# Dependencies - GTK4
# ----------------------------------------------------------------------------------------
# Use the package PkgConfig to detect GTK+ headers/library files
find_package(PkgConfig REQUIRED)

# Check for gtkmm-4.0 specifically, not just gtk4
pkg_check_modules(GTKMM REQUIRED gtkmm-4.0)
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_subdirectory(controller)
add_subdirectory(dp)
add_subdirectory(socket)
add_subdirectory(excercise)
add_subdirectory(ap)

# main application executable does NOT link to this library.
add_subdirectory(leetcode)
Expand Down
21 changes: 21 additions & 0 deletions src/ap/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Create the gtk4-setting interface library
# Link against the libraries found for GTKMM
add_library(gtk4-settings INTERFACE)
target_link_libraries(gtk4-settings INTERFACE ${GTKMM_LIBRARIES})

# Include the directories where gtkmm.h resides
target_include_directories(gtk4-settings INTERFACE ${GTKMM_INCLUDE_DIRS})

# Add compiler flags (defines, etc.) required by gtkmm
target_compile_options(gtk4-settings INTERFACE ${GTKMM_CFLAGS_OTHER})

# ap executable
add_executable(ap)
target_sources(ap
PRIVATE
simple_ap.cpp
)
target_link_libraries(ap PRIVATE gtk4-settings)

add_subdirectory(mvc)
# add_subdirectory(mvvm)
16 changes: 14 additions & 2 deletions src/ap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ View ↔ ViewModel ↔ Model # view automatically update
- **MVC (Model - View - Controller)** is an architectural pattern that separates the user interface (`View`) from the application logic and data (`Model`) using an intermediary component called the `Controller`.
- **Components:**
- **Model**: is responsible for managing and abstracting data sources (databases, APIs, etc.). The `Model` handles data retrieval, storage, and business logic.
- **View**: displays the data provided by the `Model` and represents the user interface. The `View` is responsible only for presentation and does not contain business logic.
- **View**: displays the data provided by the `Model` and represents the user interface. The `View` is responsible only for presentation and `does not contain business logic`.
- **Controller**: acts as an intermediary between the `View` and the `Model`. It receives user input from the `View`, processes it, and interacts with the `Model` to update or retrieve data. The `Controller` then determines which `View` should display the result.

- **Workflow:**
Expand All @@ -41,4 +41,16 @@ View → Controller → Model
View # view update manually
```
### 3. Examples
### 3. GTK4
- [Refer](https://docs.gtk.org/gtk4/getting_started.html)

### 4. Examples
### 4.1. simple_ap
- Cos:
- Quick, Simple
- Pos:
- Dependency: e.g. what happen when we delete Gtk::Label m_labelMonitorA;
- Scalability:
- Reusability:

### 4.2. mvc_ap
12 changes: 12 additions & 0 deletions src/ap/mvc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
add_executable(mvc_ap)

target_sources(mvc_ap
PRIVATE
mvc_ap.cpp
model/SharedData.cpp
view/EditorWidget.cpp
view/DisplayWidget.cpp
controller/Controller.cpp
)

target_link_libraries(mvc_ap PRIVATE gtk4-settings)
7 changes: 7 additions & 0 deletions src/ap/mvc/IObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

class IObserver {
public:
virtual ~IObserver() = default;
virtual void onDataChanged(const std::string& newData) = 0;
};
12 changes: 12 additions & 0 deletions src/ap/mvc/controller/Controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include "Controller.h"

Controller::Controller(std::shared_ptr<SharedData> model) : model_(model) {}

void Controller::updateRequest(const std::string& text) {
if (text.empty())
return;

// the Controller updates the Model when receiving new data from the View,
// which then notifies all Views of the change
model_->setData(text);
}
13 changes: 13 additions & 0 deletions src/ap/mvc/controller/Controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once
#include <memory>
#include "../model/SharedData.h"

class Controller {
public:
explicit Controller(std::shared_ptr<SharedData> model);

void updateRequest(const std::string& text);

private:
std::shared_ptr<SharedData> model_;
};
23 changes: 23 additions & 0 deletions src/ap/mvc/model/SharedData.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "SharedData.h"

SharedData::SharedData() : data_{"Initial Data"} {}

void SharedData::setData(const std::string& data) {
this->data_ = data;
// Notify observers of the changed data
notifyObservers();
}

void SharedData::notifyObservers() {
for (auto o : observers_) {
o->onDataChanged(this->data_);
}
}
void SharedData::addObserver(IObserver* obs) {
if (obs != nullptr)
observers_.push_back(obs);
}

std::string SharedData::getData() const {
return data_;
}
21 changes: 21 additions & 0 deletions src/ap/mvc/model/SharedData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <vector>
#include "../IObserver.h"

class SharedData {
public:
SharedData();

void setData(const std::string& data);
std::string getData() const;

void addObserver(IObserver* obs);

private:
void notifyObservers();

private:
std::string data_;
std::vector<IObserver*> observers_;
};
74 changes: 74 additions & 0 deletions src/ap/mvc/mvc_ap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <gtkmm.h>
#include <memory>
#include "controller/Controller.h"
#include "model/SharedData.h"
#include "view/DisplayWidget.h"
#include "view/EditorWidget.h"

class ContainerWindow : public Gtk::Window {
public:
ContainerWindow();

private:
// Main Layout
Gtk::Box mainLayout_;
Gtk::Box topRowLayout_; // Horizontal arrangement (2 Displays side-by-side)

// Model & Controller are shared across all Views,
// so we should use as shared pointers
// Model
std::shared_ptr<SharedData> dataModel_;
// Controllers
std::shared_ptr<Controller> controller_;

// Views
std::unique_ptr<EditorWidget> editorView_;
std::unique_ptr<DisplayWidget> displayViewLeft_;
std::unique_ptr<DisplayWidget> displayViewRight_;
};

ContainerWindow::ContainerWindow()
: mainLayout_(Gtk::Orientation::VERTICAL),
topRowLayout_(Gtk::Orientation::HORIZONTAL) {
set_title("MVC Integrated Demo");
set_default_size(600, 400);

// Init MVC
dataModel_ = std::make_shared<SharedData>();
controller_ = std::make_shared<Controller>(dataModel_);

// Create child Views (Widgets)
editorView_ =
std::make_unique<EditorWidget>(controller_, dataModel_->getData());

// Create two displays of different colors for easier viewing
displayViewLeft_ = std::make_unique<DisplayWidget>(
"ZONE 2: MONITOR A (Blue)", "blue", dataModel_->getData());
displayViewRight_ = std::make_unique<DisplayWidget>(
"ZONE 3: MONITOR B (Red)", "red", dataModel_->getData());

dataModel_->addObserver(editorView_.get());
dataModel_->addObserver(displayViewLeft_.get());
dataModel_->addObserver(displayViewRight_.get());

// Layout Arrangement (Container)
// Top Row: 2 Displays side-by-side (evenly spaced)
displayViewLeft_->set_hexpand(true);
displayViewRight_->set_hexpand(true);
topRowLayout_.append(*displayViewLeft_);
topRowLayout_.append(*displayViewRight_);

// Bottom Row: Editor
editorView_->set_vexpand(
false); // Editor doesn't need to be stretched too large

// Combine into Main Layout
mainLayout_.append(topRowLayout_); // Add top row
mainLayout_.append(*editorView_); // Add bottom row
set_child(mainLayout_);
}

int main(int argc, char* argv[]) {
auto app = Gtk::Application::create("org.gtkmm.example.singlemvc");
return app->make_window_and_run<ContainerWindow>(argc, argv);
}
29 changes: 29 additions & 0 deletions src/ap/mvc/view/DisplayWidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "DisplayWidget.h"

DisplayWidget::DisplayWidget(const std::string& title, const std::string& color,
const std::string& startData)
: Gtk::Box(Gtk::Orientation::VERTICAL),
color_(color),
innerBox_(Gtk::Orientation::VERTICAL) {
frame_.set_label(title);
frame_.set_margin(10);

updateLabel(startData);

innerBox_.append(labelData_);
innerBox_.set_margin(20);

frame_.set_child(innerBox_);
this->append(frame_);
}

void DisplayWidget::updateLabel(const std::string& text) {
// Use HTML markup to change text color
std::string markup = "<span foreground='" + color_ +
"' size='x-large' weight='bold'>" + text + "</span>";
labelData_.set_markup(markup);
}

void DisplayWidget::onDataChanged(const std::string& newData) {
updateLabel(newData);
}
19 changes: 19 additions & 0 deletions src/ap/mvc/view/DisplayWidget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <gtkmm.h>
#include "../IObserver.h"

class DisplayWidget : public Gtk::Box, public IObserver {
public:
DisplayWidget(const std::string& title, const std::string& color,
const std::string& startData);

void onDataChanged(const std::string& newData) override;

private:
void updateLabel(const std::string& text);

private:
std::string color_;
Gtk::Frame frame_;
Gtk::Box innerBox_;
Gtk::Label labelData_;
};
36 changes: 36 additions & 0 deletions src/ap/mvc/view/EditorWidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "EditorWidget.h"

EditorWidget::EditorWidget(std::shared_ptr<Controller> c,
const std::string& initData)
: Gtk::Box(Gtk::Orientation::VERTICAL),
innerBox_(Gtk::Orientation::VERTICAL),
controller_(c) {
// Create a beautiful border
frame_.set_label("ZONE 1: EDITOR (Input View)");
frame_.set_margin(10);

labelTitle_.set_text("Enter new data:");
entry_.set_text(initData);
button_.set_label("Broadcast Update");

// Layout inside the frame
innerBox_.append(labelTitle_);
innerBox_.append(entry_);
innerBox_.append(button_);
innerBox_.set_margin(15);
innerBox_.set_spacing(10);

frame_.set_child(innerBox_);
this->append(frame_); // Add a frame to the main Box of this class

// MVC logic to help the Controller receive update input from the View
button_.signal_clicked().connect(
[this]() { controller_->updateRequest(entry_.get_text()); });
}

void EditorWidget::onDataChanged(const std::string& newData) {
// Fix string comparison error or do not thing ?
if (entry_.get_text() != Glib::ustring(newData)) {
entry_.set_text(newData);
}
}
20 changes: 20 additions & 0 deletions src/ap/mvc/view/EditorWidget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include <gtkmm.h>
#include "../controller/Controller.h"
#include "../IObserver.h"

class EditorWidget : public Gtk::Box, public IObserver {
public:
EditorWidget(std::shared_ptr<Controller> c, const std::string& initData);

void onDataChanged(const std::string& newData) override;

private:
Gtk::Frame frame_;
Gtk::Box innerBox_;
Gtk::Label labelTitle_;
Gtk::Entry entry_;
Gtk::Button button_;

std::shared_ptr<Controller> controller_;
};
Loading
Loading