static void mx_combo_box_init (MxComboBox *self) { MxComboBoxPrivate *priv; ClutterActor *menu; priv = self->priv = COMBO_BOX_PRIVATE (self); priv->spacing = 8; priv->label = clutter_text_new (); clutter_actor_add_child (CLUTTER_ACTOR (self), priv->label); menu = mx_menu_new (); mx_widget_set_menu (MX_WIDGET (self), MX_MENU (menu)); g_signal_connect (menu, "action-activated", G_CALLBACK (mx_combo_box_action_activated_cb), self); g_signal_connect (self, "style-changed", G_CALLBACK (mx_combo_box_style_changed), NULL); clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE); }
static gboolean mx_menu_captured_event_handler (ClutterActor *actor, ClutterEvent *event, ClutterActor *menu) { int i; ClutterActor *source; MxMenuPrivate *priv = MX_MENU (menu)->priv; /* allow the event to continue if it is applied to the menu or any of its * children */ source = clutter_event_get_source (event); if (source == menu) return FALSE; for (i = 0; i < priv->children->len; i++) { MxMenuChild *child; child = &g_array_index (priv->children, MxMenuChild, i); if (source == (ClutterActor*) child->box) return FALSE; } if (source == priv->up_button || source == priv->down_button) return FALSE; /* hide the menu if the user clicks outside the menu */ if (event->type == CLUTTER_BUTTON_PRESS) mx_menu_close (menu); return TRUE; }
static void mx_menu_map (ClutterActor *actor) { gint i; MxMenuPrivate *priv = MX_MENU (actor)->priv; CLUTTER_ACTOR_CLASS (mx_menu_parent_class)->map (actor); clutter_actor_map (priv->up_button); clutter_actor_map (priv->down_button); for (i = 0; i < priv->children->len; i++) { MxMenuChild *child = &g_array_index (priv->children, MxMenuChild, i); clutter_actor_map (CLUTTER_ACTOR (child->box)); } /* set up a capture so we can close the menu if the user clicks outside it */ priv->stage = clutter_actor_get_stage (actor); g_object_weak_ref (G_OBJECT (priv->stage), (GWeakNotify) stage_weak_notify, actor); priv->captured_event_handler = g_signal_connect (priv->stage, "captured-event", G_CALLBACK (mx_menu_captured_event_handler), actor); }
static void mx_menu_unmap (ClutterActor *actor) { gint i; MxMenuPrivate *priv = MX_MENU (actor)->priv; CLUTTER_ACTOR_CLASS (mx_menu_parent_class)->unmap (actor); clutter_actor_unmap (priv->up_button); clutter_actor_unmap (priv->down_button); for (i = 0; i < priv->children->len; i++) { MxMenuChild *child = &g_array_index (priv->children, MxMenuChild, i); clutter_actor_unmap (CLUTTER_ACTOR (child->box)); } if (priv->stage) { g_signal_handler_disconnect (priv->stage, priv->captured_event_handler); priv->captured_event_handler = 0; g_object_weak_unref (G_OBJECT (priv->stage), (GWeakNotify) stage_weak_notify, actor); priv->stage = NULL; } }
static void mx_menu_button_clicked_cb (ClutterActor *box, MxAction *action) { MxMenu *menu; menu = MX_MENU (clutter_actor_get_parent (box)); /* set the menu unreactive to prevent other items being hilighted */ clutter_actor_set_reactive ((ClutterActor*) menu, FALSE); g_object_ref (menu); g_object_ref (action); g_signal_emit (menu, signals[ACTION_ACTIVATED], 0, action); g_signal_emit_by_name (action, "activated"); clutter_actor_animate (CLUTTER_ACTOR (menu), CLUTTER_LINEAR, 250, "opacity", (guchar) 0, "signal-swapped::completed", clutter_actor_hide, menu, NULL); g_object_unref (action); g_object_unref (menu); }
static void mx_menu_floating_pick (ClutterActor *menu, const ClutterColor *color) { gint i; MxMenuPrivate *priv = MX_MENU (menu)->priv; /* chain up to get bounding rectangle */ MX_FLOATING_WIDGET_CLASS (mx_menu_parent_class)->floating_pick (menu, color); /* pick children */ for (i = priv->id_offset; i <= priv->last_shown_id; i++) { MxMenuChild *child = &g_array_index (priv->children, MxMenuChild, i); if (clutter_actor_should_pick_paint (CLUTTER_ACTOR (child->box))) { clutter_actor_paint (CLUTTER_ACTOR (child->box)); } } if(priv->scrolling_mode) { clutter_actor_paint(priv->up_button); clutter_actor_paint(priv->down_button); } }
static MxFocusable* mx_menu_accept_focus (MxFocusable *focusable, MxFocusHint hint) { MxMenuPrivate *priv = MX_MENU (focusable)->priv; MxMenuChild *child; child = &g_array_index (priv->children, MxMenuChild, 0); return mx_focusable_accept_focus (MX_FOCUSABLE (child->box), 0); }
static gboolean mx_menu_captured_event_handler (ClutterActor *actor, ClutterEvent *event, ClutterActor *menu) { int i; ClutterActor *source; MxMenuPrivate *priv = MX_MENU (menu)->priv; /* allow the event to continue if it is applied to the menu or any of its * children */ source = clutter_event_get_source (event); if (source == menu) return FALSE; for (i = 0; i < priv->children->len; i++) { MxMenuChild *child; child = &g_array_index (priv->children, MxMenuChild, i); if (source == (ClutterActor*) child->box) return FALSE; } if (source == priv->up_button || source == priv->down_button) return FALSE; /* hide the menu if the user clicks outside the menu */ if (event->type == CLUTTER_BUTTON_PRESS) { if (clutter_actor_get_animation (menu)) { clutter_animation_completed (clutter_actor_get_animation (menu)); return FALSE; } clutter_actor_set_reactive (menu, FALSE); clutter_actor_animate (menu, CLUTTER_LINEAR, 250, "opacity", (guchar) 0, "signal-swapped::completed", clutter_actor_hide, menu, NULL); } return TRUE; }
static void mx_menu_dispose (GObject *object) { MxMenu *menu = MX_MENU (object); MxMenuPrivate *priv = menu->priv; if (priv->children) { gint i; for (i = 0; i < priv->children->len; i++) mx_menu_free_action_at (menu, i, FALSE); g_array_free (priv->children, TRUE); priv->children = NULL; } G_OBJECT_CLASS (mx_menu_parent_class)->dispose (object); }
static void mx_menu_button_clicked_cb (ClutterActor *box, MxAction *action) { MxMenu *menu; menu = MX_MENU (clutter_actor_get_parent (box)); g_object_ref (menu); g_object_ref (action); g_signal_emit (menu, signals[ACTION_ACTIVATED], 0, action); g_signal_emit_by_name (action, "activated"); mx_menu_close (CLUTTER_ACTOR (menu)); g_object_unref (action); g_object_unref (menu); }
static gboolean mx_menu_button_enter_event_cb (ClutterActor *box, ClutterEvent *event, gpointer user_data) { MxMenuPrivate *priv = MX_MENU (user_data)->priv; ClutterStage *stage; /* each menu item grabs focus when hovered */ stage = (ClutterStage *) clutter_actor_get_stage (box); /* ensure the menu is not closed when focus is pushed to another actor */ priv->internal_focus_push = TRUE; mx_focus_manager_push_focus (mx_focus_manager_get_for_stage (stage), MX_FOCUSABLE (box)); /* prevent the hover pseudo-class from being applied */ return TRUE; }
static void mx_menu_get_preferred_width (ClutterActor *actor, gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p) { gint i; MxPadding padding; gfloat min_width, nat_width; MxMenuPrivate *priv = MX_MENU (actor)->priv; /* Add padding and the size of the widest child */ mx_widget_get_padding (MX_WIDGET (actor), &padding); min_width = nat_width = 0; for (i = 0; i < priv->children->len; i++) { gfloat child_min_width, child_nat_width; MxMenuChild *child; child = &g_array_index (priv->children, MxMenuChild, i); clutter_actor_get_preferred_width (CLUTTER_ACTOR (child->box), for_height, &child_min_width, &child_nat_width); if (child_min_width > min_width) min_width = child_min_width; if (child_nat_width > nat_width) nat_width = child_nat_width; } if (min_width_p) *min_width_p = min_width + padding.left + padding.right; if (natural_width_p) *natural_width_p = nat_width + padding.left + padding.right; }
static void mx_menu_floating_paint (ClutterActor *menu) { gint i; MxMenuPrivate *priv = MX_MENU (menu)->priv; /* Chain up to get background */ MX_FLOATING_WIDGET_CLASS (mx_menu_parent_class)->floating_paint (menu); /* Paint children */ for (i = priv->id_offset; i <= priv->last_shown_id; i++) { MxMenuChild *child = &g_array_index (priv->children, MxMenuChild, i); clutter_actor_paint (CLUTTER_ACTOR (child->box)); } if(priv->scrolling_mode) { clutter_actor_paint (priv->up_button); clutter_actor_paint (priv->down_button); } }
static void mx_menu_get_preferred_height (ClutterActor *actor, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { gint i; MxPadding padding; gfloat min_height, nat_height; MxMenuPrivate *priv = MX_MENU (actor)->priv; /* Add padding and the cumulative height of the children */ mx_widget_get_padding (MX_WIDGET (actor), &padding); min_height = nat_height = padding.top + padding.bottom; for (i = 0; i < priv->children->len; i++) { gfloat child_min_height, child_nat_height; MxMenuChild *child = &g_array_index (priv->children, MxMenuChild, i); clutter_actor_get_preferred_height (CLUTTER_ACTOR (child->box), for_width, &child_min_height, &child_nat_height); min_height += child_min_height + 1; nat_height += child_nat_height + 1; } if (min_height_p) *min_height_p = min_height; if (natural_height_p) *natural_height_p = nat_height; }
static MxFocusable* mx_menu_move_focus (MxFocusable *focusable, MxFocusDirection direction, MxFocusable *from) { MxMenuPrivate *priv = MX_MENU (focusable)->priv; MxFocusable *result; MxMenuChild *child; gint i, start; /* find the current focused child */ for (i = 0; i < priv->children->len; i++) { child = &g_array_index (priv->children, MxMenuChild, i); if ((MxFocusable*) child->box == from) break; else child = NULL; } if (!child) return NULL; start = i; switch (direction) { case MX_FOCUS_DIRECTION_UP: if (i == 0) { i = priv->children->len - 1; gint nb_elts = priv->last_shown_id - priv->id_offset; priv->id_offset = i - nb_elts; clutter_actor_queue_redraw (CLUTTER_ACTOR(focusable)); } else { i--; if (i < priv->id_offset) { priv->id_offset--; clutter_actor_queue_redraw (CLUTTER_ACTOR(focusable)); } } while (i >= 0) { if (i == start) break; child = &g_array_index (priv->children, MxMenuChild, i); result = mx_focusable_accept_focus (MX_FOCUSABLE (child->box), 0); if (result) return result; /* loop */ if (i == 0) i = priv->children->len; i--; } case MX_FOCUS_DIRECTION_DOWN: if (i == priv->children->len - 1) { priv->id_offset = 0; i = 0; clutter_actor_queue_redraw (CLUTTER_ACTOR(focusable)); } else { i++; if (i > priv->last_shown_id) { priv->id_offset++; clutter_actor_queue_redraw (CLUTTER_ACTOR(focusable)); } } while (i < priv->children->len) { if (i == start) break; child = &g_array_index (priv->children, MxMenuChild, i); result = mx_focusable_accept_focus (MX_FOCUSABLE (child->box), 0); if (result) return result; /* loop */ if (i == priv->children->len - 1) i = -1; i++; } case MX_FOCUS_DIRECTION_OUT: if (priv->internal_focus_push) { /* do nothing if this notification was caused internally */ priv->internal_focus_push = FALSE; return NULL; } default: break; } clutter_actor_hide (CLUTTER_ACTOR (focusable)); return NULL; }
static void mx_menu_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { gint i; MxPadding padding; ClutterActorBox child_box; MxMenuPrivate *priv = MX_MENU (actor)->priv; gfloat available_h = box->y2-box->y1; /* * first of all, we have to check if the allocated height is our * natural height... */ gfloat menu_nat_h; mx_menu_get_preferred_height (actor, box->x2-box->x1, NULL, &menu_nat_h); if (available_h < menu_nat_h) { /* * ...if not, we have to draw 2 buttons in order to let the * user be able to navigate in the whole menu. */ priv->scrolling_mode = TRUE; } else { priv->scrolling_mode = FALSE; } /* Allocate children */ mx_widget_get_padding (MX_WIDGET (actor), &padding); child_box.x1 = padding.left; child_box.y1 = padding.top; child_box.x2 = box->x2 - box->x1 - padding.right; gfloat down_but_height; clutter_actor_get_preferred_height (priv->down_button, child_box.x2 - child_box.x1, NULL, &down_but_height); if (priv->scrolling_mode) { clutter_actor_get_preferred_height (priv->up_button, child_box.x2 - child_box.x1, NULL, &child_box.y2); child_box.y2 += child_box.y1; clutter_actor_allocate (priv->up_button, &child_box, flags); child_box.y1 = child_box.y2 + 1; available_h -= down_but_height; } for (i = priv->id_offset; i < priv->children->len; i++) { gfloat natural_height; MxMenuChild *child = &g_array_index (priv->children, MxMenuChild, i); clutter_actor_get_preferred_height (CLUTTER_ACTOR (child->box), child_box.x2 - child_box.x1, NULL, &natural_height); child_box.y2 = child_box.y1 + natural_height; if (child_box.y2 >= available_h) { priv->last_shown_id = i-1; break; } clutter_actor_allocate (CLUTTER_ACTOR (child->box), &child_box, flags); child_box.y1 = child_box.y2 + 1; } if (priv->children->len == i) { priv->last_shown_id = i-1; } if (priv->scrolling_mode) { child_box.y2 = child_box.y1 + down_but_height; clutter_actor_allocate (priv->down_button, &child_box, flags); } /* Chain up and allocate background */ CLUTTER_ACTOR_CLASS (mx_menu_parent_class)->allocate (actor, box, flags); }