/* * Copyright (c) 2024, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include "AnnotationsModel.h" #include #include GUI::Variant AnnotationsModel::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& annotation = m_annotations.at(index.row()); if (role == GUI::ModelRole::Display) { switch (index.column()) { case Column::Start: return MUST(String::formatted("{:#08X}", annotation.start_offset)); case Column::End: return MUST(String::formatted("{:#08X}", annotation.end_offset)); case Column::Comments: return annotation.comments; } } switch (to_underlying(role)) { case to_underlying(CustomRole::StartOffset): return annotation.start_offset; case to_underlying(CustomRole::EndOffset): return annotation.end_offset; case to_underlying(CustomRole::Comments): return annotation.comments; } return {}; } void AnnotationsModel::add_annotation(Annotation annotation) { m_annotations.append(move(annotation)); invalidate(); } void AnnotationsModel::delete_annotation(Annotation const& annotation) { m_annotations.remove_first_matching([&](auto& other) { return other == annotation; }); invalidate(); } Optional AnnotationsModel::closest_annotation_at(size_t position) { // FIXME: If we end up with a lot of annotations, we'll need to store them and query them in a smarter way. Optional result; for (auto& annotation : m_annotations) { if (annotation.start_offset <= position && position <= annotation.end_offset) { // If multiple annotations cover this position, use whichever starts latest. This would be the innermost one // if they overlap fully rather than partially. if (!result.has_value() || result->start_offset < annotation.start_offset) result = annotation; } } return result; } Optional AnnotationsModel::get_annotation(GUI::ModelIndex const& index) { if (index.row() < 0 || index.row() >= row_count()) return {}; return m_annotations.at(index.row()); } ErrorOr AnnotationsModel::save_to_file(Core::File& file) const { JsonArray array {}; array.ensure_capacity(m_annotations.size()); for (auto const& annotation : m_annotations) { JsonObject object; object.set("start_offset", annotation.start_offset); object.set("end_offset", annotation.end_offset); object.set("background_color", annotation.background_color.to_byte_string()); object.set("comments", annotation.comments.to_byte_string()); TRY(array.append(object)); } auto json_string = array.to_byte_string(); TRY(file.write_until_depleted(json_string.bytes())); return {}; } ErrorOr AnnotationsModel::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 annotations from file: Not a JSON array."); auto& json_array = json.as_array(); Vector new_annotations; TRY(new_annotations.try_ensure_capacity(json_array.size())); TRY(json_array.try_for_each([&](JsonValue const& json_value) -> ErrorOr { if (!json_value.is_object()) return Error::from_string_literal("Failed to read annotation from file: Annotation not a JSON object."); auto& json_object = json_value.as_object(); Annotation annotation; if (auto start_offset = json_object.get_u64("start_offset"sv); start_offset.has_value()) annotation.start_offset = start_offset.value(); if (auto end_offset = json_object.get_u64("end_offset"sv); end_offset.has_value()) annotation.end_offset = end_offset.value(); if (auto background_color = json_object.get_byte_string("background_color"sv).map([](auto& string) { return Color::from_string(string); }); background_color.has_value()) annotation.background_color = background_color->value(); if (auto comments = json_object.get_byte_string("comments"sv); comments.has_value()) annotation.comments = MUST(String::from_byte_string(comments.value())); new_annotations.append(annotation); return {}; })); m_annotations = move(new_annotations); invalidate(); return {}; }