libyui-gtk  2.49.0
ygtksteps.c
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 /* YGtkSteps widget */
6 // check the header file for information about this widget
7 
8 /*
9  Textdomain "gtk"
10  */
11 
12 #include <yui/Libyui_config.h>
13 #include "ygtksteps.h"
14 #include <gtk/gtk.h>
15 #define YGI18N_C
16 #include "YGi18n.h"
17 
18 #define CURRENT_MARK_ANIMATION_TIME 250
19 #define CURRENT_MARK_ANIMATION_OFFSET 3
20 #define CURRENT_MARK_FRAMES_NB (CURRENT_MARK_ANIMATION_OFFSET*2)
21 
22 G_DEFINE_TYPE (YGtkSteps, ygtk_steps, GTK_TYPE_BOX)
23 
24 static void ygtk_steps_init (YGtkSteps *steps)
25 {
26  gtk_box_set_spacing (GTK_BOX (steps), 8);
27  gtk_orientable_set_orientation (GTK_ORIENTABLE (steps), GTK_ORIENTATION_VERTICAL);
28  gtk_container_set_border_width (GTK_CONTAINER (steps), 4);
29 
30  const gchar *check = "\u2714", *current = "\u25b6", *todo = "\u26ab";
31  if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
32  current = "\u25c0";
33  PangoContext *context = gtk_widget_get_pango_context (GTK_WIDGET (steps));
34  steps->check_mark_layout = pango_layout_new (context);
35  steps->current_mark_layout = pango_layout_new (context);
36  steps->todo_mark_layout = pango_layout_new (context);
37  pango_layout_set_text (steps->check_mark_layout, check, -1);
38  pango_layout_set_text (steps->current_mark_layout, current, -1);
39  pango_layout_set_text (steps->todo_mark_layout, todo, -1);
40  steps->current_mark_timeout_id = steps->current_mark_frame = 0;
41 }
42 
43 static void ygtk_steps_destroy (GtkWidget *widget)
44 {
45  YGtkSteps *steps = YGTK_STEPS (widget);
46  if (steps->current_mark_timeout_id) {
47  g_source_remove (steps->current_mark_timeout_id);
48  steps->current_mark_timeout_id = 0;
49  }
50  if (steps->check_mark_layout)
51  g_object_unref (steps->check_mark_layout);
52  steps->check_mark_layout = NULL;
53  if (steps->current_mark_layout)
54  g_object_unref (steps->current_mark_layout);
55  if (steps->todo_mark_layout)
56  g_object_unref (steps->todo_mark_layout);
57  steps->todo_mark_layout = NULL;
58 
59  GTK_WIDGET_CLASS (ygtk_steps_parent_class)->destroy(widget);
60 }
61 
62 static void ygtk_step_update_layout (YGtkSteps *steps, gint step)
63 {
64  if (step < 0) return;
65  gboolean bold = steps->current_step == step;
66  GList *children = gtk_container_get_children (GTK_CONTAINER (steps));
67  GtkWidget *label = (GtkWidget *) g_list_nth_data (children, step);
68  if (g_object_get_data (G_OBJECT (label), "is-header"))
69  return;
70  if (bold) {
71  PangoAttrList *attrbs = pango_attr_list_new();
72  pango_attr_list_insert (attrbs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
73  gtk_label_set_attributes (GTK_LABEL (label), attrbs);
74  pango_attr_list_unref (attrbs);
75  atk_object_set_description (gtk_widget_get_accessible (label), _("Current step"));
76  }
77  else {
78  gtk_label_set_attributes (GTK_LABEL (label), NULL);
79  atk_object_set_description (gtk_widget_get_accessible (label), "");
80  }
81  g_list_free (children);
82 }
83 
84 static gboolean ygtk_steps_draw (GtkWidget *widget, cairo_t *cr)
85 {
86  GTK_WIDGET_CLASS (ygtk_steps_parent_class)->draw(widget, cr);
87 
88  YGtkSteps *steps = YGTK_STEPS (widget);
89  gboolean reverse = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
90  GList *children = gtk_container_get_children (GTK_CONTAINER (widget)), *i;
91 
92  cairo_set_source_rgb (cr, 0, 0, 0);
93  int n = 0;
94  for (i = children; i; i = i->next, n++) {
95  GtkWidget *label = i->data;
96  GtkAllocation alloc;
97  gtk_widget_get_allocation(label, &alloc);
98 
99  if (g_object_get_data (G_OBJECT (label), "is-header"))
100  continue;
101  PangoLayout *layout;
102  if (n < steps->current_step)
103  layout = steps->check_mark_layout;
104  else if (n == steps->current_step)
105  layout = steps->current_mark_layout;
106  else //if (n > steps->current_step)
107  layout = steps->todo_mark_layout;
108  int x = alloc.x, y = alloc.y;
109  if (reverse) {
110  PangoRectangle rect;
111  pango_layout_get_pixel_extents (layout, NULL, &rect);
112  x += alloc.width - rect.width - 4;
113  }
114  else
115  x += 4;
116  if (n == steps->current_step) {
117  int offset;
118  if (steps->current_mark_frame < CURRENT_MARK_FRAMES_NB/2)
119  offset = steps->current_mark_frame * CURRENT_MARK_ANIMATION_OFFSET;
120  else
121  offset = (CURRENT_MARK_FRAMES_NB - steps->current_mark_frame) *
122  CURRENT_MARK_ANIMATION_OFFSET;
123  x += offset * (reverse ? 1 : -1);
124  }
125 
126  cairo_move_to (cr, x, y);
127  pango_cairo_show_layout (cr, layout);
128  }
129  g_list_free (children);
130  return FALSE;
131 }
132 
133 GtkWidget* ygtk_steps_new (void)
134 {
135  return g_object_new (YGTK_TYPE_STEPS, NULL);
136 }
137 
138 gint ygtk_steps_append (YGtkSteps *steps, const gchar *text)
139 {
140  GtkWidget *label = gtk_label_new (text);
141  GdkRGBA black = { 0.0, 0.0, 0.0, 1.0 };
142  gtk_widget_override_color (label, GTK_STATE_NORMAL, &black);
143 
144 # if GTK_CHECK_VERSION (3, 14, 0)
145  gtk_widget_set_halign (label, GTK_ALIGN_START);
146  gtk_widget_set_valign (label, GTK_ALIGN_START);
147 # else
148  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
149 # endif
150 
151  int mark_width = 10;
152  pango_layout_get_pixel_size (steps->check_mark_layout, &mark_width, NULL);
153 
154 # if GTK_CHECK_VERSION (3, 14, 0)
155  gtk_widget_set_margin_start (label, mark_width+12);
156  gtk_widget_set_margin_end (label, mark_width+12);
157  gtk_widget_set_margin_top (label, 0);
158  gtk_widget_set_margin_bottom (label, 0);
159 # else
160  gtk_misc_set_padding (GTK_MISC (label), mark_width+12, 0);
161 # endif
162 
163  gtk_widget_show (label);
164  gtk_box_pack_start (GTK_BOX (steps), label, FALSE, TRUE, 0);
165  return ygtk_steps_total (steps)-1;
166 }
167 
168 void ygtk_steps_append_heading (YGtkSteps *steps, const gchar *heading)
169 {
170  GtkWidget *label = gtk_label_new (heading);
171  GdkRGBA black = { 0.0, 0.0, 0.0, 1.0 };
172  gtk_widget_override_color (label, GTK_STATE_NORMAL, &black);
173  g_object_set_data (G_OBJECT (label), "is-header", GINT_TO_POINTER (1));
174 
175 # if GTK_CHECK_VERSION (3, 14, 0)
176  gtk_widget_set_halign (label, GTK_ALIGN_START);
177  gtk_widget_set_valign (label, GTK_ALIGN_START);
178 # else
179  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
180 # endif
181 
182  PangoAttrList *attrbs = pango_attr_list_new();
183  pango_attr_list_insert (attrbs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
184  pango_attr_list_insert (attrbs, pango_attr_scale_new (PANGO_SCALE_LARGE));
185  gtk_label_set_attributes (GTK_LABEL (label), attrbs);
186  pango_attr_list_unref (attrbs);
187 
188  gtk_widget_show (label);
189  gtk_box_pack_start (GTK_BOX (steps), label, FALSE, TRUE, 6);
190 }
191 
192 static gboolean current_mark_animation_cb (void *steps_ptr)
193 {
194  YGtkSteps *steps = steps_ptr;
195 
196  // should use gtk_widget_queue_draw_area (widget, x, y, w, h)...
197  gtk_widget_queue_draw (GTK_WIDGET (steps));
198 
199  if (++steps->current_mark_frame == CURRENT_MARK_FRAMES_NB) {
200  steps->current_mark_frame = 0;
201  return FALSE;
202  }
203  return TRUE;
204 }
205 
206 void ygtk_steps_set_current (YGtkSteps *steps, gint step)
207 {
208  gint old_step = steps->current_step;
209  steps->current_step = step;
210 
211  // update step icons
212  if (old_step != step) {
213  ygtk_step_update_layout (steps, old_step);
214  ygtk_step_update_layout (steps, step);
215  }
216 
217  if (step != -1 && step != old_step) {
218  steps->current_mark_frame = 0;
219  steps->current_mark_timeout_id = g_timeout_add
220  (CURRENT_MARK_ANIMATION_TIME / CURRENT_MARK_FRAMES_NB,
221  current_mark_animation_cb, steps);
222  }
223 }
224 
225 gint ygtk_steps_total (YGtkSteps *steps)
226 {
227  GList *children = gtk_container_get_children (GTK_CONTAINER (steps));
228  int steps_nb = g_list_length (children);
229  g_list_free (children);
230  return steps_nb;
231 }
232 
233 const gchar *ygtk_steps_get_nth_label (YGtkSteps *steps, gint n)
234 {
235  if (n < 0) return NULL;
236  GtkWidget *step;
237  GList *children = gtk_container_get_children (GTK_CONTAINER (steps));
238  step = g_list_nth_data (children, n);
239  g_list_free (children);
240  if (step)
241  return gtk_label_get_text (GTK_LABEL (step));
242  return NULL;
243 }
244 
245 void ygtk_steps_clear (YGtkSteps *steps)
246 {
247  GList *children = gtk_container_get_children (GTK_CONTAINER (steps)), *i;
248  for (i = children; i; i = i->next)
249  gtk_container_remove (GTK_CONTAINER (steps), (GtkWidget *) i->data);
250  g_list_free (children);
251 }
252 
253 static void ygtk_steps_class_init (YGtkStepsClass *klass)
254 {
255  ygtk_steps_parent_class = g_type_class_peek_parent (klass);
256 
257  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
258  widget_class->draw = ygtk_steps_draw;
259  widget_class->destroy = ygtk_steps_destroy;
260 }
261