summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElias Entrup <elias-git@flump.de>2018-02-13 18:35:05 +0100
committerElias Entrup <elias-git@flump.de>2018-04-18 11:50:52 +0200
commit1014e347434e83bc85678ae0c22fd095632a5b1c (patch)
tree270152aba7993695f14d0b7a8075e4468d030e9d
parentc5a446b0235f02a4a6437919e4a6539f6c6327c6 (diff)
downloadgnome-contacts-1014e347434e83bc85678ae0c22fd095632a5b1c.tar.gz
avatar-selection: Use popover and window for crop/cheese
The avatar selection used to happen in a separate window including cropping and webcam. This commit introduces a Popover instead. For cropping and webcam, a separate window is opened again. These design changes were done according to new mockups. https://gitlab.gnome.org/GNOME/gnome-contacts/issues/84
-rw-r--r--data/contacts.gresource.xml1
-rw-r--r--data/ui/contacts-avatar-selector.ui335
-rw-r--r--data/ui/contacts-crop-cheese-dialog.ui116
-rw-r--r--data/ui/style.css5
-rw-r--r--po/POTFILES.in2
-rw-r--r--po/POTFILES.skip1
-rw-r--r--src/contacts-avatar-selector.vala292
-rw-r--r--src/contacts-contact-editor.vala8
-rw-r--r--src/contacts-crop-cheese-dialog.vala114
-rw-r--r--src/contacts-utils.vala10
-rw-r--r--src/meson.build1
11 files changed, 410 insertions, 475 deletions
diff --git a/data/contacts.gresource.xml b/data/contacts.gresource.xml
index a20079e..6564cfd 100644
--- a/data/contacts.gresource.xml
+++ b/data/contacts.gresource.xml
@@ -9,6 +9,7 @@
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-editor.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-pane.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-sheet.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks">ui/contacts-crop-cheese-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-in-app-notification.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-link-suggestion-grid.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-linked-personas-dialog.ui</file>
diff --git a/data/ui/contacts-avatar-selector.ui b/data/ui/contacts-avatar-selector.ui
index e117827..180ae6b 100644
--- a/data/ui/contacts-avatar-selector.ui
+++ b/data/ui/contacts-avatar-selector.ui
@@ -1,293 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <requires lib="gtk+" version="3.20"/>
- <template class="ContactsAvatarSelector" parent="GtkDialog">
- <property name="visible">True</property>
- <property name="title" translatable="yes">Select Picture</property>
- <property name="modal">True</property>
- <style>
- <class name="contacts-avatar-dialog"/>
- </style>
- <child internal-child="vbox">
+ <requires lib="gtk+" version="3.22"/>
+ <template class="ContactsAvatarSelector" parent="GtkPopover">
+ <property name="can_focus">False</property>
+ <child>
<object class="GtkBox">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">10</property>
+ <property name="margin_right">10</property>
+ <property name="margin_top">10</property>
+ <property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
+ <property name="spacing">10</property>
<child>
- <object class="GtkGrid" id="grid">
+ <object class="GtkFlowBox" id="personas_thumbnail_grid">
<property name="visible">True</property>
- <property name="border_width">8</property>
- <property name="column_spacing">16</property>
- <property name="row_spacing">11</property>
- <child>
- </child>
- <!-- ContactFrame -->
- <placeholder/>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">5</property>
+ <property name="selection_mode">none</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="stock_thumbnail_grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">5</property>
+ <property name="min_children_per_line">5</property>
+ <property name="max_children_per_line">8</property>
+ <property name="selection_mode">none</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="spacing">10</property>
<child>
- <object class="GtkLabel" id="contact_name_label">
- <property name="visible">True</property>
- <property name="halign">start</property>
- <property name="valign">start</property>
- <property name="hexpand">True</property>
- <property name="margin_top">4</property>
- <property name="ellipsize">end</property>
- <property name="label" translatable="yes">New Contact</property>
- <style>
- <class name="contact-display-name"/>
- </style>
+ <object class="GtkButton" id="cheese_button">
+ <property name="label" translatable="yes">Take a Picture...</property>
+ <property name="can_focus">True</property>
+ <property name="no_show_all">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_cheese_clicked" swapped="no"/>
</object>
<packing>
- <property name="top_attach">0</property>
- <property name="left_attach">1</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
- <object class="GtkFrame">
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Select a File...</property>
<property name="visible">True</property>
- <style>
- <class name="contacts-avatar-frame"/>
- </style>
- <child>
- <object class="GtkStack" id="views_stack">
- <property name="visible">True</property>
- <child>
- <object class="GtkGrid" id="thumbnail_page">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <child>
- <object class="GtkScrolledWindow">
- <property name="visible">True</property>
- <property name="hscrollbar_policy">never</property>
- <property name="vscrollbar_policy">automatic</property>
- <property name="hexpand">True</property>
- <property name="vexpand">True</property>
- <property name="height_request">300</property>
- <child>
- <object class="GtkBox">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <child>
- <object class="GtkFlowBox" id="personas_thumbnail_grid">
- <property name="visible">True</property>
- </object>
- </child>
- <child>
- <object class="GtkSeparator">
- <property name="visible">True</property>
- <property name="orientation">horizontal</property>
- </object>
- </child>
- <child>
- <object class="GtkFlowBox" id="stock_thumbnail_grid">
- <property name="visible">True</property>
- <property name="min_children_per_line">5</property>
- <property name="max_children_per_line">8</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkActionBar">
- <property name="visible">True</property>
- <child>
- <object class="GtkBox" id="webcam_button_box">
- <property name="orientation">horizontal</property>
- <style>
- <class name="linked"/>
- </style>
- <child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <signal name="clicked" handler="select_avatar_file_cb" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="pixel_size">16</property>
- <property name="icon_name">list-add-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="webcam_button">
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <signal name="clicked" handler="on_webcam_button_clicked" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="pixel_size">16</property>
- <property name="icon_name">camera-photo-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkButton">
- <property name="visible" bind-source="webcam_button_box" bind-property="visible" bind-flags="invert-boolean|sync-create" />
- <signal name="clicked" handler="select_avatar_file_cb" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="pixel_size">16</property>
- <property name="icon_name">list-add-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="name">thumbnail-page</property>
- </packing>
- </child>
- <child>
- <object class="GtkGrid" id="crop_page">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <child>
- <object class="GtkActionBar">
- <property name="visible">True</property>
- <child>
- <object class="GtkBox">
- <property name="visible">True</property>
- <property name="orientation">horizontal</property>
- <style>
- <class name="linked"/>
- </style>
- <child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <signal name="clicked" handler="on_crop_page_select_button_clicked" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="pixel_size">16</property>
- <property name="icon_name">object-select-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <signal name="clicked" handler="on_crop_page_cancel_button_clicked" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="pixel_size">16</property>
- <property name="icon_name">edit-undo-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="top_attach">1</property>
- <property name="left_attach">0</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="name">crop-page</property>
- </packing>
- </child>
- <child>
- <object class="GtkGrid" id="photobooth_page">
- <property name="orientation">vertical</property>
- <child>
- <object class="GtkActionBar">
- <property name="visible">True</property>
- <child>
- <object class="GtkBox">
- <property name="visible">True</property>
- <property name="orientation">horizontal</property>
- <style>
- <class name="linked"/>
- </style>
- <child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <signal name="clicked" handler="on_photobooth_page_select_button_clicked" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="pixel_size">16</property>
- <property name="icon_name">object-select-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <signal name="clicked" handler="on_photobooth_page_cancel_button_clicked" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="pixel_size">16</property>
- <property name="icon_name">edit-undo-symbolic</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="top_attach">1</property>
- <property name="left_attach">0</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="name">photobooth-page</property>
- </packing>
- </child>
- </object>
- </child>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_file_clicked" swapped="no"/>
</object>
<packing>
- <property name="top_attach">1</property>
- <property name="left_attach">0</property>
- <property name="width">2</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
</packing>
</child>
</object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
</child>
</object>
</child>
-
- <child type="action">
- <object class="GtkButton" id="select_button">
- <property name="visible">True</property>
- <property name="can-default">True</property>
- <property name="sensitive">False</property>
- <property name="label" translatable="yes">Select</property>
- </object>
- </child>
- <child type="action">
- <object class="GtkButton" id="cancel_button">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Cancel</property>
- </object>
- </child>
- <action-widgets>
- <action-widget response="cancel">cancel_button</action-widget>
- <action-widget response="ok" default="true">select_button</action-widget>
- </action-widgets>
</template>
</interface>
diff --git a/data/ui/contacts-crop-cheese-dialog.ui b/data/ui/contacts-crop-cheese-dialog.ui
new file mode 100644
index 0000000..bcb402c
--- /dev/null
+++ b/data/ui/contacts-crop-cheese-dialog.ui
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.22"/>
+ <template class="ContactsCropCheeseDialog" parent="GtkWindow">
+ <property name="can_focus">False</property>
+ <property name="modal">True</property>
+ <property name="default_width">400</property>
+ <property name="default_height">400</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="skip_taskbar_hint">True</property>
+ <signal name="destroy" handler="on_destroy" swapped="no"/>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="headerbar_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="visible-child-name" bind-source="stack" bind-property="visible-child-name" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkButton" id="take_another_button">
+ <property name="label" translatable="yes">Take Another...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="no_show_all">True</property>
+ <signal name="clicked" handler="on_take_another_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Done</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_done_clicked" swapped="no"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">crop</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">end</property>
+ <signal name="clicked" handler="on_take_pic_clicked" swapped="no"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">camera-photo-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="name">cheese</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/style.css b/data/ui/style.css
index 0292a61..455dd65 100644
--- a/data/ui/style.css
+++ b/data/ui/style.css
@@ -82,3 +82,8 @@ row.contact-data-row {
.contacts-avatar-popover .contact-display-name {
font-size: 20px;
}
+
+.avatar-button {
+ border-radius: 50%;
+ padding: 0 0;
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 699261d..09289c9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -9,6 +9,7 @@ data/ui/contacts-avatar-selector.ui
data/ui/contacts-contact-editor.ui
data/ui/contacts-contact-pane.ui
data/ui/contacts-contact-sheet.ui
+data/ui/contacts-crop-cheese-dialog.ui
data/ui/contacts-link-suggestion-grid.ui
data/ui/contacts-linked-personas-dialog.ui
data/ui/contacts-list-pane.ui
@@ -23,6 +24,7 @@ src/contacts-contact-list.vala
src/contacts-contact-pane.vala
src/contacts-contact-sheet.vala
src/contacts-contact.vala
+src/contacts-crop-cheese-dialog.vala
src/contacts-esd-setup.vala
src/contacts-im-service.vala
src/contacts-linked-personas-dialog.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 3125cfd..8fae880 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -11,6 +11,7 @@ src/contacts-contact-editor.c
src/contacts-contact-list.c
src/contacts-contact-pane.c
src/contacts-contact-sheet.c
+src/contacts-crop-cheese-dialog.c
src/contacts-esd-setup.c
src/contacts-im-service.c
src/contacts-linked-personas-dialog.c
diff --git a/src/contacts-avatar-selector.vala b/src/contacts-avatar-selector.vala
index 83e3631..e2e66cd 100644
--- a/src/contacts-avatar-selector.vala
+++ b/src/contacts-avatar-selector.vala
@@ -27,156 +27,117 @@ using Folks;
* After a user has initially chosen an avatar, we provide a cropping tool.
*/
[GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-avatar-selector.ui")]
-public class Contacts.AvatarSelector : Dialog {
- const int MAIN_SIZE = 128;
+public class Contacts.AvatarSelector : Popover {
const int ICONS_SIZE = 64;
-
- private Contact contact;
+ const int MAIN_SIZE = 128;
+ const string AVATAR_BUTTON_CSS_NAME = "avatar-button";
// This will provide the default thumbnails
private Gnome.DesktopThumbnailFactory thumbnail_factory;
+ private Contact contact;
[GtkChild]
- private Grid grid;
- [GtkChild]
- private Label contact_name_label;
- [GtkChild]
- private Stack views_stack;
- [GtkChild]
private FlowBox personas_thumbnail_grid;
[GtkChild]
private FlowBox stock_thumbnail_grid;
- [GtkChild]
- private Grid crop_page;
- private Cc.CropArea crop_area;
- [GtkChild]
- private Grid photobooth_page;
- [GtkChild]
- private Button webcam_button;
- [GtkChild]
- private Box webcam_button_box;
-
- private Avatar current_avatar;
#if HAVE_CHEESE
- private Cheese.Flash flash;
- private Cheese.CameraDeviceMonitor camera_monitor;
- private Cheese.Widget cheese;
+ [GtkChild]
+ private Button cheese_button;
private int num_cameras;
+ private Cheese.CameraDeviceMonitor camera_monitor;
#endif
- private Gdk.Pixbuf? new_pixbuf;
-
/**
* Fired after the user has definitely chosen a new avatar.
*/
public signal void set_avatar (GLib.Icon avatar_icon);
- public AvatarSelector (Window main_window, Contact? contact) {
- Object (
- transient_for: main_window,
- use_header_bar: 1
- );
-
+ public AvatarSelector (Gtk.Widget relative, Contact? contact) {
+ this.set_relative_to(relative);
this.thumbnail_factory = new Gnome.DesktopThumbnailFactory (Gnome.ThumbnailSize.NORMAL);
this.contact = contact;
- // Load the current avatar
- this.current_avatar = new Avatar (MAIN_SIZE, contact);
- this.current_avatar.set_hexpand (false);
- this.current_avatar.show ();
- this.grid.attach (this.current_avatar, 0, 0);
-
- if (contact != null)
- this.contact_name_label.label = contact.individual.display_name;
+ update_thumbnail_grids ();
#if HAVE_CHEESE
- this.webcam_button_box.show ();
+ this.cheese_button.visible = true;
// Look for camera devices.
this.camera_monitor = new Cheese.CameraDeviceMonitor ();
this.camera_monitor.added.connect ( () => {
this.num_cameras++;
- this.webcam_button.sensitive = (this.num_cameras > 0);
+ this.cheese_button.sensitive = (this.num_cameras > 0);
});
this.camera_monitor.removed.connect ( () => {
this.num_cameras--;
- this.webcam_button.sensitive = (this.num_cameras > 0);
+ this.cheese_button.sensitive = (this.num_cameras > 0);
});
// Do this in a separate thread, or it blocks the whole UI
new Thread<void*> ("camera-loader", () => {
this.camera_monitor.coldplug ();
return null;
});
-
- // Create a photobooth page
- this.cheese = new Cheese.Widget ();
- this.cheese.set_vexpand (true);
- this.cheese.set_hexpand (true);
- this.cheese.set_no_show_all (true);
- this.photobooth_page.attach (cheese, 0, 0);
- this.photobooth_page.show ();
-
- this.flash = new Cheese.Flash (this);
-#else
- // Don't show the camera button
- this.webcam_button_box.hide ();
#endif
-
- this.views_stack.set_visible_child_name ("thumbnail-page");
- /*
- var remove_button = new ToolButton (null, null);
- remove_button.set_icon_name ("list-remove-symbolic");
- remove_button.get_style_context ().add_class (STYLE_CLASS_RAISED);
- remove_button.is_important = true;
- toolbar.add (remove_button);
- remove_button.clicked.connect ( (button) => {
- });
- */
-
- update_thumbnail_grids ();
}
private Gdk.Pixbuf scale_pixbuf_for_avatar_use (Gdk.Pixbuf pixbuf) {
int w = pixbuf.get_width ();
int h = pixbuf.get_height ();
- if (w <= 128 && h <= 128)
+ if (w <= MAIN_SIZE && h <= MAIN_SIZE)
return pixbuf;
if (w > h) {
- h = (int)Math.round (h * 128.0 / w);
- w = 128;
+ h = (int)Math.round (h * (float) MAIN_SIZE / w);
+ w = MAIN_SIZE;
} else {
- w = (int)Math.round (w * 128.0 / h);
- h = 128;
+ w = (int)Math.round (w * (float) MAIN_SIZE / h);
+ h = MAIN_SIZE;
}
return pixbuf.scale_simple (w, h, Gdk.InterpType.HYPER);
}
- private Button create_thumbnail (Gdk.Pixbuf source_pixbuf) {
+ private void selected_pixbuf (Gdk.Pixbuf pixbuf) {
+ try {
+ uint8[] buffer;
+ pixbuf.save_to_buffer (out buffer, "png", null);
+ var icon = new BytesIcon (new Bytes (buffer));
+ set_avatar (icon);
+ } catch (GLib.Error e) {
+ warning ("Failed to set avatar: %s", e.message);
+ Utils.show_error_dialog (_("Failed to set avatar."),
+ this.get_toplevel() as Gtk.Window);
+ }
+ }
+
+ private FlowBoxChild create_thumbnail (Gdk.Pixbuf source_pixbuf) {
var avatar = new Avatar (ICONS_SIZE);
var pixbuf = source_pixbuf.scale_simple (ICONS_SIZE, ICONS_SIZE, Gdk.InterpType.HYPER);
avatar.set_pixbuf (pixbuf);
var button = new Button ();
- button.get_style_context ().add_class ("flat");
+ button.get_style_context ().add_class (AVATAR_BUTTON_CSS_NAME);
button.image = avatar;
button.clicked.connect ( () => {
selected_pixbuf (scale_pixbuf_for_avatar_use (source_pixbuf));
+ this.popdown ();
});
+ var child = new FlowBoxChild ();
+ child.add (button);
+ child.set_halign (Align.START);
- return button;
+ return child;
}
- private Button? thumbnail_for_persona (Persona persona) {
+ private FlowBoxChild? thumbnail_for_persona (Persona persona) {
var details = persona as AvatarDetails;
if (details == null || details.avatar == null)
return null;
try {
- var stream = details.avatar.load (128, null);
+ var stream = details.avatar.load (MAIN_SIZE, null);
return create_thumbnail (new Gdk.Pixbuf.from_stream (stream));
} catch {
debug ("Couldn't create frame for persona \"%s\".", persona.display_id);
@@ -185,7 +146,7 @@ public class Contacts.AvatarSelector : Dialog {
return null;
}
- private Button? thumbnail_for_filename (string filename) {
+ private FlowBoxChild? thumbnail_for_filename (string filename) {
try {
return create_thumbnail (new Gdk.Pixbuf.from_file (filename));
} catch {
@@ -195,14 +156,6 @@ public class Contacts.AvatarSelector : Dialog {
return null;
}
- private void selected_pixbuf (Gdk.Pixbuf pixbuf) {
- var p = pixbuf.scale_simple (MAIN_SIZE, MAIN_SIZE, Gdk.InterpType.HYPER);
- this.current_avatar.set_pixbuf (p);
-
- this.new_pixbuf = pixbuf;
- set_response_sensitive (ResponseType.OK, true);
- }
-
private void update_thumbnail_grids () {
if (this.contact != null) {
foreach (var p in contact.individual.personas) {
@@ -222,78 +175,18 @@ public class Contacts.AvatarSelector : Dialog {
this.stock_thumbnail_grid.show_all ();
}
- public void update_preview (FileChooser chooser) {
- var uri = chooser.get_preview_uri ();
- if (uri != null) {
- Gdk.Pixbuf? pixbuf = null;
-
- var preview = chooser.get_preview_widget () as Image;
-
- var file = File.new_for_uri (uri);
- try {
- var file_info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE,
- FileQueryInfoFlags.NONE, null);
- if (file_info != null) {
- var mime_type = file_info.get_content_type ();
-
- if (mime_type != null)
- pixbuf = thumbnail_factory.generate_thumbnail (uri, mime_type);
- }
- } catch (GLib.Error e) {
- }
-
- (chooser as Dialog).set_response_sensitive (ResponseType.ACCEPT, (pixbuf != null));
-
- if (pixbuf != null)
- preview.set_from_pixbuf (pixbuf);
- else
- preview.set_from_icon_name ("dialog-question", IconSize.DIALOG);
- }
-
- chooser.set_preview_widget_active (true);
- }
-
- private void set_crop_widget (Gdk.Pixbuf pixbuf) {
- this.crop_area = new Cc.CropArea ();
- this.crop_area.set_vexpand (true);
- this.crop_area.set_hexpand (true);
- this.crop_area.set_min_size (48, 48);
- this.crop_area.set_constrain_aspect (true);
- this.crop_area.set_picture (pixbuf);
-
- this.crop_page.attach (this.crop_area, 0, 0);
- this.crop_page.show_all ();
-
- this.views_stack.set_visible_child_name ("crop-page");
- }
-
- public override void response (int response_id) {
- if (response_id == ResponseType.OK && this.new_pixbuf != null) {
- try {
- uint8[] buffer;
- if (this.new_pixbuf.save_to_buffer (out buffer, "png", null)) {
- var icon = new BytesIcon (new Bytes (buffer));
- set_avatar (icon);
- } else {
- /* Failure. Fall through. */
- }
- } catch {
- }
- }
-
-#if HAVE_CHEESE
- /* Ensure the Vala garbage collector disposes of the Cheese widget.
- * This prevents the 'Device or resource busy' warnings, see:
- * https://bugzilla.gnome.org/show_bug.cgi?id=700959
- */
- this.cheese = null;
-#endif
-
- this.destroy ();
+ [GtkCallback]
+ private void on_cheese_clicked (Button button) {
+ var dialog = new CropCheeseDialog.for_cheese ((Window) this.get_toplevel());
+ dialog.show_all ();
+ dialog.picture_selected.connect ( (pix) => {
+ selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
+ });
+ this.popdown ();
}
[GtkCallback]
- private void select_avatar_file_cb (Button button) {
+ private void on_file_clicked (Button button) {
var chooser = new FileChooserDialog (_("Browse for more pictures"),
(Gtk.Window)this.get_toplevel (),
FileChooserAction.OPEN,
@@ -302,7 +195,7 @@ public class Contacts.AvatarSelector : Dialog {
chooser.set_modal (true);
chooser.set_local_only (false);
var preview = new Image ();
- preview.set_size_request (128, -1);
+ preview.set_size_request (MAIN_SIZE, -1);
chooser.set_preview_widget (preview);
chooser.set_use_preview_label (false);
preview.show ();
@@ -323,63 +216,58 @@ public class Contacts.AvatarSelector : Dialog {
var in_stream = file.read ();
var pixbuf = new Gdk.Pixbuf.from_stream (in_stream, null);
in_stream.close ();
- if (pixbuf.get_width () > 128 || pixbuf.get_height () > 128)
- set_crop_widget (pixbuf);
- else
+ if (pixbuf.get_width () > MAIN_SIZE || pixbuf.get_height () > MAIN_SIZE) {
+ var dialog = new CropCheeseDialog.for_crop ((Window) this.get_toplevel(),
+ pixbuf);
+ dialog.picture_selected.connect ( (pix) => {
+ selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
+ });
+ dialog.show_all();
+ } else {
selected_pixbuf (scale_pixbuf_for_avatar_use (pixbuf));
-
- update_thumbnail_grids ();
- } catch {
+ }
+ } catch (GLib.Error e) {
+ warning ("Failed to set avatar: %s", e.message);
+ Utils.show_error_dialog (_("Failed to set avatar."),
+ this.get_toplevel() as Gtk.Window);
}
chooser.destroy ();
});
chooser.present ();
+ this.popdown();
}
- [GtkCallback]
- private void on_webcam_button_clicked (Button button) {
-#if HAVE_CHEESE
- this.views_stack.set_visible_child_name ("photobooth-page");
- this.cheese.show ();
-#endif
- }
+ private void update_preview (FileChooser chooser) {
+ var uri = chooser.get_preview_uri ();
+ if (uri != null) {
+ Gdk.Pixbuf? pixbuf = null;
- [GtkCallback]
- private void on_photobooth_page_select_button_clicked (Button button) {
-#if HAVE_CHEESE
- var camera = this.cheese.get_camera () as Cheese.Camera;
- this.flash.fire ();
- camera.photo_taken.connect ( (pix) => {
- set_crop_widget (pix);
- this.cheese.hide ();
- });
+ var preview = chooser.get_preview_widget () as Image;
- if (!camera.take_photo_pixbuf ())
- warning ("Unable to take photo");
-#endif
- }
+ var file = File.new_for_uri (uri);
+ try {
+ var file_info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE,
+ FileQueryInfoFlags.NONE, null);
+ if (file_info != null) {
+ var mime_type = file_info.get_content_type ();
- [GtkCallback]
- private void on_photobooth_page_cancel_button_clicked (Button button) {
-#if HAVE_CHEESE
- this.views_stack.set_visible_child_name ("thumbnail-page");
- this.cheese.hide ();
-#endif
- }
+ if (mime_type != null)
+ pixbuf = thumbnail_factory.generate_thumbnail (uri, mime_type);
+ }
+ } catch {
+ }
- [GtkCallback]
- private void on_crop_page_select_button_clicked (Button button) {
- var pix = crop_area.get_picture ();
- selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
- this.crop_area.destroy ();
- this.views_stack.set_visible_child_name ("thumbnail-page");
- }
+ (chooser as Dialog).set_response_sensitive (ResponseType.ACCEPT, (pixbuf != null));
- [GtkCallback]
- private void on_crop_page_cancel_button_clicked (Button button) {
- this.crop_area.destroy ();
- this.views_stack.set_visible_child_name ("thumbnail-page");
+ if (pixbuf != null)
+ preview.set_from_pixbuf (pixbuf);
+ else
+ preview.set_from_icon_name ("dialog-question", IconSize.DIALOG);
+ }
+
+ chooser.set_preview_widget_active (true);
}
+
}
diff --git a/src/contacts-contact-editor.vala b/src/contacts-contact-editor.vala
index 16c11da..a311525 100644
--- a/src/contacts-contact-editor.vala
+++ b/src/contacts-contact-editor.vala
@@ -954,10 +954,10 @@ public class Contacts.ContactEditor : Grid {
this.container_grid.attach (button, 0, 0, 1, 3);
}
- // Show the avatar dialog when the avatar is clicked
+ // Show the avatar popover when the avatar is clicked
private void on_avatar_button_clicked (Button avatar_button) {
- var dialog = new AvatarSelector ((Window) get_toplevel (), this.contact);
- dialog.set_avatar.connect ( (icon) => {
+ var popover = new AvatarSelector (avatar_button, this.contact);
+ popover.set_avatar.connect ( (icon) => {
this.avatar.set_data ("value", icon);
this.avatar.set_data ("changed", true);
@@ -970,7 +970,7 @@ public class Contacts.ContactEditor : Grid {
this.avatar.set_pixbuf (a_pixbuf);
});
- dialog.run ();
+ popover.show();
}
public bool avatar_changed () {
diff --git a/src/contacts-crop-cheese-dialog.vala b/src/contacts-crop-cheese-dialog.vala
new file mode 100644
index 0000000..cc912c2
--- /dev/null
+++ b/src/contacts-crop-cheese-dialog.vala
@@ -0,0 +1,114 @@
+/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2018 Elias Entrup <elias-git@flump.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-crop-cheese-dialog.ui")]
+public class Contacts.CropCheeseDialog : Gtk.Window {
+ [GtkChild]
+ private Stack stack;
+ [GtkChild]
+ private Button take_another_button;
+
+ private Cc.CropArea crop_area;
+ private const string STACK_NAME_CROP = "crop";
+ private const string STACK_NAME_CHEESE = "cheese";
+
+#if HAVE_CHEESE
+ private Cheese.Flash flash;
+ private Cheese.Widget cheese;
+#endif
+
+ public signal void picture_selected (Gdk.Pixbuf buf);
+
+ public CropCheeseDialog.for_cheese (Gtk.Window parent) {
+#if HAVE_CHEESE
+ setup_widget (parent);
+ this.flash = new Cheese.Flash (this);
+ this.cheese = new Cheese.Widget ();
+ this.cheese.show ();
+ this.stack.add_named (this.cheese, STACK_NAME_CHEESE);
+ this.stack.set_visible_child_name (STACK_NAME_CHEESE);
+#endif
+ }
+
+ public CropCheeseDialog.for_crop (Gtk.Window parent, Gdk.Pixbuf pixbuf) {
+ setup_widget (parent);
+ this.take_another_button.visible = false;
+ this.crop_area.set_picture (pixbuf);
+ }
+
+ /* this function is called from both constructors */
+ private void setup_widget (Gtk.Window parent) {
+ this.set_transient_for (parent);
+
+ this.crop_area = new Cc.CropArea ();
+ this.crop_area.set_vexpand (true);
+ this.crop_area.set_hexpand (true);
+ this.crop_area.set_min_size (48, 48);
+ this.crop_area.set_constrain_aspect (true);
+ this.stack.add_named (this.crop_area, STACK_NAME_CROP);
+ }
+
+ [GtkCallback]
+ private void on_cancel_clicked (Button button) {
+ this.destroy ();
+ }
+
+ [GtkCallback]
+ private void on_take_another_clicked (Button button) {
+#if HAVE_CHEESE
+ this.stack.set_visible_child_name (STACK_NAME_CHEESE);
+#endif
+ }
+
+ [GtkCallback]
+ private void on_take_pic_clicked (Button button) {
+#if HAVE_CHEESE
+ var camera = this.cheese.get_camera () as Cheese.Camera;
+ this.flash.fire ();
+ camera.photo_taken.connect ( (pix) => {
+ this.stack.set_visible_child_name (STACK_NAME_CROP);
+ this.crop_area.set_picture(pix);
+ });
+
+ if (!camera.take_photo_pixbuf ()) {
+ Utils.show_error_dialog (_("Unable to take photo."),
+ this as Gtk.Window);
+ }
+#endif
+ }
+
+ [GtkCallback]
+ private void on_done_clicked (Button button) {
+ picture_selected (this.crop_area.get_picture ());
+ destroy();
+ }
+
+ [GtkCallback]
+ private void on_destroy () {
+#if HAVE_CHEESE
+ /* Ensure the Vala garbage collector disposes of the Cheese widget.
+ * This prevents the 'Device or resource busy' warnings, see:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=700959
+ */
+ this.cheese = null;
+#endif
+ }
+
+}
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index f4c7ffc..ad24d26 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -163,4 +163,14 @@ namespace Contacts.Utils {
}
return stores;
}
+
+ public void show_error_dialog (string error, Gtk.Window toplevel) {
+ var dialog = new Gtk.MessageDialog (toplevel,
+ Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.ERROR,
+ Gtk.ButtonsType.OK,
+ error);
+ dialog.run();
+ dialog.destroy();
+ }
}
diff --git a/src/meson.build b/src/meson.build
index 7dc6d27..d05d1f5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -15,6 +15,7 @@ contacts_vala_sources = files(
'contacts-contact-pane.vala',
'contacts-contact-sheet.vala',
'contacts-contact.vala',
+ 'contacts-crop-cheese-dialog.vala',
'contacts-esd-setup.vala',
'contacts-im-service.vala',
'contacts-in-app-notification.vala',