1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 05:34:58 +00:00

Maps: Add FavoritesModel and remove hacky misusage of JSONArrayModel

This commit is contained in:
Bastiaan van der Plaat 2024-02-28 22:02:01 +01:00 committed by Sam Atkins
parent 29026ce965
commit bd86beb7e4
7 changed files with 224 additions and 74 deletions

View file

@ -11,6 +11,7 @@ compile_gml(SearchPanel.gml SearchPanelGML.cpp)
set(SOURCES
main.cpp
FavoritesEditDialogGML.cpp
FavoritesModel.cpp
FavoritesPanelGML.cpp
FavoritesPanel.cpp
MapWidget.cpp

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2024, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "FavoritesModel.h"
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
namespace Maps {
GUI::Variant FavoritesModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const
{
if (index.row() < 0 || index.row() >= row_count())
return {};
if (role == GUI::ModelRole::TextAlignment)
return Gfx::TextAlignment::CenterLeft;
auto const& favorite = m_favorites.at(index.row());
if (role == GUI::ModelRole::Display)
return ByteString::formatted("{}\n{:.5}, {:.5}", favorite.name, favorite.latlng.latitude, favorite.latlng.longitude);
return {};
}
Optional<Favorite&> FavoritesModel::get_favorite(GUI::ModelIndex const& index)
{
if (index.row() < 0 || index.row() >= row_count())
return {};
return m_favorites.at(index.row());
}
void FavoritesModel::add_favorite(Favorite favorite)
{
m_favorites.append(move(favorite));
invalidate();
}
void FavoritesModel::update_favorite(GUI::ModelIndex const& index, Favorite favorite)
{
if (index.row() < 0 || index.row() >= row_count())
return;
m_favorites[index.row()] = move(favorite);
invalidate();
}
void FavoritesModel::delete_favorite(Favorite const& favorite)
{
m_favorites.remove_first_matching([&](auto& other) {
return other == favorite;
});
invalidate();
}
ErrorOr<void> FavoritesModel::save_to_file(Core::File& file) const
{
JsonArray array {};
array.ensure_capacity(m_favorites.size());
for (auto const& favorite : m_favorites) {
JsonObject object;
object.set("name", favorite.name.to_byte_string());
object.set("latitude", favorite.latlng.latitude);
object.set("longitude", favorite.latlng.longitude);
object.set("zoom", favorite.zoom);
TRY(array.append(object));
}
auto json_string = array.to_byte_string();
TRY(file.write_until_depleted(json_string.bytes()));
return {};
}
ErrorOr<void> FavoritesModel::load_from_file(Core::File& file)
{
auto json_bytes = TRY(file.read_until_eof());
StringView json_string { json_bytes };
auto json = TRY(JsonValue::from_string(json_string));
if (!json.is_array())
return Error::from_string_literal("Failed to read favorites from file: Not a JSON array.");
auto& json_array = json.as_array();
Vector<Favorite> new_favorites;
TRY(new_favorites.try_ensure_capacity(json_array.size()));
TRY(json_array.try_for_each([&](JsonValue const& json_value) -> ErrorOr<void> {
if (!json_value.is_object())
return {};
auto& json_object = json_value.as_object();
Favorite favorite;
auto name = json_object.get_byte_string("name"sv);
if (!name.has_value())
return {};
favorite.name = MUST(String::from_byte_string(*name));
auto latitude = json_object.get_double_with_precision_loss("latitude"sv);
if (!latitude.has_value())
return {};
auto longitude = json_object.get_double_with_precision_loss("longitude"sv);
if (!longitude.has_value())
return {};
favorite.latlng = { *latitude, *longitude };
auto zoom = json_object.get_i32("zoom"sv);
if (!zoom.has_value())
return {};
favorite.zoom = *zoom;
new_favorites.append(favorite);
return {};
}));
m_favorites = move(new_favorites);
invalidate();
return {};
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2024, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "MapWidget.h"
#include <LibGUI/Model.h>
namespace Maps {
struct Favorite {
String name;
MapWidget::LatLng latlng;
int zoom;
bool operator==(Favorite const& other) const = default;
};
class FavoritesModel final : public GUI::Model {
public:
static NonnullRefPtr<FavoritesModel> create()
{
return adopt_ref(*new FavoritesModel());
}
virtual int row_count(GUI::ModelIndex const& index = GUI::ModelIndex()) const override
{
if (!index.is_valid())
return m_favorites.size();
return 0;
}
virtual int column_count(GUI::ModelIndex const&) const override { return 1; }
virtual GUI::Variant data(GUI::ModelIndex const& index, GUI::ModelRole role = GUI::ModelRole::Display) const override;
Vector<Favorite> const& favorites() const { return m_favorites; }
Optional<Favorite&> get_favorite(GUI::ModelIndex const&);
void add_favorite(Favorite);
void update_favorite(GUI::ModelIndex const&, Favorite);
void delete_favorite(Favorite const&);
ErrorOr<void> save_to_file(Core::File&) const;
ErrorOr<void> load_from_file(Core::File&);
private:
Vector<Favorite> m_favorites;
};
}

View file

@ -8,6 +8,7 @@
#include "FavoritesEditDialog.h"
#include <LibCore/StandardPaths.h>
#include <LibGUI/Button.h>
#include <LibGUI/Dialog.h>
#include <LibGUI/JsonArrayModel.h>
#include <LibGUI/Menu.h>
#include <LibGUI/TextBox.h>
@ -20,31 +21,27 @@ ErrorOr<void> FavoritesPanel::initialize()
m_empty_container = *find_descendant_of_type_named<GUI::Frame>("empty_container");
m_favorites_list = *find_descendant_of_type_named<GUI::ListView>("favorites_list");
m_model = FavoritesModel::create();
m_favorites_list->set_model(m_model);
m_favorites_list->set_item_height(m_favorites_list->font().preferred_line_height() * 2 + m_favorites_list->vertical_padding());
m_favorites_list->on_selection_change = [this]() {
auto const& index = m_favorites_list->selection().first();
if (!index.is_valid())
return;
auto& model = *m_favorites_list->model();
on_selected_favorite_change({ MUST(String::from_byte_string(model.index(index.row(), 0).data().to_byte_string())),
{ model.index(index.row(), 1).data().as_double(),
model.index(index.row(), 2).data().as_double() },
model.index(index.row(), 3).data().to_i32() });
if (auto favorite = m_model->get_favorite(m_favorites_list->selection().first()); favorite.has_value())
on_selected_favorite_change(*favorite);
};
m_favorites_list->on_context_menu_request = [this](auto const& index, auto const& event) {
m_context_menu = GUI::Menu::construct();
m_context_menu->add_action(GUI::Action::create(
"&Edit...", MUST(Gfx::Bitmap::load_from_file("/res/icons/16x16/rename.png"sv)), [this, index](auto&) {
MUST(edit_favorite(index.row()));
MUST(edit_favorite(index));
},
this));
m_context_menu->add_action(GUI::CommonActions::make_delete_action(
[this, index](auto&) {
auto& model = *static_cast<GUI::JsonArrayModel*>(m_favorites_list->model());
MUST(model.remove(index.row()));
MUST(model.store());
favorites_changed();
if (auto favorite = m_model->get_favorite(index); favorite.has_value()) {
m_model->delete_favorite(*favorite);
favorites_changed();
}
},
this));
m_context_menu->popup(event.screen_position());
@ -54,48 +51,36 @@ ErrorOr<void> FavoritesPanel::initialize()
void FavoritesPanel::load_favorites()
{
Vector<GUI::JsonArrayModel::FieldSpec> favorites_fields;
favorites_fields.empend("name", "Name"_string, Gfx::TextAlignment::CenterLeft, [](JsonObject const& object) -> GUI::Variant {
ByteString name = object.get_byte_string("name"sv).release_value();
double latitude = object.get_double_with_precision_loss("latitude"sv).release_value();
double longitude = object.get_double_with_precision_loss("longitude"sv).release_value();
return ByteString::formatted("{}\n{:.5}, {:.5}", name, latitude, longitude);
});
favorites_fields.empend("latitude", "Latitude"_string, Gfx::TextAlignment::CenterLeft, [](JsonObject const& object) -> GUI::Variant {
return object.get_double_with_precision_loss("latitude"sv).release_value();
});
favorites_fields.empend("longitude", "Longitude"_string, Gfx::TextAlignment::CenterLeft, [](JsonObject const& object) -> GUI::Variant {
return object.get_double_with_precision_loss("longitude"sv).release_value();
});
favorites_fields.empend("zoom", "Zoom"_string, Gfx::TextAlignment::CenterLeft);
m_favorites_list->set_model(*GUI::JsonArrayModel::create(ByteString::formatted("{}/MapsFavorites.json", Core::StandardPaths::config_directory()), move(favorites_fields)));
m_favorites_list->model()->invalidate();
if (auto maybe_file = Core::File::open(MUST(String::formatted("{}/MapsFavorites.json", Core::StandardPaths::config_directory())), Core::File::OpenMode::Read); !maybe_file.is_error()) {
auto file = maybe_file.release_value();
(void)m_model->load_from_file(*file);
}
favorites_changed();
}
ErrorOr<void> FavoritesPanel::add_favorite(Favorite const& favorite)
{
auto& model = *static_cast<GUI::JsonArrayModel*>(m_favorites_list->model());
Vector<JsonValue> favorite_json;
favorite_json.append(favorite.name.to_byte_string());
favorite_json.append(favorite.latlng.latitude);
favorite_json.append(favorite.latlng.longitude);
favorite_json.append(favorite.zoom);
TRY(model.add(move(favorite_json)));
TRY(model.store());
favorites_changed();
return {};
}
void FavoritesPanel::reset()
{
m_favorites_list->selection().clear();
m_favorites_list->scroll_to_top();
}
ErrorOr<void> FavoritesPanel::edit_favorite(int row)
void FavoritesPanel::add_favorite(Favorite favorite)
{
auto& model = *static_cast<GUI::JsonArrayModel*>(m_favorites_list->model());
m_model->add_favorite(move(favorite));
favorites_changed();
}
void FavoritesPanel::delete_favorite(Favorite const& favorite)
{
m_model->delete_favorite(favorite);
favorites_changed();
}
ErrorOr<void> FavoritesPanel::edit_favorite(GUI::ModelIndex const& index)
{
auto favorite = m_model->get_favorite(index);
if (!favorite.has_value())
return {};
auto edit_dialog = TRY(GUI::Dialog::try_create(window()));
edit_dialog->set_title("Edit Favorite");
@ -106,19 +91,14 @@ ErrorOr<void> FavoritesPanel::edit_favorite(int row)
edit_dialog->set_main_widget(widget);
auto& name_textbox = *widget->find_descendant_of_type_named<GUI::TextBox>("name_textbox");
name_textbox.set_text(model.index(row, 0).data().to_byte_string().split('\n').at(0));
name_textbox.set_text(favorite->name);
name_textbox.set_focus(true);
name_textbox.select_all();
auto& ok_button = *widget->find_descendant_of_type_named<GUI::Button>("ok_button");
ok_button.on_click = [&](auto) {
Vector<JsonValue> favorite_json;
favorite_json.append(name_textbox.text());
favorite_json.append(model.index(row, 1).data().as_double());
favorite_json.append(model.index(row, 2).data().as_double());
favorite_json.append(model.index(row, 3).data().to_i32());
MUST(model.set(row, move(favorite_json)));
MUST(model.store());
favorite->name = MUST(String::from_byte_string(name_textbox.text()));
m_model->update_favorite(index, *favorite);
favorites_changed();
edit_dialog->done(GUI::Dialog::ExecResult::OK);
};
@ -135,18 +115,16 @@ ErrorOr<void> FavoritesPanel::edit_favorite(int row)
void FavoritesPanel::favorites_changed()
{
auto& model = *static_cast<GUI::JsonArrayModel*>(m_favorites_list->model());
m_empty_container->set_visible(model.row_count() == 0);
m_favorites_list->set_visible(model.row_count() > 0);
// Update UI
m_empty_container->set_visible(m_model->row_count() == 0);
m_favorites_list->set_visible(m_model->row_count() > 0);
on_favorites_change(m_model->favorites());
Vector<Favorite> favorites;
for (int index = 0; index < model.row_count(); index++) {
favorites.append({ MUST(String::from_byte_string(model.index(index, 0).data().to_byte_string())),
{ model.index(index, 1).data().as_double(),
model.index(index, 2).data().as_double() },
model.index(index, 3).data().to_i32() });
// Save favorites
if (auto maybe_file = Core::File::open(MUST(String::formatted("{}/MapsFavorites.json", Core::StandardPaths::config_directory())), Core::File::OpenMode::Write); !maybe_file.is_error()) {
auto file = maybe_file.release_value();
MUST(m_model->save_to_file(*file));
}
on_favorites_change(favorites);
}
}

View file

@ -6,9 +6,7 @@
#pragma once
#include "MapWidget.h"
#include <LibGUI/Dialog.h>
#include <LibGUI/ItemListModel.h>
#include "FavoritesModel.h"
#include <LibGUI/ListView.h>
namespace Maps {
@ -17,17 +15,13 @@ class FavoritesPanel final : public GUI::Widget {
C_OBJECT(FavoritesPanel)
public:
struct Favorite {
String name;
MapWidget::LatLng latlng;
int zoom;
};
static ErrorOr<NonnullRefPtr<FavoritesPanel>> try_create();
ErrorOr<void> initialize();
void load_favorites();
void reset();
ErrorOr<void> add_favorite(Favorite const& favorite);
void add_favorite(Favorite favorite);
void delete_favorite(Favorite const& favorite);
Function<void(Vector<Favorite> const&)> on_favorites_change;
Function<void(Favorite const&)> on_selected_favorite_change;
@ -36,11 +30,12 @@ protected:
FavoritesPanel() = default;
private:
ErrorOr<void> edit_favorite(int row);
ErrorOr<void> edit_favorite(GUI::ModelIndex const& index);
void favorites_changed();
RefPtr<GUI::Frame> m_empty_container;
RefPtr<GUI::ListView> m_favorites_list;
RefPtr<FavoritesModel> m_model;
RefPtr<GUI::Menu> m_context_menu;
};

View file

@ -27,6 +27,8 @@ public:
double latitude;
double longitude;
bool operator==(LatLng const& other) const = default;
double distance_to(LatLng const& other) const;
};

View file

@ -123,7 +123,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
auto favorites_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hearts.png"sv));
map_widget.add_context_menu_action(GUI::Action::create(
"Add to &Favorites", favorites_icon, [favorites_panel, &map_widget](auto&) {
MUST(favorites_panel->add_favorite({ "Unnamed place"_string, map_widget.context_menu_latlng(), map_widget.zoom() }));
favorites_panel->add_favorite({ "Unnamed place"_string, map_widget.context_menu_latlng(), map_widget.zoom() });
},
window));