mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 12:48:10 +00:00

and the CaptureInput mode. They are a source of unneeded complexity in WindowServer and have proven prone to regressions, so this patch replaces them with a simple input preemption scheme using Popups. Popup windows now have ergonomics similar to menus: When open, a popup preempts all mouse and key events for the entire window stack; however, they are fragile and will close after WindowServer swallows the first event outside them. This is similar to how combo box windows and popups work in the classic Windows DE and has the added benefit of letting the user click anywhere to dismiss a popup without having to worry about unwanted interactions with other widgets.
245 lines
9.3 KiB
C++
245 lines
9.3 KiB
C++
/*
|
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "ClockWidget.h"
|
|
#include <LibConfig/Client.h>
|
|
#include <LibGUI/Action.h>
|
|
#include <LibGUI/Menu.h>
|
|
#include <LibGUI/Painter.h>
|
|
#include <LibGUI/Process.h>
|
|
#include <LibGUI/SeparatorWidget.h>
|
|
#include <LibGUI/Window.h>
|
|
#include <LibGfx/Bitmap.h>
|
|
#include <LibGfx/Font/FontDatabase.h>
|
|
#include <LibGfx/Palette.h>
|
|
|
|
namespace Taskbar {
|
|
|
|
ClockWidget::ClockWidget()
|
|
{
|
|
set_frame_shape(Gfx::FrameShape::Box);
|
|
set_frame_shadow(Gfx::FrameShadow::Sunken);
|
|
set_frame_thickness(1);
|
|
|
|
update_format(Config::read_string("Taskbar"sv, "Clock"sv, "TimeFormat"sv, "%T"sv));
|
|
|
|
m_timer = add<Core::Timer>(1000, [this] {
|
|
static time_t last_update_time;
|
|
time_t now = time(nullptr);
|
|
if (now != last_update_time) {
|
|
tick_clock();
|
|
last_update_time = now;
|
|
set_tooltip(Core::DateTime::now().to_string("%Y-%m-%d"sv));
|
|
}
|
|
});
|
|
|
|
m_calendar_window = add<GUI::Window>(window());
|
|
m_calendar_window->resize(m_window_size.width(), m_window_size.height());
|
|
m_calendar_window->set_frameless(true);
|
|
m_calendar_window->set_resizable(false);
|
|
m_calendar_window->set_minimizable(false);
|
|
|
|
auto& root_container = m_calendar_window->set_main_widget<GUI::Frame>();
|
|
root_container.set_fill_with_background_color(true);
|
|
root_container.set_layout<GUI::VerticalBoxLayout>();
|
|
root_container.layout()->set_margins({ 2, 0 });
|
|
root_container.layout()->set_spacing(0);
|
|
root_container.set_frame_shape(Gfx::FrameShape::Window);
|
|
|
|
auto& navigation_container = root_container.add<GUI::Widget>();
|
|
navigation_container.set_fixed_height(24);
|
|
navigation_container.set_layout<GUI::HorizontalBoxLayout>();
|
|
navigation_container.layout()->set_margins({ 2 });
|
|
|
|
m_prev_date = navigation_container.add<GUI::Button>();
|
|
m_prev_date->set_button_style(Gfx::ButtonStyle::Coolbar);
|
|
m_prev_date->set_fixed_size(24, 24);
|
|
m_prev_date->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-back.png"sv).release_value_but_fixme_should_propagate_errors());
|
|
m_prev_date->on_click = [&](auto) {
|
|
unsigned view_month = m_calendar->view_month();
|
|
unsigned view_year = m_calendar->view_year();
|
|
if (m_calendar->mode() == GUI::Calendar::Month) {
|
|
view_month--;
|
|
if (m_calendar->view_month() == 1) {
|
|
view_month = 12;
|
|
view_year--;
|
|
}
|
|
} else {
|
|
view_year--;
|
|
}
|
|
m_calendar->update_tiles(view_year, view_month);
|
|
if (m_calendar->mode() == GUI::Calendar::Year)
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date(GUI::Calendar::YearOnly));
|
|
else
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date());
|
|
};
|
|
|
|
m_selected_calendar_button = navigation_container.add<GUI::Button>();
|
|
m_selected_calendar_button->set_button_style(Gfx::ButtonStyle::Coolbar);
|
|
m_selected_calendar_button->set_fixed_height(24);
|
|
m_selected_calendar_button->on_click = [&](auto) {
|
|
m_calendar->toggle_mode();
|
|
if (m_calendar->mode() == GUI::Calendar::Year)
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date(GUI::Calendar::YearOnly));
|
|
else
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date());
|
|
};
|
|
|
|
m_next_date = navigation_container.add<GUI::Button>();
|
|
m_next_date->set_button_style(Gfx::ButtonStyle::Coolbar);
|
|
m_next_date->set_fixed_size(24, 24);
|
|
m_next_date->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-forward.png"sv).release_value_but_fixme_should_propagate_errors());
|
|
m_next_date->on_click = [&](auto) {
|
|
unsigned view_month = m_calendar->view_month();
|
|
unsigned view_year = m_calendar->view_year();
|
|
if (m_calendar->mode() == GUI::Calendar::Month) {
|
|
view_month++;
|
|
if (m_calendar->view_month() == 12) {
|
|
view_month = 1;
|
|
view_year++;
|
|
}
|
|
} else {
|
|
view_year++;
|
|
}
|
|
m_calendar->update_tiles(view_year, view_month);
|
|
if (m_calendar->mode() == GUI::Calendar::Year)
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date(GUI::Calendar::YearOnly));
|
|
else
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date());
|
|
};
|
|
|
|
auto& separator1 = root_container.add<GUI::HorizontalSeparator>();
|
|
separator1.set_fixed_height(2);
|
|
|
|
auto& calendar_container = root_container.add<GUI::Widget>();
|
|
calendar_container.set_layout<GUI::HorizontalBoxLayout>();
|
|
calendar_container.layout()->set_margins({ 2 });
|
|
|
|
m_calendar = calendar_container.add<GUI::Calendar>();
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date());
|
|
|
|
m_calendar->on_tile_click = [&] {
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date());
|
|
};
|
|
|
|
m_calendar->on_month_click = [&] {
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date());
|
|
};
|
|
|
|
auto& separator2 = root_container.add<GUI::HorizontalSeparator>();
|
|
separator2.set_fixed_height(2);
|
|
|
|
auto& settings_container = root_container.add<GUI::Widget>();
|
|
settings_container.set_fixed_height(24);
|
|
settings_container.set_layout<GUI::HorizontalBoxLayout>();
|
|
settings_container.layout()->set_margins({ 2 });
|
|
settings_container.layout()->add_spacer();
|
|
|
|
m_jump_to_button = settings_container.add<GUI::Button>();
|
|
m_jump_to_button->set_button_style(Gfx::ButtonStyle::Coolbar);
|
|
m_jump_to_button->set_fixed_size(24, 24);
|
|
m_jump_to_button->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/calendar-date.png"sv).release_value_but_fixme_should_propagate_errors());
|
|
m_jump_to_button->set_tooltip("Jump to today");
|
|
m_jump_to_button->on_click = [this](auto) {
|
|
jump_to_current_date();
|
|
};
|
|
|
|
m_calendar_launcher = settings_container.add<GUI::Button>();
|
|
m_calendar_launcher->set_button_style(Gfx::ButtonStyle::Coolbar);
|
|
m_calendar_launcher->set_fixed_size(24, 24);
|
|
m_calendar_launcher->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-calendar.png"sv).release_value_but_fixme_should_propagate_errors());
|
|
m_calendar_launcher->set_tooltip("Calendar");
|
|
m_calendar_launcher->on_click = [this](auto) {
|
|
GUI::Process::spawn_or_show_error(window(), "/bin/Calendar"sv);
|
|
};
|
|
}
|
|
|
|
void ClockWidget::update_format(String const& format)
|
|
{
|
|
m_time_format = format;
|
|
m_time_width = font().width(Core::DateTime::create(122, 2, 22, 22, 22, 22).to_string(format));
|
|
set_fixed_size(m_time_width + 20, 21);
|
|
}
|
|
|
|
void ClockWidget::paint_event(GUI::PaintEvent& event)
|
|
{
|
|
GUI::Frame::paint_event(event);
|
|
auto time_text = Core::DateTime::now().to_string(m_time_format);
|
|
GUI::Painter painter(*this);
|
|
painter.add_clip_rect(frame_inner_rect());
|
|
|
|
// Render string center-left aligned, but attempt to center the string based on a constant
|
|
// "ideal" time string (i.e., the same one used to size this widget in the initializer).
|
|
// This prevents the rest of the string from shifting around while seconds tick.
|
|
Gfx::Font const& font = Gfx::FontDatabase::default_font();
|
|
int const frame_width = frame_thickness();
|
|
int const ideal_width = m_time_width;
|
|
int const widget_width = max_width().as_int();
|
|
int const translation_x = (widget_width - ideal_width) / 2 - frame_width;
|
|
|
|
painter.draw_text(frame_inner_rect().translated(translation_x, frame_width), time_text, font, Gfx::TextAlignment::CenterLeft, palette().window_text());
|
|
}
|
|
|
|
void ClockWidget::mousedown_event(GUI::MouseEvent& event)
|
|
{
|
|
if (event.button() != GUI::MouseButton::Primary) {
|
|
return;
|
|
} else {
|
|
if (!m_calendar_window->is_visible())
|
|
open();
|
|
else
|
|
close();
|
|
}
|
|
}
|
|
|
|
void ClockWidget::context_menu_event(GUI::ContextMenuEvent& event)
|
|
{
|
|
if (!m_context_menu) {
|
|
m_context_menu = GUI::Menu::construct();
|
|
|
|
auto settings_icon = MUST(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/settings.png"sv));
|
|
auto open_clock_settings_action = GUI::Action::create("Clock &Settings", *settings_icon, [this](auto&) {
|
|
GUI::Process::spawn_or_show_error(window(), "/bin/ClockSettings"sv, Array { "--open-tab", "clock" });
|
|
});
|
|
|
|
m_context_menu->add_action(open_clock_settings_action);
|
|
}
|
|
|
|
m_context_menu->popup(event.screen_position());
|
|
}
|
|
|
|
void ClockWidget::open()
|
|
{
|
|
jump_to_current_date();
|
|
position_calendar_window();
|
|
m_calendar_window->show();
|
|
}
|
|
|
|
void ClockWidget::close()
|
|
{
|
|
m_calendar_window->hide();
|
|
}
|
|
|
|
void ClockWidget::position_calendar_window()
|
|
{
|
|
constexpr auto taskbar_top_padding { 4 };
|
|
m_calendar_window->set_rect(
|
|
screen_relative_rect().right() - m_calendar_window->width() + 1,
|
|
screen_relative_rect().top() - taskbar_top_padding - m_calendar_window->height(),
|
|
m_window_size.width(),
|
|
m_window_size.height());
|
|
}
|
|
|
|
void ClockWidget::jump_to_current_date()
|
|
{
|
|
if (m_calendar->mode() == GUI::Calendar::Year)
|
|
m_calendar->toggle_mode();
|
|
m_calendar->set_selected_date(Core::DateTime::now());
|
|
m_calendar->update_tiles(Core::DateTime::now().year(), Core::DateTime::now().month());
|
|
m_selected_calendar_button->set_text(m_calendar->formatted_date());
|
|
}
|
|
|
|
}
|