BookmarksBarWidget.cpp 11 KB

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