Commit ae92efd6 authored by Stepan Salenikovich's avatar Stepan Salenikovich Committed by Alexandre Lision

improve avatar manipulation

Changes interface to mimick the one in Gnome Contacts:
- no more trash or export buttons
- the first choice is to take a photo or import
- adds return/undo buttons during selection process

Change-Id: I432f4bad48c1379f6fb26569edea32a24cbb98e9
Tuleap: #840
parent 02b283d7
......@@ -72,11 +72,18 @@ struct _AvatarManipulationPrivate
GtkWidget *video_widget;
GtkWidget *box_views_and_controls;
GtkWidget *box_controls;
GtkWidget *button_take_photo;
GtkWidget *button_box_current;
GtkWidget *button_box_photo;
GtkWidget *button_box_edit;
GtkWidget *button_start_camera;
GtkWidget *button_choose_picture;
GtkWidget *button_trash_avatar;
GtkWidget *button_take_photo;
GtkWidget *button_return_photo;
GtkWidget *button_set_avatar;
GtkWidget *button_export_avatar;
GtkWidget *button_return_edit;
GtkWidget *selector_widget;
GtkWidget *stack_views;
GtkWidget *image_avatar;
......@@ -93,6 +100,13 @@ struct _AvatarManipulationPrivate
GdkModifierType button_pressed;
AvatarManipulationState state;
AvatarManipulationState last_state;
/* this is used to keep track of the state of the preview when the camera is used to take a
* photo; if a call is in progress, then the preview should already be started and we don't want
* to stop it when the settings are closed, in this case
*/
gboolean video_started_by_avatar_manipulation;
};
G_DEFINE_TYPE_WITH_PRIVATE(AvatarManipulation, avatar_manipulation, GTK_TYPE_BOX);
......@@ -102,11 +116,11 @@ G_DEFINE_TYPE_WITH_PRIVATE(AvatarManipulation, avatar_manipulation, GTK_TYPE_BOX
static void set_state(AvatarManipulation *self, AvatarManipulationState state);
static void start_camera(AvatarManipulation *self);
static void take_a_photo(AvatarManipulation *self);
static void choose_picture(AvatarManipulation *self);
static void export_avatar(AvatarManipulation *self);
static void return_to_previous_state(AvatarManipulation *self);
static void update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview);
static void trash_photo(AvatarManipulation *self);
static void set_avatar(AvatarManipulation *self);
static void got_snapshot(AvatarManipulation *parent);
......@@ -125,7 +139,8 @@ avatar_manipulation_dispose(GObject *object)
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
/* make sure we stop the preview and the video widget */
Video::PreviewManager::instance().stopPreview();
if (priv->video_started_by_avatar_manipulation)
Video::PreviewManager::instance().stopPreview();
if (priv->video_widget) {
gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
priv->video_widget = NULL;
......@@ -162,9 +177,12 @@ avatar_manipulation_new_from_wizard(void)
{
auto self = avatar_manipulation_new();
/* in this mode, we want to automatically go to the new avatar state, unless one already exists */
if (!ProfileModel::instance().selectedProfile()->person()->photo().isValid())
set_state(AVATAR_MANIPULATION(self), AVATAR_MANIPULATION_STATE_NEW);
/* in this mode, we want to automatically go to the PHOTO avatar state, unless one already exists */
if (!ProfileModel::instance().selectedProfile()->person()->photo().isValid()) {
// check if there is a camera
if (Video::DeviceModel::instance().rowCount() > 0)
set_state(AVATAR_MANIPULATION(self), AVATAR_MANIPULATION_STATE_PHOTO);
}
return self;
}
......@@ -179,14 +197,18 @@ avatar_manipulation_class_init(AvatarManipulationClass *klass)
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_views_and_controls);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_controls);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_start_camera);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_choose_picture);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_take_photo);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_photo);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_set_avatar);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_trash_avatar);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_choose_picture);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_export_avatar);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_edit);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, stack_views);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, image_avatar);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, vbox_selector);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_current);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_photo);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_edit);
}
static void
......@@ -204,7 +226,7 @@ avatar_manipulation_init(AvatarManipulation *self)
/* Signals used to handle backing surface */
g_signal_connect(priv->selector_widget, "draw", G_CALLBACK (selector_widget_draw), self);
g_signal_connect(priv->selector_widget,"configure-event", G_CALLBACK (selector_widget_configure_event), self);
g_signal_connect(priv->selector_widget, "configure-event", G_CALLBACK (selector_widget_configure_event), self);
/* Event signals */
g_signal_connect(priv->selector_widget, "motion-notify-event", G_CALLBACK(selector_widget_motion_notify_event), self);
g_signal_connect(priv->selector_widget, "button-press-event", G_CALLBACK(selector_widget_button_press_event), self);
......@@ -216,11 +238,12 @@ avatar_manipulation_init(AvatarManipulation *self)
/* signals */
g_signal_connect_swapped(priv->button_start_camera, "clicked", G_CALLBACK(start_camera), self);
g_signal_connect_swapped(priv->button_choose_picture, "clicked", G_CALLBACK(choose_picture), self);
g_signal_connect_swapped(priv->button_take_photo, "clicked", G_CALLBACK(take_a_photo), self);
g_signal_connect_swapped(priv->button_return_photo, "clicked", G_CALLBACK(return_to_previous_state), self);
g_signal_connect_swapped(priv->button_set_avatar, "clicked", G_CALLBACK(set_avatar), self);
g_signal_connect_swapped(priv->button_trash_avatar, "clicked", G_CALLBACK(trash_photo), self);
g_signal_connect_swapped(priv->button_export_avatar, "clicked", G_CALLBACK(export_avatar), self);
g_signal_connect_swapped(priv->button_return_edit, "clicked", G_CALLBACK(return_to_previous_state), self);
set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
......@@ -234,6 +257,9 @@ set_state(AvatarManipulation *self, AvatarManipulationState state)
// been done by the caller
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
// save prev state
priv->last_state = priv->state;
switch (state) {
case AVATAR_MANIPULATION_STATE_CURRENT:
{
......@@ -251,62 +277,60 @@ set_state(AvatarManipulation *self, AvatarManipulationState state)
}
gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_avatar");
// gtk_widget_set_size_request(priv->video_widget, VIDEO_WIDTH, VIDEO_HEIGHT);
/* available actions: trash or export avatar */
gtk_widget_set_visible(priv->button_trash_avatar, true);
gtk_widget_set_visible(priv->button_export_avatar, true);
gtk_widget_set_visible(priv->button_set_avatar, false);
gtk_widget_set_visible(priv->button_take_photo, false);
gtk_widget_set_visible(priv->button_choose_picture, false);
/* available actions: start camera (if available) or choose image */
if (Video::DeviceModel::instance().rowCount() > 0) {
// TODO: update if a video device gets inserted while in this state
gtk_widget_set_visible(priv->button_start_camera, true);
}
gtk_widget_set_visible(priv->button_box_current, true);
gtk_widget_set_visible(priv->button_box_photo, false);
gtk_widget_set_visible(priv->button_box_edit, false);
/* make sure video widget and camera is no longer running */
Video::PreviewManager::instance().stopPreview();
/* make sure video widget and camera is not running */
if (priv->video_started_by_avatar_manipulation)
Video::PreviewManager::instance().stopPreview();
if (priv->video_widget) {
gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
priv->video_widget = NULL;
}
}
break;
case AVATAR_MANIPULATION_STATE_NEW:
case AVATAR_MANIPULATION_STATE_PHOTO:
{
/* check if a video device is available, if so show the camera stream; otherwise we can
* only choose a file, and we keep showing the current/default avatar
*
* TODO: update if a video device gets inserted while in this state
*/
if (Video::DeviceModel::instance().rowCount() > 0) {
priv->video_widget = video_widget_new();
// gtk_widget_set_size_request(priv->video_widget, VIDEO_WIDTH, VIDEO_HEIGHT);
video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Video::PreviewManager::instance().previewRenderer(),
VIDEO_RENDERER_REMOTE);
g_signal_connect_swapped(priv->video_widget, "snapshot-taken", G_CALLBACK (got_snapshot), self);
gtk_widget_set_vexpand_set(priv->video_widget, FALSE);
gtk_widget_set_hexpand_set(priv->video_widget, FALSE);
gtk_widget_set_visible(priv->video_widget, true);
gtk_stack_add_named(GTK_STACK(priv->stack_views), priv->video_widget, "page_photobooth");
gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_photobooth");
// start the video; if its not available we should not be in this state
priv->video_widget = video_widget_new();
g_signal_connect_swapped(priv->video_widget, "snapshot-taken", G_CALLBACK (got_snapshot), self);
gtk_widget_set_vexpand_set(priv->video_widget, FALSE);
gtk_widget_set_hexpand_set(priv->video_widget, FALSE);
gtk_widget_set_visible(priv->video_widget, true);
gtk_stack_add_named(GTK_STACK(priv->stack_views), priv->video_widget, "page_photobooth");
gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_photobooth");
/* local renderer, but set as "remote" so that it takes up the whole screen */
video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Video::PreviewManager::instance().previewRenderer(),
VIDEO_RENDERER_REMOTE);
if (!Video::PreviewManager::instance().isPreviewing()) {
priv->video_started_by_avatar_manipulation = TRUE;
Video::PreviewManager::instance().startPreview();
/* available action in this case: take snapshot */
gtk_widget_set_visible(priv->button_take_photo, true);
} else {
gtk_widget_set_visible(priv->button_take_photo, false);
priv->video_started_by_avatar_manipulation = FALSE;
}
/* available actions: choose file */
gtk_widget_set_visible(priv->button_choose_picture, true);
gtk_widget_set_visible(priv->button_trash_avatar, false);
gtk_widget_set_visible(priv->button_export_avatar, false);
gtk_widget_set_visible(priv->button_set_avatar, false);
/* available actions: take snapshot, return*/
gtk_widget_set_visible(priv->button_box_current, false);
gtk_widget_set_visible(priv->button_box_photo, true);
gtk_widget_set_visible(priv->button_box_edit, false);
}
break;
case AVATAR_MANIPULATION_STATE_EDIT:
{
/* make sure video widget and camera is no longer running */
Video::PreviewManager::instance().stopPreview();
/* make sure video widget and camera is not running */
if (priv->video_started_by_avatar_manipulation)
Video::PreviewManager::instance().stopPreview();
if (priv->video_widget) {
gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
priv->video_widget = NULL;
......@@ -317,12 +341,10 @@ set_state(AvatarManipulation *self, AvatarManipulationState state)
priv->origin[1] = 0;
priv->length = INITIAL_LENTGH;
/* available actions: set avatar, trash avatar */
gtk_widget_set_visible(priv->button_set_avatar, true);
gtk_widget_set_visible(priv->button_trash_avatar, true);
gtk_widget_set_visible(priv->button_take_photo, false);
gtk_widget_set_visible(priv->button_choose_picture, false);
gtk_widget_set_visible(priv->button_export_avatar, false);
/* available actions: set avatar, return */
gtk_widget_set_visible(priv->button_box_current, false);
gtk_widget_set_visible(priv->button_box_photo, false);
gtk_widget_set_visible(priv->button_box_edit, true);
gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_edit_view");
}
......@@ -333,20 +355,16 @@ set_state(AvatarManipulation *self, AvatarManipulationState state)
}
static void
take_a_photo(AvatarManipulation *self)
start_camera(AvatarManipulation *self)
{
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
video_widget_take_snapshot(VIDEO_WIDGET(priv->video_widget));
set_state(self, AVATAR_MANIPULATION_STATE_PHOTO);
}
static void
trash_photo(AvatarManipulation *self)
take_a_photo(AvatarManipulation *self)
{
/* clear the avatar in the profile */
ProfileModel::instance().selectedProfile()->person()->setPhoto(QVariant());
ProfileModel::instance().selectedProfile()->save();
set_state(self, AVATAR_MANIPULATION_STATE_NEW);
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
video_widget_take_snapshot(VIDEO_WIDGET(priv->video_widget));
}
static void
......@@ -389,6 +407,13 @@ set_avatar(AvatarManipulation *self)
set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
}
static void
return_to_previous_state(AvatarManipulation *self)
{
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
set_state(self, priv->last_state);
}
static void
choose_picture(AvatarManipulation *self)
{
......@@ -448,44 +473,6 @@ choose_picture(AvatarManipulation *self)
gtk_widget_destroy(dialog);
}
static void
export_avatar(AvatarManipulation *self)
{
GtkWidget *ring_main_window = gtk_widget_get_toplevel(GTK_WIDGET(self));
auto dialog = gtk_file_chooser_dialog_new(_("Save Avatar Image"),
GTK_WINDOW(ring_main_window),
GTK_FILE_CHOOSER_ACTION_SAVE,
_("_Cancel"),
GTK_RESPONSE_CANCEL,
_("_Save"),
GTK_RESPONSE_ACCEPT,
NULL);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
/* start the file chooser */
auto res = gtk_dialog_run (GTK_DIALOG (dialog));
if (res == GTK_RESPONSE_ACCEPT) {
if (auto filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))) {
GError* error = nullptr;
auto photo = ProfileModel::instance().selectedProfile()->person()->photo();
std::shared_ptr<GdkPixbuf> pixbuf_photo = photo.value<std::shared_ptr<GdkPixbuf>>();
if (photo.isValid()) {
gdk_pixbuf_save(pixbuf_photo.get(), filename, "png", &error, NULL);
if (error){
g_warning("(export_avatar) could not save avatar to file: %s\n", error->message);
g_error_free(error);
}
}
g_free (filename);
}
}
gtk_widget_destroy (dialog);
}
static void
update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview)
{
......
......@@ -37,19 +37,18 @@ typedef struct _AvatarManipulationClass AvatarManipulationClass;
/**
* AvatarManipulationState:
* @AVATAR_MANIPULATION_STATE_CURRENT: The initial state. The widget will display the current
* or the default avatar, with the possible actions being to delete (and select a new one) or to
* save to disk the current avatar.
* @AVATAR_MANIPULATION_STATE_NEW: The state in which the user is selecting a new avatar. If
* a video device is available, it will will be turned on. The possible actions being to take a
* snapshot or to select an existing image from a file.
* @AVATAR_MANIPULATION_STATE_EDIT: The state after selecting a new image when the user must
* select the square area they wish to use for the avatar. The possible actions being to confirm
* the selection or to discard it.
* or the default avatar, with the possible actions being to start the camera (if available) to
* take a new photo) or to select an existing image.
* @AVATAR_MANIPULATION_STATE_PHOTO: The state in which the camera is on and the video is being
* shown. The possible actions are to take a snapshot or to return to the previous state.
* @AVATAR_MANIPULATION_STATE_EDIT: The state after selecting a new image (or snapping a photo). The
* user must select the square area they wish to use for the avatar. The possible actions are to
* confirm the selection or to return to the previous state.
*/
typedef enum
{
AVATAR_MANIPULATION_STATE_CURRENT,
AVATAR_MANIPULATION_STATE_NEW,
AVATAR_MANIPULATION_STATE_PHOTO,
AVATAR_MANIPULATION_STATE_EDIT
} AvatarManipulationState;
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.19.0 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkImage" id="image_add_symbolic">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">list-add-symbolic</property>
</object>
<object class="GtkImage" id="image_camera_photo">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">camera-photo-symbolic</property>
</object>
<object class="GtkImage" id="image_save_symbolic">
<property name="can_focus">False</property>
<property name="icon_name">document-save-symbolic</property>
<object class="GtkImage" id="image_take_photo">
<property name="visible">True</property>
<property name="icon_name">object-select-symbolic</property>
</object>
<object class="GtkImage" id="image_select_symbolic">
<object class="GtkImage" id="image_select_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">object-select-symbolic</property>
</object>
<object class="GtkImage" id="image_trash_symbolic">
<object class="GtkImage" id="image_return_photo">
<property name="visible">True</property>
<property name="icon_name">edit-undo-symbolic</property>
</object>
<object class="GtkImage" id="image_return_edit">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">user-trash-symbolic</property>
<property name="icon_name">edit-undo-symbolic</property>
</object>
<template class="AvatarManipulation" parent="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="orientation">vertical</property>
......@@ -73,90 +71,108 @@
<child>
<object class="GtkBox" id="box_controls">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<style>
<class name="linked"/>
<class name="horizontal"/>
</style>
<child>
<object class="GtkButton" id="button_take_photo">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_camera_photo</property>
<property name="always_show_image">True</property>
<property name="tooltip-text" translatable="yes">Take photo</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<!-- initial state: take photo or choose image -->
<child>
<object class="GtkButton" id="button_trash_avatar">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_trash_symbolic</property>
<property name="always_show_image">True</property>
<property name="tooltip-text" translatable="yes">Delete current image</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_set_avatar">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_select_symbolic</property>
<property name="tooltip-text" translatable="yes">Set selection as image</property>
<object class="GtkBox" id="button_box_current">
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkButton" id="button_start_camera">
<property name="visible">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_camera_photo</property>
<property name="tooltip-text" translatable="yes">Take photo</property>
</object>
</child>
<child>
<object class="GtkButton" id="button_choose_picture">
<property name="visible">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_add_symbolic</property>
<property name="tooltip-text" translatable="yes">Choose image from file</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="fill">False</property>
</packing>
</child>
<!-- 2nd state: snap photo or return -->
<child>
<object class="GtkButton" id="button_choose_picture">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_add_symbolic</property>
<property name="tooltip-text" translatable="yes">Choose image from file</property>
<object class="GtkBox" id="button_box_photo">
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkButton" id="button_take_photo">
<property name="visible">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_take_photo</property>
<property name="tooltip-text" translatable="yes">Take photo</property>
</object>
</child>
<child>
<object class="GtkButton" id="button_return_photo">
<property name="visible">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_return_photo</property>
<property name="tooltip-text" translatable="yes">Return</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<!-- 3rd state: select photo area or return -->
<child>
<object class="GtkButton" id="button_export_avatar">
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image">image_save_symbolic</property>
<property name="tooltip-text" translatable="yes">Save image to file</property>
<object class="GtkBox" id="button_box_edit">
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkButton" id="button_set_avatar">
<property name="visible">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_select_area</property>
<property name="tooltip-text" translatable="yes">Set selection as image</property>
</object>
</child>
<child>
<object class="GtkButton" id="button_return_edit">
<property name="visible">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="image">image_return_edit</property>
<property name="tooltip-text" translatable="yes">Return</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment