diff options
author | Elias Entrup <elias-git@flump.de> | 2018-02-13 18:35:05 +0100 |
---|---|---|
committer | Elias Entrup <elias-git@flump.de> | 2018-04-18 11:50:52 +0200 |
commit | 1014e347434e83bc85678ae0c22fd095632a5b1c (patch) | |
tree | 270152aba7993695f14d0b7a8075e4468d030e9d | |
parent | c5a446b0235f02a4a6437919e4a6539f6c6327c6 (diff) | |
download | gnome-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.xml | 1 | ||||
-rw-r--r-- | data/ui/contacts-avatar-selector.ui | 335 | ||||
-rw-r--r-- | data/ui/contacts-crop-cheese-dialog.ui | 116 | ||||
-rw-r--r-- | data/ui/style.css | 5 | ||||
-rw-r--r-- | po/POTFILES.in | 2 | ||||
-rw-r--r-- | po/POTFILES.skip | 1 | ||||
-rw-r--r-- | src/contacts-avatar-selector.vala | 292 | ||||
-rw-r--r-- | src/contacts-contact-editor.vala | 8 | ||||
-rw-r--r-- | src/contacts-crop-cheese-dialog.vala | 114 | ||||
-rw-r--r-- | src/contacts-utils.vala | 10 | ||||
-rw-r--r-- | src/meson.build | 1 |
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', |