[geary/wip/135-contact-popovers: 5/20] Add initial contact popover UI and implementation



commit 37a9de05be3519e25b227efeb059b3bf17599cb1
Author: Michael Gratton <mike vee net>
Date:   Thu Mar 14 16:59:55 2019 +1100

    Add initial contact popover UI and implementation

 .../application/application-avatar-store.vala      |   3 +
 src/client/application/geary-controller.vala       |  12 +-
 .../conversation-contact-popover.vala              | 113 ++++++++++++++++++
 src/client/meson.build                             |   1 +
 ui/conversation-contact-popover.ui                 | 127 +++++++++++++++++++++
 ui/geary.css                                       |   5 +
 ui/org.gnome.Geary.gresource.xml                   |   1 +
 7 files changed, 256 insertions(+), 6 deletions(-)
---
diff --git a/src/client/application/application-avatar-store.vala 
b/src/client/application/application-avatar-store.vala
index 6d3d9402..e57bcf34 100644
--- a/src/client/application/application-avatar-store.vala
+++ b/src/client/application/application-avatar-store.vala
@@ -12,6 +12,9 @@
 public class Application.AvatarStore : Geary.BaseObject {
 
 
+    /** Default size of avatar images, in toolkit pixels */
+    public const int PIXEL_SIZE = 48;
+
     // Max age is low since we really only want to cache between
     // conversation loads.
     private const int64 MAX_CACHE_AGE_US = 5 * 1000 * 1000;
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index a0b9f789..87ab2047 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -120,7 +120,7 @@ public class GearyController : Geary.BaseObject {
 
     public AutostartManager? autostart_manager { get; private set; default = null; }
 
-    public Application.AvatarStore? avatar_store {
+    public Application.AvatarStore? avatars {
         get; private set; default = null;
     }
 
@@ -269,7 +269,7 @@ public class GearyController : Geary.BaseObject {
                 error("Error preparing Folks: %s", err.message);
             }
         }
-        this.avatar_store = new Application.AvatarStore(individuals);
+        this.avatars = new Application.AvatarStore(individuals);
 
         // Create the main window (must be done after creating actions.)
         main_window = new MainWindow(this.application);
@@ -310,7 +310,7 @@ public class GearyController : Geary.BaseObject {
         unity_launcher = new UnityLauncher(new_messages_monitor);
 
         this.libnotify = new Libnotify(
-            this.new_messages_monitor, this.avatar_store
+            this.new_messages_monitor, this.avatars
         );
         this.libnotify.invoked.connect(on_libnotify_invoked);
 
@@ -540,8 +540,8 @@ public class GearyController : Geary.BaseObject {
 
         this.autostart_manager = null;
 
-        this.avatar_store.close();
-        this.avatar_store = null;
+        this.avatars.close();
+        this.avatars = null;
 
 
         debug("Closed GearyController");
@@ -1331,7 +1331,7 @@ public class GearyController : Geary.BaseObject {
                     viewer.load_conversation.begin(
                         convo,
                         store,
-                        this.avatar_store,
+                        this.avatars,
                         (obj, ret) => {
                             try {
                                 viewer.load_conversation.end(ret);
diff --git a/src/client/conversation-viewer/conversation-contact-popover.vala 
b/src/client/conversation-viewer/conversation-contact-popover.vala
new file mode 100644
index 00000000..c781a9dc
--- /dev/null
+++ b/src/client/conversation-viewer/conversation-contact-popover.vala
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Display contact information and supported actions for a contact.
+ */
+[GtkTemplate (ui = "/org/gnome/Geary/conversation-contact-popover.ui")]
+public class Conversation.ContactPopover : Gtk.Popover {
+
+
+    public Geary.RFC822.MailboxAddress mailbox { get; private set; }
+
+    private GLib.Cancellable load_cancellable = new GLib.Cancellable();
+
+    [GtkChild]
+    private Gtk.Grid container;
+
+    [GtkChild]
+    private Gtk.Image avatar;
+
+    [GtkChild]
+    private Gtk.Label contact_name;
+
+    [GtkChild]
+    private Gtk.Label contact_address;
+
+
+    public ContactPopover(Gtk.Widget relative_to,
+                          Geary.RFC822.MailboxAddress mailbox) {
+        this.relative_to = relative_to;
+        this.mailbox = mailbox;
+        if (Geary.String.is_empty_or_whitespace(mailbox.name)) {
+            this.contact_name.set_text(mailbox.address);
+            this.contact_name.vexpand = true;
+            this.contact_name.valign = FILL;
+            this.contact_address.hide();
+        } else {
+            this.contact_name.set_text(mailbox.name);
+            this.contact_address.set_text(mailbox.address);
+        }
+    }
+
+    public void add_section(GLib.MenuModel section,
+                            Gee.Map<string,GLib.Variant> values) {
+        Gtk.Separator separator = new Gtk.Separator(Gtk.Orientation.HORIZONTAL);
+        separator.show();
+        this.container.add(separator);
+        for (int i = 0; i < section.get_n_items(); i++) {
+            GLib.MenuItem item = new MenuItem.from_model(section, i);
+            string action_fq = (string) item.get_attribute_value(
+                Menu.ATTRIBUTE_ACTION, VariantType.STRING
+            );
+
+            string action_name = action_fq.substring(action_fq.index_of(".") + 1);
+
+            Gtk.ModelButton button = new Gtk.ModelButton();
+            button.text = (string) item.get_attribute_value(
+                Menu.ATTRIBUTE_LABEL, VariantType.STRING
+            );
+            button.action_name = (string) item.get_attribute_value(
+                Menu.ATTRIBUTE_ACTION, VariantType.STRING
+            );
+            button.action_target = values[action_name];
+            button.show();
+
+            this.container.add(button);
+        }
+    }
+
+    /**
+     * Starts loading the avatar for the message's sender.
+     */
+    public async void load_avatar() {
+        MainWindow? main = this.get_toplevel() as MainWindow;
+        if (main != null) {
+            Application.AvatarStore loader = main.application.controller.avatars;
+            int window_scale = get_scale_factor();
+            int pixel_size = Application.AvatarStore.PIXEL_SIZE * window_scale;
+            try {
+                Gdk.Pixbuf? avatar_buf = yield loader.load(
+                    this.mailbox, pixel_size, this.load_cancellable
+                );
+                if (avatar_buf != null) {
+                    this.avatar.set_from_surface(
+                        Gdk.cairo_surface_create_from_pixbuf(
+                            avatar_buf, window_scale, get_window()
+                        )
+                    );
+                }
+            } catch (GLib.Error err) {
+                debug("Conversation load failed: %s", err.message);
+            }
+        }
+    }
+
+    public override void destroy() {
+        this.load_cancellable.cancel();
+        base.destroy();
+    }
+
+    [GtkCallback]
+    private void after_closed() {
+        GLib.Idle.add(() => {
+                this.destroy();
+                return GLib.Source.REMOVE;
+            } );
+    }
+
+}
diff --git a/src/client/meson.build b/src/client/meson.build
index d1c7863a..77db6b1a 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -55,6 +55,7 @@ geary_client_vala_sources = files(
   'conversation-list/conversation-list-view.vala',
   'conversation-list/formatted-conversation-data.vala',
 
+  'conversation-viewer/conversation-contact-popover.vala',
   'conversation-viewer/conversation-email.vala',
   'conversation-viewer/conversation-list-box.vala',
   'conversation-viewer/conversation-message.vala',
diff --git a/ui/conversation-contact-popover.ui b/ui/conversation-contact-popover.ui
new file mode 100644
index 00000000..fc81a9db
--- /dev/null
+++ b/ui/conversation-contact-popover.ui
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="ConversationContactPopover" parent="GtkPopover">
+    <property name="can_focus">False</property>
+    <signal name="closed" handler="after_closed" after="yes" swapped="no"/>
+    <child>
+      <object class="GtkGrid" id="container">
+        <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="row_spacing">2</property>
+        <child>
+          <object class="GtkGrid">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_bottom">6</property>
+            <property name="column_spacing">6</property>
+            <child>
+              <object class="GtkButton" id="unstarred_button">
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">non-starred-symbolic</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="starred_button">
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">starred-symbolic</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">3</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkImage" id="avatar">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="pixel_size">48</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="contact_name">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="valign">end</property>
+                    <property name="vexpand">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="contact_address">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="valign">start</property>
+                    <property name="margin_top">2</property>
+                    <property name="hexpand">False</property>
+                    <property name="vexpand">True</property>
+                    <property name="ellipsize">middle</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <style>
+      <class name="menu"/>
+      <class name="geary-contact-popover"/>
+    </style>
+  </template>
+</interface>
diff --git a/ui/geary.css b/ui/geary.css
index a11f940f..0820d8c0 100644
--- a/ui/geary.css
+++ b/ui/geary.css
@@ -174,6 +174,11 @@ grid.geary-message-summary {
   margin: 36px 16px;
 }
 
+/* ContactPopover */
+
+.geary-contact-popover .dim-label {
+  font-size: 80%;
+}
 
 /* Composer */
 
diff --git a/ui/org.gnome.Geary.gresource.xml b/ui/org.gnome.Geary.gresource.xml
index 45c0e4f6..da1d0fc9 100644
--- a/ui/org.gnome.Geary.gresource.xml
+++ b/ui/org.gnome.Geary.gresource.xml
@@ -17,6 +17,7 @@
     <file compressed="true" preprocess="xml-stripblanks">composer-widget.ui</file>
     <file compressed="true">composer-web-view.css</file>
     <file compressed="true">composer-web-view.js</file>
+    <file compressed="true" preprocess="xml-stripblanks">conversation-contact-popover.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">conversation-email.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">conversation-email-attachment-view.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">conversation-email-menus.ui</file>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]