diff options
| author | Přemysl Eric Janouch <p@janouch.name> | 2023-03-30 20:52:57 +0200 | 
|---|---|---|
| committer | Přemysl Eric Janouch <p@janouch.name> | 2023-04-11 06:04:27 +0200 | 
| commit | 1b50a834a5d6bb1584e261efd656c8153fcf41b8 (patch) | |
| tree | fbf771da3ef55a31e18c20b3c7b8501bf49dabc6 | |
| parent | 38c19edc8bbe5e6301e4bd8f8bc3277701d9fef9 (diff) | |
| download | fiv-1b50a834a5d6bb1584e261efd656c8153fcf41b8.tar.gz fiv-1b50a834a5d6bb1584e261efd656c8153fcf41b8.tar.xz fiv-1b50a834a5d6bb1584e261efd656c8153fcf41b8.zip  | |
Add optional browser filename labels
| -rw-r--r-- | fiv-browser.c | 119 | ||||
| -rw-r--r-- | fiv-io.c | 5 | ||||
| -rw-r--r-- | fiv-io.h | 5 | ||||
| -rw-r--r-- | fiv.c | 21 | 
4 files changed, 132 insertions, 18 deletions
diff --git a/fiv-browser.c b/fiv-browser.c index 192c3bc..f895a20 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -1,7 +1,7 @@  //  // fiv-browser.c: filesystem browsing widget  // -// Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name> +// Copyright (c) 2021 - 2023, Přemysl Eric Janouch <p@janouch.name>  //  // Permission to use, copy, modify, and/or distribute this software for any  // purpose with or without fee is hereby granted. @@ -45,9 +45,12 @@  //                    │ n │   ┊   glow border │ n ┊  //                    │ g ╰───────────────────╯ g ╰┄┄┄┄┄  //                    │    s  p  a  c  i  n  g +//                    │       l  a  b  e  l +//                    │    s  p  a  c  i  n  g  //                    │   ╭┄┄┄┄┄┄┄┄┄┄┄┄╮   ╭┄┄┄┄┄┄┄┄┄┄┄┄  //  // The glow is actually a glowing margin, the border is rendered in two parts. +// When labels are hidden, the surrounding spacing is collapsed.  //  typedef struct entry Entry; @@ -72,6 +75,8 @@ struct _FivBrowser {  	int item_height;                    ///< Thumbnail height in pixels  	int item_spacing;                   ///< Space between items in pixels +	gboolean show_labels;               ///< Show labels underneath items +  	FivIoModel *model;                  ///< Filesystem model  	GArray *entries;                    ///< []Entry  	GArray *layouted_rows;              ///< []Row @@ -101,10 +106,13 @@ struct _FivBrowser {  /// The "last modified" timestamp of source images for thumbnails.  static cairo_user_data_key_t fiv_browser_key_mtime_msec; +// TODO(p): Include FivIoModelEntry data by reference.  struct entry {  	gchar *uri;                         ///< GIO URI  	gchar *target_uri;                  ///< GIO URI for any target +	gchar *display_name;                ///< Label for the file  	gint64 mtime_msec;                  ///< Modification time in milliseconds +  	cairo_surface_t *thumbnail;         ///< Prescaled thumbnail  	GIcon *icon;                        ///< If no thumbnail, use this icon  }; @@ -114,6 +122,7 @@ entry_free(Entry *self)  {  	g_free(self->uri);  	g_free(self->target_uri); +	g_free(self->display_name);  	g_clear_pointer(&self->thumbnail, cairo_surface_destroy);  	g_clear_object(&self->icon);  } @@ -122,7 +131,8 @@ entry_free(Entry *self)  struct item {  	const Entry *entry; -	int x_offset;                       ///< Offset within the row +	PangoLayout *label;                 ///< Label +	int x_offset;                       ///< X offset within the row  };  struct row { @@ -135,11 +145,34 @@ struct row {  static void  row_free(Row *self)  { +	for (gsize i = 0; i < self->len; i++) +		g_clear_object(&self->items[i].label);  	g_free(self->items);  }  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static double +row_subheight(const FivBrowser *self, const Row *row) +{ +	if (!self->show_labels) +		return 0; + +	// If we didn't ellipsize labels, this should be made to account +	// for vertical centering as well. +	int tallest_label = 0; +	for (gsize i = 0; i < row->len; i++) { +		PangoRectangle ink = {}, logical = {}; +		pango_layout_get_extents(row->items[i].label, &ink, &logical); + +		int height = (logical.y + logical.height) / PANGO_SCALE; +		if (tallest_label < height) +			tallest_label = height; +	} + +	return self->item_spacing + tallest_label; +} +  static void  append_row(FivBrowser *self, int *y, int x, GArray *items_array)  { @@ -154,6 +187,7 @@ append_row(FivBrowser *self, int *y, int x, GArray *items_array)  	// Not trying to pack them vertically, but this would be the place to do it.  	*y += self->item_height;  	*y += self->item_border_y; +	*y += row_subheight(self, &row);  }  static int @@ -166,6 +200,9 @@ relayout(FivBrowser *self, int width)  	gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);  	int available_width = width - padding.left - padding.right, max_width = 0; +	// TODO(p): Remember the first visible item and the vertical offset into it, +	// then try to ensure its visibility at the end (useful for reloads). +  	g_array_set_size(self->layouted_rows, 0);  	// Whatever self->drag_begin_* used to point at might no longer be there,  	// but thumbnail reloading would disrupt mouse clicks if we cleared them. @@ -189,8 +226,26 @@ relayout(FivBrowser *self, int width)  			x = 0;  		} -		g_array_append_val(items, -			((Item) {.entry = entry, .x_offset = x + self->item_border_x})); +		PangoLayout *label = NULL; +		if (self->show_labels) { +			label = gtk_widget_create_pango_layout(widget, entry->display_name); +			pango_layout_set_width( +				label, (width - 2 * self->glow_w) * PANGO_SCALE); +			pango_layout_set_alignment(label, PANGO_ALIGN_CENTER); +			pango_layout_set_wrap(label, PANGO_WRAP_WORD_CHAR); +			pango_layout_set_ellipsize(label, PANGO_ELLIPSIZE_END); + +			PangoAttrList *attrs = pango_attr_list_new(); +			pango_attr_list_insert(attrs, pango_attr_insert_hyphens_new(FALSE)); +			pango_layout_set_attributes(label, attrs); +			pango_attr_list_unref (attrs); +		} + +		g_array_append_val(items, ((Item) { +			.entry = entry, +			.label = label, +			.x_offset = x + self->item_border_x, +		}));  		x += width;  		if (max_width < width) @@ -366,6 +421,16 @@ draw_row(FivBrowser *self, cairo_t *cr, const Row *row)  			// the whole rectangle with the selection color.  		} +		if (self->show_labels) { +			gtk_style_context_save(style); +			gtk_style_context_add_class(style, "label"); +			gtk_render_layout(style, cr, -border.left, +				border.top + extents.height + self->item_border_y + +					self->item_spacing, +				item->label); +			gtk_style_context_restore(style); +		} +  		cairo_restore(cr);  		gtk_style_context_restore(style);  	} @@ -747,6 +812,7 @@ G_DEFINE_TYPE_EXTENDED(FivBrowser, fiv_browser, GTK_TYPE_WIDGET, 0,  enum {  	PROP_THUMBNAIL_SIZE = 1, +	PROP_SHOW_LABELS,  	N_PROPERTIES,  	// These are overriden, we do not register them. @@ -827,6 +893,9 @@ fiv_browser_get_property(  	case PROP_THUMBNAIL_SIZE:  		g_value_set_enum(value, self->item_size);  		break; +	case PROP_SHOW_LABELS: +		g_value_set_boolean(value, self->show_labels); +		break;  	case PROP_HADJUSTMENT:  		g_value_set_object(value, self->hadjustment);  		break; @@ -872,6 +941,13 @@ fiv_browser_set_property(  	case PROP_THUMBNAIL_SIZE:  		set_item_size(self, g_value_get_enum(value));  		break; +	case PROP_SHOW_LABELS: +		if (self->show_labels != g_value_get_boolean(value)) { +			self->show_labels = g_value_get_boolean(value); +			gtk_widget_queue_resize(GTK_WIDGET(self)); +			g_object_notify_by_pspec(object, pspec); +		} +		break;  	case PROP_HADJUSTMENT:  		if (replace_adjustment(  				self, &self->hadjustment, g_value_get_object(value))) @@ -1283,12 +1359,13 @@ scroll_to_row(FivBrowser *self, const Row *row)  	double y1 = gtk_adjustment_get_value(self->vadjustment);  	double ph = gtk_adjustment_get_page_size(self->vadjustment); +	double sh = self->item_border_y + row_subheight(self, row);  	if (row->y_offset < y1) {  		gtk_adjustment_set_value(  			self->vadjustment, row->y_offset - self->item_border_y); -	} else if (row->y_offset + self->item_height > y1 + ph) { -		gtk_adjustment_set_value(self->vadjustment, -			row->y_offset - ph + self->item_height + self->item_border_y); +	} else if (row->y_offset + self->item_height + sh > y1 + ph) { +		gtk_adjustment_set_value( +			self->vadjustment, row->y_offset - ph + self->item_height + sh);  	}  } @@ -1459,20 +1536,16 @@ fiv_browser_query_tooltip(GtkWidget *widget, gint x, gint y,  	G_GNUC_UNUSED gboolean keyboard_tooltip, GtkTooltip *tooltip)  {  	FivBrowser *self = FIV_BROWSER(widget); -	const Entry *entry = entry_at(self, x, y); -	if (!entry) + +	// TODO(p): Consider getting rid of tooltips altogether. +	if (self->show_labels)  		return FALSE; -	GFile *file = g_file_new_for_uri(entry->uri); -	GFileInfo *info = -		g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, -			G_FILE_QUERY_INFO_NONE, NULL, NULL); -	g_object_unref(file); -	if (!info) +	const Entry *entry = entry_at(self, x, y); +	if (!entry)  		return FALSE; -	gtk_tooltip_set_text(tooltip, g_file_info_get_display_name(info)); -	g_object_unref(info); +	gtk_tooltip_set_text(tooltip, entry->display_name);  	return TRUE;  } @@ -1553,6 +1626,13 @@ fiv_browser_style_updated(GtkWidget *widget)  	gtk_style_context_add_class(style, "item");  	gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);  	gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); +	// XXX: Right now, specifying custom fonts within our CSS pseudo-regions +	// has no effect, so it might be appropriate to also add .label/.symbolic +	// classes here, remember the resulting GTK_STYLE_PROPERTY_FONT, +	// and apply them in relayout() with pango_layout_set_font_description(). +	// There is virtually nothing to be gained from this flexibility, though. +	// XXX: We should also invoke relayout() here, because different states +	// might theoretically use different fonts.  	gtk_style_context_restore(style);  	self->glow_w = (margin.left + margin.right) / 2; @@ -1634,6 +1714,9 @@ fiv_browser_class_init(FivBrowserClass *klass)  		"thumbnail-size", "Thumbnail size", "The thumbnail height to use",  		FIV_TYPE_THUMBNAIL_SIZE, FIV_THUMBNAIL_SIZE_NORMAL,  		G_PARAM_READWRITE); +	browser_properties[PROP_SHOW_LABELS] = g_param_spec_boolean( +		"show-labels", "Show labels", "Whether to show filename labels", +		FALSE, G_PARAM_READWRITE);  	g_object_class_install_properties(  		object_class, N_PROPERTIES, browser_properties); @@ -1703,6 +1786,7 @@ fiv_browser_init(FivBrowser *self)  	g_queue_init(&self->thumbnailers_queue);  	set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL); +	self->show_labels = FALSE;  	self->glow_padded = cairo_pattern_create_rgba(0, 0, 0, 0);  	self->glow = cairo_pattern_create_rgba(0, 0, 0, 0); @@ -1741,6 +1825,7 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)  		Entry e = {.thumbnail = NULL,  			.uri = g_strdup(files[i].uri),  			.target_uri = g_strdup(files[i].target_uri), +			.display_name = g_strdup(files[i].display_name),  			.mtime_msec = files[i].mtime_msec};  		g_array_append_val(self->entries, e);  	} @@ -3049,6 +3049,7 @@ model_entry_finalize(FivIoModelEntry *entry)  {  	g_free(entry->uri);  	g_free(entry->target_uri); +	g_free(entry->display_name);  	g_free(entry->collate_key);  } @@ -3171,6 +3172,7 @@ model_reload_to(FivIoModel *self, GFile *directory,  	GFileEnumerator *enumerator = g_file_enumerate_children(directory,  		G_FILE_ATTRIBUTE_STANDARD_TYPE ","  		G_FILE_ATTRIBUTE_STANDARD_NAME "," +		G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","  		G_FILE_ATTRIBUTE_STANDARD_TARGET_URI ","  		G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","  		G_FILE_ATTRIBUTE_TIME_MODIFIED "," @@ -3205,7 +3207,8 @@ model_reload_to(FivIoModel *self, GFile *directory,  		FivIoModelEntry entry = {.uri = g_file_get_uri(child),  			.target_uri = g_strdup(g_file_info_get_attribute_string( -				info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI))}; +				info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI)), +			.display_name = g_strdup(g_file_info_get_display_name(info))};  		GDateTime *mtime = g_file_info_get_modification_date_time(info);  		if (mtime) {  			entry.mtime_msec = g_date_time_to_unix(mtime) * 1000 + @@ -135,9 +135,14 @@ GFile *fiv_io_model_get_previous_directory(FivIoModel *self);  /// Returns the next VFS directory in order, or NULL.  GFile *fiv_io_model_get_next_directory(FivIoModel *self); +// TODO(p): Turn this into a reference-counted object. +//  - If using g_rc_box_*(), we should wrap the {_acquire,_release_full}() +//    functions as fiv_io_model_entry_{ref,unref}(). +//  - Ideally, all the strings would follow the struct immediately.  typedef struct {  	gchar *uri;                         ///< GIO URI  	gchar *target_uri;                  ///< GIO URI for any target +	gchar *display_name;                ///< Label for the file  	gchar *collate_key;                 ///< Collate key for the filename  	gint64 mtime_msec;                  ///< Modification time in milliseconds  } FivIoModelEntry; @@ -132,6 +132,7 @@ static struct key_group help_keys_browser[] = {  		{"F9", "Toggle navigation sidebar"},  		{"F5 r <Control>r", "Reload"},  		{"h <Control>h", "Toggle hiding unsupported files"}, +		{"t <Control>t", "Toggle showing filenames"},  		{"<Control>plus", "Larger thumbnails"},  		{"<Control>minus", "Smaller thumbnails"},  		{} @@ -1115,6 +1116,14 @@ on_dir_next(void)  }  static void +on_toggle_labels(void) +{ +	gboolean old = FALSE; +	g_object_get(g.browser, "show-labels", &old, NULL); +	g_object_set(g.browser, "show-labels", !old, NULL); +} + +static void  on_toolbar_zoom(G_GNUC_UNUSED GtkButton *button, gpointer user_data)  {  	FivThumbnailSize size = FIV_THUMBNAIL_SIZE_COUNT; @@ -1421,6 +1430,9 @@ on_key_press_browser_paned(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,  		case GDK_KEY_r:  			load_directory(NULL);  			return TRUE; +		case GDK_KEY_t: +			on_toggle_labels(); +			return TRUE;  		}  		break;  	case GDK_MOD1_MASK: @@ -1465,6 +1477,9 @@ on_key_press_browser_paned(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,  		case GDK_KEY_r:  			load_directory(NULL);  			return TRUE; +		case GDK_KEY_t: +			on_toggle_labels(); +			return TRUE;  		}  	}  	return FALSE; @@ -1935,6 +1950,12 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \  		background-size: 40px 40px; \  		background-position: 0 0, 0 20px, 20px -20px, -20px 0px; \  	} \ +	fiv-browser.item.label, fiv-browser.item.symbolic.label { \ +		color: @theme_fg_color; \ +	} \ +	fiv-browser.item.label:backdrop:not(:selected) { \ +		color: @theme_unfocused_fg_color; \ +	} \  	fiv-browser.item:selected { \  		color: @theme_selected_bg_color; \  		border-color: @theme_selected_bg_color; \  | 
