--- caja-1.28.0/src/timescale.c.orig 2024-02-26 08:42:41.085817612 +0100 +++ caja-1.28.0/src/timescale.c 2024-02-26 08:42:41.085766910 +0100 @@ -0,0 +1,1286 @@ +/* + * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. + * + */ + +#include "config.h" +#include "timescale.h" +#include +#include +#include +#include +#include + +#define BAR_W_MAX 20 +#define BAR_SPACE 2 + + +typedef struct +{ + char *name; + char *mountpoint; + char *mtime_str; + char *mtime_short_str; + time_t mtime; + float used_space; + char *used_space_str; + SnapType type; + char *type_str; +} Snap; + + +struct TimeScalePrivate +{ + GList* all_snaps; + GList* snaps; + int* bar_x_end; + int current_pos; + int num_snaps; + char *num_rev_string; + int current_period; + GList *today; + GList *yesterday; + GList *this_week; + GList *last_week; + GList *this_month; + GList *last_month; + gboolean scrollbar_set; + gboolean key_pressed; + GtkWidget *darea; + GtkWidget *period; + GtkWidget *info; + GtkWidget *scrolled; + GtkWidget *label_tip; +}; + +enum { + VALUE_CHANGED, + LAST_SIGNAL +}; + +enum { + ALL, + TODAY, + YESTERDAY, + THIS_WEEK, + LAST_WEEK, + THIS_MONTH, + LAST_MONTH +}; + +enum { + COLUMN_INDEX, + COLUMN_STRING +}; + + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE (TimeScale, timescale, GTK_TYPE_HBOX) + +#define TIMESCALE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_TIMESCALE, TimeScalePrivate)) + +static gboolean +timescale_expose (GtkWidget *widget, + GdkEventExpose *event, TimeScale *ts); + +static gboolean +query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer data); +static int +key_pressed (GtkWidget *widget, GdkEventKey *event, TimeScale *ts); +static void +button_pressed (GtkWidget *widget, GdkEventButton *event, TimeScale *ts); + +static void +print_snaps (GList *list) +{ + GList* tmp = list; + int i = 0; + + while (tmp) + { + Snap *snap = (Snap*) tmp->data; + printf ("=-= %d =-=\nname: %s\nmountpoint: %s\nmtime: %s\nused_space: %s\n",i, + snap->name, snap->mountpoint, snap->mtime_str, snap->used_space_str); + i++; + tmp = tmp->next; + } +} + +static char * +get_num_snap_string (GList *snap_list) +{ + goffset total = 0; + int i = 0; + GList *tmp = snap_list; + char *num_rev; + + char *size_str = NULL; + + for (tmp; tmp; tmp = tmp->next) + { + Snap *snap = ((Snap*) tmp->data); + total += snap->used_space; + i++; + } + + total *= 1024; + + size_str = g_format_size (total); + + /* SUN_BRANDING */ + num_rev = g_strdup_printf (_("%d %s\n%s"), + i - 1 /* Account for "Now" snapshot */, + /* SUN_BRANDING */ + ngettext ("snapshot", "snapshots", i), + size_str); + g_free (size_str); + + return num_rev; +} + + +static char * +get_date (GDate *date) +{ + return g_strdup_printf ("%d/%d/%d", g_date_get_day (date), + g_date_get_month (date), + g_date_get_year (date)); +} + +static GList * +trim_list_by_date (GList *list, int type) +{ + GDate then; + GDate now; + GDate range_min; + GDate range_max; + GDateWeekday weekday; + + time_t time_now; + int diff = 0; + time_now = time (NULL); + g_date_set_time_t (&now, time_now); + g_date_set_time_t (&range_min, time_now); + GList *return_list = NULL; + int days_diff = 0; + gboolean range = FALSE; + + switch (type) + { + case TODAY: + days_diff = 0; + break; + case YESTERDAY: + days_diff = 1; + break; + case THIS_WEEK: + weekday = g_date_get_weekday(&now); + days_diff = weekday - G_DATE_MONDAY; + memcpy (&range_max, &range_min, sizeof (GDate)); + g_date_subtract_days (&range_min, days_diff); + range = TRUE; + break; + case LAST_WEEK: + g_date_subtract_days (&range_min, 7); + weekday = g_date_get_weekday(&range_min); + days_diff = weekday - G_DATE_MONDAY; + g_date_subtract_days (&range_min, days_diff); + memcpy (&range_max, &range_min, sizeof (GDate)); + g_date_add_days (&range_max, 6); + range = TRUE; + break; + case THIS_MONTH: + g_date_set_dmy (&range_min, 1, g_date_get_month (&now), + g_date_get_year (&now)); + g_date_set_time_t (&range_max, time_now); + range = TRUE; + break; + case LAST_MONTH: + g_date_subtract_months (&range_min, 1); + g_date_set_dmy (&range_min, 1, + g_date_get_month (&range_min), + g_date_get_year (&range_min)); + memcpy (&range_max, &range_min, sizeof (GDate)); + g_date_add_days (&range_max, g_date_get_days_in_month (g_date_get_month (&range_min), g_date_get_year (&range_min)) - 1); + range = TRUE; + break; + } + + while (list) + { + Snap* snap = (Snap*) list->data; + + if (snap->mtime != 0) + { + g_date_set_time_t (&then, snap->mtime); + + if (!range) + { + if (g_date_get_julian (&now) - g_date_get_julian (&then) == days_diff) + return_list = g_list_append (return_list, snap); + } + else + { + if (g_date_compare (&then, &range_min) >= 0 && g_date_compare (&then, &range_max) <= 0) + return_list = g_list_append (return_list, snap); + } + } + list = list->next; + } + return return_list; +} + +static void +free_periods (TimeScale *ts) +{ + if (ts->priv->today) + { + g_list_free (ts->priv->today); + ts->priv->today = NULL; + } + if (ts->priv->yesterday) + { + g_list_free (ts->priv->yesterday); + ts->priv->yesterday = NULL; + } + if (ts->priv->this_week) + { + g_list_free (ts->priv->this_week); + ts->priv->this_week = NULL; + } + if (ts->priv->last_week) + { + g_list_free (ts->priv->last_week); + ts->priv->last_week = NULL; + } + if (ts->priv->this_month) + { + g_list_free (ts->priv->this_month); + ts->priv->this_month = NULL; + } + if (ts->priv->last_month) + { + g_list_free (ts->priv->last_month); + ts->priv->last_month = NULL; + } +} + +static GtkListStore * +create_periods (TimeScale *ts) +{ + GtkTreeIter iter; + GtkListStore* periods = gtk_list_store_new (3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_POINTER); + + free_periods (ts); + + gtk_list_store_append (periods, &iter); + gtk_list_store_set (periods, &iter, 0, ALL, 1, _("All"), 2, ts->priv->all_snaps, -1); + + ts->priv->today = trim_list_by_date (ts->priv->all_snaps, TODAY); + + if (ts->priv->today) + { + gtk_list_store_append (periods, &iter); + gtk_list_store_set (periods, &iter, 0, TODAY, 1, _("Today"), 2, ts->priv->today, -1); + } + + ts->priv->yesterday = trim_list_by_date (ts->priv->all_snaps, YESTERDAY); + if (ts->priv->yesterday) + { + gtk_list_store_append (periods, &iter); + gtk_list_store_set (periods, &iter, 0, YESTERDAY, 1, _("Yesterday"), 2, ts->priv->yesterday, -1); + } + + ts->priv->this_week = trim_list_by_date (ts->priv->all_snaps, THIS_WEEK); + if (ts->priv->this_week) + { + gtk_list_store_append (periods, &iter); + gtk_list_store_set (periods, &iter, 0, THIS_WEEK, 1, _("This Week"), 2, ts->priv->this_week, -1); + } + + ts->priv->last_week = trim_list_by_date (ts->priv->all_snaps, LAST_WEEK); + if (ts->priv->last_week) + { + gtk_list_store_append (periods, &iter); + gtk_list_store_set (periods, &iter, 0, LAST_WEEK, 1, _("Last Week"), 2, ts->priv->last_week, -1); + } + + ts->priv->this_month = trim_list_by_date (ts->priv->all_snaps, THIS_MONTH); + if (ts->priv->this_month) + { + gtk_list_store_append (periods, &iter); + gtk_list_store_set (periods, &iter, 0, THIS_MONTH, 1, _("This Month"), 2, ts->priv->this_month, -1); + } + + ts->priv->last_month = trim_list_by_date (ts->priv->all_snaps, LAST_MONTH); + if (ts->priv->last_month) + { + gtk_list_store_append (periods, &iter); + gtk_list_store_set (periods, &iter, 0, LAST_MONTH, 1, _("Last Month"), 2, ts->priv->last_month, -1); + } + + return periods; +} + +static void +period_changed (GtkComboBox *combo, + TimeScale *ts) +{ + gint type; + GtkTreePath *path; + GtkTreeModel *model; + GtkTreeIter iter; + GList *period; + + if (!gtk_combo_box_get_active_iter (combo, &iter)) + return; + + model = gtk_combo_box_get_model (combo); + + gtk_tree_model_get (model, &iter, 0, &type, 2, &period, -1); + + if (ts->priv->current_period == type) + return; + + ts->priv->current_period = type; + + ts->priv->snaps = period; + + ts->priv->num_snaps = g_list_length (ts->priv->snaps); + if (ts->priv->bar_x_end) + g_free (ts->priv->bar_x_end); + + ts->priv->bar_x_end = g_new (int, g_list_length (ts->priv->snaps)); + + ts->priv->current_pos = -1; + + if (ts->priv->num_rev_string) + g_free (ts->priv->num_rev_string); + + ts->priv->num_rev_string = get_num_snap_string (ts->priv->snaps); + gtk_label_set_label (GTK_LABEL (ts->priv->info), ts->priv->num_rev_string); + + gtk_widget_set_size_request (ts->priv->darea, ((BAR_SPACE + BAR_W_MAX ) * ts->priv->num_snaps) + PADDING * 2, 60); + + gtk_widget_queue_draw (ts->priv->darea); +} + +static void +timescale_init (TimeScale *ts) +{ + GtkWidget *vbox; + GtkCellRenderer *renderer; + + ts->priv = TIMESCALE_GET_PRIVATE (ts); + ts->priv->snaps = NULL; + ts->priv->all_snaps = NULL; + ts->priv->num_snaps = 0; + ts->priv->bar_x_end = NULL; + ts->priv->current_pos = 0; + ts->priv->num_rev_string = NULL; + ts->priv->current_period = ALL; + ts->priv->today = NULL; + ts->priv->yesterday = NULL; + ts->priv->this_week = NULL; + ts->priv->last_week = NULL; + ts->priv->this_month = NULL; + ts->priv->last_month = NULL; + ts->priv->key_pressed = FALSE; + + gtk_box_set_homogeneous (GTK_BOX (ts), FALSE); + + /* setup drawing area */ + + ts->priv->darea = gtk_drawing_area_new (); + + g_signal_connect(ts->priv->darea, "draw", + G_CALLBACK(timescale_expose), ts); + + g_signal_connect (ts->priv->darea, "query-tooltip", + G_CALLBACK (query_tooltip), ts); + + g_signal_connect(ts->priv->darea, "key-press-event", + G_CALLBACK(key_pressed), ts); + g_signal_connect(ts->priv->darea, "button_press_event", + G_CALLBACK(button_pressed), ts); + gtk_widget_set_can_focus(GTK_WIDGET (ts->priv->darea), TRUE); + gtk_widget_add_events (GTK_WIDGET (ts->priv->darea), GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK); + g_object_set (G_OBJECT (ts->priv->darea), "has-tooltip", TRUE, NULL); + gtk_widget_set_size_request (GTK_WIDGET (ts->priv->darea), -1, 60); + + ts->priv->scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ts->priv->scrolled), + GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (ts->priv->scrolled), + ts->priv->darea); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (ts->priv->scrolled))), GTK_SHADOW_NONE); + gtk_widget_show (ts->priv->scrolled); + + ts->priv->label_tip = gtk_label_new ("Hello"); + gtk_widget_set_name (ts->priv->label_tip, "gtk-tooltip"); + g_object_ref_sink (ts->priv->label_tip); + gtk_label_set_justify (GTK_LABEL (ts->priv->label_tip), GTK_JUSTIFY_CENTER); + + /* setup period combo and snap info */ + + vbox = gtk_vbox_new (FALSE, 5); + ts->priv->period = gtk_combo_box_new (); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ts->priv->period), + renderer, + TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (ts->priv->period), renderer, + "text", 1, + NULL); + g_signal_connect (ts->priv->period, "changed", G_CALLBACK (period_changed), ts); + + ts->priv->info = gtk_label_new ("info\ninfo"); + gtk_label_set_justify (GTK_LABEL (ts->priv->info), GTK_JUSTIFY_CENTER); + gtk_box_pack_start (GTK_BOX (vbox), ts->priv->period, FALSE, FALSE, 5); + gtk_box_pack_end (GTK_BOX (vbox), ts->priv->info, FALSE, FALSE, 5); + + /* setup container */ + + gtk_box_pack_start (GTK_BOX (ts), vbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (ts), ts->priv->scrolled, TRUE, TRUE, 0); + + gtk_widget_set_can_focus(GTK_WIDGET (ts), TRUE); + gtk_widget_show_all (GTK_WIDGET (ts)); +} + +static float +get_max_snap_size (GList *snaps) +{ + Snap *snap; + float max_size = 0; + while (snaps) + { + GList *next = snaps->next; + snap = (Snap*) snaps->data; + if (snap->used_space > max_size) + max_size = snap->used_space; + snaps = next; + } + return max_size; +} + +static Snap* +get_now_snap (TimeScale* ts) +{ + Snap *last_snap; + last_snap = g_new0 (Snap, 1); + last_snap->name = g_strdup (_("Now")); + last_snap->mountpoint = g_strdup (_("None")); + last_snap->mtime_str = g_strdup (_("Now")); + last_snap->mtime_short_str = g_strdup (_("Now")); + last_snap->used_space_str = g_strdup (_("-")); + last_snap->used_space = 0.0; + last_snap->type = NOW; + last_snap->type_str = g_strdup (_("Current Directory")); + return last_snap; +} + +static char * +get_type_str (SnapType type) +{ + switch (type) + { + case LOCAL_AUTOMATIC: + return g_strdup (_("Automatic Snapshot")); + break; + case LOCAL_MANUAL: + return g_strdup (_("Manual Snapshot")); + break; + case REMOTE_AUTOMATIC: + return g_strdup (_("Automatic Remote Backup")); + break; + case REMOTE_MANUAL: + return g_strdup (_("Manual Remote Backup")); + break; + default: + break; + } + return NULL; +} + +static SnapType +get_type (ZfsDataSet* snap) +{ + if (snap->type) + { + if (strstr (snap->name, "zfs-auto-snap")) + return LOCAL_AUTOMATIC; + else + return LOCAL_MANUAL; + } + else + { + if (strstr (snap->name, "zfs-auto-snap")) + return REMOTE_AUTOMATIC; + else + return REMOTE_MANUAL; + } +} + +static GList * +copy_zfs_list (GList *list) +{ + ZfsDataSet *snap; + GList *new_list = NULL; + Snap *ts_shot; + GList *tmp_list = list; + while (tmp_list) + { + snap = (ZfsDataSet*) tmp_list->data; + ts_shot = g_new0 (Snap, 1); + ts_shot->name = g_strdup (snap->name); + ts_shot->mountpoint = g_strdup (snap->mountpoint); + ts_shot->mtime_str = g_strdup (snap->mtime_str); + ts_shot->mtime_short_str = caja_date_as_string (snap->mtime, TRUE); + ts_shot->used_space_str = g_strdup (snap->used_space_str); + ts_shot->used_space = snap->used_space; + ts_shot->mtime = snap->mtime; + ts_shot->type = get_type (snap); + ts_shot->type_str = get_type_str (ts_shot->type); + new_list = g_list_append (new_list, ts_shot); + tmp_list = tmp_list->next; + } + return new_list; +} + +static void +free_snap (Snap *snap) +{ + if (snap->name) + g_free (snap->name); + if (snap->mountpoint) + g_free (snap->mountpoint); + if (snap->mtime_str) + g_free (snap->mtime_str); + if (snap->mtime_short_str) + g_free (snap->mtime_short_str); + if (snap->used_space_str) + g_free (snap->used_space_str); + if (snap->type_str) + g_free (snap->type_str); + + g_free (snap); +} + +static void +free_snap_list (GList *list) +{ + GList *tmp_list = list; + while (tmp_list) + { + free_snap ((Snap*) tmp_list->data); + tmp_list = tmp_list->next; + } +} + + +void +timescale_set_snapshots (TimeScale* ts, GList *list, int init_position) +{ + if (ts->priv->bar_x_end) + g_free (ts->priv->bar_x_end); + + if (ts->priv->num_rev_string) + g_free (ts->priv->num_rev_string); + + if (ts->priv->all_snaps) + free_snap_list (ts->priv->all_snaps); + + ts->priv->all_snaps = copy_zfs_list (list); + ts->priv->all_snaps = g_list_append (ts->priv->all_snaps, get_now_snap(ts)); + ts->priv->snaps = ts->priv->all_snaps; + ts->priv->num_snaps = g_list_length (ts->priv->snaps); + + ts->priv->bar_x_end = g_new (int, g_list_length (ts->priv->snaps)); + ts->priv->current_pos = ts->priv->num_snaps - 1; + + if (init_position >= 0 && init_position <= ts->priv->num_snaps - 1) + ts->priv->current_pos = init_position; + + ts->priv->num_rev_string = get_num_snap_string (ts->priv->snaps); + + gtk_label_set_label (GTK_LABEL (ts->priv->info), ts->priv->num_rev_string); + ts->priv->current_period = ALL; + + gtk_combo_box_set_model (GTK_COMBO_BOX (ts->priv->period), GTK_TREE_MODEL (create_periods (ts))); + gtk_combo_box_set_active (GTK_COMBO_BOX (ts->priv->period), ALL); + + gtk_widget_set_size_request (ts->priv->darea, ((BAR_SPACE + BAR_W_MAX) * ts->priv->num_snaps) + PADDING * 2, 60); + ts->priv->scrollbar_set = FALSE; +} + +GtkWidget* +timescale_new () +{ + return g_object_new (TYPE_TIMESCALE, NULL); +} + +static void +draw_type (cairo_t *cr, int x_c, int y_c, int w, SnapType type) +{ + int x = x_c - w/2; + int y = y_c - w/2; + + + + switch (type) + { + case LOCAL_MANUAL: + cairo_move_to (cr,x,y_c); + cairo_line_to (cr,x+w/2,y_c-w/2); + cairo_line_to (cr,x+w,y_c); + /*y = y_c - w/4; + cairo_rectangle (cr, x, y, w, w/2);*/ + break; + case LOCAL_AUTOMATIC: + cairo_arc (cr, x_c, y_c, w/2, M_PI, M_PI*2); + break; + case REMOTE_MANUAL: + /* cairo_rectangle (cr, x, y, w, w); */ + cairo_move_to (cr,x,y_c); + cairo_line_to (cr,x+w/2,y_c+w/2); + cairo_line_to (cr,x+w,y_c); + cairo_line_to (cr,x+w/2,y_c-w/2); + break; + case REMOTE_AUTOMATIC: + cairo_arc (cr, x_c, y_c, w/2, 0.0, M_PI*2); + break; + default: + break; + } +} + +static void +draw_rounded_bar (cairo_t *cr, int x, int y, double w, double h, int r) +{ + /* bottom y instead of top */ + y -= h; + + if (h == 0 || h < (w / 2) + r) + { + y += h; + cairo_arc (cr, x+(w/2), y+.5, w/2, M_PI, M_PI*2); + cairo_line_to (cr,x,y+.5); + return; + } + + /* A ****BQ + H C + * * + G D + F **** E */ + + + cairo_arc (cr, x+(w/2), y+(w/2), w/2, M_PI, M_PI*2); /* arc from H to C */ + cairo_line_to (cr,x+w,y+h-r); /* Move to D */ + cairo_curve_to(cr, x+w,y+h,x+w,y+h,x+w-r,y+h); /* Curve to E */ + cairo_line_to(cr, x+r,y+h); /* Line to F */ + cairo_curve_to(cr, x,y+h,x,y+h,x,y+h-r); /* Curve to G */ + cairo_line_to(cr, x,y+(w/2)); /* Line to H */ + +} + + + +static void +draw_rounded_rec (cairo_t *cr, int x, int y, double w, double h, int r) +{ + /* bottom y instead of top */ + y -= h; + + if (h == 0) + { + cairo_set_line_width (cr, 1); + cairo_move_to (cr,x,y - .5); + cairo_line_to (cr, x + w, y -.5); + return; + } + + if (h < r * 2) + r = h / 2; + + /* A****BQ + H C + * * + G D + F****E */ + + cairo_move_to (cr,x+r,y); /* Move to A */ + cairo_line_to (cr,x+w-r,y); /* Straight line to B */ + cairo_curve_to (cr,x+w,y,x+w,y,x+w,y+r); /* Curve to C, Control points are both at Q */ + cairo_line_to (cr,x+w,y+h-r); /* Move to D */ + cairo_curve_to(cr, x+w,y+h,x+w,y+h,x+w-r,y+h); /* Curve to E */ + cairo_line_to(cr, x+r,y+h); /* Line to F */ + cairo_curve_to(cr, x,y+h,x,y+h,x,y+h-r); /* Curve to G */ + cairo_line_to(cr, x,y+r); /* Line to H */ + cairo_curve_to(cr, x,y,x,y,x+r,y); /* Curve to A */ +} + + static void +set_cr_color (GtkWidget *widget, cairo_t* cr, SnapType type, double alpha) +{ + GtkStyle * s; + + switch (type) + { + case LOCAL_MANUAL: + case REMOTE_MANUAL: + s = gtk_widget_get_style(widget); + gdk_cairo_set_source_color (cr, &s->dark[GTK_STATE_SELECTED]); + break; + case LOCAL_AUTOMATIC: + case REMOTE_AUTOMATIC: + s = gtk_widget_get_style(widget); + gdk_cairo_set_source_color (cr, &s->light[GTK_STATE_SELECTED]); + break; + case NOW: + s = gtk_widget_get_style(widget); + gdk_cairo_set_source_color (cr, &s->black); + break; + default: + break; + } +} + +int get_snap_index_from_coord (TimeScale* ts, gdouble x, gdouble y) +{ + int i; + + for (i = 0; i < ts->priv->num_snaps; i++) + { + if (x < ts->priv->bar_x_end[i]) + return i; + } + return -1; +} + +static gboolean +timescale_expose (GtkWidget *widget, + GdkEventExpose *event, TimeScale* ts) +{ + PangoContext *context; + PangoFontMetrics *metrics; + PangoRectangle logical_rect; + gint ascent, descent; + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget)); + int i = 0; + double x; + int y; + int selected_x = -1; + int remaining_timeline_space; + int remaining_x_start; + int remaining_x_end; + int bar_space = BAR_SPACE; + int bar_w = BAR_W_MAX; + int line_padding = 3; + float bar_max_h_inc; + float bar_max_h; + int last_bar_x; + int line_height = 0; + int timeline_line_height = 0; + float max_size = 0; + GList *view_snaps = NULL; + int num_view_snaps = 0; + PangoLayout *layout = pango_cairo_create_layout (cr); + Snap *tmp_snap = NULL; + gboolean space_left = TRUE; + PangoFontDescription *timeline_font = NULL; + + if (gtk_widget_has_focus (widget)) { + gtk_paint_focus (gtk_widget_get_style (widget), cr, gtk_widget_get_state (widget), + widget, NULL, + 0, 0, + gtk_widget_get_allocated_width(widget)-1, gtk_widget_get_allocated_height(widget)-1); + } + + if (!ts->priv->snaps) + goto end; + + /* smaller font for timeline */ + + timeline_font = pango_font_description_copy_static ((gtk_widget_get_style (widget))->font_desc); + pango_font_description_set_size (timeline_font, pango_font_description_get_size (timeline_font) - (PANGO_SCALE*2)); + + /* determine space needed for 2 lines of text + padding with + * current widget->style->font_desc + * and widget->style->font_desc - 1*/ + + context = gtk_widget_get_pango_context (widget); + metrics = pango_context_get_metrics (context, (gtk_widget_get_style (widget))->font_desc, + pango_context_get_language (context)); + ascent = pango_font_metrics_get_ascent (metrics); + descent = pango_font_metrics_get_descent (metrics); + pango_font_metrics_unref (metrics); + + line_height = PANGO_PIXELS (ascent + descent); + + metrics = pango_context_get_metrics (context, timeline_font, + pango_context_get_language (context)); + ascent = pango_font_metrics_get_ascent (metrics); + descent = pango_font_metrics_get_descent (metrics); + pango_font_metrics_unref (metrics); + + timeline_line_height = PANGO_PIXELS (ascent + descent); + + + x = PADDING; + bar_max_h = gtk_widget_get_allocated_height(widget) - ((line_height + timeline_line_height) + PADDING * 4); + y = gtk_widget_get_allocated_height(widget) - (line_height + (PADDING * 2)); + + num_view_snaps = ts->priv->num_snaps; + view_snaps = ts->priv->snaps; + + max_size = log ((float)get_max_snap_size (view_snaps)); + + bar_max_h_inc = max_size / bar_max_h; + + if (!ts->priv->scrollbar_set) + { + GtkAdjustment *adj; + adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled)); + gtk_adjustment_set_value (adj, gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size (adj)); + gtk_adjustment_set_step_increment(adj, BAR_W_MAX + BAR_SPACE); + gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj); + ts->priv->scrollbar_set = TRUE; + } + + /* draw the bars */ + + cairo_set_line_width (cr, 1); + + for (i = 0; i < num_view_snaps; i++) + { + tmp_snap = g_list_nth_data (view_snaps, i); + int rounded_radius = ROUNDED_RADIUS; + int height = 0; + gboolean draw_bar = TRUE; + double alpha = 1.0; + + if (tmp_snap->used_space != 0) + height = (int) log (tmp_snap->used_space) / bar_max_h_inc; + + /*printf ("drawing %d height %d size %f log of size %f\n", i, + height, + tmp_snap->used_space, log (tmp_snap->used_space));*/ + + if (height == 0 & tmp_snap->used_space != 0) + height = 1; + + if (tmp_snap->type == REMOTE_AUTOMATIC || + tmp_snap->type == REMOTE_MANUAL) + height = bar_max_h - bar_max_h_inc; /* placeholder until we get rsync size */ + + if (height < ROUNDED_RADIUS) + height = 0; + + /* printf ("height %d name %s size %s\n", height, tmp_snap->name, tmp_snap->used_space_str); */ + + if (i == ts->priv->current_pos) + { + + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1); + draw_rounded_rec (cr, x-1, y+2, bar_w+2, bar_max_h+8, ROUNDED_RADIUS); + + cairo_fill (cr); + cairo_stroke(cr); + + alpha = 0.5; + selected_x = x-1 + ((bar_w+2) / 2); + + if (tmp_snap->type != NOW) + { + char *selected_time_size = g_strdup_printf ("%s - %s", + tmp_snap->mtime_str, + tmp_snap->used_space_str); + gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text); + pango_layout_set_font_description (layout, gtk_widget_get_style(widget)->font_desc); + pango_layout_set_text (layout, selected_time_size, -1); + g_free (selected_time_size); + } + else + pango_layout_set_text (layout, tmp_snap->mtime_str, -1); + } + + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.8); + + ts->priv->bar_x_end[i] = x + bar_w; + + if (draw_bar) + { + int tmp_y = y; + float tmp_w = bar_w; + set_cr_color (widget, cr, tmp_snap->type, 1.0); + if (tmp_snap->type == LOCAL_AUTOMATIC || tmp_snap->type == LOCAL_MANUAL) + { + tmp_y -= 1; + tmp_w -= 1; + } + + if (tmp_snap->type == LOCAL_AUTOMATIC || tmp_snap->type == REMOTE_AUTOMATIC) + draw_rounded_bar (cr, x, tmp_y, tmp_w, height, rounded_radius); + else if (tmp_snap->type == LOCAL_MANUAL || tmp_snap->type == REMOTE_MANUAL) + draw_rounded_rec (cr, x, tmp_y, tmp_w, height, rounded_radius); + else /* Now Shape */ + draw_type (cr, (x + ((bar_w+bar_space)/2)) - .5, y - (bar_max_h/2), bar_w - 4, REMOTE_MANUAL); + + if (tmp_snap->type == REMOTE_MANUAL || tmp_snap->type == REMOTE_AUTOMATIC || tmp_snap->type == NOW) + cairo_fill (cr); + } + + cairo_stroke(cr); + + x += bar_w + bar_space; + } + + last_bar_x = x; + + /* ensure selected bar is visible on key press when scrollbar is enabled */ + + if (ts->priv->current_pos != -1 && ts->priv->key_pressed) + { + + GtkAdjustment *adj; + adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled)); + ts->priv->key_pressed = FALSE; + + if (gtk_adjustment_get_value (adj) > selected_x) + { + gtk_adjustment_set_value (adj, selected_x - BAR_W_MAX); + gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj); + } + if (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj) < selected_x) + { + gtk_adjustment_set_value (adj, (selected_x + BAR_W_MAX) - gtk_adjustment_get_page_size (adj)); + gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj); + } + } + + pango_layout_set_font_description (layout, gtk_widget_get_style(widget)->font_desc); + + /* try to center the selected text */ + + pango_layout_get_pixel_extents (layout,NULL, &logical_rect); + + if (ts->priv->current_pos != -1 && selected_x != -1) + { + + int right_space = last_bar_x - selected_x; + + if (logical_rect.width /2 > selected_x) + /* no space on the left, left align */ + selected_x = PADDING; + else if (logical_rect.width /2 > right_space) + /* no space on the right, right align */ + selected_x = last_bar_x - logical_rect.width; + else + selected_x -= logical_rect.width /2; + + if (selected_x < 0) + selected_x = PADDING; + + /* draw background */ + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1); + draw_rounded_rec (cr, selected_x-2, line_height + 3 , logical_rect.width + 4, line_height+1, ROUNDED_RADIUS); + cairo_fill (cr); + cairo_stroke(cr); + + /* then selected text */ + gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text); + cairo_move_to(cr, selected_x, PADDING); + pango_cairo_show_layout (cr, layout); + } + + /* timeline */ + + /* what to do + * try to draw oldest + * then "now" first + * then in between dates + * draw additional is possible starting with newest first */ + + x = PADDING; + + + /* Can the oldest time fit ? */ + tmp_snap = g_list_nth_data (view_snaps, 0); + pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1); + pango_layout_set_font_description (layout, timeline_font); + pango_layout_get_pixel_extents (layout,NULL, &logical_rect); + + if (logical_rect.width > gtk_widget_get_allocated_width(widget)) + goto end; + + /* draw anchor line */ + + x += bar_w/2 + .5; + + cairo_set_line_width (cr, 1); + gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text_aa); + cairo_move_to (cr, x, gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-1)); + cairo_line_to (cr, x, gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5); + cairo_line_to (cr, x+2.5, gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5); + cairo_stroke(cr); + + x += 3; + + gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text_aa); + cairo_move_to(cr, x, gtk_widget_get_allocated_height(widget) - (line_height + PADDING)); + pango_cairo_show_layout (cr, layout); + + remaining_timeline_space = last_bar_x - (x + logical_rect.width); + + remaining_x_start = x + logical_rect.width; + + /* try to draw last item */ + + tmp_snap = g_list_nth_data (view_snaps, num_view_snaps-1); + pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1); + pango_layout_get_pixel_extents (layout,NULL, &logical_rect); + + if (remaining_timeline_space < (logical_rect.width + bar_w)) + goto end; + + remaining_timeline_space -= logical_rect.width + bar_w; + + x = last_bar_x; + + + x -= bar_space + bar_w/2 + 0.5; + + cairo_move_to (cr, x, gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-2)); + cairo_line_to (cr, x, gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5); + cairo_line_to (cr, x-2.5, gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5); + cairo_stroke(cr); + + cairo_move_to(cr, x - (logical_rect.width + 3.5), + gtk_widget_get_allocated_height(widget) - (line_height + PADDING)); + pango_cairo_show_layout (cr, layout); + + remaining_x_end = x - (logical_rect.width + 3.5); + + /* now find the next bar that we can to the timeline and loop */ + + + while (space_left) + { + int bar = get_snap_index_from_coord (ts, remaining_x_start, 0); + /* get the next snap */ + bar++; + if (bar >= num_view_snaps) + goto end; + + tmp_snap = g_list_nth_data (view_snaps, bar); + pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1); + + pango_layout_get_pixel_extents (layout,NULL, &logical_rect); + + if (remaining_timeline_space < logical_rect.width + 4) + goto end; + + /* get middle x coord of current bar */ + + x = PADDING + ((bar_w + bar_space) * bar) + bar_w / 2; + + if ( x + 4 + logical_rect.width > remaining_x_end) + goto end; + + /* draw anchor and text */ + x += 0.5; + + cairo_move_to (cr, x, gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-1)); + cairo_line_to (cr, x, gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5); + cairo_line_to (cr, x+2.5, gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5); + cairo_stroke(cr); + + x += 3; + + cairo_move_to(cr, x, + gtk_widget_get_allocated_height(widget) - (line_height + PADDING)); + pango_cairo_show_layout (cr, layout); + + remaining_x_start = x + logical_rect.width; + + remaining_timeline_space = remaining_x_end - remaining_x_start; + + if (remaining_timeline_space <= 0) + space_left = FALSE; + } +end: + if (timeline_font) + pango_font_description_free(timeline_font); + cairo_destroy(cr); + + return FALSE; +} + +static int +key_pressed (GtkWidget *widget, GdkEventKey *event, TimeScale* ts) +{ + switch (event->keyval) + { + case GDK_KEY_KP_Left: + case GDK_KEY_KP_Up: + case GDK_KEY_Left: + case GDK_KEY_Up: + + if (ts->priv->current_pos >= 1) + { + ts->priv->current_pos -= 1; + gtk_widget_queue_draw (widget); + ts->priv->key_pressed = TRUE; + g_signal_emit (ts, signals[VALUE_CHANGED], 0); + /* printf ("key_pressed back %d\n", ts->priv->current_pos); */ + } + return TRUE; + case GDK_KEY_KP_Right: + case GDK_KEY_KP_Down: + case GDK_KEY_Right: + case GDK_KEY_Down: + if (ts->priv->current_pos <= ts->priv->num_snaps - 2) + { + ts->priv->current_pos += 1; + gtk_widget_queue_draw (widget); + ts->priv->key_pressed = TRUE; + g_signal_emit (ts, signals[VALUE_CHANGED], 0); + /* printf ("key_pressed forward %d\n", ts->priv->current_pos); */ + } + return TRUE; + + default: + return FALSE; + } +} + +int timescale_get_position (TimeScale* ts) +{ + /* translate into all_snaps position */ + gconstpointer data; + data = g_list_nth_data (ts->priv->snaps, ts->priv->current_pos); + return g_list_index (ts->priv->all_snaps, data); +} + +static int match_func (ZfsDataSet *set, char *dir) +{ + return strcmp (set->mountpoint, dir); +} + +void +timescale_set_position (TimeScale* ts, char *mountpoint) +{ + gboolean redraw = FALSE; + int num_snaps = g_list_length (ts->priv->all_snaps); + if (mountpoint) + { + int pos; + GList* match = NULL; + match = g_list_find_custom (ts->priv->all_snaps, mountpoint, (GCompareFunc)match_func); + pos = g_list_index (ts->priv->all_snaps, match->data); + if (pos != -1 && ts->priv->current_pos != pos) + { + redraw = TRUE; + ts->priv->current_pos = pos; + } + } + else + { /* Now special case */ + if (ts->priv->current_pos != num_snaps - 1) + { + redraw = TRUE; + ts->priv->current_pos = num_snaps - 1; + } + } + + if (redraw) + gtk_widget_queue_draw (GTK_WIDGET (ts)); +} + +static void +button_pressed (GtkWidget *widget, GdkEventButton *event, TimeScale* ts) +{ + int new_pos; + + gtk_widget_grab_focus (widget); + new_pos = get_snap_index_from_coord (ts, event->x, event->y); + + if (new_pos == -1) + return; + + if (new_pos != ts->priv->current_pos) + { + ts->priv->current_pos = new_pos; + /* printf ("button_pressed (%g,%g) selected %d\n", event->x, event->y, ts->priv->current_pos); */ + g_signal_emit (ts, signals[VALUE_CHANGED], 0); + gtk_widget_queue_draw (widget); + } +} + +static gboolean +query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer data) +{ + static gboolean set = FALSE; + static int old_pos = -1; + TimeScale* ts = TIMESCALE (data); + int new_pos = get_snap_index_from_coord (ts, x, y); + /* printf ("in query_tooltip new_pos %d total %d num_snap %d\n", new_pos, g_list_length (ts->priv->snaps), ts->priv->num_snaps); */ + Snap *tmp_snap = NULL; + char *tip = NULL; + + if (new_pos == -1) + return FALSE; + + if (new_pos != old_pos) + { + old_pos = new_pos; + return FALSE; + } + + if (new_pos >= ts->priv->num_snaps) + return FALSE; + + tmp_snap = g_list_nth_data (ts->priv->snaps, new_pos); + + tip = g_strdup_printf ("%s\nCreated: %s\nSize: %s\nName: %s", tmp_snap->type_str, tmp_snap->mtime_str, tmp_snap->used_space_str, tmp_snap->name); + gtk_label_set_text (GTK_LABEL(ts->priv->label_tip), tip); + gtk_tooltip_set_custom (tooltip, ts->priv->label_tip); + g_free (tip); + return TRUE; +} + +static void +timescale_class_init (TimeScaleClass *class) +{ + GtkWidgetClass *widget_class; + GtkScaleClass *scale_class; + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + g_type_class_add_private (class, sizeof (TimeScalePrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + + signals[VALUE_CHANGED] = + g_signal_new ("value-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TimeScaleClass, value_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + --- caja-1.28.0/src/timescale.h.orig 2024-02-26 08:42:41.085975314 +0100 +++ caja-1.28.0/src/timescale.h 2024-02-26 08:42:41.085928246 +0100 @@ -0,0 +1,58 @@ +#ifndef __TIMESCALE_H__ +#define __TIMESCALE_H__ + + +#include +#include + + +G_BEGIN_DECLS + +#define TYPE_TIMESCALE (timescale_get_type ()) +#define TIMESCALE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TIMESCALE, TimeScale)) +#define TIMESCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_TIMESCALE, TimeScaleClass)) +#define IS_TIMESCALE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TIMESCALE)) +#define IS_TIMESCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_TIMESCALE)) +#define TIMESCALE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_TIMESCALE, TimeScaleClass)) + + +typedef struct _TimeScale TimeScale; +typedef struct _TimeScaleClass TimeScaleClass; +typedef struct TimeScalePrivate TimeScalePrivate; + +struct _TimeScale +{ + GtkHBox hbox; + TimeScalePrivate *priv; +}; + +struct _TimeScaleClass +{ + GtkHBoxClass parent_class; + void (* value_changed) (TimeScale *timescale); + +}; + + +GType timescale_get_type (void) G_GNUC_CONST; +GtkWidget* timescale_new (); +void timescale_set_snapshots (TimeScale* ts, GList *list, int init_position); +int timescale_get_position (TimeScale* ts); +void timescale_set_position (TimeScale* ts, char *mountpoint); + + +#define ROUNDED_RADIUS 6 +#define PADDING 3 + +typedef enum { + LOCAL_MANUAL, + LOCAL_AUTOMATIC, + REMOTE_MANUAL, + REMOTE_AUTOMATIC, + NOW +} SnapType; + + +G_END_DECLS + +#endif /* __TIMESCALE_H__ */