Serenity Operating System
at master 342 lines 11 kB view raw
1/* 2 * Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com> 3 * Copyright (c) 2022, networkException <networkexception@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <Applications/Browser/BookmarksBarWidget.h> 9#include <Applications/Browser/Browser.h> 10#include <Applications/Browser/EditBookmarkGML.h> 11#include <LibGUI/Action.h> 12#include <LibGUI/BoxLayout.h> 13#include <LibGUI/Button.h> 14#include <LibGUI/Dialog.h> 15#include <LibGUI/Event.h> 16#include <LibGUI/JsonArrayModel.h> 17#include <LibGUI/Menu.h> 18#include <LibGUI/Model.h> 19#include <LibGUI/TextBox.h> 20#include <LibGUI/Widget.h> 21#include <LibGUI/Window.h> 22#include <LibGfx/Palette.h> 23 24namespace Browser { 25 26namespace { 27 28class BookmarkEditor final : public GUI::Dialog { 29 C_OBJECT(BookmarkEditor) 30 31public: 32 static Vector<JsonValue> 33 edit_bookmark(Window* parent_window, StringView title, StringView url) 34 { 35 auto editor = BookmarkEditor::construct(parent_window, title, url); 36 editor->set_title("Edit Bookmark"); 37 editor->set_icon(g_icon_bag.bookmark_filled); 38 39 if (editor->exec() == ExecResult::OK) { 40 return Vector<JsonValue> { editor->title(), editor->url() }; 41 } 42 43 return {}; 44 } 45 46private: 47 BookmarkEditor(Window* parent_window, StringView title, StringView url) 48 : Dialog(parent_window) 49 { 50 auto widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors(); 51 widget->load_from_gml(edit_bookmark_gml).release_value_but_fixme_should_propagate_errors(); 52 53 set_resizable(false); 54 resize(260, 85); 55 56 m_title_textbox = *widget->find_descendant_of_type_named<GUI::TextBox>("title_textbox"); 57 m_title_textbox->set_text(title); 58 m_title_textbox->set_focus(true); 59 m_title_textbox->select_all(); 60 61 auto& ok_button = *widget->find_descendant_of_type_named<GUI::Button>("ok_button"); 62 ok_button.on_click = [this](auto) { 63 done(ExecResult::OK); 64 }; 65 ok_button.set_default(true); 66 67 m_url_textbox = *widget->find_descendant_of_type_named<GUI::TextBox>("url_textbox"); 68 m_url_textbox->set_text(url); 69 m_url_textbox->on_change = [this, &ok_button]() { 70 auto has_url = !m_url_textbox->text().is_empty(); 71 ok_button.set_enabled(has_url); 72 }; 73 74 auto& cancel_button = *widget->find_descendant_of_type_named<GUI::Button>("cancel_button"); 75 cancel_button.on_click = [this](auto) { 76 done(ExecResult::Cancel); 77 }; 78 } 79 80 DeprecatedString title() const 81 { 82 return m_title_textbox->text(); 83 } 84 85 DeprecatedString url() const 86 { 87 return m_url_textbox->text(); 88 } 89 90 RefPtr<GUI::TextBox> m_title_textbox; 91 RefPtr<GUI::TextBox> m_url_textbox; 92}; 93 94} 95 96static BookmarksBarWidget* s_the; 97 98BookmarksBarWidget& BookmarksBarWidget::the() 99{ 100 return *s_the; 101} 102 103BookmarksBarWidget::BookmarksBarWidget(DeprecatedString const& bookmarks_file, bool enabled) 104{ 105 s_the = this; 106 set_layout<GUI::HorizontalBoxLayout>(2, 0); 107 108 set_fixed_height(20); 109 110 if (!enabled) 111 set_visible(false); 112 113 m_additional = GUI::Button::construct(); 114 m_additional->set_tooltip("Show hidden bookmarks"); 115 m_additional->set_menu(m_additional_menu); 116 auto bitmap_or_error = Gfx::Bitmap::load_from_file("/res/icons/16x16/overflow-menu.png"sv); 117 if (!bitmap_or_error.is_error()) 118 m_additional->set_icon(bitmap_or_error.release_value()); 119 m_additional->set_button_style(Gfx::ButtonStyle::Coolbar); 120 m_additional->set_fixed_size(22, 20); 121 m_additional->set_focus_policy(GUI::FocusPolicy::TabFocus); 122 123 m_separator = GUI::Widget::construct(); 124 125 m_context_menu = GUI::Menu::construct(); 126 auto default_action = GUI::Action::create( 127 "&Open", g_icon_bag.go_to, [this](auto&) { 128 if (on_bookmark_click) 129 on_bookmark_click(m_context_menu_url, Open::InSameTab); 130 }, 131 this); 132 m_context_menu_default_action = default_action; 133 m_context_menu->add_action(default_action); 134 m_context_menu->add_action(GUI::Action::create( 135 "Open in New &Tab", g_icon_bag.new_tab, [this](auto&) { 136 if (on_bookmark_click) 137 on_bookmark_click(m_context_menu_url, Open::InNewTab); 138 }, 139 this)); 140 m_context_menu->add_action(GUI::Action::create( 141 "Open in New Window", g_icon_bag.new_window, [this](auto&) { 142 if (on_bookmark_click) { 143 on_bookmark_click(m_context_menu_url, Open::InNewWindow); 144 } 145 }, 146 this)); 147 m_context_menu->add_separator(); 148 m_context_menu->add_action(GUI::Action::create( 149 "&Edit...", g_icon_bag.rename, [this](auto&) { 150 edit_bookmark(m_context_menu_url); 151 }, 152 this)); 153 m_context_menu->add_action(GUI::CommonActions::make_delete_action( 154 [this](auto&) { 155 remove_bookmark(m_context_menu_url); 156 }, 157 this)); 158 159 Vector<GUI::JsonArrayModel::FieldSpec> fields; 160 fields.empend("title", "Title", Gfx::TextAlignment::CenterLeft); 161 fields.empend("url", "Url", Gfx::TextAlignment::CenterRight); 162 set_model(GUI::JsonArrayModel::create(bookmarks_file, move(fields))); 163 model()->invalidate(); 164} 165 166BookmarksBarWidget::~BookmarksBarWidget() 167{ 168 if (m_model) 169 m_model->unregister_client(*this); 170} 171 172void BookmarksBarWidget::set_model(RefPtr<GUI::Model> model) 173{ 174 if (model == m_model) 175 return; 176 if (m_model) 177 m_model->unregister_client(*this); 178 m_model = move(model); 179 m_model->register_client(*this); 180} 181 182void BookmarksBarWidget::resize_event(GUI::ResizeEvent& event) 183{ 184 Widget::resize_event(event); 185 update_content_size(); 186} 187 188void BookmarksBarWidget::model_did_update(unsigned) 189{ 190 remove_all_children(); 191 192 m_bookmarks.clear(); 193 194 int width = 0; 195 for (int item_index = 0; item_index < model()->row_count(); ++item_index) { 196 197 auto title = model()->index(item_index, 0).data().to_deprecated_string(); 198 auto url = model()->index(item_index, 1).data().to_deprecated_string(); 199 200 Gfx::IntRect rect { width, 0, static_cast<int>(ceilf(font().width(title))) + 32, height() }; 201 202 auto& button = add<GUI::Button>(); 203 m_bookmarks.append(button); 204 205 button.set_button_style(Gfx::ButtonStyle::Coolbar); 206 button.set_text(String::from_deprecated_string(title).release_value_but_fixme_should_propagate_errors()); 207 button.set_icon(g_icon_bag.filetype_html); 208 button.set_fixed_size(font().width(title) + 32, 20); 209 button.set_relative_rect(rect); 210 button.set_focus_policy(GUI::FocusPolicy::TabFocus); 211 button.set_tooltip(url); 212 button.set_allowed_mouse_buttons_for_pressing(GUI::MouseButton::Primary | GUI::MouseButton::Middle); 213 214 button.on_click = [title, url, this](auto) { 215 if (on_bookmark_click) 216 on_bookmark_click(url, Open::InSameTab); 217 }; 218 219 button.on_middle_mouse_click = [title, url, this](auto) { 220 if (on_bookmark_click) 221 on_bookmark_click(url, Open::InNewTab); 222 }; 223 224 button.on_context_menu_request = [this, url](auto& context_menu_event) { 225 m_context_menu_url = url; 226 m_context_menu->popup(context_menu_event.screen_position(), m_context_menu_default_action); 227 }; 228 229 width += rect.width(); 230 } 231 232 add_child(*m_separator); 233 add_child(*m_additional); 234 235 update_content_size(); 236 update(); 237} 238 239void BookmarksBarWidget::update_content_size() 240{ 241 int x_position = 0; 242 m_last_visible_index = -1; 243 244 for (size_t i = 0; i < m_bookmarks.size(); ++i) { 245 auto& bookmark = m_bookmarks.at(i); 246 if (x_position + bookmark->width() + m_additional->width() > width()) { 247 m_last_visible_index = i; 248 break; 249 } 250 bookmark->set_x(x_position); 251 bookmark->set_visible(true); 252 x_position += bookmark->width(); 253 } 254 255 if (m_last_visible_index < 0) { 256 m_additional->set_visible(false); 257 } else { 258 // hide all items > m_last_visible_index and create new bookmarks menu for them 259 m_additional->set_visible(true); 260 m_additional_menu = GUI::Menu::construct("Additional Bookmarks"); 261 m_additional->set_menu(m_additional_menu); 262 for (size_t i = m_last_visible_index; i < m_bookmarks.size(); ++i) { 263 auto& bookmark = m_bookmarks.at(i); 264 bookmark->set_visible(false); 265 m_additional_menu->add_action(GUI::Action::create(bookmark->text().to_deprecated_string(), g_icon_bag.filetype_html, [&](auto&) { bookmark->on_click(0); })); 266 } 267 } 268} 269 270bool BookmarksBarWidget::contains_bookmark(DeprecatedString const& url) 271{ 272 for (int item_index = 0; item_index < model()->row_count(); ++item_index) { 273 274 auto item_title = model()->index(item_index, 0).data().to_deprecated_string(); 275 auto item_url = model()->index(item_index, 1).data().to_deprecated_string(); 276 if (item_url == url) { 277 return true; 278 } 279 } 280 return false; 281} 282 283bool BookmarksBarWidget::remove_bookmark(DeprecatedString const& url) 284{ 285 for (int item_index = 0; item_index < model()->row_count(); ++item_index) { 286 287 auto item_title = model()->index(item_index, 0).data().to_deprecated_string(); 288 auto item_url = model()->index(item_index, 1).data().to_deprecated_string(); 289 if (item_url == url) { 290 auto& json_model = *static_cast<GUI::JsonArrayModel*>(model()); 291 292 auto const item_removed = json_model.remove(item_index); 293 if (item_removed) 294 json_model.store(); 295 296 return item_removed; 297 } 298 } 299 300 return false; 301} 302 303bool BookmarksBarWidget::add_bookmark(DeprecatedString const& url, DeprecatedString const& title) 304{ 305 Vector<JsonValue> values; 306 values.append(title); 307 values.append(url); 308 309 auto& json_model = *static_cast<GUI::JsonArrayModel*>(model()); 310 if (json_model.add(move(values))) { 311 json_model.store(); 312 return true; 313 } 314 return false; 315} 316 317bool BookmarksBarWidget::edit_bookmark(DeprecatedString const& url) 318{ 319 for (int item_index = 0; item_index < model()->row_count(); ++item_index) { 320 auto item_title = model()->index(item_index, 0).data().to_deprecated_string(); 321 auto item_url = model()->index(item_index, 1).data().to_deprecated_string(); 322 323 if (item_url == url) { 324 auto values = BookmarkEditor::edit_bookmark(window(), item_title, item_url); 325 bool item_replaced = false; 326 327 if (!values.is_empty()) { 328 auto& json_model = *static_cast<GUI::JsonArrayModel*>(model()); 329 item_replaced = json_model.set(item_index, move(values)); 330 331 if (item_replaced) 332 json_model.store(); 333 } 334 335 return item_replaced; 336 } 337 } 338 339 return false; 340} 341 342}