diff --git a/Userland/Applications/PDFViewer/CMakeLists.txt b/Userland/Applications/PDFViewer/CMakeLists.txt index 13eb8b9330..44bed3e5b2 100644 --- a/Userland/Applications/PDFViewer/CMakeLists.txt +++ b/Userland/Applications/PDFViewer/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES + NumericInput.cpp OutlineModel.cpp PDFViewer.cpp PDFViewerWidget.cpp diff --git a/Userland/Applications/PDFViewer/NumericInput.cpp b/Userland/Applications/PDFViewer/NumericInput.cpp new file mode 100644 index 0000000000..a11e38795f --- /dev/null +++ b/Userland/Applications/PDFViewer/NumericInput.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "NumericInput.h" +#include "ctype.h" + +NumericInput::NumericInput() +{ + set_text("0"); + + on_change = [&] { + auto number_opt = text().to_int(); + if (number_opt.has_value()) { + set_current_number(number_opt.value(), false); + return; + } + + StringBuilder builder; + bool first = true; + for (auto& ch : text()) { + if (isdigit(ch) || (first && ((ch == '-' && m_min_number < 0) || ch == '+'))) + builder.append(ch); + first = false; + } + + auto new_number_opt = builder.to_string().to_int(); + if (!new_number_opt.has_value()) { + m_needs_text_reset = true; + set_text(builder.to_string()); + return; + } else { + m_needs_text_reset = false; + } + + set_text(builder.to_string()); + set_current_number(new_number_opt.value(), false); + }; + + on_up_pressed = [&] { + if (m_current_number < m_max_number) + set_current_number(m_current_number + 1); + }; + + on_down_pressed = [&] { + if (m_current_number > m_min_number) + set_current_number(m_current_number - 1); + }; + + on_focusout = [&] { on_focus_lost(); }; + on_return_pressed = [&] { on_focus_lost(); }; + on_escape_pressed = [&] { on_focus_lost(); }; +} + +void NumericInput::set_min_number(i32 number) +{ + m_min_number = number; + if (m_current_number < number) + set_current_number(number); +} + +void NumericInput::set_max_number(i32 number) +{ + m_max_number = number; + if (m_current_number > number) + set_current_number(number); +} + +void NumericInput::on_focus_lost() +{ + if (m_needs_text_reset) { + set_text(String::number(m_current_number)); + m_needs_text_reset = false; + } + if (on_number_changed) + on_number_changed(m_current_number); +} + +void NumericInput::set_current_number(i32 number, bool call_change_handler) +{ + if (number == m_current_number) + return; + + m_current_number = clamp(number, m_min_number, m_max_number); + set_text(String::number(m_current_number)); + if (on_number_changed && call_change_handler) + on_number_changed(m_current_number); +} diff --git a/Userland/Applications/PDFViewer/NumericInput.h b/Userland/Applications/PDFViewer/NumericInput.h new file mode 100644 index 0000000000..8f256bd62f --- /dev/null +++ b/Userland/Applications/PDFViewer/NumericInput.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +class NumericInput final : public GUI::TextBox { + C_OBJECT(NumericInput) +public: + NumericInput(); + virtual ~NumericInput() override = default; + + Function on_number_changed; + + void set_min_number(i32 number); + void set_max_number(i32 number); + void set_current_number(i32 number, bool call_change_handler = true); + +private: + void on_focus_lost(); + + bool m_needs_text_reset { false }; + i32 m_current_number { 0 }; + i32 m_min_number { NumericLimits::min() }; + i32 m_max_number { NumericLimits::max() }; +}; diff --git a/Userland/Applications/PDFViewer/PDFViewer.h b/Userland/Applications/PDFViewer/PDFViewer.h index 66ea4eb73c..1dbad98028 100644 --- a/Userland/Applications/PDFViewer/PDFViewer.h +++ b/Userland/Applications/PDFViewer/PDFViewer.h @@ -41,6 +41,10 @@ class PDFViewer : public GUI::AbstractScrollableWidget { public: virtual ~PDFViewer() override = default; + ALWAYS_INLINE u32 current_page() const { return m_current_page_index; } + ALWAYS_INLINE void set_current_page(u32 current_page) { m_current_page_index = current_page; } + + ALWAYS_INLINE const RefPtr& document() const { return m_document; } void set_document(RefPtr); protected: diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.cpp b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp index 9e0fbad983..16ce92e071 100644 --- a/Userland/Applications/PDFViewer/PDFViewerWidget.cpp +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp @@ -9,15 +9,20 @@ #include #include #include +#include #include #include #include +#include +#include PDFViewerWidget::PDFViewerWidget() { set_fill_with_background_color(true); set_layout(); + create_toolbar(); + auto& splitter = add(); m_sidebar = splitter.add(); @@ -37,21 +42,53 @@ void PDFViewerWidget::initialize_menubar(GUI::Menubar& menubar) file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) { GUI::Application::the()->quit(); })); +} - auto& view_menu = menubar.add_menu("&View"); +void PDFViewerWidget::create_toolbar() +{ + auto& toolbar_container = add(); + auto& toolbar = toolbar_container.add(); - auto open_sidebar_action = GUI::Action::create( + auto open_outline_action = GUI::Action::create( "Open &Sidebar", { Mod_Ctrl, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/sidebar.png"), [&](auto& action) { m_sidebar_open = !m_sidebar_open; m_sidebar->set_fixed_width(m_sidebar_open ? 0 : 200); action.set_text(m_sidebar_open ? "Open &Sidebar" : "Close &Sidebar"); }, nullptr); - open_sidebar_action->set_enabled(false); + open_outline_action->set_enabled(false); + m_toggle_sidebar_action = open_outline_action; - view_menu.add_action(open_sidebar_action); + toolbar.add_action(*open_outline_action); + toolbar.add_separator(); - m_open_outline_action = open_sidebar_action; + m_go_to_prev_page_action = GUI::Action::create("Go to &Previous Page", Gfx::Bitmap::load_from_file("/res/icons/16x16/go-up.png"), [&](auto&) { + if (m_viewer->current_page() > 0) + m_page_text_box->set_current_number(m_viewer->current_page()); + }); + m_go_to_prev_page_action->set_enabled(false); + + m_go_to_next_page_action = GUI::Action::create("Go to &Next Page", Gfx::Bitmap::load_from_file("/res/icons/16x16/go-down.png"), [&](auto&) { + if (m_viewer->current_page() < m_viewer->document()->get_page_count() - 1) + m_page_text_box->set_current_number(m_viewer->current_page() + 2); + }); + m_go_to_next_page_action->set_enabled(false); + + toolbar.add_action(*m_go_to_prev_page_action); + toolbar.add_action(*m_go_to_next_page_action); + + m_page_text_box = toolbar.add(); + m_page_text_box->set_enabled(false); + m_page_text_box->set_fixed_width(30); + m_page_text_box->set_min_number(1); + + m_page_text_box->on_number_changed = [&](i32 number) { + VERIFY(number >= 1 && static_cast(number) <= m_viewer->document()->get_page_count()); + m_viewer->set_current_page(static_cast(number) - 1); + m_viewer->update(); + }; + + m_total_page_label = toolbar.add(); } void PDFViewerWidget::open_file(const String& path) @@ -62,17 +99,24 @@ void PDFViewerWidget::open_file(const String& path) m_buffer = file_result.value()->read_all(); auto document = adopt_ref(*new PDF::Document(m_buffer)); m_viewer->set_document(document); + m_total_page_label->set_text(String::formatted("of {}", document->get_page_count())); + m_total_page_label->set_fixed_width(30); + + m_page_text_box->set_text(String::number(m_viewer->current_page() + 1)); + m_page_text_box->set_enabled(true); + m_page_text_box->set_max_number(document->get_page_count()); + m_go_to_prev_page_action->set_enabled(true); + m_go_to_next_page_action->set_enabled(true); + m_toggle_sidebar_action->set_enabled(true); if (document->outline()) { auto outline = document->outline(); m_sidebar->set_outline(outline.release_nonnull()); m_sidebar->set_fixed_width(200); m_sidebar_open = true; - m_open_outline_action->set_enabled(true); } else { m_sidebar->set_outline({}); m_sidebar->set_fixed_width(0); m_sidebar_open = false; - m_open_outline_action->set_enabled(false); } } diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.h b/Userland/Applications/PDFViewer/PDFViewerWidget.h index dd07d9639a..9b8e4011bf 100644 --- a/Userland/Applications/PDFViewer/PDFViewerWidget.h +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.h @@ -6,9 +6,11 @@ #pragma once +#include "NumericInput.h" #include "PDFViewer.h" #include "SidebarWidget.h" #include +#include #include class PDFViewer; @@ -19,16 +21,20 @@ class PDFViewerWidget final : public GUI::Widget { public: ~PDFViewerWidget() override = default; - void open_file(const String& path); void initialize_menubar(GUI::Menubar&); + void create_toolbar(); + void open_file(const String& path); private: PDFViewerWidget(); - RefPtr m_open_outline_action; RefPtr m_viewer; RefPtr m_sidebar; + RefPtr m_page_text_box; + RefPtr m_total_page_label; + RefPtr m_go_to_prev_page_action; + RefPtr m_go_to_next_page_action; + RefPtr m_toggle_sidebar_action; bool m_sidebar_open { false }; ByteBuffer m_buffer; - RefPtr m_open_action; };