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:
parent
29026ce965
commit
bd86beb7e4
7 changed files with 224 additions and 74 deletions
|
@ -11,6 +11,7 @@ compile_gml(SearchPanel.gml SearchPanelGML.cpp)
|
|||
set(SOURCES
|
||||
main.cpp
|
||||
FavoritesEditDialogGML.cpp
|
||||
FavoritesModel.cpp
|
||||
FavoritesPanelGML.cpp
|
||||
FavoritesPanel.cpp
|
||||
MapWidget.cpp
|
||||
|
|
121
Userland/Applications/Maps/FavoritesModel.cpp
Normal file
121
Userland/Applications/Maps/FavoritesModel.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
53
Userland/Applications/Maps/FavoritesModel.h
Normal file
53
Userland/Applications/Maps/FavoritesModel.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ public:
|
|||
double latitude;
|
||||
double longitude;
|
||||
|
||||
bool operator==(LatLng const& other) const = default;
|
||||
|
||||
double distance_to(LatLng const& other) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue