From 37d5ef75306c45ca95550f4af40d9750c5b9cb88 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Mon, 6 May 2024 13:42:55 +0200 Subject: initial commit yup, i definitely wrote all of this in a single commit --- src/panel.c | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 src/panel.c (limited to 'src/panel.c') diff --git a/src/panel.c b/src/panel.c new file mode 100644 index 0000000..4833b72 --- /dev/null +++ b/src/panel.c @@ -0,0 +1,348 @@ +#include "panel.h" +#include "widgets/panel/panel-item-icon.h" +#include "widgets/panel/panel-item-clock.h" +#include "widgets/panel/panel-item-workspaces.h" +#include "widgets/panel/panel-item-status.h" +#include + +// +// FoobarPanel: +// +// A panel that is divided into start, center and end sections. Each section can be configured to present a range of +// items. +// + +struct _FoobarPanel +{ + GtkWindow parent_instance; + GtkWidget* main_layout; + GtkWidget* start_items; + GtkWidget* center_items; + GtkWidget* end_items; + GPtrArray* item_widgets; + GdkMonitor* monitor; + FoobarBatteryService* battery_service; + FoobarClockService* clock_service; + FoobarBrightnessService* brightness_service; + FoobarWorkspaceService* workspace_service; + FoobarAudioService* audio_service; + FoobarNetworkService* network_service; + FoobarBluetoothService* bluetooth_service; + FoobarNotificationService* notification_service; + FoobarConfigurationService* configuration_service; + gulong config_handler_id; +}; + +static void foobar_panel_class_init ( FoobarPanelClass* klass ); +static void foobar_panel_init ( FoobarPanel* self ); +static void foobar_panel_finalize ( GObject* object ); +static void foobar_panel_handle_config_change( GObject* object, + GParamSpec* pspec, + gpointer userdata ); + +G_DEFINE_FINAL_TYPE( FoobarPanel, foobar_panel, GTK_TYPE_APPLICATION_WINDOW ) + +// --------------------------------------------------------------------------------------------------------------------- +// Window Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for the panel. +// +void foobar_panel_class_init( FoobarPanelClass* klass ) +{ + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->finalize = foobar_panel_finalize; +} + +// +// Instance initialization for the panel. +// +void foobar_panel_init( FoobarPanel* self ) +{ + self->start_items = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + self->center_items = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + self->end_items = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + + self->main_layout = gtk_center_box_new( ); + gtk_center_box_set_start_widget( GTK_CENTER_BOX( self->main_layout ), self->start_items ); + gtk_center_box_set_center_widget( GTK_CENTER_BOX( self->main_layout ), self->center_items ); + gtk_center_box_set_end_widget( GTK_CENTER_BOX( self->main_layout ), self->end_items ); + + gtk_window_set_child( GTK_WINDOW( self ), self->main_layout ); + gtk_window_set_title( GTK_WINDOW( self ), "Foobar Panel" ); + gtk_widget_add_css_class( GTK_WIDGET( self ), "panel" ); + gtk_layer_init_for_window( GTK_WINDOW( self ) ); + gtk_layer_set_namespace( GTK_WINDOW( self ), "foobar-panel" ); + gtk_layer_auto_exclusive_zone_enable( GTK_WINDOW( self ) ); +} + +// +// Instance cleanup for the panel. +// +void foobar_panel_finalize( GObject* object ) +{ + FoobarPanel* self = (FoobarPanel*)object; + + g_clear_signal_handler( &self->config_handler_id, self->configuration_service ); + g_clear_object( &self->monitor ); + g_clear_object( &self->battery_service ); + g_clear_object( &self->clock_service ); + g_clear_object( &self->brightness_service ); + g_clear_object( &self->workspace_service ); + g_clear_object( &self->audio_service ); + g_clear_object( &self->network_service ); + g_clear_object( &self->bluetooth_service ); + g_clear_object( &self->notification_service ); + g_clear_object( &self->configuration_service ); + g_clear_pointer( &self->item_widgets, g_ptr_array_unref ); + + G_OBJECT_CLASS( foobar_panel_parent_class )->finalize( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new panel instance. +// +// An optional GdkMonitor instance can be provided for multi-monitor setups. Otherwise, the default monitor is used. +// +FoobarPanel* foobar_panel_new( + GtkApplication* app, + GdkMonitor* monitor, + FoobarBatteryService* battery_service, + FoobarClockService* clock_service, + FoobarBrightnessService* brightness_service, + FoobarWorkspaceService* workspace_service, + FoobarAudioService* audio_service, + FoobarNetworkService* network_service, + FoobarBluetoothService* bluetooth_service, + FoobarNotificationService* notification_service, + FoobarConfigurationService* configuration_service ) +{ + g_return_val_if_fail( GTK_IS_APPLICATION( app ), NULL ); + g_return_val_if_fail( monitor == NULL || GDK_IS_MONITOR( monitor ), NULL ); + g_return_val_if_fail( FOOBAR_IS_BATTERY_SERVICE( battery_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_CLOCK_SERVICE( clock_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_BRIGHTNESS_SERVICE( brightness_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_WORKSPACE_SERVICE( workspace_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_AUDIO_SERVICE( audio_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_NETWORK_SERVICE( network_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( bluetooth_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_NOTIFICATION_SERVICE( notification_service ), NULL ); + g_return_val_if_fail( FOOBAR_IS_CONFIGURATION_SERVICE( configuration_service ), NULL ); + + FoobarPanel* self = g_object_new( FOOBAR_TYPE_PANEL, "application", app, NULL ); + self->battery_service = g_object_ref( battery_service ); + self->clock_service = g_object_ref( clock_service ); + self->brightness_service = g_object_ref( brightness_service ); + self->workspace_service = g_object_ref( workspace_service ); + self->audio_service = g_object_ref( audio_service ); + self->network_service = g_object_ref( network_service ); + self->bluetooth_service = g_object_ref( bluetooth_service ); + self->notification_service = g_object_ref( notification_service ); + self->configuration_service = g_object_ref( configuration_service ); + + // Set the monitor. + + if ( monitor ) + { + self->monitor = g_object_ref( monitor ); + gtk_layer_set_monitor( GTK_WINDOW( self ), self->monitor ); + } + + // Apply the configuration and subscribe to changes. + + FoobarConfiguration const* config = foobar_configuration_service_get_current( self->configuration_service ); + foobar_panel_apply_configuration( self, foobar_configuration_get_panel( config ) ); + self->config_handler_id = g_signal_connect( + self->configuration_service, + "notify::current", + G_CALLBACK( foobar_panel_handle_config_change ), + self ); + + return self; +} + +// +// Apply the panel configuration provided by the configuration service. +// +void foobar_panel_apply_configuration( + FoobarPanel* self, + FoobarPanelConfiguration const* config ) +{ + g_return_if_fail( FOOBAR_IS_PANEL( self ) ); + g_return_if_fail( config != NULL ); + + // Derive the orientation from the panel's position. + + FoobarScreenEdge position = foobar_panel_configuration_get_position( config ); + GtkOrientation orientation; + gchar const* orientation_css_class; + switch ( position ) + { + case FOOBAR_SCREEN_EDGE_LEFT: + case FOOBAR_SCREEN_EDGE_RIGHT: + orientation = GTK_ORIENTATION_VERTICAL; + orientation_css_class = "vertical"; + break; + case FOOBAR_SCREEN_EDGE_TOP: + case FOOBAR_SCREEN_EDGE_BOTTOM: + orientation = GTK_ORIENTATION_HORIZONTAL; + orientation_css_class = "horizontal"; + break; + default: + g_warn_if_reached( ); + return; + } + + // Configure the window. + + gtk_widget_remove_css_class( GTK_WIDGET( self ), "horizontal" ); + gtk_widget_remove_css_class( GTK_WIDGET( self ), "vertical" ); + gtk_widget_add_css_class( GTK_WIDGET( self ), orientation_css_class ); + + gint size = foobar_panel_configuration_get_size( config ); + gtk_window_set_default_size( + GTK_WINDOW( self ), + orientation == GTK_ORIENTATION_VERTICAL ? size : -1, + orientation == GTK_ORIENTATION_HORIZONTAL ? size : -1 ); + + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_LEFT, position != FOOBAR_SCREEN_EDGE_RIGHT ); + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_RIGHT, position != FOOBAR_SCREEN_EDGE_LEFT ); + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_TOP, position != FOOBAR_SCREEN_EDGE_BOTTOM ); + gtk_layer_set_anchor( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_BOTTOM, position != FOOBAR_SCREEN_EDGE_TOP ); + + // Configure layout spacing. + + gint margin = foobar_panel_configuration_get_margin( config ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_LEFT, position != FOOBAR_SCREEN_EDGE_RIGHT ? margin : 0 ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_RIGHT, position != FOOBAR_SCREEN_EDGE_LEFT ? margin : 0 ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_TOP, position != FOOBAR_SCREEN_EDGE_BOTTOM ? margin : 0 ); + gtk_layer_set_margin( GTK_WINDOW( self ), GTK_LAYER_SHELL_EDGE_BOTTOM, position != FOOBAR_SCREEN_EDGE_TOP ? margin : 0 ); + + gint padding = foobar_panel_configuration_get_padding( config ); + gtk_widget_set_margin_start( GTK_WIDGET( self->main_layout ), orientation == GTK_ORIENTATION_HORIZONTAL ? padding : 0 ); + gtk_widget_set_margin_end( GTK_WIDGET( self->main_layout ), orientation == GTK_ORIENTATION_HORIZONTAL ? padding : 0 ); + gtk_widget_set_margin_top( GTK_WIDGET( self->main_layout ), orientation == GTK_ORIENTATION_VERTICAL ? padding : 0 ); + gtk_widget_set_margin_bottom( GTK_WIDGET( self->main_layout ), orientation == GTK_ORIENTATION_VERTICAL ? padding : 0 ); + + gint spacing = foobar_panel_configuration_get_spacing( config ); + gtk_box_set_spacing( GTK_BOX(self->start_items), spacing ); + gtk_box_set_spacing( GTK_BOX(self->center_items), spacing ); + gtk_box_set_spacing( GTK_BOX(self->end_items), spacing ); + gtk_widget_set_margin_top( GTK_WIDGET( self->center_items ), orientation == GTK_ORIENTATION_VERTICAL ? spacing : 0 ); + gtk_widget_set_margin_bottom( GTK_WIDGET( self->center_items ), orientation == GTK_ORIENTATION_VERTICAL ? spacing : 0 ); + gtk_widget_set_margin_start( GTK_WIDGET( self->center_items ), orientation == GTK_ORIENTATION_HORIZONTAL ? spacing : 0 ); + gtk_widget_set_margin_end( GTK_WIDGET( self->center_items ), orientation == GTK_ORIENTATION_HORIZONTAL ? spacing : 0 ); + + // Configure the container orientations. + + gtk_orientable_set_orientation( GTK_ORIENTABLE( self->main_layout ), orientation ); + gtk_orientable_set_orientation( GTK_ORIENTABLE( self->start_items ), orientation ); + gtk_orientable_set_orientation( GTK_ORIENTABLE( self->center_items ), orientation ); + gtk_orientable_set_orientation( GTK_ORIENTABLE( self->end_items ), orientation ); + + // Re-create panel items. + + if ( self->item_widgets ) + { + for ( guint i = 0; i < self->item_widgets->len; ++i ) + { + gtk_widget_unparent( g_ptr_array_index( self->item_widgets, i ) ); + } + g_clear_pointer( &self->item_widgets, g_ptr_array_unref ); + } + + gsize items_count; + FoobarPanelItemConfiguration const* const* items = foobar_panel_configuration_get_items( config, &items_count ); + self->item_widgets = g_ptr_array_sized_new( items_count ); + for ( gsize i = 0; i < items_count; ++i ) + { + // Create an item for the specified type and configuration. + + FoobarPanelItemConfiguration const* item = items[i]; + FoobarPanelItem* item_widget; + switch ( foobar_panel_item_configuration_get_kind( item ) ) + { + case FOOBAR_PANEL_ITEM_KIND_ICON: + item_widget = foobar_panel_item_icon_new( item ); + break; + case FOOBAR_PANEL_ITEM_KIND_CLOCK: + item_widget = foobar_panel_item_clock_new( + item, + self->clock_service ); + break; + case FOOBAR_PANEL_ITEM_KIND_WORKSPACES: + item_widget = foobar_panel_item_workspaces_new( + item, + self->monitor, + self->workspace_service ); + break; + case FOOBAR_PANEL_ITEM_KIND_STATUS: + item_widget = foobar_panel_item_status_new( + item, + self->battery_service, + self->brightness_service, + self->audio_service, + self->network_service, + self->bluetooth_service, + self->notification_service ); + break; + default: + g_warn_if_reached( ); + continue; + } + + // Propagate the panel's orientation if the item supports it. + + if ( GTK_IS_ORIENTABLE( item_widget ) ) + { + gtk_orientable_set_orientation( GTK_ORIENTABLE( item_widget ), orientation ); + } + + // Add the item to the correct container, depending on its configured position. + + switch ( foobar_panel_item_configuration_get_position( item ) ) + { + case FOOBAR_PANEL_ITEM_POSITION_START: + gtk_box_append( GTK_BOX( self->start_items ), GTK_WIDGET( item_widget ) ); + break; + case FOOBAR_PANEL_ITEM_POSITION_CENTER: + gtk_box_append( GTK_BOX( self->center_items ), GTK_WIDGET( item_widget ) ); + break; + case FOOBAR_PANEL_ITEM_POSITION_END: + gtk_box_append( GTK_BOX( self->end_items ), GTK_WIDGET( item_widget ) ); + break; + default: + g_warn_if_reached( ); + break; + } + + // Add the item to the pointer array for easier cleanup. + + g_ptr_array_add( self->item_widgets, GTK_WIDGET( item_widget ) ); + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// Signal Handlers +// --------------------------------------------------------------------------------------------------------------------- + +// +// Signal handler called when the global configuration file has changed. +// +void foobar_panel_handle_config_change( + GObject* object, + GParamSpec* pspec, + gpointer userdata ) +{ + (void)object; + (void)pspec; + FoobarPanel* self = (FoobarPanel*)userdata; + + FoobarConfiguration const* config = foobar_configuration_service_get_current( self->configuration_service ); + foobar_panel_apply_configuration( self, foobar_configuration_get_panel( config ) ); +} \ No newline at end of file -- cgit v1.3.1