New Avatar UI
This commit is contained in:
parent
d818296520
commit
db3b0d5f23
|
@ -12,6 +12,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
public string id { get { return IDENTITY.id; } }
|
public string id { get { return IDENTITY.id; } }
|
||||||
|
|
||||||
public signal void received_avatar(Jid jid, Account account);
|
public signal void received_avatar(Jid jid, Account account);
|
||||||
|
public signal void fetched_avatar(Jid jid, Account account);
|
||||||
|
|
||||||
private enum Source {
|
private enum Source {
|
||||||
USER_AVATARS,
|
USER_AVATARS,
|
||||||
|
@ -25,6 +26,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
|
||||||
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
|
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
|
||||||
private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
|
private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
|
||||||
|
private HashSet<string> pending_fetch = new HashSet<string>();
|
||||||
private const int MAX_PIXEL = 192;
|
private const int MAX_PIXEL = 192;
|
||||||
|
|
||||||
public static void start(StreamInteractor stream_interactor, Database db) {
|
public static void start(StreamInteractor stream_interactor, Database db) {
|
||||||
|
@ -45,6 +47,18 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File? get_avatar_file(Account account, Jid jid_) {
|
||||||
|
string? hash = get_avatar_hash(account, jid_);
|
||||||
|
if (hash == null) return null;
|
||||||
|
File file = File.new_for_path(Path.build_filename(folder, hash));
|
||||||
|
if (!file.query_exists()) {
|
||||||
|
fetch_and_store_for_jid(account, jid_);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string? get_avatar_hash(Account account, Jid jid_) {
|
private string? get_avatar_hash(Account account, Jid jid_) {
|
||||||
Jid jid = jid_;
|
Jid jid = jid_;
|
||||||
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
||||||
|
@ -59,6 +73,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Version (deprecated = true)]
|
||||||
public bool has_avatar_cached(Account account, Jid jid) {
|
public bool has_avatar_cached(Account account, Jid jid) {
|
||||||
string? hash = get_avatar_hash(account, jid);
|
string? hash = get_avatar_hash(account, jid);
|
||||||
return hash != null && cached_pixbuf.has_key(hash);
|
return hash != null && cached_pixbuf.has_key(hash);
|
||||||
|
@ -68,6 +83,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return get_avatar_hash(account, jid) != null;
|
return get_avatar_hash(account, jid) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Version (deprecated = true)]
|
||||||
public Pixbuf? get_cached_avatar(Account account, Jid jid_) {
|
public Pixbuf? get_cached_avatar(Account account, Jid jid_) {
|
||||||
string? hash = get_avatar_hash(account, jid_);
|
string? hash = get_avatar_hash(account, jid_);
|
||||||
if (hash == null) return null;
|
if (hash == null) return null;
|
||||||
|
@ -75,6 +91,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Version (deprecated = true)]
|
||||||
public async Pixbuf? get_avatar(Account account, Jid jid_) {
|
public async Pixbuf? get_avatar(Account account, Jid jid_) {
|
||||||
Jid jid = jid_;
|
Jid jid = jid_;
|
||||||
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid_, account)) {
|
||||||
|
@ -111,17 +128,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
cached_pixbuf[hash] = image;
|
cached_pixbuf[hash] = image;
|
||||||
} else {
|
} else {
|
||||||
Bytes? bytes = null;
|
if (yield fetch_and_store(stream, account, jid, source, hash)) {
|
||||||
if (source == 1) {
|
|
||||||
bytes = yield Xmpp.Xep.UserAvatars.fetch_image(stream, jid, hash);
|
|
||||||
} else if (source == 2) {
|
|
||||||
bytes = yield Xmpp.Xep.VCard.fetch_image(stream, jid, hash);
|
|
||||||
if (bytes == null && jid.is_bare()) {
|
|
||||||
db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(jid)).perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bytes != null) {
|
|
||||||
store_image(hash, bytes);
|
|
||||||
image = yield get_image(hash);
|
image = yield get_image(hash);
|
||||||
}
|
}
|
||||||
cached_pixbuf[hash] = image;
|
cached_pixbuf[hash] = image;
|
||||||
|
@ -162,7 +169,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) {
|
foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) {
|
||||||
user_avatars[entry.key] = entry.value;
|
on_user_avatar_received(account, entry.key, entry.value);
|
||||||
}
|
}
|
||||||
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
|
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
|
||||||
|
|
||||||
|
@ -172,7 +179,7 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
vcard_avatars[entry.key] = entry.value;
|
on_vcard_avatar_received(account, entry.key, entry.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,12 +225,53 @@ public class AvatarManager : StreamInteractionModule, Object {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void store_image(string id, Bytes data) {
|
public async bool fetch_and_store_for_jid(Account account, Jid jid) {
|
||||||
|
int source = -1;
|
||||||
|
string? hash = null;
|
||||||
|
if (user_avatars.has_key(jid)) {
|
||||||
|
hash = user_avatars[jid];
|
||||||
|
source = 1;
|
||||||
|
} else if (vcard_avatars.has_key(jid)) {
|
||||||
|
hash = vcard_avatars[jid];
|
||||||
|
source = 2;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
XmppStream? stream = stream_interactor.get_stream(account);
|
||||||
|
if (stream == null || !stream.negotiation_complete) return false;
|
||||||
|
|
||||||
|
return yield fetch_and_store(stream, account, jid, source, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async bool fetch_and_store(XmppStream stream, Account account, Jid jid, int source, string? hash) {
|
||||||
|
if (hash == null || pending_fetch.contains(hash)) return false;
|
||||||
|
|
||||||
|
pending_fetch.add(hash);
|
||||||
|
Bytes? bytes = null;
|
||||||
|
if (source == 1) {
|
||||||
|
bytes = yield Xmpp.Xep.UserAvatars.fetch_image(stream, jid, hash);
|
||||||
|
} else if (source == 2) {
|
||||||
|
bytes = yield Xmpp.Xep.VCard.fetch_image(stream, jid, hash);
|
||||||
|
if (bytes == null && jid.is_bare()) {
|
||||||
|
db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(jid)).perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes != null) {
|
||||||
|
yield store_image(hash, bytes);
|
||||||
|
fetched_avatar(jid, account);
|
||||||
|
}
|
||||||
|
pending_fetch.remove(hash);
|
||||||
|
return bytes != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void store_image(string id, Bytes data) {
|
||||||
File file = File.new_for_path(Path.build_filename(folder, id));
|
File file = File.new_for_path(Path.build_filename(folder, id));
|
||||||
try {
|
try {
|
||||||
if (file.query_exists()) file.delete(); //TODO y?
|
if (file.query_exists()) file.delete(); //TODO y?
|
||||||
DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));
|
DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));
|
||||||
fos.write_bytes_async.begin(data);
|
yield fos.write_bytes_async(data);
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
// Ignore: we failed in storing, so we refuse to display later...
|
// Ignore: we failed in storing, so we refuse to display later...
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,8 +124,6 @@ SOURCES
|
||||||
src/main.vala
|
src/main.vala
|
||||||
|
|
||||||
src/ui/application.vala
|
src/ui/application.vala
|
||||||
src/ui/avatar_drawer.vala
|
|
||||||
src/ui/avatar_image.vala
|
|
||||||
src/ui/conversation_list_titlebar.vala
|
src/ui/conversation_list_titlebar.vala
|
||||||
src/ui/conversation_view.vala
|
src/ui/conversation_view.vala
|
||||||
src/ui/conversation_view_controller.vala
|
src/ui/conversation_view_controller.vala
|
||||||
|
@ -209,6 +207,7 @@ SOURCES
|
||||||
src/ui/util/sizing_bin.vala
|
src/ui/util/sizing_bin.vala
|
||||||
src/ui/util/size_request_box.vala
|
src/ui/util/size_request_box.vala
|
||||||
|
|
||||||
|
src/ui/widgets/avatar_picture.vala
|
||||||
src/ui/widgets/date_separator.vala
|
src/ui/widgets/date_separator.vala
|
||||||
src/ui/widgets/fixed_ratio_picture.vala
|
src/ui/widgets/fixed_ratio_picture.vala
|
||||||
src/ui/widgets/natural_size_increase.vala
|
src/ui/widgets/natural_size_increase.vala
|
||||||
|
|
|
@ -8,10 +8,9 @@
|
||||||
<property name="margin-bottom">3</property>
|
<property name="margin-bottom">3</property>
|
||||||
<property name="column-spacing">10</property>
|
<property name="column-spacing">10</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="allow_gray">False</property>
|
<property name="height-request">30</property>
|
||||||
<property name="height">30</property>
|
<property name="width-request">30</property>
|
||||||
<property name="width">30</property>
|
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
|
@ -30,10 +30,10 @@
|
||||||
<property name="margin-start">100</property>
|
<property name="margin-start">100</property>
|
||||||
<property name="column-spacing">10</property>
|
<property name="column-spacing">10</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="avatar">
|
<object class="DinoUiAvatarPicture" id="avatar">
|
||||||
<property name="height">50</property>
|
<property name="height-request">50</property>
|
||||||
<property name="width">50</property>
|
<property name="width-request">50</property>
|
||||||
<property name="allow_gray">False</property>
|
<property name="valign">center</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">0</property>
|
<property name="row">0</property>
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<property name="column-spacing">7</property>
|
<property name="column-spacing">7</property>
|
||||||
<property name="row-spacing">2</property>
|
<property name="row-spacing">2</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="avatar_image">
|
<object class="DinoUiAvatarPicture" id="avatar_picture">
|
||||||
<property name="height">35</property>
|
<property name="height-request">35</property>
|
||||||
<property name="width">35</property>
|
<property name="width-request">35</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
<property name="margin-top">2</property>
|
<property name="margin-top">2</property>
|
||||||
<layout>
|
<layout>
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
<property name="margin-start">7</property>
|
<property name="margin-start">7</property>
|
||||||
<property name="margin-end">14</property>
|
<property name="margin-end">14</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">35</property>
|
<property name="height-request">35</property>
|
||||||
<property name="width">35</property>
|
<property name="width-request">35</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
<property name="margin-bottom">6</property>
|
<property name="margin-bottom">6</property>
|
||||||
<property name="column-spacing">6</property>
|
<property name="column-spacing">6</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">40</property>
|
<property name="height-request">40</property>
|
||||||
<property name="width">40</property>
|
<property name="width-request">40</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|
|
@ -93,11 +93,9 @@
|
||||||
<class name="image-button"/>
|
<class name="image-button"/>
|
||||||
</style>
|
</style>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">50</property>
|
<property name="height-request">50</property>
|
||||||
<property name="width">50</property>
|
<property name="width-request">50</property>
|
||||||
<!-- <property name="xalign">1</property>-->
|
|
||||||
<property name="allow_gray">False</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
<property name="margin-end">7</property>
|
<property name="margin-end">7</property>
|
||||||
<property name="column-spacing">10</property>
|
<property name="column-spacing">10</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="height">30</property>
|
<property name="height-request">30</property>
|
||||||
<property name="width">30</property>
|
<property name="width-request">30</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
<class name="dino-quote"/>
|
<class name="dino-quote"/>
|
||||||
</style>
|
</style>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="avatar">
|
<object class="DinoUiAvatarPicture" id="avatar">
|
||||||
<property name="allow_gray">False</property>
|
<property name="height-request">15</property>
|
||||||
<property name="height">15</property>
|
<property name="width-request">15</property>
|
||||||
<property name="width">15</property>
|
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
<object class="GtkBox" id="root">
|
<object class="GtkBox" id="root">
|
||||||
<property name="orientation">horizontal</property>
|
<property name="orientation">horizontal</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="DinoUiAvatarImage" id="image">
|
<object class="DinoUiAvatarPicture" id="picture">
|
||||||
<property name="margin-top">4</property>
|
<property name="margin-top">4</property>
|
||||||
<property name="margin-bottom">4</property>
|
<property name="margin-bottom">4</property>
|
||||||
<property name="margin-start">6</property>
|
<property name="margin-start">6</property>
|
||||||
<property name="margin-end">6</property>
|
<property name="margin-end">6</property>
|
||||||
<property name="height">24</property>
|
<property name="height-request">24</property>
|
||||||
<property name="width">24</property>
|
<property name="width-request">24</property>
|
||||||
<property name="allow_gray">False</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|
|
@ -31,8 +31,8 @@ window.dino-main .dino-conversation viewport /* Some themes set this */ {
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes highlight {
|
@keyframes highlight {
|
||||||
from { background: alpha(@warning_color, 0.5); }
|
from { background-color: alpha(@accent_color, 0.5); }
|
||||||
to { background: transparent; }
|
to { background-color: transparent; }
|
||||||
}
|
}
|
||||||
|
|
||||||
window.dino-main .dino-conversation .highlight-once {
|
window.dino-main .dino-conversation .highlight-once {
|
||||||
|
@ -42,7 +42,7 @@ window.dino-main .dino-conversation .highlight-once {
|
||||||
animation-name: highlight;
|
animation-name: highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.dino-main .dino-conversation .message-box.highlight {
|
window.dino-main .dino-conversation .message-box.highlight:not(.highlight-once) {
|
||||||
background: @window_bg_color;
|
background: @window_bg_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +119,10 @@ window.dino-main .dino-quote:hover {
|
||||||
background: alpha(@theme_fg_color, 0.08);
|
background: alpha(@theme_fg_color, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
picture.avatar {
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Overlay Toolbar */
|
/* Overlay Toolbar */
|
||||||
|
|
||||||
.dino-main .overlay-toolbar {
|
.dino-main .overlay-toolbar {
|
||||||
|
|
|
@ -112,7 +112,7 @@ internal class ConferenceListRow : ListRow {
|
||||||
via_label.visible = false;
|
via_label.visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
image.set_conversation(stream_interactor, new Conversation(jid, account, Conversation.Type.GROUPCHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(new Conversation(jid, account, Conversation.Type.GROUPCHAT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Dino.Ui {
|
||||||
public class ListRow : Widget {
|
public class ListRow : Widget {
|
||||||
|
|
||||||
public Grid outer_grid;
|
public Grid outer_grid;
|
||||||
public AvatarImage image;
|
public AvatarPicture picture;
|
||||||
public Label name_label;
|
public Label name_label;
|
||||||
public Label via_label;
|
public Label via_label;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ public class ListRow : Widget {
|
||||||
construct {
|
construct {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/add_conversation/list_row.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/add_conversation/list_row.ui");
|
||||||
outer_grid = (Grid) builder.get_object("outer_grid");
|
outer_grid = (Grid) builder.get_object("outer_grid");
|
||||||
image = (AvatarImage) builder.get_object("image");
|
picture = (AvatarPicture) builder.get_object("picture");
|
||||||
name_label = (Label) builder.get_object("name_label");
|
name_label = (Label) builder.get_object("name_label");
|
||||||
via_label = (Label) builder.get_object("via_label");
|
via_label = (Label) builder.get_object("via_label");
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ public class ListRow : Widget {
|
||||||
via_label.visible = false;
|
via_label.visible = false;
|
||||||
}
|
}
|
||||||
name_label.label = display_name;
|
name_label.label = display_name;
|
||||||
image.set_conversation(stream_interactor, conv);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conv);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void dispose() {
|
public override void dispose() {
|
||||||
|
|
|
@ -132,7 +132,7 @@ public class SelectJidFragment : Gtk.Box {
|
||||||
} else {
|
} else {
|
||||||
via_label.visible = false;
|
via_label.visible = false;
|
||||||
}
|
}
|
||||||
image.set_text("?");
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add("+");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
using Cairo;
|
|
||||||
using Gee;
|
|
||||||
using Gdk;
|
|
||||||
using Gtk;
|
|
||||||
using Xmpp.Util;
|
|
||||||
|
|
||||||
namespace Dino.Ui {
|
|
||||||
|
|
||||||
public class AvatarDrawer {
|
|
||||||
public const string GRAY = "555753";
|
|
||||||
|
|
||||||
private Gee.List<AvatarTile> tiles = new ArrayList<AvatarTile>();
|
|
||||||
private int height = 35;
|
|
||||||
private int width = 35;
|
|
||||||
private bool gray;
|
|
||||||
private int base_factor = 1;
|
|
||||||
private string font_family = "Sans";
|
|
||||||
|
|
||||||
public AvatarDrawer size(int height, int width = height) {
|
|
||||||
this.height = height;
|
|
||||||
this.width = width;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer grayscale() {
|
|
||||||
this.gray = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer tile(Pixbuf? image, string? name, string? hex_color) {
|
|
||||||
tiles.add(new AvatarTile(image, name, hex_color));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer plus() {
|
|
||||||
tiles.add(new AvatarTile(null, "…", GRAY));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer scale(int base_factor) {
|
|
||||||
this.base_factor = base_factor;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarDrawer font(string font_family) {
|
|
||||||
this.font_family = font_family;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageSurface draw_image_surface() {
|
|
||||||
ImageSurface surface = new ImageSurface(Format.ARGB32, width, height);
|
|
||||||
draw_on_context(new Context(surface));
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void draw_on_context(Cairo.Context ctx) {
|
|
||||||
double radius = 3 * base_factor;
|
|
||||||
double degrees = Math.PI / 180.0;
|
|
||||||
ctx.new_sub_path();
|
|
||||||
ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees);
|
|
||||||
ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees);
|
|
||||||
ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees);
|
|
||||||
ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees);
|
|
||||||
ctx.close_path();
|
|
||||||
ctx.clip();
|
|
||||||
|
|
||||||
if (this.tiles.size == 4) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
bufctx.scale(0.5, 0.5);
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height - 1, 2 * base_factor), width + 1, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 3, width - 1, height - 1, 2 * base_factor), width + 1, height + 1);
|
|
||||||
bufctx.paint();
|
|
||||||
|
|
||||||
ctx.set_source_surface(buffer, 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 3) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
bufctx.scale(0.5, 0.5);
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1);
|
|
||||||
bufctx.paint();
|
|
||||||
|
|
||||||
ctx.set_source_surface(buffer, 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 2) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
bufctx.scale(0.5, 0.5);
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height * 2, 2 * base_factor), 0, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0);
|
|
||||||
bufctx.paint();
|
|
||||||
|
|
||||||
ctx.set_source_surface(buffer, 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 1) {
|
|
||||||
ctx.set_source_surface(sub_surface_idx(ctx, 0, width, height, base_factor), 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
} else if (this.tiles.size == 0) {
|
|
||||||
ctx.set_source_surface(sub_surface_idx(ctx, -1, width, height, base_factor), 0, 0);
|
|
||||||
ctx.paint();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gray) {
|
|
||||||
// convert to greyscale
|
|
||||||
ctx.set_operator(Cairo.Operator.HSL_COLOR);
|
|
||||||
ctx.set_source_rgb(1, 1, 1);
|
|
||||||
ctx.rectangle(0, 0, width, height);
|
|
||||||
ctx.fill();
|
|
||||||
// make the visible part more light
|
|
||||||
ctx.set_operator(Cairo.Operator.ATOP);
|
|
||||||
ctx.set_source_rgba(1, 1, 1, 0.7);
|
|
||||||
ctx.rectangle(0, 0, width, height);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
ctx.set_source_rgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) {
|
|
||||||
Gdk.Pixbuf? avatar = idx >= 0 ? tiles[idx].image : null;
|
|
||||||
string? name = idx >= 0 ? tiles[idx].name : "";
|
|
||||||
string hex_color = !gray && idx >= 0 ? tiles[idx].hex_color : GRAY;
|
|
||||||
return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, string? hex_color, int width, int height, int font_factor = 1) {
|
|
||||||
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
|
||||||
Cairo.Context bufctx = new Cairo.Context(buffer);
|
|
||||||
if (avatar == null) {
|
|
||||||
set_source_hex_color(bufctx, hex_color ?? GRAY);
|
|
||||||
bufctx.rectangle(0, 0, width, height);
|
|
||||||
bufctx.fill();
|
|
||||||
|
|
||||||
string text = name == null ? "…" : name.get_char(0).toupper().to_string();
|
|
||||||
bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
|
|
||||||
bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25);
|
|
||||||
Cairo.TextExtents extents;
|
|
||||||
bufctx.text_extents(text, out extents);
|
|
||||||
double x_pos = width/2 - (extents.width/2 + extents.x_bearing);
|
|
||||||
double y_pos = height/2 - (extents.height/2 + extents.y_bearing);
|
|
||||||
bufctx.move_to(x_pos, y_pos);
|
|
||||||
bufctx.set_source_rgba(1, 1, 1, 1);
|
|
||||||
bufctx.show_text(text);
|
|
||||||
} else {
|
|
||||||
double w_scale = (double) width / avatar.width;
|
|
||||||
double h_scale = (double) height / avatar.height;
|
|
||||||
double scale = double.max(w_scale, h_scale);
|
|
||||||
bufctx.scale(scale, scale);
|
|
||||||
|
|
||||||
double x_off = 0, y_off = 0;
|
|
||||||
if (scale == h_scale) {
|
|
||||||
x_off = (width / scale - avatar.width) / 2.0;
|
|
||||||
} else {
|
|
||||||
y_off = (height / scale - avatar.height) / 2.0;
|
|
||||||
}
|
|
||||||
Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off);
|
|
||||||
bufctx.get_source().set_filter(Cairo.Filter.BEST);
|
|
||||||
bufctx.paint();
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void set_source_hex_color(Cairo.Context ctx, string hex_color) {
|
|
||||||
ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255,
|
|
||||||
(double) from_hex(hex_color.substring(2, 2)) / 255,
|
|
||||||
(double) from_hex(hex_color.substring(4, 2)) / 255,
|
|
||||||
hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AvatarTile {
|
|
||||||
public Pixbuf? image { get; private set; }
|
|
||||||
public string? name { get; private set; }
|
|
||||||
public string? hex_color { get; private set; }
|
|
||||||
|
|
||||||
public AvatarTile(Pixbuf? image, string? name, string? hex_color) {
|
|
||||||
this.image = image;
|
|
||||||
this.name = name;
|
|
||||||
this.hex_color = hex_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,267 +0,0 @@
|
||||||
using Gtk;
|
|
||||||
using Dino.Entities;
|
|
||||||
using Xmpp;
|
|
||||||
using Xmpp.Util;
|
|
||||||
|
|
||||||
namespace Dino.Ui {
|
|
||||||
|
|
||||||
public class AvatarImage : Widget {
|
|
||||||
public int height { get; set; default = 35; }
|
|
||||||
public int width { get; set; default = 35; }
|
|
||||||
public bool allow_gray { get; set; default = true; }
|
|
||||||
public bool force_gray { get; set; default = false; }
|
|
||||||
public StreamInteractor? stream_interactor { get; set; }
|
|
||||||
public AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
|
||||||
public MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
|
||||||
public PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } }
|
|
||||||
public ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } }
|
|
||||||
public Account account { get { return conversation.account; } }
|
|
||||||
private AvatarDrawer? drawer;
|
|
||||||
private Conversation conversation;
|
|
||||||
private Jid[] jids;
|
|
||||||
private Cairo.ImageSurface? cached_surface;
|
|
||||||
private static int8 use_image_surface = -1;
|
|
||||||
|
|
||||||
public AvatarImage() {
|
|
||||||
can_focus = false;
|
|
||||||
add_css_class("avatar");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void dispose() {
|
|
||||||
base.dispose();
|
|
||||||
drawer = null;
|
|
||||||
cached_surface = null;
|
|
||||||
disconnect_stream_interactor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
|
||||||
if (orientation == Orientation.HORIZONTAL) {
|
|
||||||
minimum = width;
|
|
||||||
natural = width;
|
|
||||||
} else {
|
|
||||||
minimum = height;
|
|
||||||
natural = height;
|
|
||||||
}
|
|
||||||
minimum_baseline = natural_baseline = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void snapshot(Snapshot snapshot) {
|
|
||||||
Cairo.Context context = snapshot.append_cairo(Graphene.Rect.alloc().init(0, 0, width, height));
|
|
||||||
draw(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool draw(Cairo.Context ctx_in) {
|
|
||||||
Cairo.Context ctx = ctx_in;
|
|
||||||
int width = this.width, height = this.height, base_factor = 1;
|
|
||||||
if (use_image_surface == -1) {
|
|
||||||
// TODO: detect if we have to buffer in image surface
|
|
||||||
use_image_surface = 1;
|
|
||||||
}
|
|
||||||
if (use_image_surface == 1) {
|
|
||||||
ctx_in.scale(1f / scale_factor, 1f / scale_factor);
|
|
||||||
if (cached_surface != null) {
|
|
||||||
ctx_in.set_source_surface(cached_surface, 0, 0);
|
|
||||||
ctx_in.paint();
|
|
||||||
ctx_in.set_source_rgb(0, 0, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
width *= scale_factor;
|
|
||||||
height *= scale_factor;
|
|
||||||
base_factor *= scale_factor;
|
|
||||||
cached_surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
|
|
||||||
ctx = new Cairo.Context(cached_surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
AvatarDrawer drawer = this.drawer;
|
|
||||||
Jid[] jids = this.jids;
|
|
||||||
if (drawer == null && jids.length == 0) {
|
|
||||||
switch (conversation.type_) {
|
|
||||||
case Conversation.Type.CHAT:
|
|
||||||
case Conversation.Type.GROUPCHAT_PM:
|
|
||||||
// In direct chats or group chats, conversation avatar is same as counterpart avatar
|
|
||||||
jids = { conversation.counterpart };
|
|
||||||
break;
|
|
||||||
case Conversation.Type.GROUPCHAT:
|
|
||||||
string user_color = Util.get_avatar_hex_color(stream_interactor, account, conversation.counterpart, conversation);
|
|
||||||
if (avatar_manager.has_avatar_cached(account, conversation.counterpart)) {
|
|
||||||
drawer = new AvatarDrawer().tile(avatar_manager.get_cached_avatar(account, conversation.counterpart), "#", user_color);
|
|
||||||
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
|
||||||
} else {
|
|
||||||
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, account);
|
|
||||||
if (muc_manager.is_private_room(account, conversation.counterpart) && occupants != null && occupants.size > 0) {
|
|
||||||
jids = occupants.to_array();
|
|
||||||
} else {
|
|
||||||
drawer = new AvatarDrawer().tile(null, "#", user_color);
|
|
||||||
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
|
||||||
}
|
|
||||||
try_load_avatar_async(conversation.counterpart);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (drawer == null && jids.length > 0) {
|
|
||||||
drawer = new AvatarDrawer();
|
|
||||||
for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) {
|
|
||||||
Jid avatar_jid = jids[i];
|
|
||||||
Jid? real_avatar_jid = null;
|
|
||||||
if (conversation.type_ != Conversation.Type.CHAT && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(account, conversation.counterpart.bare_jid)) {
|
|
||||||
// In private room, consider real jid
|
|
||||||
real_avatar_jid = muc_manager.get_real_jid(avatar_jid, account) ?? avatar_jid;
|
|
||||||
}
|
|
||||||
string display_name = Util.get_participant_display_name(stream_interactor, conversation, jids[i]);
|
|
||||||
string user_color = Util.get_avatar_hex_color(stream_interactor, account, jids[i], conversation);
|
|
||||||
if (avatar_manager.has_avatar_cached(account, avatar_jid)) {
|
|
||||||
drawer.tile(avatar_manager.get_cached_avatar(account, avatar_jid), display_name, user_color);
|
|
||||||
} else if (real_avatar_jid != null && avatar_manager.has_avatar_cached(account, real_avatar_jid)) {
|
|
||||||
drawer.tile(avatar_manager.get_cached_avatar(account, real_avatar_jid), display_name, user_color);
|
|
||||||
} else {
|
|
||||||
drawer.tile(null, display_name, user_color);
|
|
||||||
try_load_avatar_async(avatar_jid);
|
|
||||||
if (real_avatar_jid != null) try_load_avatar_async(real_avatar_jid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jids.length > 4) {
|
|
||||||
drawer.plus();
|
|
||||||
}
|
|
||||||
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (drawer == null) return false;
|
|
||||||
drawer.size(height, width)
|
|
||||||
.scale(base_factor)
|
|
||||||
.font(get_pango_context().get_font_description().get_family())
|
|
||||||
.draw_on_context(ctx);
|
|
||||||
|
|
||||||
if (use_image_surface == 1) {
|
|
||||||
ctx_in.set_source_surface(ctx.get_target(), 0, 0);
|
|
||||||
ctx_in.paint();
|
|
||||||
ctx_in.set_source_rgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void try_load_avatar_async(Jid jid) {
|
|
||||||
if (avatar_manager.has_avatar(account, jid)) {
|
|
||||||
avatar_manager.get_avatar.begin(account, jid, (_, res) => {
|
|
||||||
var avatar = avatar_manager.get_avatar.end(res);
|
|
||||||
if (avatar != null) force_redraw();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void force_redraw() {
|
|
||||||
this.cached_surface = null;
|
|
||||||
queue_draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disconnect_stream_interactor() {
|
|
||||||
if (stream_interactor != null) {
|
|
||||||
presence_manager.show_received.disconnect(on_show_received);
|
|
||||||
presence_manager.received_offline_presence.disconnect(on_show_received);
|
|
||||||
avatar_manager.received_avatar.disconnect(on_received_avatar);
|
|
||||||
stream_interactor.connection_manager.connection_state_changed.disconnect(on_connection_changed);
|
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.disconnect(on_roster_updated);
|
|
||||||
muc_manager.private_room_occupant_updated.disconnect(on_private_room_occupant_updated);
|
|
||||||
muc_manager.room_info_updated.disconnect(on_room_info_updated);
|
|
||||||
stream_interactor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_show_received(Jid jid, Account account) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_received_avatar(Jid jid, Account account) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_avatar_if_jid(Jid jid) {
|
|
||||||
if (jid.equals_bare(this.conversation.counterpart)) {
|
|
||||||
force_redraw();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach (Jid ours in this.jids) {
|
|
||||||
if (jid.equals_bare(ours)) {
|
|
||||||
force_redraw();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_connection_changed(Account account, ConnectionManager.ConnectionState state) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
force_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_roster_updated(Account account, Jid jid, Roster.Item roster_item) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_private_room_occupant_updated(Account account, Jid room, Jid occupant) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_room_info_updated(Account account, Jid muc_jid) {
|
|
||||||
if (!account.equals(this.account)) return;
|
|
||||||
update_avatar_if_jid(muc_jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool is_self_online() {
|
|
||||||
if (connection_manager != null) {
|
|
||||||
return connection_manager.get_state(account) == ConnectionManager.ConnectionState.CONNECTED;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool is_counterpart_online() {
|
|
||||||
return presence_manager.get_full_jids(conversation.counterpart, account) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_conversation(StreamInteractor stream_interactor, Conversation conversation) {
|
|
||||||
set_avatar(stream_interactor, conversation, new Jid[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_conversation_participant(StreamInteractor stream_interactor, Conversation conversation, Jid sub_jid) {
|
|
||||||
set_avatar(stream_interactor, conversation, new Jid[] {sub_jid});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_conversation_participants(StreamInteractor stream_interactor, Conversation conversation, Jid[] sub_jids) {
|
|
||||||
set_avatar(stream_interactor, conversation, sub_jids);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_avatar(StreamInteractor stream_interactor, Conversation conversation, Jid[] jids) {
|
|
||||||
if (this.stream_interactor != null && stream_interactor != this.stream_interactor) {
|
|
||||||
disconnect_stream_interactor();
|
|
||||||
}
|
|
||||||
if (this.stream_interactor != stream_interactor) {
|
|
||||||
this.stream_interactor = stream_interactor;
|
|
||||||
presence_manager.show_received.connect(on_show_received);
|
|
||||||
presence_manager.received_offline_presence.connect(on_show_received);
|
|
||||||
stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar);
|
|
||||||
stream_interactor.connection_manager.connection_state_changed.connect(on_connection_changed);
|
|
||||||
stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated);
|
|
||||||
muc_manager.private_room_occupant_updated.connect(on_private_room_occupant_updated);
|
|
||||||
muc_manager.room_info_updated.connect(on_room_info_updated);
|
|
||||||
}
|
|
||||||
this.cached_surface = null;
|
|
||||||
this.conversation = conversation;
|
|
||||||
this.jids = jids;
|
|
||||||
|
|
||||||
force_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_text(string text, bool gray = true) {
|
|
||||||
disconnect_stream_interactor();
|
|
||||||
this.drawer = new AvatarDrawer().tile(null, text, null);
|
|
||||||
if (gray) drawer.grayscale();
|
|
||||||
force_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -96,11 +96,11 @@ namespace Dino.Ui {
|
||||||
shows_video = false;
|
shows_video = false;
|
||||||
Box box = new Box(Orientation.HORIZONTAL, 0);
|
Box box = new Box(Orientation.HORIZONTAL, 0);
|
||||||
box.add_css_class("video-placeholder-box");
|
box.add_css_class("video-placeholder-box");
|
||||||
AvatarImage avatar = new AvatarImage() { allow_gray=false, hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height=100, width=100 };
|
AvatarPicture avatar = new AvatarPicture() { hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height_request=100, width_request=100 };
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
avatar.set_conversation(stream_interactor, conversation);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation);
|
||||||
} else {
|
} else {
|
||||||
avatar.set_text("?", false);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add("?");
|
||||||
}
|
}
|
||||||
box.append(avatar);
|
box.append(avatar);
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Dino.Ui.ContactDetails {
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")]
|
||||||
public class Dialog : Gtk.Dialog {
|
public class Dialog : Gtk.Dialog {
|
||||||
|
|
||||||
[GtkChild] public unowned AvatarImage avatar;
|
[GtkChild] public unowned AvatarPicture avatar;
|
||||||
[GtkChild] public unowned Util.EntryLabelHybrid name_hybrid;
|
[GtkChild] public unowned Util.EntryLabelHybrid name_hybrid;
|
||||||
[GtkChild] public unowned Label name_label;
|
[GtkChild] public unowned Label name_label;
|
||||||
[GtkChild] public unowned Label jid_label;
|
[GtkChild] public unowned Label jid_label;
|
||||||
|
@ -87,7 +87,7 @@ public class Dialog : Gtk.Dialog {
|
||||||
}
|
}
|
||||||
jid_label.label = conversation.counterpart.to_string();
|
jid_label.label = conversation.counterpart.to_string();
|
||||||
account_label.label = "via " + conversation.account.bare_jid.to_string();
|
account_label.label = "via " + conversation.account.bare_jid.to_string();
|
||||||
avatar.set_conversation(stream_interactor, conversation);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add_entry(string category, string label, string? description, Object wo) {
|
private void add_entry(string category, string label, string? description, Object wo) {
|
||||||
|
|
|
@ -94,15 +94,15 @@ namespace Dino.Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Jid counterpart in call.counterparts) {
|
foreach (Jid counterpart in call.counterparts) {
|
||||||
AvatarImage image = new AvatarImage() { force_gray=true, margin_top=2 };
|
AvatarPicture picture = new AvatarPicture() { margin_top=2 };
|
||||||
image.set_conversation_participant(stream_interactor, conversation, counterpart.bare_jid);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, counterpart.bare_jid);
|
||||||
multiparty_peer_box.append(image);
|
multiparty_peer_box.append(picture);
|
||||||
multiparty_peer_widgets.add(image);
|
multiparty_peer_widgets.add(picture);
|
||||||
}
|
}
|
||||||
AvatarImage image2 = new AvatarImage() { force_gray=true, margin_top=2 };
|
AvatarPicture picture2 = new AvatarPicture() { margin_top=2 };
|
||||||
image2.set_conversation_participant(stream_interactor, conversation, call.account.bare_jid);
|
picture2.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, call.account.bare_jid);
|
||||||
multiparty_peer_box.append(image2);
|
multiparty_peer_box.append(picture2);
|
||||||
multiparty_peer_widgets.add(image2);
|
multiparty_peer_widgets.add(picture2);
|
||||||
|
|
||||||
outer_additional_box.add_css_class("multiparty-participants");
|
outer_additional_box.add_css_class("multiparty-participants");
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
private Conversation conversation;
|
private Conversation conversation;
|
||||||
private Gee.List<Jid> jids = new ArrayList<Jid>();
|
private Gee.List<Jid> jids = new ArrayList<Jid>();
|
||||||
private Label label;
|
private Label label;
|
||||||
private AvatarImage image;
|
private AvatarPicture picture;
|
||||||
|
|
||||||
public MetaChatStateItem(StreamInteractor stream_interactor, Conversation conversation, Gee.List<Jid> jids) {
|
public MetaChatStateItem(StreamInteractor stream_interactor, Conversation conversation, Gee.List<Jid> jids) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
|
@ -79,10 +79,10 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType widget_type) {
|
public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType widget_type) {
|
||||||
label = new Label("") { xalign=0, vexpand=true };
|
label = new Label("") { xalign=0, vexpand=true };
|
||||||
label.add_css_class("dim-label");
|
label.add_css_class("dim-label");
|
||||||
image = new AvatarImage() { margin_top=2, valign=Align.START };
|
picture = new AvatarPicture() { margin_top=2, valign=Align.START };
|
||||||
|
|
||||||
Box image_content_box = new Box(Orientation.HORIZONTAL, 8);
|
Box image_content_box = new Box(Orientation.HORIZONTAL, 8);
|
||||||
image_content_box.append(image);
|
image_content_box.append(picture);
|
||||||
image_content_box.append(label);
|
image_content_box.append(label);
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
@ -97,9 +97,7 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
if (image == null || label == null) return;
|
if (picture == null || label == null) return;
|
||||||
|
|
||||||
image.set_conversation_participants(stream_interactor, conversation, jids.to_array());
|
|
||||||
|
|
||||||
Gee.List<string> display_names = new ArrayList<string>();
|
Gee.List<string> display_names = new ArrayList<string>();
|
||||||
foreach (Jid jid in jids) {
|
foreach (Jid jid in jids) {
|
||||||
|
@ -108,12 +106,26 @@ private class MetaChatStateItem : Plugins.MetaConversationItem {
|
||||||
string new_text = "";
|
string new_text = "";
|
||||||
if (jids.size > 3) {
|
if (jids.size > 3) {
|
||||||
new_text = _("%s, %s and %i others are typing…").printf(display_names[0], display_names[1], jids.size - 2);
|
new_text = _("%s, %s and %i others are typing…").printf(display_names[0], display_names[1], jids.size - 2);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0])
|
||||||
|
.add_participant(conversation, jids[1])
|
||||||
|
.add_participant(conversation, jids[2])
|
||||||
|
.add("+");
|
||||||
} else if (jids.size == 3) {
|
} else if (jids.size == 3) {
|
||||||
new_text = _("%s, %s and %s are typing…").printf(display_names[0], display_names[1], display_names[2]);
|
new_text = _("%s, %s and %s are typing…").printf(display_names[0], display_names[1], display_names[2]);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0])
|
||||||
|
.add_participant(conversation, jids[1])
|
||||||
|
.add_participant(conversation, jids[2]);
|
||||||
} else if (jids.size == 2) {
|
} else if (jids.size == 2) {
|
||||||
new_text =_("%s and %s are typing…").printf(display_names[0], display_names[1]);
|
new_text =_("%s and %s are typing…").printf(display_names[0], display_names[1]);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0])
|
||||||
|
.add_participant(conversation, jids[1]);
|
||||||
} else {
|
} else {
|
||||||
new_text = _("%s is typing…").printf(display_names[0]);
|
new_text = _("%s is typing…").printf(display_names[0]);
|
||||||
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor)
|
||||||
|
.add_participant(conversation, jids[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
label.label = new_text;
|
label.label = new_text;
|
||||||
|
|
|
@ -12,7 +12,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
public Grid main_grid { get; set; }
|
public Grid main_grid { get; set; }
|
||||||
public Label name_label { get; set; }
|
public Label name_label { get; set; }
|
||||||
public Label time_label { get; set; }
|
public Label time_label { get; set; }
|
||||||
public AvatarImage avatar_image { get; set; }
|
public AvatarPicture avatar_picture { get; set; }
|
||||||
public Image encryption_image { get; set; }
|
public Image encryption_image { get; set; }
|
||||||
public Image received_image { get; set; }
|
public Image received_image { get; set; }
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
main_grid.add_css_class("message-box");
|
main_grid.add_css_class("message-box");
|
||||||
name_label = (Label) builder.get_object("name_label");
|
name_label = (Label) builder.get_object("name_label");
|
||||||
time_label = (Label) builder.get_object("time_label");
|
time_label = (Label) builder.get_object("time_label");
|
||||||
avatar_image = (AvatarImage) builder.get_object("avatar_image");
|
avatar_picture = (AvatarPicture) builder.get_object("avatar_picture");
|
||||||
encryption_image = (Image) builder.get_object("encrypted_image");
|
encryption_image = (Image) builder.get_object("encrypted_image");
|
||||||
received_image = (Image) builder.get_object("marked_image");
|
received_image = (Image) builder.get_object("marked_image");
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.requires_header) {
|
if (item.requires_header) {
|
||||||
avatar_image.set_conversation_participant(stream_interactor, conversation, item.jid);
|
// TODO: For MUC messags, use real jid from message if known
|
||||||
|
avatar_picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, item.jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notify["show-skeleton"].connect(update_margin);
|
this.notify["show-skeleton"].connect(update_margin);
|
||||||
|
@ -116,7 +117,7 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_margin() {
|
private void update_margin() {
|
||||||
avatar_image.visible = show_skeleton;
|
avatar_picture.visible = show_skeleton;
|
||||||
name_label.visible = show_skeleton;
|
name_label.visible = show_skeleton;
|
||||||
time_label.visible = show_skeleton;
|
time_label.visible = show_skeleton;
|
||||||
encryption_image.visible = show_skeleton;
|
encryption_image.visible = show_skeleton;
|
||||||
|
@ -286,10 +287,10 @@ public class ConversationItemSkeleton : Plugins.ConversationItemWidgetInterface,
|
||||||
time_label.dispose();
|
time_label.dispose();
|
||||||
time_label = null;
|
time_label = null;
|
||||||
}
|
}
|
||||||
if (avatar_image != null) {
|
if (avatar_picture != null) {
|
||||||
avatar_image.unparent();
|
avatar_picture.unparent();
|
||||||
avatar_image.dispose();
|
avatar_picture.dispose();
|
||||||
avatar_image = null;
|
avatar_picture = null;
|
||||||
}
|
}
|
||||||
if (encryption_image != null) {
|
if (encryption_image != null) {
|
||||||
encryption_image.unparent();
|
encryption_image.unparent();
|
||||||
|
|
|
@ -61,13 +61,13 @@ namespace Dino.Ui.Quote {
|
||||||
|
|
||||||
public Widget get_widget(Model model) {
|
public Widget get_widget(Model model) {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/quote.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/quote.ui");
|
||||||
AvatarImage avatar = (AvatarImage) builder.get_object("avatar");
|
AvatarPicture avatar = (AvatarPicture) builder.get_object("avatar");
|
||||||
Label author = (Label) builder.get_object("author");
|
Label author = (Label) builder.get_object("author");
|
||||||
Label time = (Label) builder.get_object("time");
|
Label time = (Label) builder.get_object("time");
|
||||||
Label message = (Label) builder.get_object("message");
|
Label message = (Label) builder.get_object("message");
|
||||||
Button abort_button = (Button) builder.get_object("abort-button");
|
Button abort_button = (Button) builder.get_object("abort-button");
|
||||||
|
|
||||||
avatar.set_conversation_participant(model.stream_interactor, model.conversation, model.author_jid);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(model.stream_interactor).add_participant(model.conversation, model.author_jid);
|
||||||
model.bind_property("display-name", author, "label", BindingFlags.SYNC_CREATE);
|
model.bind_property("display-name", author, "label", BindingFlags.SYNC_CREATE);
|
||||||
model.bind_property("display-time", time, "label", BindingFlags.SYNC_CREATE);
|
model.bind_property("display-time", time, "label", BindingFlags.SYNC_CREATE);
|
||||||
model.bind_property("message", message, "label", BindingFlags.SYNC_CREATE);
|
model.bind_property("message", message, "label", BindingFlags.SYNC_CREATE);
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Dino.Ui {
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/conversation_row.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/conversation_row.ui")]
|
||||||
public class ConversationSelectorRow : ListBoxRow {
|
public class ConversationSelectorRow : ListBoxRow {
|
||||||
|
|
||||||
[GtkChild] protected unowned AvatarImage image;
|
[GtkChild] protected unowned AvatarPicture picture;
|
||||||
[GtkChild] protected unowned Label name_label;
|
[GtkChild] protected unowned Label name_label;
|
||||||
[GtkChild] protected unowned Label time_label;
|
[GtkChild] protected unowned Label time_label;
|
||||||
[GtkChild] protected unowned Label nick_label;
|
[GtkChild] protected unowned Label nick_label;
|
||||||
|
@ -101,7 +101,7 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||||
x_button.clicked.connect(() => {
|
x_button.clicked.connect(() => {
|
||||||
stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation);
|
stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation);
|
||||||
});
|
});
|
||||||
image.set_conversation(stream_interactor, conversation);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation);
|
||||||
conversation.notify["read-up-to-item"].connect(() => update_read());
|
conversation.notify["read-up-to-item"].connect(() => update_read());
|
||||||
conversation.notify["pinned"].connect(() => { update_pinned_icon(); });
|
conversation.notify["pinned"].connect(() => { update_pinned_icon(); });
|
||||||
|
|
||||||
|
|
|
@ -116,15 +116,15 @@ public class GlobalSearch {
|
||||||
// Populate new suggestions
|
// Populate new suggestions
|
||||||
foreach(SearchSuggestion suggestion in suggestions) {
|
foreach(SearchSuggestion suggestion in suggestions) {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/search_autocomplete.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/search_autocomplete.ui");
|
||||||
AvatarImage avatar = (AvatarImage)builder.get_object("image");
|
AvatarPicture avatar = (AvatarPicture)builder.get_object("picture");
|
||||||
Label label = (Label)builder.get_object("label");
|
Label label = (Label)builder.get_object("label");
|
||||||
string display_name;
|
string display_name;
|
||||||
if (suggestion.conversation.type_ == Conversation.Type.GROUPCHAT && !suggestion.conversation.counterpart.equals(suggestion.jid) || suggestion.conversation.type_ == Conversation.Type.GROUPCHAT_PM) {
|
if (suggestion.conversation.type_ == Conversation.Type.GROUPCHAT && !suggestion.conversation.counterpart.equals(suggestion.jid) || suggestion.conversation.type_ == Conversation.Type.GROUPCHAT_PM) {
|
||||||
display_name = Util.get_participant_display_name(stream_interactor, suggestion.conversation, suggestion.jid);
|
display_name = Util.get_participant_display_name(stream_interactor, suggestion.conversation, suggestion.jid);
|
||||||
avatar.set_conversation_participant(stream_interactor, suggestion.conversation, suggestion.jid);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(suggestion.conversation, suggestion.jid);
|
||||||
} else {
|
} else {
|
||||||
display_name = Util.get_conversation_display_name(stream_interactor, suggestion.conversation);
|
display_name = Util.get_conversation_display_name(stream_interactor, suggestion.conversation);
|
||||||
avatar.set_conversation(stream_interactor, suggestion.conversation);
|
avatar.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(suggestion.conversation);
|
||||||
}
|
}
|
||||||
if (display_name != suggestion.jid.to_string()) {
|
if (display_name != suggestion.jid.to_string()) {
|
||||||
label.set_markup("%s <span font_weight='light' fgalpha='80%%'>%s</span>".printf(Markup.escape_text(display_name), Markup.escape_text(suggestion.jid.to_string())));
|
label.set_markup("%s <span font_weight='light' fgalpha='80%%'>%s</span>".printf(Markup.escape_text(display_name), Markup.escape_text(suggestion.jid.to_string())));
|
||||||
|
@ -289,10 +289,10 @@ public class GlobalSearch {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid get_skeleton(MessageItem item) {
|
private Grid get_skeleton(MessageItem item) {
|
||||||
AvatarImage image = new AvatarImage() { height=32, width=32, margin_end=7, valign=Align.START, allow_gray = false };
|
AvatarPicture picture = new AvatarPicture() { height_request=32, width_request=32, margin_end=7, valign=Align.START };
|
||||||
image.set_conversation_participant(stream_interactor, item.conversation, item.jid);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(item.conversation, item.jid);
|
||||||
Grid grid = new Grid() { row_homogeneous=false };
|
Grid grid = new Grid() { row_homogeneous=false };
|
||||||
grid.attach(image, 0, 0, 1, 2);
|
grid.attach(picture, 0, 0, 1, 2);
|
||||||
|
|
||||||
string display_name = Util.get_participant_display_name(stream_interactor, item.conversation, item.jid);
|
string display_name = Util.get_participant_display_name(stream_interactor, item.conversation, item.jid);
|
||||||
Label name_label = new Label(display_name) { ellipsize=EllipsizeMode.END, xalign=0 };
|
Label name_label = new Label(display_name) { ellipsize=EllipsizeMode.END, xalign=0 };
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace Dino.Ui.ManageAccounts {
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/manage_accounts/account_row.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/manage_accounts/account_row.ui")]
|
||||||
public class AccountRow : Gtk.ListBoxRow {
|
public class AccountRow : Gtk.ListBoxRow {
|
||||||
|
|
||||||
[GtkChild] public unowned AvatarImage image;
|
[GtkChild] public unowned AvatarPicture picture;
|
||||||
[GtkChild] public unowned Label jid_label;
|
[GtkChild] public unowned Label jid_label;
|
||||||
[GtkChild] public unowned Image icon;
|
[GtkChild] public unowned Image icon;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ public class AccountRow : Gtk.ListBoxRow {
|
||||||
public AccountRow(StreamInteractor stream_interactor, Account account) {
|
public AccountRow(StreamInteractor stream_interactor, Account account) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
this.account = account;
|
this.account = account;
|
||||||
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid);
|
||||||
jid_label.set_label(account.bare_jid.to_string());
|
jid_label.set_label(account.bare_jid.to_string());
|
||||||
|
|
||||||
stream_interactor.connection_manager.connection_error.connect((account, error) => {
|
stream_interactor.connection_manager.connection_error.connect((account, error) => {
|
||||||
|
|
|
@ -19,7 +19,7 @@ public class Dialog : Gtk.Dialog {
|
||||||
[GtkChild] public unowned Button no_accounts_add;
|
[GtkChild] public unowned Button no_accounts_add;
|
||||||
[GtkChild] public unowned Button add_account_button;
|
[GtkChild] public unowned Button add_account_button;
|
||||||
[GtkChild] public unowned Button remove_account_button;
|
[GtkChild] public unowned Button remove_account_button;
|
||||||
[GtkChild] public unowned AvatarImage image;
|
[GtkChild] public unowned AvatarPicture picture;
|
||||||
[GtkChild] public unowned Button image_button;
|
[GtkChild] public unowned Button image_button;
|
||||||
[GtkChild] public unowned Label jid_label;
|
[GtkChild] public unowned Label jid_label;
|
||||||
[GtkChild] public unowned Label state_label;
|
[GtkChild] public unowned Label state_label;
|
||||||
|
@ -178,14 +178,14 @@ public class Dialog : Gtk.Dialog {
|
||||||
|
|
||||||
private void on_received_avatar(Jid jid, Account account) {
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
if (selected_account.equals(account) && jid.equals(account.bare_jid)) {
|
if (selected_account.equals(account) && jid.equals(account.bare_jid)) {
|
||||||
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populate_grid_data(Account account) {
|
private void populate_grid_data(Account account) {
|
||||||
active_switch.state_set.disconnect(change_account_state);
|
active_switch.state_set.disconnect(change_account_state);
|
||||||
|
|
||||||
image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT));
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid);
|
||||||
active_switch.set_active(account.enabled);
|
active_switch.set_active(account.enabled);
|
||||||
jid_label.label = account.bare_jid.to_string();
|
jid_label.label = account.bare_jid.to_string();
|
||||||
|
|
||||||
|
|
|
@ -287,8 +287,12 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Variant get_conversation_icon(Conversation conversation) {
|
private async Variant get_conversation_icon(Conversation conversation) {
|
||||||
AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation);
|
CompatAvatarDrawer drawer = new CompatAvatarDrawer() {
|
||||||
Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface();
|
model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation),
|
||||||
|
width_request = 40,
|
||||||
|
height_request = 40
|
||||||
|
};
|
||||||
|
Cairo.ImageSurface surface = drawer.draw_image_surface();
|
||||||
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
||||||
var bytes = avatar.pixel_bytes;
|
var bytes = avatar.pixel_bytes;
|
||||||
var image_bytes = Variant.new_from_data<Bytes>(new VariantType("ay"), bytes.get_data(), true, bytes);
|
var image_bytes = Variant.new_from_data<Bytes>(new VariantType("ay"), bytes.get_data(), true, bytes);
|
||||||
|
|
|
@ -171,8 +171,12 @@ namespace Dino.Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Icon get_conversation_icon(Conversation conversation) throws Error {
|
private async Icon get_conversation_icon(Conversation conversation) throws Error {
|
||||||
AvatarDrawer drawer = yield Util.get_conversation_avatar_drawer(stream_interactor, conversation);
|
CompatAvatarDrawer drawer = new CompatAvatarDrawer() {
|
||||||
Cairo.ImageSurface surface = drawer.size(40, 40).draw_image_surface();
|
model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation),
|
||||||
|
width_request = 40,
|
||||||
|
height_request = 40
|
||||||
|
};
|
||||||
|
Cairo.ImageSurface surface = drawer.draw_image_surface();
|
||||||
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());
|
||||||
uint8[] buffer;
|
uint8[] buffer;
|
||||||
avatar.save_to_buffer(out buffer, "png");
|
avatar.save_to_buffer(out buffer, "png");
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace Dino.Ui.OccupantMenu {
|
||||||
public class ListRow : Object {
|
public class ListRow : Object {
|
||||||
|
|
||||||
private Grid main_grid;
|
private Grid main_grid;
|
||||||
private AvatarImage image;
|
private AvatarPicture picture;
|
||||||
public Label name_label;
|
public Label name_label;
|
||||||
|
|
||||||
public Conversation? conversation;
|
public Conversation? conversation;
|
||||||
|
@ -17,7 +17,7 @@ public class ListRow : Object {
|
||||||
construct {
|
construct {
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/occupant_list_item.ui");
|
Builder builder = new Builder.from_resource("/im/dino/Dino/occupant_list_item.ui");
|
||||||
main_grid = (Grid) builder.get_object("main_grid");
|
main_grid = (Grid) builder.get_object("main_grid");
|
||||||
image = (AvatarImage) builder.get_object("image");
|
picture = (AvatarPicture) builder.get_object("picture");
|
||||||
name_label = (Label) builder.get_object("name_label");
|
name_label = (Label) builder.get_object("name_label");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +26,12 @@ public class ListRow : Object {
|
||||||
this.jid = jid;
|
this.jid = jid;
|
||||||
|
|
||||||
name_label.label = Util.get_participant_display_name(stream_interactor, conversation, jid);
|
name_label.label = Util.get_participant_display_name(stream_interactor, conversation, jid);
|
||||||
image.set_conversation_participant(stream_interactor, conversation, jid);
|
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(conversation, jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListRow.label(string c, string text) {
|
public ListRow.label(string c, string text) {
|
||||||
name_label.label = text;
|
name_label.label = text;
|
||||||
image.set_text(c);
|
picture.model = new ViewModel.CompatAvatarPictureModel(null).add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Widget get_widget() {
|
public Widget get_widget() {
|
||||||
|
|
|
@ -61,62 +61,6 @@ public static string color_for_show(string show) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async AvatarDrawer get_conversation_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation) {
|
|
||||||
return yield get_conversation_participants_avatar_drawer(stream_interactor, conversation, new Jid[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async AvatarDrawer get_conversation_participants_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation, owned Jid[] jids) {
|
|
||||||
AvatarManager avatar_manager = stream_interactor.get_module(AvatarManager.IDENTITY);
|
|
||||||
MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY);
|
|
||||||
if (conversation.type_ != Conversation.Type.GROUPCHAT) {
|
|
||||||
Jid jid = jids.length == 1 ? jids[0] : conversation.counterpart;
|
|
||||||
Jid avatar_jid = jid;
|
|
||||||
if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid;
|
|
||||||
return new AvatarDrawer().tile(yield avatar_manager.get_avatar(conversation.account, avatar_jid), jids.length == 1 ?
|
|
||||||
get_participant_display_name(stream_interactor, conversation, jid) :
|
|
||||||
get_conversation_display_name(stream_interactor, conversation),
|
|
||||||
Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation));
|
|
||||||
}
|
|
||||||
if (jids.length > 0) {
|
|
||||||
AvatarDrawer drawer = new AvatarDrawer();
|
|
||||||
for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) {
|
|
||||||
Jid avatar_jid = jids[i];
|
|
||||||
Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
|
|
||||||
avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid;
|
|
||||||
part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
}
|
|
||||||
drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jids[i]),
|
|
||||||
Util.get_avatar_hex_color(stream_interactor, conversation.account, jids[i], conversation));
|
|
||||||
}
|
|
||||||
if (jids.length > 4) {
|
|
||||||
drawer.plus();
|
|
||||||
}
|
|
||||||
return drawer;
|
|
||||||
}
|
|
||||||
Gdk.Pixbuf? room_avatar = yield avatar_manager.get_avatar(conversation.account, conversation.counterpart);
|
|
||||||
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account);
|
|
||||||
if (room_avatar != null || !muc_manager.is_private_room(conversation.account, conversation.counterpart) || occupants == null || occupants.size == 0) {
|
|
||||||
return new AvatarDrawer().tile(room_avatar, "#", Util.get_avatar_hex_color(stream_interactor, conversation.account, conversation.counterpart, conversation));
|
|
||||||
}
|
|
||||||
AvatarDrawer drawer = new AvatarDrawer();
|
|
||||||
for (int i = 0; i < (occupants.size <= 4 ? occupants.size : 3); i++) {
|
|
||||||
Jid jid = occupants[i];
|
|
||||||
Jid avatar_jid = jid;
|
|
||||||
Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
|
|
||||||
avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid;
|
|
||||||
part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid);
|
|
||||||
}
|
|
||||||
drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jid),
|
|
||||||
Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation));
|
|
||||||
}
|
|
||||||
if (occupants.size > 4) {
|
|
||||||
drawer.plus();
|
|
||||||
}
|
|
||||||
return drawer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) {
|
public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) {
|
||||||
return Dino.get_conversation_display_name(stream_interactor, conversation, _("%s from %s"));
|
return Dino.get_conversation_display_name(stream_interactor, conversation, _("%s from %s"));
|
||||||
}
|
}
|
||||||
|
@ -137,27 +81,6 @@ public static string get_occupant_display_name(StreamInteractor stream_interacto
|
||||||
return Dino.get_occupant_display_name(stream_interactor, conversation, jid, me_is_me ? _("Me") : null);
|
return Dino.get_occupant_display_name(stream_interactor, conversation, jid, me_is_me ? _("Me") : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this has no usages?
|
|
||||||
//public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0, int width = 0, int height = 0) {
|
|
||||||
// if (scale == 0) scale = image.scale_factor;
|
|
||||||
// Cairo.Surface surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window());
|
|
||||||
// if (height == 0 && width != 0) {
|
|
||||||
// height = (int) ((double) width / pixbuf.width * pixbuf.height);
|
|
||||||
// } else if (height != 0 && width == 0) {
|
|
||||||
// width = (int) ((double) height / pixbuf.height * pixbuf.width);
|
|
||||||
// }
|
|
||||||
// if (width != 0) {
|
|
||||||
// Cairo.Surface surface_new = new Cairo.Surface.similar_image(surface, Cairo.Format.ARGB32, width, height);
|
|
||||||
// Cairo.Context context = new Cairo.Context(surface_new);
|
|
||||||
// context.scale((double) width * scale / pixbuf.width, (double) height * scale / pixbuf.height);
|
|
||||||
// context.set_source_surface(surface, 0, 0);
|
|
||||||
// context.get_source().set_filter(Cairo.Filter.BEST);
|
|
||||||
// context.paint();
|
|
||||||
// surface = surface_new;
|
|
||||||
// }
|
|
||||||
// image.set_from_surface(surface);
|
|
||||||
//}
|
|
||||||
|
|
||||||
public static Gdk.RGBA get_label_pango_color(Label label, string css_color) {
|
public static Gdk.RGBA get_label_pango_color(Label label, string css_color) {
|
||||||
Gtk.CssProvider provider = force_color(label, css_color);
|
Gtk.CssProvider provider = force_color(label, css_color);
|
||||||
Gdk.RGBA color_rgba = label.get_style_context().get_color();
|
Gdk.RGBA color_rgba = label.get_style_context().get_color();
|
||||||
|
|
519
main/src/ui/widgets/avatar_picture.vala
Normal file
519
main/src/ui/widgets/avatar_picture.vala
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
using Dino.Entities;
|
||||||
|
using Gtk;
|
||||||
|
using Xmpp;
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.AvatarPictureTileModel : Object {
|
||||||
|
public string display_text { get; set; }
|
||||||
|
public Gdk.RGBA background_color { get; set; }
|
||||||
|
public File? image_file { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.AvatarPictureModel : Object {
|
||||||
|
public ListModel tiles { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : AvatarPictureTileModel {
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
||||||
|
private MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
||||||
|
private Conversation? conversation;
|
||||||
|
private Jid? primary_avatar_jid;
|
||||||
|
private Jid? secondary_avatar_jid;
|
||||||
|
private Jid? display_name_jid;
|
||||||
|
|
||||||
|
public ConversationParticipantAvatarPictureTileModel(StreamInteractor stream_interactor, Conversation conversation, Jid jid) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
this.conversation = conversation;
|
||||||
|
this.primary_avatar_jid = jid;
|
||||||
|
this.display_name_jid = jid;
|
||||||
|
|
||||||
|
string color_id = jid.to_string();
|
||||||
|
if (conversation.type_ != Conversation.Type.CHAT && primary_avatar_jid.equals_bare(conversation.counterpart)) {
|
||||||
|
Jid? real_jid = muc_manager.get_real_jid(primary_avatar_jid, conversation.account);
|
||||||
|
if (real_jid != null && muc_manager.is_private_room(conversation.account, conversation.counterpart.bare_jid)) {
|
||||||
|
secondary_avatar_jid = primary_avatar_jid;
|
||||||
|
primary_avatar_jid = real_jid.bare_jid;
|
||||||
|
color_id = primary_avatar_jid.to_string();
|
||||||
|
} else {
|
||||||
|
color_id = jid.resourcepart.to_string();
|
||||||
|
}
|
||||||
|
} else if (conversation.type_ == Conversation.Type.CHAT) {
|
||||||
|
primary_avatar_jid = jid.bare_jid;
|
||||||
|
color_id = primary_avatar_jid.to_string();
|
||||||
|
}
|
||||||
|
string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid);
|
||||||
|
display_text = display.get_char(0).toupper().to_string();
|
||||||
|
stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated);
|
||||||
|
|
||||||
|
float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
|
||||||
|
background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f};
|
||||||
|
|
||||||
|
update_image_file();
|
||||||
|
avatar_manager.received_avatar.connect(on_received_avatar);
|
||||||
|
avatar_manager.fetched_avatar.connect(on_received_avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_image_file() {
|
||||||
|
File image_file = avatar_manager.get_avatar_file(conversation.account, primary_avatar_jid);
|
||||||
|
if (image_file == null && secondary_avatar_jid != null) {
|
||||||
|
image_file = avatar_manager.get_avatar_file(conversation.account, secondary_avatar_jid);
|
||||||
|
}
|
||||||
|
this.image_file = image_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
|
if (account.equals(conversation.account) && (jid.equals(primary_avatar_jid) || jid.equals(secondary_avatar_jid))) {
|
||||||
|
update_image_file();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_roster_updated(Account account, Jid jid) {
|
||||||
|
if (account.equals(conversation.account) && jid.equals(display_name_jid)) {
|
||||||
|
string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid);
|
||||||
|
display_text = display.get_char(0).toupper().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel {
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
||||||
|
private MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
||||||
|
private PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } }
|
||||||
|
private ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } }
|
||||||
|
private Conversation? conversation;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
tiles = new GLib.ListStore(typeof(ViewModel.AvatarPictureTileModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel(StreamInteractor? stream_interactor) {
|
||||||
|
this.stream_interactor = stream_interactor;
|
||||||
|
if (stream_interactor != null) {
|
||||||
|
connect_signals_weak(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void connect_signals_weak(CompatAvatarPictureModel model_) {
|
||||||
|
WeakRef model_weak = WeakRef(model_);
|
||||||
|
ulong muc_manager_private_room_occupant_updated_handler_id = 0;
|
||||||
|
ulong muc_manager_proom_info_updated_handler_id = 0;
|
||||||
|
ulong avatar_manager_received_avatar_handler_id = 0;
|
||||||
|
ulong avatar_manager_fetched_avatar_handler_id = 0;
|
||||||
|
muc_manager_private_room_occupant_updated_handler_id = model_.muc_manager.private_room_occupant_updated.connect((muc_manager, account, room, jid) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_room_updated(account, room);
|
||||||
|
} else if (muc_manager_private_room_occupant_updated_handler_id != 0) {
|
||||||
|
muc_manager.disconnect(muc_manager_private_room_occupant_updated_handler_id);
|
||||||
|
muc_manager_private_room_occupant_updated_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
muc_manager_proom_info_updated_handler_id = model_.muc_manager.room_info_updated.connect((muc_manager, account, room) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_room_updated(account, room);
|
||||||
|
} else if (muc_manager_proom_info_updated_handler_id != 0) {
|
||||||
|
muc_manager.disconnect(muc_manager_proom_info_updated_handler_id);
|
||||||
|
muc_manager_proom_info_updated_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatar_manager_received_avatar_handler_id = model_.avatar_manager.received_avatar.connect((avatar_manager, jid, account) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_received_avatar(jid, account);
|
||||||
|
} else if (avatar_manager_received_avatar_handler_id != 0) {
|
||||||
|
avatar_manager.disconnect(avatar_manager_received_avatar_handler_id);
|
||||||
|
avatar_manager_received_avatar_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatar_manager_fetched_avatar_handler_id = model_.avatar_manager.fetched_avatar.connect((avatar_manager, jid, account) => {
|
||||||
|
CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
|
||||||
|
if (model != null) {
|
||||||
|
model.on_received_avatar(jid, account);
|
||||||
|
} else if (avatar_manager_fetched_avatar_handler_id != 0) {
|
||||||
|
avatar_manager.disconnect(avatar_manager_fetched_avatar_handler_id);
|
||||||
|
avatar_manager_fetched_avatar_handler_id = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_room_updated(Account account, Jid room) {
|
||||||
|
if (conversation != null && account.equals(conversation.account) && conversation.counterpart.equals_bare(room)) {
|
||||||
|
reset();
|
||||||
|
set_conversation(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_received_avatar(Jid jid, Account account) {
|
||||||
|
on_room_updated(account, jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
(tiles as GLib.ListStore).remove_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel set_conversation(Conversation conversation) {
|
||||||
|
if (stream_interactor == null) {
|
||||||
|
critical("set_conversation() used on CompatAvatarPictureModel without stream_interactor");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.conversation = conversation;
|
||||||
|
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
|
||||||
|
if (avatar_manager.has_avatar(conversation.account, conversation.counterpart)) {
|
||||||
|
add_internal("#", conversation.counterpart.to_string(), avatar_manager.get_avatar_file(conversation.account, conversation.counterpart));
|
||||||
|
} else {
|
||||||
|
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account);
|
||||||
|
if (occupants != null && !occupants.is_empty && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
|
||||||
|
int count = occupants.size > 4 ? 3 : occupants.size;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
add_participant(conversation, occupants[i]);
|
||||||
|
}
|
||||||
|
if (occupants.size > 4) {
|
||||||
|
add_internal("+");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_internal("#", conversation.counterpart.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_participant(conversation, conversation.counterpart);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel add_participant(Conversation conversation, Jid jid) {
|
||||||
|
if (stream_interactor == null) {
|
||||||
|
critical("add_participant() used on CompatAvatarPictureModel without stream_interactor");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
(tiles as GLib.ListStore).append(new ConversationParticipantAvatarPictureTileModel(stream_interactor, conversation, jid));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompatAvatarPictureModel add(string display, string? color_id = null, File? image_file = null) {
|
||||||
|
add_internal(display, color_id, image_file);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AvatarPictureTileModel add_internal(string display, string? color_id = null, File? image_file = null) {
|
||||||
|
GLib.ListStore store = tiles as GLib.ListStore;
|
||||||
|
float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
|
||||||
|
var model = new ViewModel.AvatarPictureTileModel() {
|
||||||
|
display_text = display.get_char(0).toupper().to_string(),
|
||||||
|
background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f},
|
||||||
|
image_file = image_file
|
||||||
|
};
|
||||||
|
store.append(model);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Dino.Ui.CompatAvatarDrawer {
|
||||||
|
public float radius_percent { get; set; default = 0.2f; }
|
||||||
|
public ViewModel.AvatarPictureModel? model { get; set; }
|
||||||
|
public int height_request { get; set; default = 35; }
|
||||||
|
public int width_request { get; set; default = 35; }
|
||||||
|
public string font_family { get; set; default = "Sans"; }
|
||||||
|
|
||||||
|
public Cairo.ImageSurface draw_image_surface() {
|
||||||
|
Cairo.ImageSurface surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width_request, height_request);
|
||||||
|
draw_on_context(new Cairo.Context(surface));
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw_on_context(Cairo.Context ctx) {
|
||||||
|
double radius = (width_request + height_request) * 0.25f * radius_percent;
|
||||||
|
double degrees = Math.PI / 180.0;
|
||||||
|
ctx.new_sub_path();
|
||||||
|
ctx.arc(width_request - radius, radius, radius, -90 * degrees, 0 * degrees);
|
||||||
|
ctx.arc(width_request - radius, height_request - radius, radius, 0 * degrees, 90 * degrees);
|
||||||
|
ctx.arc(radius, height_request - radius, radius, 90 * degrees, 180 * degrees);
|
||||||
|
ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees);
|
||||||
|
ctx.close_path();
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
if (this.model.tiles.get_n_items() == 4) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
bufctx.scale(0.5, 0.5);
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request - 1, 2), 0, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request - 1, 2), width_request + 1, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width_request - 1, height_request - 1, 2), 0, height_request + 1);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 3, width_request - 1, height_request - 1, 2), width_request + 1, height_request + 1);
|
||||||
|
bufctx.paint();
|
||||||
|
|
||||||
|
ctx.set_source_surface(buffer, 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 3) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
bufctx.scale(0.5, 0.5);
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request - 1, 2), 0, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request * 2, 2), width_request + 1, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 2, width_request - 1, height_request - 1, 2), 0, width_request + 1);
|
||||||
|
bufctx.paint();
|
||||||
|
|
||||||
|
ctx.set_source_surface(buffer, 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 2) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
bufctx.scale(0.5, 0.5);
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request * 2, 2), 0, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request * 2, 2), width_request + 1, 0);
|
||||||
|
bufctx.paint();
|
||||||
|
|
||||||
|
ctx.set_source_surface(buffer, 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 1) {
|
||||||
|
ctx.set_source_surface(sub_surface_idx(ctx, 0, width_request, height_request, 1), 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
} else if (this.model.tiles.get_n_items() == 0) {
|
||||||
|
ctx.set_source_surface(sub_surface_idx(ctx, -1, width_request, height_request, 1), 0, 0);
|
||||||
|
ctx.paint();
|
||||||
|
}
|
||||||
|
ctx.set_source_rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) {
|
||||||
|
ViewModel.AvatarPictureTileModel tile = (ViewModel.AvatarPictureTileModel) this.model.tiles.get_item(idx);
|
||||||
|
Gdk.Pixbuf? avatar = new Gdk.Pixbuf.from_file(tile.image_file.get_path());
|
||||||
|
string? name = idx >= 0 ? tile.display_text : "";
|
||||||
|
Gdk.RGBA hex_color = tile.background_color;
|
||||||
|
return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, Gdk.RGBA background_color, int width, int height, int font_factor = 1) {
|
||||||
|
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
|
||||||
|
Cairo.Context bufctx = new Cairo.Context(buffer);
|
||||||
|
if (avatar == null) {
|
||||||
|
Gdk.cairo_set_source_rgba(bufctx, background_color);
|
||||||
|
bufctx.rectangle(0, 0, width, height);
|
||||||
|
bufctx.fill();
|
||||||
|
|
||||||
|
string text = name == null ? "…" : name.get_char(0).toupper().to_string();
|
||||||
|
bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
|
||||||
|
bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25);
|
||||||
|
Cairo.TextExtents extents;
|
||||||
|
bufctx.text_extents(text, out extents);
|
||||||
|
double x_pos = width/2 - (extents.width/2 + extents.x_bearing);
|
||||||
|
double y_pos = height/2 - (extents.height/2 + extents.y_bearing);
|
||||||
|
bufctx.move_to(x_pos, y_pos);
|
||||||
|
bufctx.set_source_rgba(1, 1, 1, 1);
|
||||||
|
bufctx.show_text(text);
|
||||||
|
} else {
|
||||||
|
double w_scale = (double) width / avatar.width;
|
||||||
|
double h_scale = (double) height / avatar.height;
|
||||||
|
double scale = double.max(w_scale, h_scale);
|
||||||
|
bufctx.scale(scale, scale);
|
||||||
|
|
||||||
|
double x_off = 0, y_off = 0;
|
||||||
|
if (scale == h_scale) {
|
||||||
|
x_off = (width / scale - avatar.width) / 2.0;
|
||||||
|
} else {
|
||||||
|
y_off = (height / scale - avatar.height) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off);
|
||||||
|
bufctx.get_source().set_filter(Cairo.Filter.BEST);
|
||||||
|
bufctx.paint();
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Dino.Ui.AvatarPicture : Gtk.Widget {
|
||||||
|
public float radius_percent { get; set; default = 0.2f; }
|
||||||
|
public ViewModel.AvatarPictureModel? model { get; set; }
|
||||||
|
private Gee.List<Tile> tiles = new Gee.ArrayList<Tile>();
|
||||||
|
|
||||||
|
private ViewModel.AvatarPictureModel? old_model;
|
||||||
|
private ulong model_tiles_items_changed_handler;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
height_request = 35;
|
||||||
|
width_request = 35;
|
||||||
|
set_css_name("picture");
|
||||||
|
add_css_class("avatar");
|
||||||
|
notify["radius-percent"].connect(queue_draw);
|
||||||
|
notify["model"].connect(on_model_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_model_changed() {
|
||||||
|
if (old_model != null) {
|
||||||
|
old_model.tiles.disconnect(model_tiles_items_changed_handler);
|
||||||
|
}
|
||||||
|
foreach (Tile tile in tiles) {
|
||||||
|
tile.unparent();
|
||||||
|
tile.dispose();
|
||||||
|
}
|
||||||
|
tiles.clear();
|
||||||
|
old_model = model;
|
||||||
|
if (model != null) {
|
||||||
|
model_tiles_items_changed_handler = model.tiles.items_changed.connect(on_model_items_changed);
|
||||||
|
for(int i = 0; i < model.tiles.get_n_items(); i++) {
|
||||||
|
Tile tile = new Tile();
|
||||||
|
tile.model = model.tiles.get_item(i) as ViewModel.AvatarPictureTileModel;
|
||||||
|
tile.insert_after(this, tiles.is_empty ? null : tiles.last());
|
||||||
|
tiles.add(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_model_items_changed(uint position, uint removed, uint added) {
|
||||||
|
while (removed > 0) {
|
||||||
|
Tile old = tiles.remove_at((int) position);
|
||||||
|
old.unparent();
|
||||||
|
old.dispose();
|
||||||
|
removed--;
|
||||||
|
}
|
||||||
|
while (added > 0) {
|
||||||
|
Tile tile = new Tile();
|
||||||
|
tile.model = model.tiles.get_item(position) as ViewModel.AvatarPictureTileModel;
|
||||||
|
tile.insert_after(this, position == 0 ? null : tiles[(int) position - 1]);
|
||||||
|
tiles.insert((int) position, tile);
|
||||||
|
position++;
|
||||||
|
added--;
|
||||||
|
}
|
||||||
|
queue_allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
|
||||||
|
minimum_baseline = natural_baseline = -1;
|
||||||
|
if (orientation == Orientation.HORIZONTAL) {
|
||||||
|
minimum = natural = width_request;
|
||||||
|
} else {
|
||||||
|
minimum = natural = height_request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void size_allocate(int width, int height, int baseline) {
|
||||||
|
int half_width_size = width / 2;
|
||||||
|
int half_height_size = height / 2;
|
||||||
|
int half_width_offset = (width % 2 == 0) ? half_width_size : half_width_size + 1;
|
||||||
|
int half_height_offset = (height % 2 == 0) ? half_height_size : half_height_size + 1;
|
||||||
|
if (tiles.size == 1) {
|
||||||
|
tiles[0].allocate(width, height, baseline, null);
|
||||||
|
} else if (tiles.size == 2) {
|
||||||
|
tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = height }, baseline);
|
||||||
|
tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = height }, baseline);
|
||||||
|
} else if (tiles.size == 3) {
|
||||||
|
tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = height }, baseline);
|
||||||
|
tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[2].allocate_size(Allocation() { x = half_width_offset, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
} else if (tiles.size == 4) {
|
||||||
|
tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[2].allocate_size(Allocation() { x = 0, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
tiles[3].allocate_size(Allocation() { x = half_width_offset, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SizeRequestMode get_request_mode() {
|
||||||
|
return SizeRequestMode.CONSTANT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void snapshot(Gtk.Snapshot snapshot) {
|
||||||
|
Graphene.Rect bounds = Graphene.Rect();
|
||||||
|
bounds.init(0, 0, get_width(), get_height());
|
||||||
|
Gsk.RoundedRect rounded_rect = Gsk.RoundedRect();
|
||||||
|
rounded_rect.init_from_rect(bounds, (get_width() + get_height()) * 0.25f * radius_percent);
|
||||||
|
snapshot.push_rounded_clip(rounded_rect);
|
||||||
|
base.snapshot(snapshot);
|
||||||
|
snapshot.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
model = null;
|
||||||
|
on_model_changed();
|
||||||
|
base.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Tile : Gtk.Widget {
|
||||||
|
public ViewModel.AvatarPictureTileModel? model { get; set; }
|
||||||
|
public Gdk.RGBA background_color { get; set; default = Gdk.RGBA(){ red = 1.0f, green = 1.0f, blue = 1.0f, alpha = 0.0f }; }
|
||||||
|
public string display_text { get { return label.get_text(); } set { label.set_text(value); } }
|
||||||
|
public File? image_file { get { return picture.file; } set { picture.file = value; } }
|
||||||
|
|
||||||
|
private Binding? background_color_binding;
|
||||||
|
private Binding? display_text_binding;
|
||||||
|
private Binding? image_file_binding;
|
||||||
|
|
||||||
|
private Label label = new Label("");
|
||||||
|
private Picture picture = new Picture();
|
||||||
|
|
||||||
|
construct {
|
||||||
|
label.insert_after(this, null);
|
||||||
|
label.attributes = new Pango.AttrList();
|
||||||
|
label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX));
|
||||||
|
#if GTK_4_8 && VALA_0_58
|
||||||
|
picture.content_fit = Gtk.ContentFit.COVER;
|
||||||
|
#elif GTK_4_8
|
||||||
|
picture.@set("content-fit", 2);
|
||||||
|
#endif
|
||||||
|
picture.insert_after(this, label);
|
||||||
|
this.notify["model"].connect(on_model_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_model_changed() {
|
||||||
|
if (background_color_binding != null) background_color_binding.unbind();
|
||||||
|
if (display_text_binding != null) display_text_binding.unbind();
|
||||||
|
if (image_file_binding != null) image_file_binding.unbind();
|
||||||
|
if (model != null) {
|
||||||
|
background_color_binding = model.bind_property("background-color", this, "background-color", BindingFlags.SYNC_CREATE);
|
||||||
|
display_text_binding = model.bind_property("display-text", this, "display-text", BindingFlags.SYNC_CREATE);
|
||||||
|
image_file_binding = model.bind_property("image-file", this, "image-file", BindingFlags.SYNC_CREATE);
|
||||||
|
} else {
|
||||||
|
background_color_binding = null;
|
||||||
|
display_text_binding = null;
|
||||||
|
image_file_binding = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void dispose() {
|
||||||
|
if (background_color_binding != null) background_color_binding.unbind();
|
||||||
|
if (display_text_binding != null) display_text_binding.unbind();
|
||||||
|
if (image_file_binding != null) image_file_binding.unbind();
|
||||||
|
background_color_binding = null;
|
||||||
|
display_text_binding = null;
|
||||||
|
image_file_binding = null;
|
||||||
|
label.unparent();
|
||||||
|
picture.unparent();
|
||||||
|
base.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void size_allocate(int width, int height, int baseline) {
|
||||||
|
int min, nat, bl_min, bl_nat;
|
||||||
|
picture.measure(Orientation.HORIZONTAL, -1, out min, out nat, out bl_min, out bl_nat);
|
||||||
|
if (nat > 0) {
|
||||||
|
picture.allocate(width, height, baseline, null);
|
||||||
|
label.visible = false;
|
||||||
|
} else {
|
||||||
|
picture.allocate(0, 0, 0, null);
|
||||||
|
label.attributes = new Pango.AttrList();
|
||||||
|
label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX));
|
||||||
|
label.attributes.insert(Pango.attr_scale_new(double.min((double)width, (double)height) * 0.05));
|
||||||
|
label.margin_bottom = height/40;
|
||||||
|
label.visible = true;
|
||||||
|
label.allocate(width, height, baseline, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void snapshot(Gtk.Snapshot snapshot) {
|
||||||
|
if (label.visible) {
|
||||||
|
Graphene.Rect bounds = Graphene.Rect();
|
||||||
|
bounds.init(0, 0, get_width(), get_height());
|
||||||
|
snapshot.append_node(new Gsk.ColorNode(background_color, bounds));
|
||||||
|
}
|
||||||
|
base.snapshot(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue