Compare commits

...

3 Commits

Author SHA1 Message Date
8da5f807cf
Move and extend the browser toolbar
This makes the user interface more cohesive, and easier to use.

Both toolbars should ideally be made configurable.
2023-04-11 06:33:22 +02:00
1b50a834a5
Add optional browser filename labels 2023-04-11 06:04:27 +02:00
38c19edc8b
Bump Wuffs 2023-04-07 16:23:31 +02:00
10 changed files with 496 additions and 140 deletions

View File

@ -1,7 +1,7 @@
// //
// fiv-browser.c: filesystem browsing widget // 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted. // purpose with or without fee is hereby granted.
@ -45,9 +45,12 @@
// │ n │ ┊ glow border │ n ┊ // │ n │ ┊ glow border │ n ┊
// │ g ╰───────────────────╯ g ╰┄┄┄┄┄ // │ g ╰───────────────────╯ g ╰┄┄┄┄┄
// │ s p a c i n 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. // 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; typedef struct entry Entry;
@ -72,6 +75,8 @@ struct _FivBrowser {
int item_height; ///< Thumbnail height in pixels int item_height; ///< Thumbnail height in pixels
int item_spacing; ///< Space between items in pixels int item_spacing; ///< Space between items in pixels
gboolean show_labels; ///< Show labels underneath items
FivIoModel *model; ///< Filesystem model FivIoModel *model; ///< Filesystem model
GArray *entries; ///< []Entry GArray *entries; ///< []Entry
GArray *layouted_rows; ///< []Row GArray *layouted_rows; ///< []Row
@ -101,10 +106,13 @@ struct _FivBrowser {
/// The "last modified" timestamp of source images for thumbnails. /// The "last modified" timestamp of source images for thumbnails.
static cairo_user_data_key_t fiv_browser_key_mtime_msec; static cairo_user_data_key_t fiv_browser_key_mtime_msec;
// TODO(p): Include FivIoModelEntry data by reference.
struct entry { struct entry {
gchar *uri; ///< GIO URI gchar *uri; ///< GIO URI
gchar *target_uri; ///< GIO URI for any target gchar *target_uri; ///< GIO URI for any target
gchar *display_name; ///< Label for the file
gint64 mtime_msec; ///< Modification time in milliseconds gint64 mtime_msec; ///< Modification time in milliseconds
cairo_surface_t *thumbnail; ///< Prescaled thumbnail cairo_surface_t *thumbnail; ///< Prescaled thumbnail
GIcon *icon; ///< If no thumbnail, use this icon GIcon *icon; ///< If no thumbnail, use this icon
}; };
@ -114,6 +122,7 @@ entry_free(Entry *self)
{ {
g_free(self->uri); g_free(self->uri);
g_free(self->target_uri); g_free(self->target_uri);
g_free(self->display_name);
g_clear_pointer(&self->thumbnail, cairo_surface_destroy); g_clear_pointer(&self->thumbnail, cairo_surface_destroy);
g_clear_object(&self->icon); g_clear_object(&self->icon);
} }
@ -122,7 +131,8 @@ entry_free(Entry *self)
struct item { struct item {
const Entry *entry; const Entry *entry;
int x_offset; ///< Offset within the row PangoLayout *label; ///< Label
int x_offset; ///< X offset within the row
}; };
struct row { struct row {
@ -135,11 +145,34 @@ struct row {
static void static void
row_free(Row *self) row_free(Row *self)
{ {
for (gsize i = 0; i < self->len; i++)
g_clear_object(&self->items[i].label);
g_free(self->items); 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 static void
append_row(FivBrowser *self, int *y, int x, GArray *items_array) 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. // Not trying to pack them vertically, but this would be the place to do it.
*y += self->item_height; *y += self->item_height;
*y += self->item_border_y; *y += self->item_border_y;
*y += row_subheight(self, &row);
} }
static int static int
@ -166,6 +200,9 @@ relayout(FivBrowser *self, int width)
gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding); gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
int available_width = width - padding.left - padding.right, max_width = 0; 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); g_array_set_size(self->layouted_rows, 0);
// Whatever self->drag_begin_* used to point at might no longer be there, // Whatever self->drag_begin_* used to point at might no longer be there,
// but thumbnail reloading would disrupt mouse clicks if we cleared them. // but thumbnail reloading would disrupt mouse clicks if we cleared them.
@ -189,8 +226,26 @@ relayout(FivBrowser *self, int width)
x = 0; x = 0;
} }
g_array_append_val(items, PangoLayout *label = NULL;
((Item) {.entry = entry, .x_offset = x + self->item_border_x})); 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; x += width;
if (max_width < 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. // 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); cairo_restore(cr);
gtk_style_context_restore(style); gtk_style_context_restore(style);
} }
@ -747,6 +812,7 @@ G_DEFINE_TYPE_EXTENDED(FivBrowser, fiv_browser, GTK_TYPE_WIDGET, 0,
enum { enum {
PROP_THUMBNAIL_SIZE = 1, PROP_THUMBNAIL_SIZE = 1,
PROP_SHOW_LABELS,
N_PROPERTIES, N_PROPERTIES,
// These are overriden, we do not register them. // These are overriden, we do not register them.
@ -827,6 +893,9 @@ fiv_browser_get_property(
case PROP_THUMBNAIL_SIZE: case PROP_THUMBNAIL_SIZE:
g_value_set_enum(value, self->item_size); g_value_set_enum(value, self->item_size);
break; break;
case PROP_SHOW_LABELS:
g_value_set_boolean(value, self->show_labels);
break;
case PROP_HADJUSTMENT: case PROP_HADJUSTMENT:
g_value_set_object(value, self->hadjustment); g_value_set_object(value, self->hadjustment);
break; break;
@ -872,6 +941,13 @@ fiv_browser_set_property(
case PROP_THUMBNAIL_SIZE: case PROP_THUMBNAIL_SIZE:
set_item_size(self, g_value_get_enum(value)); set_item_size(self, g_value_get_enum(value));
break; 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: case PROP_HADJUSTMENT:
if (replace_adjustment( if (replace_adjustment(
self, &self->hadjustment, g_value_get_object(value))) 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 y1 = gtk_adjustment_get_value(self->vadjustment);
double ph = gtk_adjustment_get_page_size(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) { if (row->y_offset < y1) {
gtk_adjustment_set_value( gtk_adjustment_set_value(
self->vadjustment, row->y_offset - self->item_border_y); self->vadjustment, row->y_offset - self->item_border_y);
} else if (row->y_offset + self->item_height > y1 + ph) { } else if (row->y_offset + self->item_height + sh > y1 + ph) {
gtk_adjustment_set_value(self->vadjustment, gtk_adjustment_set_value(
row->y_offset - ph + self->item_height + self->item_border_y); 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) G_GNUC_UNUSED gboolean keyboard_tooltip, GtkTooltip *tooltip)
{ {
FivBrowser *self = FIV_BROWSER(widget); FivBrowser *self = FIV_BROWSER(widget);
// TODO(p): Consider getting rid of tooltips altogether.
if (self->show_labels)
return FALSE;
const Entry *entry = entry_at(self, x, y); const Entry *entry = entry_at(self, x, y);
if (!entry) if (!entry)
return FALSE; return FALSE;
GFile *file = g_file_new_for_uri(entry->uri); gtk_tooltip_set_text(tooltip, entry->display_name);
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)
return FALSE;
gtk_tooltip_set_text(tooltip, g_file_info_get_display_name(info));
g_object_unref(info);
return TRUE; return TRUE;
} }
@ -1553,6 +1626,13 @@ fiv_browser_style_updated(GtkWidget *widget)
gtk_style_context_add_class(style, "item"); gtk_style_context_add_class(style, "item");
gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin); gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border); 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); gtk_style_context_restore(style);
self->glow_w = (margin.left + margin.right) / 2; 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", "thumbnail-size", "Thumbnail size", "The thumbnail height to use",
FIV_TYPE_THUMBNAIL_SIZE, FIV_THUMBNAIL_SIZE_NORMAL, FIV_TYPE_THUMBNAIL_SIZE, FIV_THUMBNAIL_SIZE_NORMAL,
G_PARAM_READWRITE); 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( g_object_class_install_properties(
object_class, N_PROPERTIES, browser_properties); object_class, N_PROPERTIES, browser_properties);
@ -1703,6 +1786,7 @@ fiv_browser_init(FivBrowser *self)
g_queue_init(&self->thumbnailers_queue); g_queue_init(&self->thumbnailers_queue);
set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL); 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_padded = cairo_pattern_create_rgba(0, 0, 0, 0);
self->glow = 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, Entry e = {.thumbnail = NULL,
.uri = g_strdup(files[i].uri), .uri = g_strdup(files[i].uri),
.target_uri = g_strdup(files[i].target_uri), .target_uri = g_strdup(files[i].target_uri),
.display_name = g_strdup(files[i].display_name),
.mtime_msec = files[i].mtime_msec}; .mtime_msec = files[i].mtime_msec};
g_array_append_val(self->entries, e); g_array_append_val(self->entries, e);
} }

View File

@ -3049,6 +3049,7 @@ model_entry_finalize(FivIoModelEntry *entry)
{ {
g_free(entry->uri); g_free(entry->uri);
g_free(entry->target_uri); g_free(entry->target_uri);
g_free(entry->display_name);
g_free(entry->collate_key); g_free(entry->collate_key);
} }
@ -3171,6 +3172,7 @@ model_reload_to(FivIoModel *self, GFile *directory,
GFileEnumerator *enumerator = g_file_enumerate_children(directory, GFileEnumerator *enumerator = g_file_enumerate_children(directory,
G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
G_FILE_ATTRIBUTE_STANDARD_TARGET_URI "," G_FILE_ATTRIBUTE_STANDARD_TARGET_URI ","
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_TIME_MODIFIED ","
@ -3205,7 +3207,8 @@ model_reload_to(FivIoModel *self, GFile *directory,
FivIoModelEntry entry = {.uri = g_file_get_uri(child), FivIoModelEntry entry = {.uri = g_file_get_uri(child),
.target_uri = g_strdup(g_file_info_get_attribute_string( .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); GDateTime *mtime = g_file_info_get_modification_date_time(info);
if (mtime) { if (mtime) {
entry.mtime_msec = g_date_time_to_unix(mtime) * 1000 + entry.mtime_msec = g_date_time_to_unix(mtime) * 1000 +

View File

@ -135,9 +135,14 @@ GFile *fiv_io_model_get_previous_directory(FivIoModel *self);
/// Returns the next VFS directory in order, or NULL. /// Returns the next VFS directory in order, or NULL.
GFile *fiv_io_model_get_next_directory(FivIoModel *self); 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 { typedef struct {
gchar *uri; ///< GIO URI gchar *uri; ///< GIO URI
gchar *target_uri; ///< GIO URI for any target gchar *target_uri; ///< GIO URI for any target
gchar *display_name; ///< Label for the file
gchar *collate_key; ///< Collate key for the filename gchar *collate_key; ///< Collate key for the filename
gint64 mtime_msec; ///< Modification time in milliseconds gint64 mtime_msec; ///< Modification time in milliseconds
} FivIoModelEntry; } FivIoModelEntry;

View File

@ -25,7 +25,6 @@
struct _FivSidebar { struct _FivSidebar {
GtkScrolledWindow parent_instance; GtkScrolledWindow parent_instance;
GtkPlacesSidebar *places; GtkPlacesSidebar *places;
GtkWidget *toolbar;
GtkWidget *listbox; GtkWidget *listbox;
FivIoModel *model; FivIoModel *model;
}; };
@ -78,7 +77,7 @@ fiv_sidebar_class_init(FivSidebarClass *klass)
// You're giving me no choice, Adwaita. // You're giving me no choice, Adwaita.
// Your style is hardcoded to match against the class' CSS name. // Your style is hardcoded to match against the class' CSS name.
// And I need replicate the internal widget structure. // And I need to replicate the internal widget structure.
gtk_widget_class_set_css_name(widget_class, "placessidebar"); gtk_widget_class_set_css_name(widget_class, "placessidebar");
// TODO(p): Consider a return value, and using it. // TODO(p): Consider a return value, and using it.
@ -583,12 +582,6 @@ fiv_sidebar_init(FivSidebar *self)
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->places), gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->places),
GTK_POLICY_NEVER, GTK_POLICY_NEVER); GTK_POLICY_NEVER, GTK_POLICY_NEVER);
// None of GtkActionBar, GtkToolbar, .inline-toolbar is appropriate.
// It is either side-favouring borders or excess button padding.
self->toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
gtk_style_context_add_class(
gtk_widget_get_style_context(self->toolbar), GTK_STYLE_CLASS_TOOLBAR);
self->listbox = gtk_list_box_new(); self->listbox = gtk_list_box_new();
gtk_list_box_set_selection_mode( gtk_list_box_set_selection_mode(
GTK_LIST_BOX(self->listbox), GTK_SELECTION_NONE); GTK_LIST_BOX(self->listbox), GTK_SELECTION_NONE);
@ -602,10 +595,6 @@ fiv_sidebar_init(FivSidebar *self)
GTK_CONTAINER(superbox), GTK_WIDGET(self->places)); GTK_CONTAINER(superbox), GTK_WIDGET(self->places));
gtk_container_add( gtk_container_add(
GTK_CONTAINER(superbox), gtk_separator_new(GTK_ORIENTATION_VERTICAL)); GTK_CONTAINER(superbox), gtk_separator_new(GTK_ORIENTATION_VERTICAL));
gtk_container_add(
GTK_CONTAINER(superbox), self->toolbar);
gtk_container_add(
GTK_CONTAINER(superbox), gtk_separator_new(GTK_ORIENTATION_VERTICAL));
gtk_container_add( gtk_container_add(
GTK_CONTAINER(superbox), self->listbox); GTK_CONTAINER(superbox), self->listbox);
gtk_container_add(GTK_CONTAINER(self), superbox); gtk_container_add(GTK_CONTAINER(self), superbox);
@ -648,10 +637,3 @@ fiv_sidebar_show_enter_location(FivSidebar *self)
g_return_if_fail(FIV_IS_SIDEBAR(self)); g_return_if_fail(FIV_IS_SIDEBAR(self));
g_signal_emit_by_name(self->places, "show-enter-location"); g_signal_emit_by_name(self->places, "show-enter-location");
} }
GtkBox *
fiv_sidebar_get_toolbar(FivSidebar *self)
{
g_return_val_if_fail(FIV_IS_SIDEBAR(self), NULL);
return GTK_BOX(self->toolbar);
}

View File

@ -26,4 +26,3 @@ G_DECLARE_FINAL_TYPE(FivSidebar, fiv_sidebar, FIV, SIDEBAR, GtkScrolledWindow)
GtkWidget *fiv_sidebar_new(FivIoModel *model); GtkWidget *fiv_sidebar_new(FivIoModel *model);
void fiv_sidebar_show_enter_location(FivSidebar *self); void fiv_sidebar_show_enter_location(FivSidebar *self);
GtkBox *fiv_sidebar_get_toolbar(FivSidebar *self);

329
fiv.c
View File

@ -129,9 +129,11 @@ static struct key_group help_keys_browser[] = {
{} {}
}}, }},
{"View", (struct key[]) { {"View", (struct key[]) {
{"F7", "Toggle toolbar"},
{"F9", "Toggle navigation sidebar"}, {"F9", "Toggle navigation sidebar"},
{"F5 r <Control>r", "Reload"}, {"F5 r <Control>r", "Reload"},
{"h <Control>h", "Toggle hiding unsupported files"}, {"h <Control>h", "Toggle hiding unsupported files"},
{"t <Control>t", "Toggle showing filenames"},
{"<Control>plus", "Larger thumbnails"}, {"<Control>plus", "Larger thumbnails"},
{"<Control>minus", "Smaller thumbnails"}, {"<Control>minus", "Smaller thumbnails"},
{} {}
@ -150,7 +152,7 @@ static struct key_group help_keys_viewer[] = {
{} {}
}}, }},
{"View", (struct key[]) { {"View", (struct key[]) {
{"F9", "Toggle toolbar"}, {"F7", "Toggle toolbar"},
{"F5 r <Primary>r", "Reload"}, {"F5 r <Primary>r", "Reload"},
{} {}
}}, }},
@ -523,6 +525,33 @@ show_about_dialog(GtkWidget *parent)
// by some minor amount of pixels, margin-wise. // by some minor amount of pixels, margin-wise.
#define B make_toolbar_button #define B make_toolbar_button
#define T make_toolbar_toggle #define T make_toolbar_toggle
#define R make_toolbar_radio
#define BROWSEBAR(XX) \
XX(SIDEBAR, T("sidebar-show-symbolic", "Show sidebar")) \
XX(S1, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
XX(DIR_PREVIOUS, B("go-previous-symbolic", "Previous directory")) \
XX(DIR_NEXT, B("go-next-symbolic", "Next directory")) \
XX(S2, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
XX(PLUS, B("zoom-in-symbolic", "Larger thumbnails")) \
XX(MINUS, B("zoom-out-symbolic", "Smaller thumbnails")) \
XX(S3, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
XX(FILENAMES, T("text-symbolic", "Show filenames")) \
XX(FILTER, T("funnel-symbolic", "Hide unsupported files")) \
XX(S4, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
XX(SORT_DIR, B("view-sort-ascending-symbolic", "Sort ascending")) \
XX(SORT_NAME, R("Name", "Sort by filename")) \
XX(SORT_TIME, R("Time", "Sort by time of last modification")) \
XX(S5, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
/* We are YouTube. */ \
XX(FULLSCREEN, B("view-fullscreen-symbolic", "Fullscreen"))
enum {
#define XX(id, constructor) BROWSEBAR_ ## id,
BROWSEBAR(XX)
#undef XX
BROWSEBAR_COUNT
};
#define TOOLBAR(XX) \ #define TOOLBAR(XX) \
XX(BROWSE, B("view-grid-symbolic", "Browse")) \ XX(BROWSE, B("view-grid-symbolic", "Browse")) \
XX(FILE_PREVIOUS, B("go-previous-symbolic", "Previous file")) \ XX(FILE_PREVIOUS, B("go-previous-symbolic", "Previous file")) \
@ -585,11 +614,8 @@ struct {
GtkWidget *browser_paned; GtkWidget *browser_paned;
GtkWidget *browser_sidebar; GtkWidget *browser_sidebar;
GtkWidget *plus; GtkWidget *browser_toolbar;
GtkWidget *minus; GtkWidget *browsebar[BROWSEBAR_COUNT];
GtkWidget *funnel;
GtkWidget *sort_field[FIV_IO_MODEL_SORT_COUNT];
GtkWidget *sort_direction[2];
GtkWidget *browser_scroller; GtkWidget *browser_scroller;
GtkWidget *browser; GtkWidget *browser;
@ -771,6 +797,13 @@ on_model_files_changed(FivIoModel *model, G_GNUC_UNUSED gpointer user_data)
g.toolbar[TOOLBAR_FILE_NEXT], g.files->len > 1); g.toolbar[TOOLBAR_FILE_NEXT], g.files->len > 1);
} }
static void
on_sidebar_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data)
{
gboolean active = gtk_toggle_button_get_active(button);
gtk_widget_set_visible(g.browser_sidebar, active);
}
static void static void
on_filtering_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data) on_filtering_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data)
{ {
@ -779,8 +812,19 @@ on_filtering_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data)
} }
static void static void
on_sort_field(G_GNUC_UNUSED GtkMenuItem *item, gpointer data) on_filenames_toggled(GtkToggleButton *button, G_GNUC_UNUSED gpointer user_data)
{ {
gboolean active = gtk_toggle_button_get_active(button);
g_object_set(g.browser, "show-labels", active, NULL);
}
static void
on_sort_field(G_GNUC_UNUSED GtkToggleButton *button, gpointer data)
{
gboolean active = gtk_toggle_button_get_active(button);
if (!active)
return;
int old = -1, new = (int) (intptr_t) data; int old = -1, new = (int) (intptr_t) data;
g_object_get(g.model, "sort-field", &old, NULL); g_object_get(g.model, "sort-field", &old, NULL);
if (old != new) if (old != new)
@ -788,12 +832,12 @@ on_sort_field(G_GNUC_UNUSED GtkMenuItem *item, gpointer data)
} }
static void static void
on_sort_direction(G_GNUC_UNUSED GtkMenuItem *item, gpointer data) on_sort_direction(G_GNUC_UNUSED GtkToggleButton *button,
G_GNUC_UNUSED gpointer data)
{ {
gboolean old = FALSE, new = (gboolean) (intptr_t) data; gboolean old = FALSE;
g_object_get(g.model, "sort-descending", &old, NULL); g_object_get(g.model, "sort-descending", &old, NULL);
if (old != new) g_object_set(g.model, "sort-descending", !old, NULL);
g_object_set(g.model, "sort-descending", new, NULL);
} }
static void static void
@ -1090,6 +1134,16 @@ on_view_drag_data_received(G_GNUC_UNUSED GtkWidget *widget,
g_strfreev(uris); g_strfreev(uris);
} }
static void
on_notify_sidebar_visible(
GObject *object, GParamSpec *param_spec, G_GNUC_UNUSED gpointer user_data)
{
gboolean b = FALSE;
g_object_get(object, g_param_spec_get_name(param_spec), &b, NULL);
gtk_toggle_button_set_active(
GTK_TOGGLE_BUTTON(g.browsebar[BROWSEBAR_SIDEBAR]), b);
}
static void static void
on_dir_previous(void) on_dir_previous(void)
{ {
@ -1133,8 +1187,20 @@ on_notify_thumbnail_size(
{ {
FivThumbnailSize size = 0; FivThumbnailSize size = 0;
g_object_get(object, g_param_spec_get_name(param_spec), &size, NULL); g_object_get(object, g_param_spec_get_name(param_spec), &size, NULL);
gtk_widget_set_sensitive(g.plus, size < FIV_THUMBNAIL_SIZE_MAX); gtk_widget_set_sensitive(
gtk_widget_set_sensitive(g.minus, size > FIV_THUMBNAIL_SIZE_MIN); g.browsebar[BROWSEBAR_PLUS], size < FIV_THUMBNAIL_SIZE_MAX);
gtk_widget_set_sensitive(
g.browsebar[BROWSEBAR_MINUS], size > FIV_THUMBNAIL_SIZE_MIN);
}
static void
on_notify_show_labels(
GObject *object, GParamSpec *param_spec, G_GNUC_UNUSED gpointer user_data)
{
gboolean show_labels = 0;
g_object_get(object, g_param_spec_get_name(param_spec), &show_labels, NULL);
gtk_toggle_button_set_active(
GTK_TOGGLE_BUTTON(g.browsebar[BROWSEBAR_FILENAMES]), show_labels);
} }
static void static void
@ -1143,7 +1209,8 @@ on_notify_filtering(
{ {
gboolean b = FALSE; gboolean b = FALSE;
g_object_get(object, g_param_spec_get_name(param_spec), &b, NULL); g_object_get(object, g_param_spec_get_name(param_spec), &b, NULL);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g.funnel), b); gtk_toggle_button_set_active(
GTK_TOGGLE_BUTTON(g.browsebar[BROWSEBAR_FILTER]), b);
} }
static void static void
@ -1152,8 +1219,8 @@ on_notify_sort_field(
{ {
gint field = -1; gint field = -1;
g_object_get(object, g_param_spec_get_name(param_spec), &field, NULL); g_object_get(object, g_param_spec_get_name(param_spec), &field, NULL);
gtk_check_menu_item_set_active( gtk_toggle_button_set_active(
GTK_CHECK_MENU_ITEM(g.sort_field[field]), TRUE); GTK_TOGGLE_BUTTON(g.browsebar[BROWSEBAR_SORT_NAME + field]), TRUE);
} }
static void static void
@ -1162,8 +1229,18 @@ on_notify_sort_descending(
{ {
gboolean b = FALSE; gboolean b = FALSE;
g_object_get(object, g_param_spec_get_name(param_spec), &b, NULL); g_object_get(object, g_param_spec_get_name(param_spec), &b, NULL);
gtk_check_menu_item_set_active(
GTK_CHECK_MENU_ITEM(g.sort_direction[b]), TRUE); const char *title = b
? "Sort ascending"
: "Sort descending";
const char *name = b
? "view-sort-ascending-symbolic"
: "view-sort-descending-symbolic";
GtkButton *button = GTK_BUTTON(g.browsebar[BROWSEBAR_SORT_DIR]);
GtkImage *image = GTK_IMAGE(gtk_button_get_image(button));
gtk_widget_set_tooltip_text(GTK_WIDGET(button), title);
gtk_image_set_from_icon_name(image, name, GTK_ICON_SIZE_BUTTON);
} }
static void static void
@ -1187,9 +1264,14 @@ on_window_state_event(G_GNUC_UNUSED GtkWidget *widget,
? "view-restore-symbolic" ? "view-restore-symbolic"
: "view-fullscreen-symbolic"; : "view-fullscreen-symbolic";
GtkButton *button = GTK_BUTTON(g.toolbar[TOOLBAR_FULLSCREEN]); gtk_image_set_from_icon_name(
GtkImage *image = GTK_IMAGE(gtk_button_get_image(button)); GTK_IMAGE(gtk_button_get_image(
gtk_image_set_from_icon_name(image, name, GTK_ICON_SIZE_BUTTON); GTK_BUTTON(g.toolbar[TOOLBAR_FULLSCREEN]))),
name, GTK_ICON_SIZE_BUTTON);
gtk_image_set_from_icon_name(
GTK_IMAGE(gtk_button_get_image(
GTK_BUTTON(g.browsebar[BROWSEBAR_FULLSCREEN]))),
name, GTK_ICON_SIZE_BUTTON);
} }
static void static void
@ -1287,7 +1369,7 @@ on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
case GDK_CONTROL_MASK | GDK_SHIFT_MASK: case GDK_CONTROL_MASK | GDK_SHIFT_MASK:
switch (event->keyval) { switch (event->keyval) {
case GDK_KEY_h: case GDK_KEY_h:
gtk_button_clicked(GTK_BUTTON(g.funnel)); gtk_button_clicked(GTK_BUTTON(g.browsebar[BROWSEBAR_FILTER]));
return TRUE; return TRUE;
case GDK_KEY_l: case GDK_KEY_l:
fiv_sidebar_show_enter_location(FIV_SIDEBAR(g.browser_sidebar)); fiv_sidebar_show_enter_location(FIV_SIDEBAR(g.browser_sidebar));
@ -1384,7 +1466,7 @@ on_key_press_view(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
switch (event->state & gtk_accelerator_get_default_mod_mask()) { switch (event->state & gtk_accelerator_get_default_mod_mask()) {
case 0: case 0:
switch (event->keyval) { switch (event->keyval) {
case GDK_KEY_F9: case GDK_KEY_F7:
gtk_widget_set_visible(g.view_toolbar, gtk_widget_set_visible(g.view_toolbar,
!gtk_widget_is_visible(g.view_toolbar)); !gtk_widget_is_visible(g.view_toolbar));
return TRUE; return TRUE;
@ -1421,6 +1503,9 @@ on_key_press_browser_paned(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
case GDK_KEY_r: case GDK_KEY_r:
load_directory(NULL); load_directory(NULL);
return TRUE; return TRUE;
case GDK_KEY_t:
gtk_button_clicked(GTK_BUTTON(g.browsebar[BROWSEBAR_FILENAMES]));
return TRUE;
} }
break; break;
case GDK_MOD1_MASK: case GDK_MOD1_MASK:
@ -1443,6 +1528,10 @@ on_key_press_browser_paned(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
break; break;
case 0: case 0:
switch (event->keyval) { switch (event->keyval) {
case GDK_KEY_F7:
gtk_widget_set_visible(g.browser_toolbar,
!gtk_widget_is_visible(g.browser_toolbar));
return TRUE;
case GDK_KEY_F9: case GDK_KEY_F9:
gtk_widget_set_visible(g.browser_sidebar, gtk_widget_set_visible(g.browser_sidebar,
!gtk_widget_is_visible(g.browser_sidebar)); !gtk_widget_is_visible(g.browser_sidebar));
@ -1459,12 +1548,15 @@ on_key_press_browser_paned(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
fiv_browser_select(FIV_BROWSER(g.browser), NULL); fiv_browser_select(FIV_BROWSER(g.browser), NULL);
return TRUE; return TRUE;
case GDK_KEY_h: case GDK_KEY_h:
gtk_button_clicked(GTK_BUTTON(g.funnel)); gtk_button_clicked(GTK_BUTTON(g.browsebar[BROWSEBAR_FILTER]));
return TRUE; return TRUE;
case GDK_KEY_F5: case GDK_KEY_F5:
case GDK_KEY_r: case GDK_KEY_r:
load_directory(NULL); load_directory(NULL);
return TRUE; return TRUE;
case GDK_KEY_t:
gtk_button_clicked(GTK_BUTTON(g.browsebar[BROWSEBAR_FILENAMES]));
return TRUE;
} }
} }
return FALSE; return FALSE;
@ -1543,6 +1635,79 @@ make_toolbar_toggle(const char *symbolic, const char *tooltip)
return button; return button;
} }
static GtkWidget *
make_toolbar_radio(const char *label, const char *tooltip)
{
GtkWidget *button = gtk_radio_button_new_with_label(NULL, label);
gtk_widget_set_tooltip_text(button, tooltip);
gtk_widget_set_focus_on_click(button, FALSE);
return button;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
browsebar_connect(int index, GCallback callback)
{
g_signal_connect_swapped(g.browsebar[index], "clicked", callback, NULL);
}
static GtkWidget *
make_browser_toolbar(void)
{
#define XX(id, constructor) g.browsebar[BROWSEBAR_ ## id] = constructor;
BROWSEBAR(XX)
#undef XX
// GtkStatusBar solves a problem we do not have here.
GtkWidget *browser_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_style_context_add_class(
gtk_widget_get_style_context(browser_toolbar), "fiv-toolbar");
GtkBox *box = GTK_BOX(browser_toolbar);
// Exploring different versions of awkward layouts.
for (int i = 0; i <= BROWSEBAR_S2; i++)
gtk_box_pack_start(box, g.browsebar[i], FALSE, FALSE, 0);
for (int i = BROWSEBAR_COUNT; --i >= BROWSEBAR_S5; )
gtk_box_pack_end(box, g.browsebar[i], FALSE, FALSE, 0);
GtkWidget *center = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
for (int i = BROWSEBAR_S2; ++i < BROWSEBAR_S5; )
gtk_box_pack_start(GTK_BOX(center), g.browsebar[i], FALSE, FALSE, 0);
gtk_box_set_center_widget(box, center);
g_signal_connect(g.browsebar[BROWSEBAR_SIDEBAR], "toggled",
G_CALLBACK(on_sidebar_toggled), NULL);
browsebar_connect(BROWSEBAR_DIR_PREVIOUS, G_CALLBACK(on_dir_previous));
browsebar_connect(BROWSEBAR_DIR_NEXT, G_CALLBACK(on_dir_next));
browsebar_connect(BROWSEBAR_SORT_DIR, G_CALLBACK(on_sort_direction));
browsebar_connect(BROWSEBAR_FULLSCREEN, G_CALLBACK(toggle_fullscreen));
g_signal_connect(g.browsebar[BROWSEBAR_PLUS], "clicked",
G_CALLBACK(on_toolbar_zoom), (gpointer) +1);
g_signal_connect(g.browsebar[BROWSEBAR_MINUS], "clicked",
G_CALLBACK(on_toolbar_zoom), (gpointer) -1);
g_signal_connect(g.browsebar[BROWSEBAR_FILTER], "toggled",
G_CALLBACK(on_filtering_toggled), NULL);
g_signal_connect(g.browsebar[BROWSEBAR_FILENAMES], "toggled",
G_CALLBACK(on_filenames_toggled), NULL);
GtkRadioButton *last = GTK_RADIO_BUTTON(g.browsebar[BROWSEBAR_SORT_NAME]);
for (int i = BROWSEBAR_SORT_NAME; i <= BROWSEBAR_SORT_TIME; i++) {
GtkRadioButton *radio = GTK_RADIO_BUTTON(g.browsebar[i]);
g_signal_connect(radio, "toggled", G_CALLBACK(on_sort_field),
(gpointer) (gintptr) i - BROWSEBAR_SORT_NAME);
gtk_radio_button_join_group(radio, last);
last = radio;
}
return browser_toolbar;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void static void
on_view_actions_changed(void) on_view_actions_changed(void)
{ {
@ -1688,7 +1853,8 @@ make_view_toolbar(void)
// GtkStatusBar solves a problem we do not have here. // GtkStatusBar solves a problem we do not have here.
GtkWidget *view_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); GtkWidget *view_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_name(view_toolbar, "toolbar"); gtk_style_context_add_class(
gtk_widget_get_style_context(view_toolbar), "fiv-toolbar");
GtkBox *box = GTK_BOX(view_toolbar); GtkBox *box = GTK_BOX(view_toolbar);
// Exploring different versions of awkward layouts. // Exploring different versions of awkward layouts.
@ -1779,86 +1945,27 @@ make_browser_sidebar(FivIoModel *model)
g_signal_connect(sidebar, "open-location", g_signal_connect(sidebar, "open-location",
G_CALLBACK(on_open_location), NULL); G_CALLBACK(on_open_location), NULL);
g.plus = gtk_button_new_from_icon_name("zoom-in-symbolic", g_signal_connect(sidebar, "notify::visible",
GTK_ICON_SIZE_BUTTON); G_CALLBACK(on_notify_sidebar_visible), NULL);
gtk_widget_set_tooltip_text(g.plus, "Larger thumbnails");
g_signal_connect(g.plus, "clicked",
G_CALLBACK(on_toolbar_zoom), (gpointer) +1);
g.minus = gtk_button_new_from_icon_name("zoom-out-symbolic", g_object_notify(G_OBJECT(sidebar), "visible");
GTK_ICON_SIZE_BUTTON);
gtk_widget_set_tooltip_text(g.minus, "Smaller thumbnails");
g_signal_connect(g.minus, "clicked",
G_CALLBACK(on_toolbar_zoom), (gpointer) -1);
GtkWidget *zoom_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_style_context_add_class(
gtk_widget_get_style_context(zoom_group), GTK_STYLE_CLASS_LINKED);
gtk_box_pack_start(GTK_BOX(zoom_group), g.plus, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(zoom_group), g.minus, FALSE, FALSE, 0);
g.funnel = gtk_toggle_button_new();
gtk_container_add(GTK_CONTAINER(g.funnel),
gtk_image_new_from_icon_name("funnel-symbolic", GTK_ICON_SIZE_BUTTON));
gtk_widget_set_tooltip_text(g.funnel, "Hide unsupported files");
g_signal_connect(g.funnel, "toggled",
G_CALLBACK(on_filtering_toggled), NULL);
GtkWidget *menu = gtk_menu_new();
g.sort_field[0] = gtk_radio_menu_item_new_with_mnemonic(NULL, "By _Name");
g.sort_field[1] = gtk_radio_menu_item_new_with_mnemonic(
gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(g.sort_field[0])),
"By _Modification Time");
for (int i = FIV_IO_MODEL_SORT_MIN; i <= FIV_IO_MODEL_SORT_MAX; i++) {
g_signal_connect(g.sort_field[i], "activate",
G_CALLBACK(on_sort_field), (void *) (intptr_t) i);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), g.sort_field[i]);
}
g.sort_direction[0] =
gtk_radio_menu_item_new_with_mnemonic(NULL, "_Ascending");
g.sort_direction[1] = gtk_radio_menu_item_new_with_mnemonic(
gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(g.sort_direction[0])),
"_Descending");
g_signal_connect(g.sort_direction[0], "activate",
G_CALLBACK(on_sort_direction), (void *) 0);
g_signal_connect(g.sort_direction[1], "activate",
G_CALLBACK(on_sort_direction), (void *) 1);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
gtk_menu_shell_append(GTK_MENU_SHELL(menu), g.sort_direction[0]);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), g.sort_direction[1]);
gtk_widget_show_all(menu);
GtkWidget *sort = gtk_menu_button_new();
gtk_widget_set_tooltip_text(sort, "Sort order");
gtk_button_set_image(GTK_BUTTON(sort),
gtk_image_new_from_icon_name(
"view-sort-ascending-symbolic", GTK_ICON_SIZE_BUTTON));
gtk_menu_button_set_popup(GTK_MENU_BUTTON(sort), menu);
GtkWidget *model_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_style_context_add_class(
gtk_widget_get_style_context(model_group), GTK_STYLE_CLASS_LINKED);
gtk_box_pack_start(GTK_BOX(model_group), g.funnel, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(model_group), sort, FALSE, FALSE, 0);
GtkBox *toolbar = fiv_sidebar_get_toolbar(FIV_SIDEBAR(sidebar));
gtk_box_pack_start(toolbar, zoom_group, FALSE, FALSE, 0);
gtk_box_pack_start(toolbar, model_group, FALSE, FALSE, 0);
gtk_widget_set_halign(GTK_WIDGET(toolbar), GTK_ALIGN_CENTER);
g_signal_connect(g.browser, "notify::thumbnail-size", g_signal_connect(g.browser, "notify::thumbnail-size",
G_CALLBACK(on_notify_thumbnail_size), NULL); G_CALLBACK(on_notify_thumbnail_size), NULL);
g_signal_connect(g.browser, "notify::show-labels",
G_CALLBACK(on_notify_show_labels), NULL);
g_signal_connect(model, "notify::filtering", g_signal_connect(model, "notify::filtering",
G_CALLBACK(on_notify_filtering), NULL); G_CALLBACK(on_notify_filtering), NULL);
g_signal_connect(model, "notify::sort-field", g_signal_connect(model, "notify::sort-field",
G_CALLBACK(on_notify_sort_field), NULL); G_CALLBACK(on_notify_sort_field), NULL);
g_signal_connect(model, "notify::sort-descending", g_signal_connect(model, "notify::sort-descending",
G_CALLBACK(on_notify_sort_descending), NULL); G_CALLBACK(on_notify_sort_descending), NULL);
on_toolbar_zoom(NULL, (gpointer) 0); on_toolbar_zoom(NULL, (gpointer) 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g.funnel), TRUE);
// TODO(p): Invoke sort configuration notifications explicitly. g_object_notify(G_OBJECT(g.model), "filtering");
g_object_notify(G_OBJECT(g.model), "sort-field");
g_object_notify(G_OBJECT(g.model), "sort-descending");
return sidebar; return sidebar;
} }
@ -1911,12 +2018,11 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \
@define-color fiv-semiselected \ @define-color fiv-semiselected \
mix(@theme_selected_bg_color, @content_view_bg, 0.5); \ mix(@theme_selected_bg_color, @content_view_bg, 0.5); \
fiv-view, fiv-browser { background: @content_view_bg; } \ fiv-view, fiv-browser { background: @content_view_bg; } \
placessidebar.fiv .toolbar { padding: 2px 6px; } \
placessidebar.fiv box > separator { margin: 4px 0; } \ placessidebar.fiv box > separator { margin: 4px 0; } \
#toolbar button { padding-left: 0; padding-right: 0; } \ .fiv-toolbar button { padding-left: 0; padding-right: 0; } \
#toolbar > button:first-child { padding-left: 4px; } \ .fiv-toolbar > button:first-child { padding-left: 4px; } \
#toolbar > button:last-child { padding-right: 4px; } \ .fiv-toolbar > button:last-child { padding-right: 4px; } \
#toolbar separator { \ .fiv-toolbar separator { \
background: mix(@insensitive_fg_color, \ background: mix(@insensitive_fg_color, \
@insensitive_bg_color, 0.4); margin: 6px 8px; \ @insensitive_bg_color, 0.4); margin: 6px 8px; \
} \ } \
@ -1935,6 +2041,12 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \
background-size: 40px 40px; \ background-size: 40px 40px; \
background-position: 0 0, 0 20px, 20px -20px, -20px 0px; \ 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 { \ fiv-browser.item:selected { \
color: @theme_selected_bg_color; \ color: @theme_selected_bg_color; \
border-color: @theme_selected_bg_color; \ border-color: @theme_selected_bg_color; \
@ -2140,7 +2252,7 @@ main(int argc, char *argv[])
G_CALLBACK(on_view_drag_data_received), NULL); G_CALLBACK(on_view_drag_data_received), NULL);
gtk_container_add(GTK_CONTAINER(view_scroller), g.view); gtk_container_add(GTK_CONTAINER(view_scroller), g.view);
// We need to hide it together with the separator. // We need to hide it together with its separator.
g.view_toolbar = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); g.view_toolbar = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(g.view_toolbar), gtk_box_pack_start(GTK_BOX(g.view_toolbar),
make_view_toolbar(), FALSE, FALSE, 0); make_view_toolbar(), FALSE, FALSE, 0);
@ -2180,10 +2292,23 @@ main(int argc, char *argv[])
G_CALLBACK(on_item_activated), NULL); G_CALLBACK(on_item_activated), NULL);
gtk_container_add(GTK_CONTAINER(g.browser_scroller), g.browser); gtk_container_add(GTK_CONTAINER(g.browser_scroller), g.browser);
// We need to hide it together with its separator.
g.browser_toolbar = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(g.browser_toolbar),
make_browser_toolbar(), FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(g.browser_toolbar),
gtk_separator_new(GTK_ORIENTATION_VERTICAL), FALSE, FALSE, 0);
GtkWidget *browser_right = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(browser_right),
g.browser_toolbar, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(browser_right),
g.browser_scroller, TRUE, TRUE, 0);
g.browser_sidebar = make_browser_sidebar(g.model); g.browser_sidebar = make_browser_sidebar(g.model);
g.browser_paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); g.browser_paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
gtk_paned_add1(GTK_PANED(g.browser_paned), g.browser_sidebar); gtk_paned_add1(GTK_PANED(g.browser_paned), g.browser_sidebar);
gtk_paned_add2(GTK_PANED(g.browser_paned), g.browser_scroller); gtk_paned_add2(GTK_PANED(g.browser_paned), browser_right);
g_signal_connect(g.browser_paned, "key-press-event", g_signal_connect(g.browser_paned, "key-press-event",
G_CALLBACK(on_key_press_browser_paned), NULL); G_CALLBACK(on_key_press_browser_paned), NULL);
g_signal_connect(g.browser_paned, "button-press-event", g_signal_connect(g.browser_paned, "button-press-event",
@ -2217,6 +2342,8 @@ main(int argc, char *argv[])
gtk_widget_show_all(menu_box); gtk_widget_show_all(menu_box);
gtk_widget_set_visible(g.browser_sidebar, gtk_widget_set_visible(g.browser_sidebar,
g_settings_get_boolean(settings, "show-browser-sidebar")); g_settings_get_boolean(settings, "show-browser-sidebar"));
gtk_widget_set_visible(g.browser_toolbar,
g_settings_get_boolean(settings, "show-browser-toolbar"));
gtk_widget_set_visible(g.view_toolbar, gtk_widget_set_visible(g.view_toolbar,
g_settings_get_boolean(settings, "show-view-toolbar")); g_settings_get_boolean(settings, "show-view-toolbar"));

View File

@ -25,6 +25,10 @@
<default>true</default> <default>true</default>
<summary>Show the browser's sidebar</summary> <summary>Show the browser's sidebar</summary>
</key> </key>
<key name='show-browser-toolbar' type='b'>
<default>true</default>
<summary>Show a toolbar in the browser view</summary>
</key>
<key name='show-view-toolbar' type='b'> <key name='show-view-toolbar' type='b'>
<default>true</default> <default>true</default>
<summary>Show a toolbar in the image view</summary> <summary>Show a toolbar in the image view</summary>

View File

@ -4,6 +4,7 @@
<file alias="LICENSE">../LICENSE</file> <file alias="LICENSE">../LICENSE</file>
</gresource> </gresource>
<gresource prefix="/org/gnome/design/IconLibrary/scalable/actions/"> <gresource prefix="/org/gnome/design/IconLibrary/scalable/actions/">
<file preprocess="xml-stripblanks">text-symbolic.svg</file>
<file preprocess="xml-stripblanks">circle-filled-symbolic.svg</file> <file preprocess="xml-stripblanks">circle-filled-symbolic.svg</file>
<file preprocess="xml-stripblanks">funnel-symbolic.svg</file> <file preprocess="xml-stripblanks">funnel-symbolic.svg</file>
<file preprocess="xml-stripblanks">blend-tool-symbolic.svg</file> <file preprocess="xml-stripblanks">blend-tool-symbolic.svg</file>

150
resources/text-symbolic.svg Normal file
View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="a" height="100%" width="100%" x="0%" y="0%">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="b">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/>
</g>
</mask>
<clipPath id="c">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="d">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="e">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="f">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="g">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="h">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="i">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="j">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="k">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="l">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="m">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="n">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/>
</g>
</mask>
<clipPath id="o">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="p">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/>
</g>
</mask>
<clipPath id="q">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="r">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="s">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="t">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.4"/>
</g>
</mask>
<clipPath id="u">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="v">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.4"/>
</g>
</mask>
<clipPath id="w">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="x">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="y">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<mask id="z">
<g filter="url(#a)">
<path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/>
</g>
</mask>
<clipPath id="A">
<path d="m 0 0 h 1024 v 800 h -1024 z"/>
</clipPath>
<g clip-path="url(#c)" mask="url(#b)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 562.460938 212.058594 h 10.449218 c -1.183594 0.492187 -1.296875 2.460937 0 3 h -10.449218 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#e)" mask="url(#d)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 16 632 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#g)" mask="url(#f)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 17 631 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#i)" mask="url(#h)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 18 634 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#k)" mask="url(#j)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 16 634 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#m)" mask="url(#l)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 17 635 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<g clip-path="url(#o)" mask="url(#n)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 19 635 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/>
</g>
<path d="m 6 1 l -5 14 h 3 c 1.484375 -4 0.023438 0 1.507812 -4 h 4.984376 l 1.507812 4 h 3 l -5 -14 z m 2 3 l 2.023438 5 h -4 z m 0 0" fill="#2e3436"/>
<g clip-path="url(#q)" mask="url(#p)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 136 660 v 7 h 7 v -7 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#s)" mask="url(#r)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 199 642 h 3 v 12 h -3 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#u)" mask="url(#t)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 209.5 144.160156 c 0.277344 0 0.5 0.222656 0.5 0.5 v 1 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -1 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#w)" mask="url(#v)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 206.5 144.160156 c 0.277344 0 0.5 0.222656 0.5 0.5 v 1 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -1 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#y)" mask="url(#x)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 229.5 143.160156 c -0.546875 0 -1 0.457032 -1 1 c 0 0.546875 0.453125 1 1 1 s 1 -0.453125 1 -1 c 0 -0.542968 -0.453125 -1 -1 -1 z m 0 0" fill="#2e3436"/>
</g>
<g clip-path="url(#A)" mask="url(#z)" transform="matrix(1 0 0 1 -56 -640)">
<path d="m 226.453125 143.160156 c -0.519531 0 -0.953125 0.433594 -0.953125 0.953125 v 0.09375 c 0 0.519531 0.433594 0.953125 0.953125 0.953125 h 0.09375 c 0.519531 0 0.953125 -0.433594 0.953125 -0.953125 v -0.09375 c 0 -0.519531 -0.433594 -0.953125 -0.953125 -0.953125 z m 0 0" fill="#2e3436"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

@ -1 +1 @@
Subproject commit 40ff91b31b3286aa92fd3cb4656975b275ef8b10 Subproject commit c63c4a9348fb1b52a9b60a6eb62328a97d979d9e