mirror of
https://gitlab.gnome.org/World/tootle
synced 2025-02-17 03:51:11 +01:00
Rewrite attachment view
This commit is contained in:
parent
09119b4584
commit
3305fac395
@ -20,6 +20,7 @@ executable(
|
||||
asresources,
|
||||
'src/Application.vala',
|
||||
'src/Desktop.vala',
|
||||
'src/Drawing.vala',
|
||||
'src/Html.vala',
|
||||
'src/MainWindow.vala',
|
||||
'src/Settings.vala',
|
||||
@ -45,8 +46,8 @@ executable(
|
||||
'src/Widgets/StatusWidget.vala',
|
||||
'src/Widgets/AccountWidget.vala',
|
||||
'src/Widgets/NotificationWidget.vala',
|
||||
'src/Widgets/AttachmentWidget.vala',
|
||||
'src/Widgets/AttachmentBox.vala',
|
||||
'src/Widgets/ImageAttachment.vala',
|
||||
'src/Widgets/AttachmentGrid.vala',
|
||||
'src/Dialogs/NewAccountDialog.vala',
|
||||
'src/Dialogs/PostDialog.vala',
|
||||
'src/Dialogs/SettingsDialog.vala',
|
||||
|
@ -1,4 +1,4 @@
|
||||
public class Tootle.Attachment{
|
||||
public class Tootle.Attachment {
|
||||
|
||||
public int64 id;
|
||||
public string type;
|
||||
@ -6,24 +6,24 @@ public class Tootle.Attachment{
|
||||
public string preview_url;
|
||||
public string? description;
|
||||
|
||||
public Attachment(int64 _id){
|
||||
public Attachment(int64 _id) {
|
||||
id = _id;
|
||||
}
|
||||
|
||||
public static Attachment parse (Json.Object obj){
|
||||
|
||||
public static Attachment parse (Json.Object obj) {
|
||||
var id = int64.parse (obj.get_string_member ("id"));
|
||||
var attachment = new Attachment (id);
|
||||
|
||||
|
||||
attachment.type = obj.get_string_member ("type");
|
||||
attachment.preview_url = obj.get_string_member ("preview_url");
|
||||
attachment.url = obj.get_string_member ("url");
|
||||
|
||||
|
||||
if (obj.has_member ("description"))
|
||||
attachment.description = obj.get_string_member ("description");
|
||||
|
||||
|
||||
return attachment;
|
||||
}
|
||||
|
||||
|
||||
public Json.Node? serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
@ -35,7 +35,7 @@ public class Tootle.Attachment{
|
||||
builder.add_string_value (url);
|
||||
builder.set_member_name ("preview_url");
|
||||
builder.add_string_value (preview_url);
|
||||
|
||||
|
||||
if (description != null) {
|
||||
builder.set_member_name ("description");
|
||||
builder.add_string_value (description);
|
||||
|
@ -2,9 +2,9 @@ using Gtk;
|
||||
using Tootle;
|
||||
|
||||
public class Tootle.PostDialog : Gtk.Dialog {
|
||||
|
||||
|
||||
private static PostDialog dialog;
|
||||
|
||||
|
||||
protected TextView text;
|
||||
private ScrolledWindow scroll;
|
||||
private Label counter;
|
||||
@ -13,10 +13,10 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
private Button attach;
|
||||
private Button cancel;
|
||||
private Button publish;
|
||||
protected AttachmentBox attachments;
|
||||
protected Widgets.AttachmentGrid attachments;
|
||||
private Revealer spoiler_revealer;
|
||||
private Entry spoiler_text;
|
||||
|
||||
|
||||
protected Status? replying_to;
|
||||
protected Status? redrafting;
|
||||
protected StatusVisibility visibility_opt = StatusVisibility.PUBLIC;
|
||||
@ -31,23 +31,23 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
char_limit = settings.char_limit;
|
||||
replying_to = _replying_to;
|
||||
redrafting = _redrafting;
|
||||
|
||||
|
||||
if (replying_to != null)
|
||||
visibility_opt = replying_to.visibility;
|
||||
if (redrafting != null)
|
||||
visibility_opt = redrafting.visibility;
|
||||
|
||||
|
||||
var actions = get_action_area ().get_parent () as Gtk.Box;
|
||||
var content = get_content_area ();
|
||||
get_action_area ().hexpand = false;
|
||||
|
||||
|
||||
visibility = get_visibility_btn ();
|
||||
visibility.tooltip_text = _("Post Visibility");
|
||||
visibility.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
visibility.get_style_context ().remove_class ("image-button");
|
||||
visibility.can_default = false;
|
||||
(visibility as Widget).set_focus_on_click (false);
|
||||
|
||||
|
||||
attach = new Button.from_icon_name ("mail-attachment-symbolic");
|
||||
attach.tooltip_text = _("Add Media");
|
||||
attach.valign = Gtk.Align.CENTER;
|
||||
@ -56,7 +56,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
attach.can_default = false;
|
||||
(attach as Widget).set_focus_on_click (false);
|
||||
attach.clicked.connect (() => attachments.select ());
|
||||
|
||||
|
||||
spoiler = new ImageToggleButton ("image-red-eye-symbolic");
|
||||
spoiler.tooltip_text = _("Spoiler Warning");
|
||||
spoiler.set_action ();
|
||||
@ -64,10 +64,10 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
spoiler_revealer.reveal_child = spoiler.active;
|
||||
validate ();
|
||||
});
|
||||
|
||||
|
||||
cancel = add_button (_("Cancel"), 5) as Button;
|
||||
cancel.clicked.connect(() => destroy ());
|
||||
|
||||
|
||||
if (redrafting != null) {
|
||||
publish = add_button (_("Redraft"), 5) as Button;
|
||||
publish.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
|
||||
@ -78,23 +78,23 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
publish.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
|
||||
publish.clicked.connect (publish_post);
|
||||
}
|
||||
|
||||
|
||||
spoiler_text = new Gtk.Entry ();
|
||||
spoiler_text.margin_start = 6;
|
||||
spoiler_text.margin_end = 6;
|
||||
spoiler_text.placeholder_text = _("Write your warning here");
|
||||
spoiler_text.changed.connect (validate);
|
||||
|
||||
|
||||
spoiler_revealer = new Gtk.Revealer ();
|
||||
spoiler_revealer.add (spoiler_text);
|
||||
|
||||
|
||||
text = new TextView ();
|
||||
text.get_style_context ().add_class ("toot-text");
|
||||
text.wrap_mode = Gtk.WrapMode.WORD;
|
||||
text.accepts_tab = false;
|
||||
text.vexpand = true;
|
||||
text.buffer.changed.connect (validate);
|
||||
|
||||
|
||||
scroll = new ScrolledWindow (null, null);
|
||||
scroll.hscrollbar_policy = Gtk.PolicyType.NEVER;
|
||||
scroll.min_content_height = 120;
|
||||
@ -104,10 +104,10 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
scroll.margin_end = 6;
|
||||
scroll.add (text);
|
||||
scroll.show_all ();
|
||||
|
||||
attachments = new AttachmentBox (true);
|
||||
|
||||
attachments = new Widgets.AttachmentGrid (true);
|
||||
counter = new Label ("");
|
||||
|
||||
|
||||
actions.pack_start (counter, false, false, 6);
|
||||
actions.pack_end (spoiler, false, false, 6);
|
||||
actions.pack_end (visibility, false, false, 0);
|
||||
@ -116,7 +116,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
content.pack_start (scroll, false, false, 6);
|
||||
content.pack_start (attachments, false, false, 6);
|
||||
content.set_size_request (350, 120);
|
||||
|
||||
|
||||
if (replying_to != null) {
|
||||
spoiler.active = replying_to.sensitive;
|
||||
var status_spoiler_text = replying_to.spoiler_text != null ? replying_to.spoiler_text : "";
|
||||
@ -127,15 +127,15 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
var status_spoiler_text = redrafting.spoiler_text != null ? redrafting.spoiler_text : "";
|
||||
spoiler_text.set_text (status_spoiler_text);
|
||||
}
|
||||
|
||||
|
||||
destroy.connect (() => dialog = null);
|
||||
|
||||
|
||||
show_all ();
|
||||
attachments.hide ();
|
||||
text.grab_focus ();
|
||||
validate ();
|
||||
}
|
||||
|
||||
|
||||
private Gtk.MenuButton get_visibility_btn () {
|
||||
var button = new Gtk.MenuButton ();
|
||||
var menu = new Gtk.Popover (null);
|
||||
@ -144,13 +144,13 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
menu.add (box);
|
||||
button.direction = Gtk.ArrowType.DOWN;
|
||||
button.image = new Gtk.Image.from_icon_name (visibility_opt.get_icon (), Gtk.IconSize.BUTTON);
|
||||
|
||||
|
||||
Gtk.RadioButton? first = null;
|
||||
foreach (StatusVisibility opt in StatusVisibility.get_all ()){
|
||||
var item = new Gtk.RadioButton.with_label_from_widget (first, opt.get_desc ());
|
||||
if (first == null)
|
||||
first = item;
|
||||
|
||||
|
||||
item.toggled.connect (() => {
|
||||
visibility_opt = opt;
|
||||
(button.image as Gtk.Image).icon_name = visibility_opt.get_icon ();
|
||||
@ -158,7 +158,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
item.active = visibility_opt == opt;
|
||||
box.pack_start (item, false, false, 0);
|
||||
}
|
||||
|
||||
|
||||
box.show_all ();
|
||||
button.use_popover = true;
|
||||
button.popover = menu;
|
||||
@ -166,62 +166,62 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
button.show ();
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
private void validate () {
|
||||
var remain = char_limit - text.buffer.text.length;
|
||||
if (spoiler.active)
|
||||
remain -= spoiler_text.buffer.text.length;
|
||||
|
||||
|
||||
counter.label = remain.to_string ();
|
||||
publish.sensitive = remain >= 0;
|
||||
publish.sensitive = remain >= 0;
|
||||
}
|
||||
|
||||
|
||||
public static void open (string? text = null, Status? reply_to = null) {
|
||||
if (dialog == null){
|
||||
dialog = new PostDialog (reply_to);
|
||||
|
||||
|
||||
if (text != null)
|
||||
dialog.text.buffer.text = text;
|
||||
}
|
||||
else if (text != null)
|
||||
dialog.text.buffer.text += text;
|
||||
}
|
||||
|
||||
|
||||
public static void reply (Status status) {
|
||||
if (dialog != null)
|
||||
return;
|
||||
|
||||
|
||||
open (null, status);
|
||||
dialog.text.buffer.text = status.get_reply_mentions ();
|
||||
}
|
||||
|
||||
|
||||
public static void redraft (Status status) {
|
||||
if (dialog != null)
|
||||
return;
|
||||
dialog = new PostDialog (null, status);
|
||||
|
||||
|
||||
if (status.attachments != null) {
|
||||
foreach (Attachment attachment in status.attachments)
|
||||
dialog.attachments.append (attachment);
|
||||
}
|
||||
|
||||
|
||||
var content = Html.simplify (status.content);
|
||||
content = Html.remove_tags (content);
|
||||
content = RichLabel.restore_entities (content);
|
||||
dialog.text.buffer.text = content;
|
||||
}
|
||||
|
||||
|
||||
private void publish_post () {
|
||||
var pars = "?status=%s&visibility=%s".printf (Html.uri_encode (text.buffer.text), visibility_opt.to_string ());
|
||||
pars += attachments.get_uri_array ();
|
||||
if (replying_to != null)
|
||||
pars += "&in_reply_to_id=%s".printf (replying_to.id.to_string ());
|
||||
|
||||
|
||||
if (spoiler.active) {
|
||||
pars += "&sensitive=true";
|
||||
pars += "&spoiler_text=" + Html.uri_encode (spoiler_text.buffer.text);
|
||||
}
|
||||
|
||||
|
||||
var url = "%s/api/v1/statuses%s".printf (accounts.formal.instance, pars);
|
||||
var msg = new Soup.Message ("POST", url);
|
||||
network.queue (msg, (sess, mess) => {
|
||||
@ -237,7 +237,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void redraft_post () {
|
||||
redrafting.poof (false).finished.connect (publish_post);
|
||||
}
|
||||
|
35
src/Drawing.vala
Normal file
35
src/Drawing.vala
Normal file
@ -0,0 +1,35 @@
|
||||
using Gdk;
|
||||
using GLib;
|
||||
|
||||
public class Tootle.Drawing {
|
||||
|
||||
public static void draw_rounded_rect (Cairo.Context ctx, double x, double y, double w, double h, double r) {
|
||||
double degr = Math.PI / 180.0;
|
||||
ctx.new_sub_path ();
|
||||
ctx.arc (x + w - r, y + r, r, -90 * degr, 0 * degr);
|
||||
ctx.arc (x + w - r, y + h - r, r, 0 * degr, 90 * degr);
|
||||
ctx.arc (x + r, y + h - r, r, 90 * degr, 180 * degr);
|
||||
ctx.arc (x + r, y + r, r, 180 * degr, 270 * degr);
|
||||
ctx.close_path ();
|
||||
}
|
||||
|
||||
public static Pixbuf make_pixbuf_thumbnail (Pixbuf pixbuf, int view_w, int view_h, bool fill_parent = false) {
|
||||
// Don't resize if parent view is bigger than actual image
|
||||
if (view_w >= pixbuf.width && view_h >= pixbuf.height)
|
||||
return pixbuf;
|
||||
|
||||
//Otherwise fit the image into the parent view
|
||||
var resized_w = view_w;
|
||||
var resized_h = view_h;
|
||||
//resized_w = (pixbuf.width * view_h) / pixbuf.height;
|
||||
//resized_h = (pixbuf.height * view_w) / pixbuf.width;
|
||||
|
||||
if (fill_parent)
|
||||
resized_h = (pixbuf.height * view_w) / pixbuf.width;
|
||||
else
|
||||
resized_w = (pixbuf.width * view_h) / pixbuf.height;
|
||||
|
||||
return pixbuf.scale_simple (resized_w, resized_h, InterpType.BILINEAR);
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
using Gtk;
|
||||
|
||||
public class Tootle.MainWindow: Gtk.Window {
|
||||
|
||||
|
||||
private Overlay overlay;
|
||||
private Granite.Widgets.Toast toast;
|
||||
private Grid grid;
|
||||
private Stack primary_stack;
|
||||
private Stack secondary_stack;
|
||||
|
||||
|
||||
public HeaderBar header;
|
||||
public Granite.Widgets.ModeButton button_mode;
|
||||
private AccountsButton button_accounts;
|
||||
private Spinner spinner;
|
||||
private Button button_toot;
|
||||
private Button button_back;
|
||||
|
||||
|
||||
public HomeView home = new HomeView ();
|
||||
public NotificationsView notifications = new NotificationsView ();
|
||||
public LocalView local = new LocalView ();
|
||||
@ -24,7 +24,7 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
var provider = new Gtk.CssProvider ();
|
||||
provider.load_from_resource ("/com/github/bleakgrey/tootle/app.css");
|
||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
|
||||
settings.changed.connect (update_theme);
|
||||
update_theme ();
|
||||
|
||||
@ -37,18 +37,18 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
primary_stack.add_named (secondary_stack, "0");
|
||||
primary_stack.hexpand = true;
|
||||
primary_stack.vexpand = true;
|
||||
|
||||
|
||||
spinner = new Spinner ();
|
||||
spinner.active = true;
|
||||
|
||||
button_accounts = new AccountsButton ();
|
||||
|
||||
|
||||
button_back = new Button ();
|
||||
button_back.valign = Align.CENTER;
|
||||
button_back.label = _("Back");
|
||||
button_back.get_style_context ().add_class (Granite.STYLE_CLASS_BACK_BUTTON);
|
||||
button_back.clicked.connect (() => back ());
|
||||
|
||||
|
||||
button_toot = new Button ();
|
||||
button_toot.valign = Align.CENTER;
|
||||
button_toot.tooltip_text = _("Toot");
|
||||
@ -61,7 +61,7 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
button_mode.valign = Align.FILL;
|
||||
button_mode.mode_changed.connect (on_mode_changed);
|
||||
button_mode.show ();
|
||||
|
||||
|
||||
header = new HeaderBar ();
|
||||
header.get_style_context ().add_class ("compact");
|
||||
header.show_close_button = true;
|
||||
@ -72,16 +72,16 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
header.pack_end (button_accounts);
|
||||
header.pack_end (spinner);
|
||||
header.show_all ();
|
||||
|
||||
|
||||
grid = new Grid ();
|
||||
grid.attach (primary_stack, 0, 0, 1, 1);
|
||||
|
||||
|
||||
add_header_view (home);
|
||||
add_header_view (notifications);
|
||||
add_header_view (local);
|
||||
add_header_view (federated);
|
||||
button_mode.set_active (0);
|
||||
|
||||
|
||||
toast = new Granite.Widgets.Toast ("");
|
||||
overlay = new Overlay ();
|
||||
overlay.add_overlay (grid);
|
||||
@ -90,7 +90,7 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
add (overlay);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
|
||||
public MainWindow (Gtk.Application _app) {
|
||||
application = _app;
|
||||
icon_name = "com.github.bleakgrey.tootle";
|
||||
@ -98,7 +98,7 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
window_position = WindowPosition.CENTER;
|
||||
set_titlebar (header);
|
||||
update_header ();
|
||||
|
||||
|
||||
app.toast.connect (on_toast);
|
||||
network.started.connect (() => spinner.show ());
|
||||
network.finished.connect (() => spinner.hide ());
|
||||
@ -111,22 +111,22 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void add_header_view (AbstractView view) {
|
||||
var img = new Image.from_icon_name (view.get_icon (), IconSize.LARGE_TOOLBAR);
|
||||
img.tooltip_text = view.get_name ();
|
||||
button_mode.append (img);
|
||||
view.image = img;
|
||||
secondary_stack.add_named (view, view.get_name ());
|
||||
|
||||
|
||||
if (view is NotificationsView)
|
||||
img.pixel_size = 20; // For some reason Notifications icon is too small without this
|
||||
}
|
||||
|
||||
|
||||
public int get_visible_id () {
|
||||
return int.parse (primary_stack.get_visible_child_name ());
|
||||
}
|
||||
|
||||
|
||||
public void open_view (AbstractView widget) {
|
||||
var i = get_visible_id ();
|
||||
i++;
|
||||
@ -136,18 +136,18 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
primary_stack.set_visible_child_name (i.to_string ());
|
||||
update_header ();
|
||||
}
|
||||
|
||||
|
||||
public void back () {
|
||||
var i = get_visible_id ();
|
||||
if (i == 0)
|
||||
return;
|
||||
|
||||
|
||||
var child = primary_stack.get_child_by_name (i.to_string ());
|
||||
primary_stack.set_visible_child_name ((i-1).to_string ());
|
||||
child.destroy ();
|
||||
update_header ();
|
||||
}
|
||||
|
||||
|
||||
public void reopen_view (int view_id) {
|
||||
var i = get_visible_id ();
|
||||
while (i != view_id && view_id != 0) {
|
||||
@ -155,7 +155,7 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
i = get_visible_id ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override bool delete_event (Gdk.EventAny event) {
|
||||
this.destroy.connect (() => {
|
||||
if (!settings.always_online || accounts.is_empty ())
|
||||
@ -164,11 +164,11 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void switch_timeline (int32 timeline_no) {
|
||||
button_mode.set_active (timeline_no);
|
||||
}
|
||||
|
||||
|
||||
private void update_theme () {
|
||||
var provider = new Gtk.CssProvider ();
|
||||
var is_dark = settings.dark_theme;
|
||||
@ -177,7 +177,7 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = is_dark;
|
||||
}
|
||||
|
||||
|
||||
private void update_header () {
|
||||
bool primary_mode = get_visible_id () == 0;
|
||||
button_mode.sensitive = primary_mode;
|
||||
@ -195,9 +195,9 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
private void on_mode_changed (Widget widget) {
|
||||
var visible = secondary_stack.get_visible_child () as AbstractView;
|
||||
visible.current = false;
|
||||
|
||||
|
||||
secondary_stack.set_visible_child_name (widget.tooltip_text);
|
||||
|
||||
|
||||
visible = secondary_stack.get_visible_child () as AbstractView;
|
||||
visible.current = true;
|
||||
visible.on_set_current ();
|
||||
|
@ -7,10 +7,10 @@ public class Tootle.Network : GLib.Object {
|
||||
|
||||
public signal void started ();
|
||||
public signal void finished ();
|
||||
|
||||
|
||||
public signal void notification (Notification notification);
|
||||
public signal void status_removed (int64 id);
|
||||
|
||||
|
||||
private int requests_processing = 0;
|
||||
private Soup.Session session;
|
||||
|
||||
@ -19,31 +19,31 @@ public class Tootle.Network : GLib.Object {
|
||||
session.ssl_strict = true;
|
||||
session.ssl_use_system_ca_file = true;
|
||||
session.timeout = 20;
|
||||
session.max_conns = 15;
|
||||
session.max_conns = 20;
|
||||
session.request_unqueued.connect (msg => {
|
||||
requests_processing--;
|
||||
if(requests_processing <= 0)
|
||||
finished ();
|
||||
});
|
||||
|
||||
|
||||
// Soup.Logger logger = new Soup.Logger (Soup.LoggerLogLevel.BODY, -1);
|
||||
// session.add_feature (logger);
|
||||
}
|
||||
|
||||
public Network () {}
|
||||
|
||||
|
||||
public async WebsocketConnection stream (Soup.Message msg) throws GLib.Error {
|
||||
return yield session.websocket_connect_async (msg, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
public Soup.Message queue (Soup.Message msg, owned Soup.SessionCallback? cb = null) {
|
||||
requests_processing++;
|
||||
started ();
|
||||
|
||||
|
||||
var formal = accounts.formal;
|
||||
if(formal != null)
|
||||
msg.request_headers.append ("Authorization", "Bearer " + formal.token);
|
||||
|
||||
|
||||
session.queue_message (msg, (sess, mess) => {
|
||||
switch (mess.tls_errors){
|
||||
case GLib.TlsCertificateFlags.UNKNOWN_CA:
|
||||
@ -60,16 +60,16 @@ public class Tootle.Network : GLib.Object {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (mess.status_code != Soup.Status.OK) {
|
||||
var phrase = Soup.Status.get_phrase (mess.status_code);
|
||||
app.toast (_("Error: %s").printf(phrase));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (cb != null)
|
||||
cb (sess, mess);
|
||||
|
||||
|
||||
msg.request_body.free ();
|
||||
msg.response_body.free ();
|
||||
msg.request_headers.free ();
|
||||
@ -77,11 +77,11 @@ public class Tootle.Network : GLib.Object {
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
public void queue_custom (Soup.Message msg, owned Soup.SessionCallback? cb = null) {
|
||||
requests_processing++;
|
||||
started ();
|
||||
|
||||
|
||||
msg.finished.connect_after (() => {
|
||||
msg.request_body.free ();
|
||||
msg.response_body.free ();
|
||||
@ -90,40 +90,40 @@ public class Tootle.Network : GLib.Object {
|
||||
});
|
||||
session.queue_message (msg, cb);
|
||||
}
|
||||
|
||||
|
||||
public Json.Object parse (Soup.Message msg) throws GLib.Error {
|
||||
// debug ("Status Code: %u", msg.status_code);
|
||||
// debug ("Message length: %lld", msg.response_body.length);
|
||||
// debug ("Object: %s", (string) msg.response_body.data);
|
||||
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
return parser.get_root ().get_object ();
|
||||
}
|
||||
|
||||
|
||||
public Json.Array parse_array (Soup.Message msg) throws GLib.Error {
|
||||
// debug ("Status Code: %u", msg.status_code);
|
||||
// debug ("Message length: %lld", msg.response_body.length);
|
||||
// debug ("Array: %s", (string) msg.response_body.data);
|
||||
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||
return parser.get_root ().get_array ();
|
||||
}
|
||||
|
||||
|
||||
public void load_avatar (string url, Granite.Widgets.Avatar avatar, int size){
|
||||
if (settings.cache) {
|
||||
image_cache.load_avatar (url, avatar, size);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.finished.connect(() => {
|
||||
if (msg.status_code != Soup.Status.OK) {
|
||||
avatar.show_default (size);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var data = msg.response_body.data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
||||
@ -131,20 +131,47 @@ public class Tootle.Network : GLib.Object {
|
||||
});
|
||||
network.queue_custom (msg);
|
||||
}
|
||||
|
||||
|
||||
public delegate void PixbufCallback (Gdk.Pixbuf pixbuf);
|
||||
public void load_pixbuf (string url, PixbufCallback cb) {
|
||||
var msg = new Soup.Message("GET", url);
|
||||
|
||||
ulong signal_id = 0;
|
||||
signal_id = msg.finished.connect (() => {
|
||||
Gdk.Pixbuf? pixbuf = null;
|
||||
try {
|
||||
var data = msg.response_body.flatten().data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
||||
}
|
||||
catch (Error e) {
|
||||
warning ("Can't get image: %s".printf (url));
|
||||
warning ("Reason: " + e.message);
|
||||
}
|
||||
finally {
|
||||
if (msg.status_code != Soup.Status.OK)
|
||||
warning ("Response code %s: %s".printf (msg.status_code.to_string (), url));
|
||||
}
|
||||
|
||||
cb (pixbuf);
|
||||
msg.disconnect (signal_id);
|
||||
});
|
||||
network.queue_custom (msg);
|
||||
}
|
||||
|
||||
public void load_image (string url, Gtk.Image image) {
|
||||
if (settings.cache) {
|
||||
image_cache.load_image (url, image);
|
||||
return;
|
||||
}
|
||||
|
||||
// if (settings.cache) {
|
||||
// image_cache.load_image (url, image);
|
||||
// return;
|
||||
// }
|
||||
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.finished.connect(() => {
|
||||
if (msg.status_code != Soup.Status.OK) {
|
||||
image.set_from_icon_name ("image-missing", Gtk.IconSize.LARGE_TOOLBAR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var data = msg.response_body.data;
|
||||
var stream = new MemoryInputStream.from_data (data);
|
||||
var pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
||||
@ -152,13 +179,13 @@ public class Tootle.Network : GLib.Object {
|
||||
});
|
||||
network.queue_custom (msg);
|
||||
}
|
||||
|
||||
|
||||
public void load_scaled_image (string url, Gtk.Image image, int size) {
|
||||
if (settings.cache) {
|
||||
image_cache.load_scaled_image (url, image, size);
|
||||
return;
|
||||
}
|
||||
|
||||
// if (settings.cache) {
|
||||
// image_cache.load_scaled_image (url, image, size);
|
||||
// return;
|
||||
// }
|
||||
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.finished.connect(() => {
|
||||
if (msg.status_code != Soup.Status.OK) {
|
||||
@ -173,5 +200,5 @@ public class Tootle.Network : GLib.Object {
|
||||
});
|
||||
network.queue_custom (msg);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ using Gtk;
|
||||
using Granite;
|
||||
|
||||
public class Tootle.AccountView : TimelineView {
|
||||
|
||||
|
||||
const int AVATAR_SIZE = 128;
|
||||
protected Account account;
|
||||
|
||||
|
||||
protected Grid header_image;
|
||||
protected Box header_info;
|
||||
protected Granite.Widgets.Avatar avatar;
|
||||
@ -16,7 +16,7 @@ public class Tootle.AccountView : TimelineView {
|
||||
protected Grid counters;
|
||||
protected Box actions;
|
||||
protected Button button_follow;
|
||||
|
||||
|
||||
protected Gtk.Menu menu;
|
||||
protected Gtk.MenuItem menu_edit;
|
||||
protected Gtk.MenuItem menu_mention;
|
||||
@ -24,8 +24,8 @@ public class Tootle.AccountView : TimelineView {
|
||||
protected Gtk.MenuItem menu_block;
|
||||
protected Gtk.MenuItem menu_report;
|
||||
protected Gtk.MenuButton button_menu;
|
||||
|
||||
|
||||
|
||||
|
||||
construct {
|
||||
header = new Grid ();
|
||||
header_info = new Box (Orientation.VERTICAL, 0);
|
||||
@ -36,26 +36,26 @@ public class Tootle.AccountView : TimelineView {
|
||||
actions.vexpand = false;
|
||||
actions.valign = Align.START;
|
||||
actions.margin = 12;
|
||||
|
||||
|
||||
relationship = new Label ("");
|
||||
relationship.get_style_context ().add_class ("relationship");
|
||||
relationship.halign = Align.START;
|
||||
relationship.valign = Align.START;
|
||||
relationship.margin = 12;
|
||||
header.attach (relationship, 0, 0, 1, 1);
|
||||
|
||||
|
||||
avatar = new Granite.Widgets.Avatar.with_default_icon (AVATAR_SIZE);
|
||||
avatar.hexpand = true;
|
||||
avatar.margin_bottom = 6;
|
||||
header_info.pack_start (avatar, false, false, 0);
|
||||
|
||||
|
||||
display_name = new RichLabel ("");
|
||||
display_name.get_style_context ().add_class (Granite.STYLE_CLASS_H2_LABEL);
|
||||
header_info.pack_start (display_name, false, false, 0);
|
||||
|
||||
|
||||
username = new Gtk.Label ("");
|
||||
header_info.pack_start (username, false, false, 0);
|
||||
|
||||
|
||||
note = new RichLabel ("");
|
||||
note.set_line_wrap (true);
|
||||
note.selectable = true;
|
||||
@ -65,16 +65,16 @@ public class Tootle.AccountView : TimelineView {
|
||||
header_info.pack_start (note, false, false, 0);
|
||||
header_info.show_all ();
|
||||
header.attach (header_info, 0, 0, 1, 1);
|
||||
|
||||
|
||||
counters = new Grid ();
|
||||
counters.column_homogeneous = true;
|
||||
counters.get_style_context ().add_class ("header-counters");
|
||||
header.attach (counters, 0, 1, 1, 1);
|
||||
|
||||
|
||||
header_image = new Grid ();
|
||||
header_image.get_style_context ().add_class ("header");
|
||||
header.attach (header_image, 0, 0, 2, 2);
|
||||
|
||||
|
||||
menu = new Gtk.Menu ();
|
||||
menu_edit = new Gtk.MenuItem.with_label (_("Edit Profile"));
|
||||
menu_mention = new Gtk.MenuItem.with_label (_("Mention"));
|
||||
@ -88,7 +88,7 @@ public class Tootle.AccountView : TimelineView {
|
||||
//menu.add (menu_report); //TODO: Report users
|
||||
//menu.add (menu_edit); //TODO: Edit profile
|
||||
menu.show_all ();
|
||||
|
||||
|
||||
button_follow = add_counter ("contact-new-symbolic");
|
||||
button_menu = new Gtk.MenuButton ();
|
||||
button_menu.image = new Image.from_icon_name ("view-more-symbolic", IconSize.LARGE_TOOLBAR);
|
||||
@ -103,15 +103,15 @@ public class Tootle.AccountView : TimelineView {
|
||||
button_menu.hide ();
|
||||
button_follow.hide ();
|
||||
header.attach (actions, 0, 0, 2, 2);
|
||||
|
||||
|
||||
view.pack_start (header, false, false, 0);
|
||||
}
|
||||
|
||||
|
||||
public AccountView (Account acc) {
|
||||
base ("");
|
||||
account = acc;
|
||||
account.updated.connect(rebind);
|
||||
|
||||
|
||||
add_counter (_("Toots"), 1, account.statuses_count);
|
||||
add_counter (_("Follows"), 2, account.following_count).clicked.connect (() => {
|
||||
var view = new FollowingView (account);
|
||||
@ -121,34 +121,35 @@ public class Tootle.AccountView : TimelineView {
|
||||
var view = new FollowersView (account);
|
||||
window.open_view (view);
|
||||
});
|
||||
|
||||
|
||||
show_all ();
|
||||
|
||||
var stylesheet = ".header{background-image: url(\"%s\")}".printf (account.header);
|
||||
var css_provider = Granite.Widgets.Utils.get_css_provider (stylesheet);
|
||||
header_image.get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
|
||||
//TODO: Has this thing always been synchronous???
|
||||
//var stylesheet = ".header{background-image: url(\"%s\")}".printf (account.header);
|
||||
//var css_provider = Granite.Widgets.Utils.get_css_provider (stylesheet);
|
||||
//header_image.get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
menu_mention.activate.connect (() => PostDialog.open ("@%s ".printf (account.acct)));
|
||||
menu_mute.activate.connect (() => account.set_muted (!account.rs.muting));
|
||||
menu_block.activate.connect (() => account.set_blocked (!account.rs.blocking));
|
||||
button_follow.clicked.connect (() => account.set_following (!account.rs.following));
|
||||
|
||||
|
||||
rebind ();
|
||||
account.get_relationship ();
|
||||
request ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void rebind (){
|
||||
display_name.set_label ("<b>%s</b>".printf (account.display_name));
|
||||
username.label = "@" + account.acct;
|
||||
note.set_label (Html.simplify (account.note));
|
||||
button_follow.visible = !account.is_self ();
|
||||
network.load_avatar (account.avatar, avatar, 128);
|
||||
|
||||
|
||||
menu_edit.visible = account.is_self ();
|
||||
|
||||
|
||||
if (account.rs != null && !account.is_self ()) {
|
||||
button_follow.show ();
|
||||
if (account.rs.following) {
|
||||
@ -160,13 +161,13 @@ public class Tootle.AccountView : TimelineView {
|
||||
(button_follow.get_image () as Image).icon_name = "contact-new-symbolic";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (account.rs != null){
|
||||
button_menu.show ();
|
||||
menu_block.label = account.rs.blocking ? _("Unblock") : _("Block");
|
||||
menu_mute.label = account.rs.muting ? _("Unmute") : _("Mute");
|
||||
menu_report.visible = menu_mute.visible = menu_block.visible = !account.is_self ();
|
||||
|
||||
|
||||
var rs_label = get_relationship_label ();
|
||||
if (rs_label != null) {
|
||||
relationship.label = rs_label;
|
||||
@ -178,11 +179,11 @@ public class Tootle.AccountView : TimelineView {
|
||||
else
|
||||
relationship.hide ();
|
||||
}
|
||||
|
||||
|
||||
public override bool is_status_owned (Status status) {
|
||||
return status.is_owned ();
|
||||
}
|
||||
|
||||
|
||||
private Gtk.Button add_counter (string name, int? i = null, int64? val = null) {
|
||||
Button btn;
|
||||
if (val != null){
|
||||
@ -195,34 +196,34 @@ public class Tootle.AccountView : TimelineView {
|
||||
}
|
||||
else
|
||||
btn = new Button.from_icon_name (name, IconSize.LARGE_TOOLBAR);
|
||||
|
||||
|
||||
btn.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
(btn as Widget).set_focus_on_click (false);
|
||||
btn.can_default = false;
|
||||
btn.can_focus = false;
|
||||
|
||||
|
||||
if (i != null)
|
||||
counters.attach (btn, i, 1, 1, 1);
|
||||
return btn;
|
||||
}
|
||||
|
||||
|
||||
public override bool is_empty () {
|
||||
return view.get_children ().length () <= 2;
|
||||
}
|
||||
|
||||
|
||||
public override string get_url () {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
|
||||
var url = "%s/api/v1/accounts/%lld/statuses?limit=%i".printf (accounts.formal.instance, account.id, this.limit);
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
public override void request () {
|
||||
if(account != null)
|
||||
base.request ();
|
||||
}
|
||||
|
||||
|
||||
private string? get_relationship_label () {
|
||||
if (account.rs.requested)
|
||||
return _("Sent follow request");
|
||||
@ -235,7 +236,7 @@ public class Tootle.AccountView : TimelineView {
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static void open_from_id (int64 id){
|
||||
var url = "%s/api/v1/accounts/%lld".printf (accounts.formal.instance, id);
|
||||
var msg = new Soup.Message ("GET", url);
|
||||
@ -252,7 +253,7 @@ public class Tootle.AccountView : TimelineView {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static void open_from_name (string name){
|
||||
var url = "%s/api/v1/accounts/search?limit=1&q=%s".printf (accounts.formal.instance, name);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
@ -273,5 +274,5 @@ public class Tootle.AccountView : TimelineView {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -3,15 +3,15 @@ public class Tootle.HomeView : TimelineView {
|
||||
public HomeView () {
|
||||
base ("home");
|
||||
}
|
||||
|
||||
|
||||
public override string get_icon () {
|
||||
return "user-home-symbolic";
|
||||
}
|
||||
|
||||
|
||||
public override string get_name () {
|
||||
return _("Home");
|
||||
}
|
||||
|
||||
|
||||
public override Soup.Message? get_stream () {
|
||||
return accounts.formal.get_stream ();
|
||||
}
|
||||
|
@ -2,56 +2,56 @@ using Gtk;
|
||||
using Gdk;
|
||||
|
||||
public class Tootle.TimelineView : AbstractView {
|
||||
|
||||
|
||||
protected string timeline;
|
||||
protected string pars;
|
||||
protected int limit = 25;
|
||||
protected bool is_last_page = false;
|
||||
protected string? page_next;
|
||||
protected string? page_prev;
|
||||
|
||||
|
||||
protected Notificator? notificator;
|
||||
|
||||
public TimelineView (string timeline, string pars = "") {
|
||||
base ();
|
||||
this.timeline = timeline;
|
||||
this.pars = pars;
|
||||
|
||||
|
||||
accounts.switched.connect (on_account_changed);
|
||||
app.refresh.connect (on_refresh);
|
||||
destroy.connect (() => {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
});
|
||||
|
||||
|
||||
setup_notificator ();
|
||||
request ();
|
||||
}
|
||||
|
||||
|
||||
public override string get_icon () {
|
||||
return "user-home-symbolic";
|
||||
}
|
||||
|
||||
|
||||
public override string get_name () {
|
||||
return _("Home");
|
||||
}
|
||||
|
||||
|
||||
public virtual void on_status_added (Status status) {
|
||||
prepend (status);
|
||||
}
|
||||
|
||||
|
||||
public virtual bool is_status_owned (Status status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void prepend (Status status) {
|
||||
append (status, true);
|
||||
}
|
||||
|
||||
|
||||
public void append (Status status, bool first = false){
|
||||
if (empty != null)
|
||||
empty.destroy ();
|
||||
|
||||
|
||||
var separator = new Separator (Orientation.HORIZONTAL);
|
||||
separator.show ();
|
||||
|
||||
@ -62,26 +62,26 @@ public class Tootle.TimelineView : AbstractView {
|
||||
widget.avatar.button_press_event.connect (widget.open_account);
|
||||
view.pack_start (separator, false, false, 0);
|
||||
view.pack_start (widget, false, false, 0);
|
||||
|
||||
|
||||
if (first || status.pinned) {
|
||||
var new_index = header == null ? 1 : 0;
|
||||
view.reorder_child (separator, new_index);
|
||||
view.reorder_child (widget, new_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void clear () {
|
||||
this.page_prev = null;
|
||||
this.page_next = null;
|
||||
this.is_last_page = false;
|
||||
base.clear ();
|
||||
}
|
||||
|
||||
|
||||
public void get_pages (string? header) {
|
||||
page_next = page_prev = null;
|
||||
if (header == null)
|
||||
return;
|
||||
|
||||
|
||||
var pages = header.split (",");
|
||||
foreach (var page in pages) {
|
||||
var sanitized = page
|
||||
@ -94,25 +94,25 @@ public class Tootle.TimelineView : AbstractView {
|
||||
else
|
||||
page_next = sanitized;
|
||||
}
|
||||
|
||||
|
||||
is_last_page = page_prev != null & page_next == null;
|
||||
}
|
||||
|
||||
|
||||
public virtual string get_url () {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
|
||||
var url = "%s/api/v1/timelines/%s?limit=%i".printf (accounts.formal.instance, this.timeline, this.limit);
|
||||
url += this.pars;
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
public virtual void request (){
|
||||
if (accounts.current == null) {
|
||||
empty_state ();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var msg = new Soup.Message("GET", get_url ());
|
||||
msg.finished.connect (() => empty_state ());
|
||||
network.queue(msg, (sess, mess) => {
|
||||
@ -132,20 +132,20 @@ public class Tootle.TimelineView : AbstractView {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public virtual void on_refresh (){
|
||||
clear ();
|
||||
request ();
|
||||
}
|
||||
|
||||
|
||||
public virtual Soup.Message? get_stream (){
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public virtual void on_account_changed (Account? account){
|
||||
if(account == null)
|
||||
return;
|
||||
|
||||
|
||||
var stream = get_stream ();
|
||||
if (notificator != null && stream != null) {
|
||||
var old_url = notificator.get_url ();
|
||||
@ -155,18 +155,18 @@ public class Tootle.TimelineView : AbstractView {
|
||||
setup_notificator ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
on_refresh ();
|
||||
}
|
||||
|
||||
|
||||
protected void setup_notificator () {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
|
||||
|
||||
var stream = get_stream ();
|
||||
if (stream == null)
|
||||
return;
|
||||
|
||||
|
||||
notificator = new Notificator (stream);
|
||||
notificator.status_added.connect ((status) => {
|
||||
if (can_stream ())
|
||||
@ -174,19 +174,19 @@ public class Tootle.TimelineView : AbstractView {
|
||||
});
|
||||
notificator.start ();
|
||||
}
|
||||
|
||||
|
||||
protected virtual bool is_public () {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected virtual bool can_stream () {
|
||||
var allowed_public = true;
|
||||
if (is_public ())
|
||||
allowed_public = settings.live_updates_public;
|
||||
|
||||
|
||||
return settings.live_updates && allowed_public;
|
||||
}
|
||||
|
||||
|
||||
protected override void on_bottom_reached () {
|
||||
if (is_last_page) {
|
||||
debug ("Last page reached");
|
||||
|
@ -1,72 +0,0 @@
|
||||
using Gtk;
|
||||
using GLib;
|
||||
|
||||
public class Tootle.AttachmentBox : Gtk.ScrolledWindow {
|
||||
|
||||
private Gtk.Box box;
|
||||
private bool edit_mode;
|
||||
|
||||
construct {
|
||||
box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
|
||||
box.hexpand = true;
|
||||
add (box);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public AttachmentBox (bool edit = false) {
|
||||
Object ();
|
||||
edit_mode = edit;
|
||||
vscrollbar_policy = Gtk.PolicyType.NEVER;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
box.forall (widget => widget.destroy ());
|
||||
}
|
||||
|
||||
public void append (Attachment attachment) {
|
||||
show ();
|
||||
var widget = new AttachmentWidget (attachment, edit_mode);
|
||||
box.add (widget);
|
||||
}
|
||||
|
||||
public void select () {
|
||||
var filter = new Gtk.FileFilter ();
|
||||
filter.add_mime_type ("image/jpeg");
|
||||
filter.add_mime_type ("image/png");
|
||||
filter.add_mime_type ("image/gif");
|
||||
filter.add_mime_type ("video/webm");
|
||||
filter.add_mime_type ("video/mp4");
|
||||
|
||||
var chooser = new Gtk.FileChooserDialog (
|
||||
_("Select media files to add"),
|
||||
null,
|
||||
Gtk.FileChooserAction.OPEN,
|
||||
_("_Cancel"),
|
||||
Gtk.ResponseType.CANCEL,
|
||||
_("_Open"),
|
||||
Gtk.ResponseType.ACCEPT);
|
||||
|
||||
chooser.select_multiple = true;
|
||||
chooser.set_filter (filter);
|
||||
|
||||
if (chooser.run () == Gtk.ResponseType.ACCEPT) {
|
||||
show ();
|
||||
foreach (unowned string uri in chooser.get_uris ()) {
|
||||
var widget = new AttachmentWidget.upload (uri);
|
||||
box.pack_start (widget, false, false, 6);
|
||||
}
|
||||
}
|
||||
chooser.close ();
|
||||
}
|
||||
|
||||
public string get_uri_array () {
|
||||
var str = "";
|
||||
box.get_children ().@foreach (widget => {
|
||||
var w = (AttachmentWidget) widget;
|
||||
if (w.attachment != null)
|
||||
str += "&media_ids[]=%lld".printf (w.attachment.id);
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
91
src/Widgets/AttachmentGrid.vala
Normal file
91
src/Widgets/AttachmentGrid.vala
Normal file
@ -0,0 +1,91 @@
|
||||
using Gtk;
|
||||
using GLib;
|
||||
|
||||
public class Tootle.Widgets.AttachmentGrid : Grid {
|
||||
|
||||
private int counter = 0;
|
||||
private bool allow_editing;
|
||||
|
||||
construct {
|
||||
hexpand = true;
|
||||
}
|
||||
|
||||
public AttachmentGrid (bool edit = false) {
|
||||
allow_editing = edit;
|
||||
}
|
||||
|
||||
public void append (Attachment attachment) {
|
||||
var widget = new ImageAttachment (attachment);
|
||||
attach_widget (widget);
|
||||
}
|
||||
public void append_widget (ImageAttachment widget) {
|
||||
attach_widget (widget);
|
||||
}
|
||||
|
||||
private void attach_widget (ImageAttachment widget) {
|
||||
attach (widget, counter++, 1);
|
||||
column_spacing = row_spacing = 12;
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public void pack (Attachment[] attachments) {
|
||||
clear ();
|
||||
var len = attachments.length;
|
||||
|
||||
if (len == 1) {
|
||||
var widget = new ImageAttachment (attachments[0]);
|
||||
attach_widget (widget);
|
||||
widget.fill_parent ();
|
||||
}
|
||||
else {
|
||||
foreach (Attachment attachment in attachments) {
|
||||
append (attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clear () {
|
||||
forall (widget => widget.destroy ());
|
||||
}
|
||||
|
||||
public void select () {
|
||||
var filter = new Gtk.FileFilter ();
|
||||
filter.add_mime_type ("image/jpeg");
|
||||
filter.add_mime_type ("image/png");
|
||||
filter.add_mime_type ("image/gif");
|
||||
filter.add_mime_type ("video/webm");
|
||||
filter.add_mime_type ("video/mp4");
|
||||
|
||||
var chooser = new Gtk.FileChooserDialog (
|
||||
_("Select media files to add"),
|
||||
null,
|
||||
Gtk.FileChooserAction.OPEN,
|
||||
_("_Cancel"),
|
||||
Gtk.ResponseType.CANCEL,
|
||||
_("_Open"),
|
||||
Gtk.ResponseType.ACCEPT);
|
||||
|
||||
chooser.select_multiple = true;
|
||||
chooser.set_filter (filter);
|
||||
|
||||
if (chooser.run () == Gtk.ResponseType.ACCEPT) {
|
||||
show ();
|
||||
foreach (unowned string uri in chooser.get_uris ()) {
|
||||
var widget = new ImageAttachment.upload (uri);
|
||||
append_widget (widget);
|
||||
}
|
||||
}
|
||||
chooser.close ();
|
||||
}
|
||||
|
||||
public string get_uri_array () {
|
||||
var str = "";
|
||||
get_children ().@foreach (w => {
|
||||
var widget = (ImageAttachment) w;
|
||||
if (widget.attachment != null)
|
||||
str += "&media_ids[]=%lld".printf (widget.attachment.id);
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
using Gtk;
|
||||
using Gdk;
|
||||
|
||||
public class Tootle.AttachmentWidget : Gtk.EventBox {
|
||||
|
||||
public Attachment? attachment;
|
||||
private bool editable;
|
||||
private const int PREVIEW_SIZE = 350;
|
||||
private const int SMALL_SIZE = 64;
|
||||
|
||||
public Gtk.Label label;
|
||||
private Gtk.Grid grid;
|
||||
private Gtk.Image? image;
|
||||
|
||||
construct {
|
||||
set_size_request (SMALL_SIZE, SMALL_SIZE);
|
||||
hexpand = false;
|
||||
grid = new Gtk.Grid ();
|
||||
get_style_context ().add_class ("attachment");
|
||||
|
||||
label = new Gtk.Label ("");
|
||||
label.wrap = true;
|
||||
label.vexpand = true;
|
||||
label.margin_start = label.margin_end = 8;
|
||||
grid.attach (label, 0, 0);
|
||||
|
||||
add (grid);
|
||||
grid.show ();
|
||||
label.hide ();
|
||||
|
||||
destroy.connect (() => {
|
||||
if (image != null)
|
||||
image.clear ();
|
||||
});
|
||||
}
|
||||
|
||||
public AttachmentWidget (Attachment att, bool _editable = false) {
|
||||
attachment = att;
|
||||
editable = _editable;
|
||||
rebind ();
|
||||
}
|
||||
|
||||
public int get_size (int size) {
|
||||
return size * get_style_context ().get_scale ();
|
||||
}
|
||||
|
||||
public void rebind () {
|
||||
var type = attachment.type;
|
||||
switch (type){
|
||||
case "image":
|
||||
image = new Gtk.Image ();
|
||||
image.vexpand = true;
|
||||
image.hexpand = true;
|
||||
image.valign = Gtk.Align.CENTER;
|
||||
image.halign = Gtk.Align.CENTER;
|
||||
image.margin = 3;
|
||||
image.set_tooltip_text (attachment.description);
|
||||
image.show ();
|
||||
|
||||
var size = editable ? SMALL_SIZE : PREVIEW_SIZE;
|
||||
network.load_scaled_image (attachment.preview_url, image, get_size (size));
|
||||
|
||||
grid.attach (image, 0, 0);
|
||||
label.hide ();
|
||||
break;
|
||||
default:
|
||||
label.label = _("Click to open %s media").printf (type);
|
||||
label.show ();
|
||||
break;
|
||||
}
|
||||
show ();
|
||||
button_press_event.connect (on_clicked);
|
||||
}
|
||||
|
||||
public AttachmentWidget.upload (string uri) {
|
||||
try {
|
||||
GLib.File file = File.new_for_uri (uri);
|
||||
uint8[] contents;
|
||||
file.load_contents (null, out contents, null);
|
||||
var type = file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0);
|
||||
var mime = type.get_content_type ();
|
||||
|
||||
debug ("Uploading %s (%s)", uri, mime);
|
||||
label.label = _("Uploading...");
|
||||
label.show ();
|
||||
show ();
|
||||
|
||||
var buffer = new Soup.Buffer.take (contents);
|
||||
var multipart = new Soup.Multipart (Soup.FORM_MIME_TYPE_MULTIPART);
|
||||
multipart.append_form_file ("file", mime.replace ("/", "."), mime, buffer);
|
||||
var url = "%s/api/v1/media".printf (accounts.formal.instance);
|
||||
var msg = Soup.Form.request_new_from_multipart (url, multipart);
|
||||
|
||||
network.queue(msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
attachment = Attachment.parse (root);
|
||||
editable = true;
|
||||
|
||||
rebind ();
|
||||
debug ("Uploaded media: %lld", attachment.id);
|
||||
});
|
||||
}
|
||||
catch (Error e) {
|
||||
error (e.message);
|
||||
app.error (_("File read error"), _("Can't read file %s: %s").printf (uri, e.message));
|
||||
}
|
||||
}
|
||||
|
||||
private bool on_clicked (EventButton ev){
|
||||
if (ev.button == 8)
|
||||
return false;
|
||||
|
||||
if (ev.button == 3)
|
||||
return open_menu (ev.button, ev.time);
|
||||
|
||||
Desktop.open_uri (attachment.url);
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool open_menu (uint button, uint32 time) {
|
||||
var menu = new Gtk.Menu ();
|
||||
|
||||
if (editable && attachment != null) {
|
||||
var item_remove = new Gtk.MenuItem.with_label (_("Remove"));
|
||||
item_remove.activate.connect (() => destroy ());
|
||||
menu.add (item_remove);
|
||||
menu.add (new Gtk.SeparatorMenuItem ());
|
||||
}
|
||||
|
||||
var item_open_link = new Gtk.MenuItem.with_label (_("Open in Browser"));
|
||||
item_open_link.activate.connect (() => Desktop.open_uri (attachment.url));
|
||||
var item_copy_link = new Gtk.MenuItem.with_label (_("Copy Link"));
|
||||
item_copy_link.activate.connect (() => Desktop.copy (attachment.url));
|
||||
var item_download = new Gtk.MenuItem.with_label (_("Download"));
|
||||
item_download.activate.connect (() => Desktop.download_file (attachment.url));
|
||||
menu.add (item_open_link);
|
||||
if (attachment.type != "unknown")
|
||||
menu.add (item_download);
|
||||
menu.add (new Gtk.SeparatorMenuItem ());
|
||||
menu.add (item_copy_link);
|
||||
|
||||
menu.show_all ();
|
||||
menu.attach_widget = this;
|
||||
menu.popup_at_pointer ();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
182
src/Widgets/ImageAttachment.vala
Normal file
182
src/Widgets/ImageAttachment.vala
Normal file
@ -0,0 +1,182 @@
|
||||
using Gtk;
|
||||
using Gdk;
|
||||
|
||||
public class Tootle.Widgets.ImageAttachment : Gtk.DrawingArea {
|
||||
|
||||
public Attachment? attachment;
|
||||
private bool editable = false;
|
||||
private bool fill = false;
|
||||
|
||||
private Pixbuf? pixbuf = null;
|
||||
private static Pixbuf? pixbuf_error;
|
||||
private int center_x = 0;
|
||||
private int center_y = 0;
|
||||
|
||||
construct {
|
||||
if (pixbuf_error == null)
|
||||
pixbuf_error = IconTheme.get_default ().load_icon ("image-missing", 32, IconLookupFlags.GENERIC_FALLBACK);
|
||||
|
||||
hexpand = true;
|
||||
vexpand = true;
|
||||
add_events (EventMask.BUTTON_PRESS_MASK);
|
||||
draw.connect (on_draw);
|
||||
button_press_event.connect (on_clicked);
|
||||
}
|
||||
|
||||
public ImageAttachment (Attachment obj) {
|
||||
attachment = obj;
|
||||
network.load_pixbuf (attachment.preview_url, on_ready);
|
||||
set_size_request (32, 128);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public ImageAttachment.upload (string uri) {
|
||||
halign = Align.START;
|
||||
valign = Align.START;
|
||||
set_size_request (100, 100);
|
||||
show_all ();
|
||||
try {
|
||||
GLib.File file = File.new_for_uri (uri);
|
||||
uint8[] contents;
|
||||
file.load_contents (null, out contents, null);
|
||||
var type = file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0);
|
||||
var mime = type.get_content_type ();
|
||||
|
||||
info ("Uploading %s (%s)", uri, mime);
|
||||
show ();
|
||||
|
||||
var buffer = new Soup.Buffer.take (contents);
|
||||
var multipart = new Soup.Multipart (Soup.FORM_MIME_TYPE_MULTIPART);
|
||||
multipart.append_form_file ("file", mime.replace ("/", "."), mime, buffer);
|
||||
var url = "%s/api/v1/media".printf (accounts.formal.instance);
|
||||
var msg = Soup.Form.request_new_from_multipart (url, multipart);
|
||||
|
||||
network.queue (msg, (sess, mess) => {
|
||||
var root = network.parse (mess);
|
||||
attachment = Attachment.parse (root);
|
||||
editable = true;
|
||||
invalidate ();
|
||||
network.load_pixbuf (attachment.preview_url, on_ready);
|
||||
info ("Uploaded media: %lld", attachment.id);
|
||||
});
|
||||
}
|
||||
catch (Error e) {
|
||||
error (e.message);
|
||||
app.error (_("File read error"), _("Can't read file %s: %s").printf (uri, e.message));
|
||||
}
|
||||
}
|
||||
|
||||
private void on_ready (Pixbuf? result) {
|
||||
if (result == null)
|
||||
result = pixbuf_error;
|
||||
|
||||
pixbuf = result;
|
||||
invalidate ();
|
||||
}
|
||||
|
||||
private void invalidate () {
|
||||
var w = get_allocated_width ();
|
||||
var h = get_allocated_height ();
|
||||
if (fill) {
|
||||
var h_scaled = (pixbuf.height * w) / pixbuf.width;
|
||||
if (h_scaled > pixbuf.height) {
|
||||
halign = Align.START;
|
||||
set_size_request (pixbuf.width, pixbuf.height);
|
||||
}
|
||||
else {
|
||||
halign = Align.FILL;
|
||||
set_size_request (1, h_scaled);
|
||||
}
|
||||
}
|
||||
queue_draw_area (0, 0, w, h);
|
||||
}
|
||||
|
||||
private void calc_center (int w, int h, int size_w, int size_h, Cairo.Context? ctx = null) {
|
||||
center_x = w/2 - size_w/2;
|
||||
center_y = h/2 - size_h/2;
|
||||
|
||||
if (ctx != null)
|
||||
ctx.translate (center_x, center_y);
|
||||
}
|
||||
|
||||
public void fill_parent () {
|
||||
fill = true;
|
||||
size_allocate.connect (on_size_changed);
|
||||
on_size_changed ();
|
||||
}
|
||||
|
||||
public void on_size_changed () {
|
||||
if (fill && pixbuf != null)
|
||||
invalidate ();
|
||||
}
|
||||
|
||||
private bool on_draw (Widget widget, Cairo.Context ctx) {
|
||||
var w = widget.get_allocated_width ();
|
||||
var h = widget.get_allocated_height ();
|
||||
if (halign == Align.START) {
|
||||
w = pixbuf.width;
|
||||
h = pixbuf.height;
|
||||
}
|
||||
|
||||
//Draw frame
|
||||
ctx.set_source_rgba (1, 1, 1, 1);
|
||||
Drawing.draw_rounded_rect (ctx, 0, 0, w, h, 4);
|
||||
ctx.fill ();
|
||||
|
||||
//Draw image, spinner or an error icon
|
||||
if (pixbuf != null) {
|
||||
var thumbnail = Drawing.make_pixbuf_thumbnail (pixbuf, w, h, fill);
|
||||
Drawing.draw_rounded_rect (ctx, 0, 0, w, h, 4);
|
||||
calc_center (w, h, thumbnail.width, thumbnail.height, ctx);
|
||||
Gdk.cairo_set_source_pixbuf (ctx, thumbnail, 0, 0);
|
||||
ctx.fill ();
|
||||
}
|
||||
else {
|
||||
calc_center (w, h, 32, 32, ctx);
|
||||
set_state_flags (StateFlags.CHECKED, false); //Y U NO SPIN
|
||||
get_style_context ().render_activity (ctx, 0, 0, 32, 32);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool on_clicked (EventButton ev){
|
||||
switch (ev.button) {
|
||||
case 3:
|
||||
return open_menu (ev.button, ev.time);
|
||||
case 1:
|
||||
Desktop.open_uri (attachment.url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool open_menu (uint button, uint32 time) {
|
||||
var menu = new Gtk.Menu ();
|
||||
|
||||
if (editable && attachment != null) {
|
||||
var item_remove = new Gtk.MenuItem.with_label (_("Remove"));
|
||||
item_remove.activate.connect (() => destroy ());
|
||||
menu.add (item_remove);
|
||||
menu.add (new Gtk.SeparatorMenuItem ());
|
||||
}
|
||||
|
||||
var item_open_link = new Gtk.MenuItem.with_label (_("Open in Browser"));
|
||||
item_open_link.activate.connect (() => Desktop.open_uri (attachment.url));
|
||||
var item_copy_link = new Gtk.MenuItem.with_label (_("Copy Link"));
|
||||
item_copy_link.activate.connect (() => Desktop.copy (attachment.url));
|
||||
var item_download = new Gtk.MenuItem.with_label (_("Download"));
|
||||
item_download.activate.connect (() => Desktop.download_file (attachment.url));
|
||||
menu.add (item_open_link);
|
||||
if (attachment.type != "unknown")
|
||||
menu.add (item_download);
|
||||
menu.add (new Gtk.SeparatorMenuItem ());
|
||||
menu.add (item_copy_link);
|
||||
|
||||
menu.show_all ();
|
||||
menu.attach_widget = this;
|
||||
menu.popup_at_pointer ();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,7 @@ using Gtk;
|
||||
using Granite;
|
||||
|
||||
public class Tootle.NotificationWidget : Grid {
|
||||
|
||||
|
||||
private Notification notification;
|
||||
|
||||
public Separator? separator;
|
||||
@ -13,7 +13,7 @@ public class Tootle.NotificationWidget : Grid {
|
||||
|
||||
construct {
|
||||
margin = 6;
|
||||
|
||||
|
||||
image = new Image.from_icon_name ("notification-symbolic", IconSize.BUTTON);
|
||||
image.margin_start = 32;
|
||||
image.margin_end = 6;
|
||||
@ -27,7 +27,7 @@ public class Tootle.NotificationWidget : Grid {
|
||||
notification.dismiss ();
|
||||
destroy ();
|
||||
});
|
||||
|
||||
|
||||
attach (image, 1, 2);
|
||||
attach (label, 2, 2);
|
||||
attach (dismiss, 3, 2);
|
||||
@ -39,25 +39,25 @@ public class Tootle.NotificationWidget : Grid {
|
||||
image.icon_name = notification.type.get_icon ();
|
||||
label.set_label (notification.type.get_desc (notification.account));
|
||||
get_style_context ().add_class ("notification");
|
||||
|
||||
|
||||
if (notification.status != null)
|
||||
network.status_removed.connect (on_status_removed);
|
||||
|
||||
|
||||
destroy.connect (() => {
|
||||
if (separator != null)
|
||||
separator.destroy ();
|
||||
separator = null;
|
||||
status_widget = null;
|
||||
});
|
||||
|
||||
|
||||
if (notification.status != null){
|
||||
status_widget = new StatusWidget (notification.status);
|
||||
status_widget = new StatusWidget (notification.status, true);
|
||||
status_widget.is_notification = true;
|
||||
status_widget.button_press_event.connect (status_widget.open);
|
||||
status_widget.avatar.button_press_event.connect (status_widget.open_account);
|
||||
attach (status_widget, 1, 3, 3, 1);
|
||||
}
|
||||
|
||||
|
||||
if (notification.type == NotificationType.FOLLOW_REQUEST) {
|
||||
var box = new Box (Orientation.HORIZONTAL, 6);
|
||||
box.margin_start = 32 + 16 + 8;
|
||||
@ -65,10 +65,10 @@ public class Tootle.NotificationWidget : Grid {
|
||||
box.pack_start (accept, false, false, 0);
|
||||
var reject = new Button.with_label (_("Reject"));
|
||||
box.pack_start (reject, false, false, 0);
|
||||
|
||||
|
||||
attach (box, 1, 3, 3, 1);
|
||||
box.show_all ();
|
||||
|
||||
|
||||
accept.clicked.connect (() => {
|
||||
destroy ();
|
||||
notification.accept_follow_request ();
|
||||
@ -79,12 +79,12 @@ public class Tootle.NotificationWidget : Grid {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void on_status_removed (int64 id) {
|
||||
if (id == notification.status.id) {
|
||||
if (notification.type == NotificationType.WATCHLIST)
|
||||
notification.dismiss ();
|
||||
|
||||
|
||||
destroy ();
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ using Gdk;
|
||||
using Granite;
|
||||
|
||||
public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
|
||||
|
||||
public Status status;
|
||||
public bool is_notification = false;
|
||||
public const int AVATAR_SIZE = 32;
|
||||
|
||||
|
||||
public Separator? separator;
|
||||
public Granite.Widgets.Avatar avatar;
|
||||
protected Grid grid;
|
||||
@ -19,9 +19,9 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
protected RichLabel? content_spoiler;
|
||||
protected Button? spoiler_button;
|
||||
protected Box title_box;
|
||||
protected AttachmentBox attachments;
|
||||
protected Widgets.AttachmentGrid attachments;
|
||||
protected Image pin_indicator;
|
||||
|
||||
|
||||
protected Box counters;
|
||||
protected Label replies;
|
||||
protected Label reblogs;
|
||||
@ -32,40 +32,40 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
|
||||
construct {
|
||||
grid = new Grid ();
|
||||
|
||||
|
||||
avatar = new Granite.Widgets.Avatar.with_default_icon (AVATAR_SIZE);
|
||||
avatar.valign = Align.START;
|
||||
avatar.margin_top = 6;
|
||||
avatar.margin_start = 6;
|
||||
avatar.margin_end = 6;
|
||||
|
||||
|
||||
title_box = new Box (Gtk.Orientation.HORIZONTAL, 6);
|
||||
title_box.hexpand = true;
|
||||
title_box.margin_end = 12;
|
||||
title_box.margin_top = 6;
|
||||
|
||||
|
||||
title_user = new RichLabel ("");
|
||||
title_box.pack_start (title_user, false, false, 0);
|
||||
|
||||
|
||||
title_acct = new Gtk.Label ("");
|
||||
title_acct.opacity = 0.5;
|
||||
title_acct.ellipsize = Pango.EllipsizeMode.END;
|
||||
title_box.pack_start (title_acct, false, false, 0);
|
||||
|
||||
|
||||
title_date = new Gtk.Label ("");
|
||||
title_date.opacity = 0.5;
|
||||
title_date.ellipsize = Pango.EllipsizeMode.END;
|
||||
title_box.pack_end (title_date, false, false, 0);
|
||||
title_box.show_all ();
|
||||
|
||||
|
||||
pin_indicator = new Image.from_icon_name ("view-pin-symbolic", IconSize.MENU);
|
||||
pin_indicator.opacity = 0.5;
|
||||
title_box.pack_end (pin_indicator, false, false, 0);
|
||||
|
||||
|
||||
content_label = new RichLabel ("");
|
||||
content_label.wrap_words ();
|
||||
|
||||
attachments = new AttachmentBox ();
|
||||
|
||||
attachments = new Widgets.AttachmentGrid ();
|
||||
|
||||
var revealer_box = new Box (Orientation.VERTICAL, 6);
|
||||
revealer_box.margin_end = 12;
|
||||
@ -74,11 +74,11 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
revealer = new Revealer ();
|
||||
revealer.reveal_child = true;
|
||||
revealer.add (revealer_box);
|
||||
|
||||
|
||||
reblogs = new Label ("0");
|
||||
favorites = new Label ("0");
|
||||
replies = new Label ("0");
|
||||
|
||||
|
||||
reblog = new ImageToggleButton ("media-playlist-repeat-symbolic");
|
||||
reblog.set_action ();
|
||||
reblog.tooltip_text = _("Boost");
|
||||
@ -100,7 +100,7 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
reply.set_active (false);
|
||||
PostDialog.reply (status.get_formal ());
|
||||
});
|
||||
|
||||
|
||||
counters = new Box (Orientation.HORIZONTAL, 6);
|
||||
counters.margin_top = 6;
|
||||
counters.margin_bottom = 6;
|
||||
@ -111,43 +111,44 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
counters.add (reply);
|
||||
counters.add (replies);
|
||||
counters.show_all ();
|
||||
|
||||
|
||||
add (grid);
|
||||
grid.attach (avatar, 1, 1, 1, 4);
|
||||
grid.attach (title_box, 2, 2, 1, 1);
|
||||
grid.attach (revealer, 2, 4, 1, 1);
|
||||
grid.attach (counters, 2, 5, 1, 1);
|
||||
show_all ();
|
||||
|
||||
|
||||
button_press_event.connect (on_clicked);
|
||||
}
|
||||
|
||||
public StatusWidget (Status status) {
|
||||
public StatusWidget (Status status, bool notification = false) {
|
||||
this.status = status;
|
||||
this.status.updated.connect (rebind);
|
||||
|
||||
if (this.status.reblog != null) {
|
||||
is_notification = notification;
|
||||
|
||||
if (status.reblog != null) {
|
||||
var image = new Image.from_icon_name("media-playlist-repeat-symbolic", IconSize.BUTTON);
|
||||
image.halign = Align.END;
|
||||
image.margin_end = 6;
|
||||
image.margin_top = 6;
|
||||
image.show ();
|
||||
|
||||
|
||||
var label_text = _("<a href=\"%s\"><b>%s</b></a> boosted").printf (this.status.account.url, this.status.account.display_name);
|
||||
var label = new RichLabel (label_text);
|
||||
label.halign = Align.START;
|
||||
label.margin_top = 6;
|
||||
label.show ();
|
||||
|
||||
|
||||
grid.attach (image, 1, 0, 1, 1);
|
||||
grid.attach (label, 2, 0, 2, 1);
|
||||
}
|
||||
|
||||
|
||||
if (is_spoiler ()) {
|
||||
revealer.reveal_child = false;
|
||||
var spoiler_box = new Box (Orientation.HORIZONTAL, 6);
|
||||
spoiler_box.margin_end = 12;
|
||||
|
||||
|
||||
var spoiler_button_text = _("Toggle content");
|
||||
if (status.sensitive && status.attachments != null) {
|
||||
spoiler_button = new Button.from_icon_name ("mail-attachment-symbolic", Gtk.IconSize.BUTTON);
|
||||
@ -161,129 +162,126 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
spoiler_button.hexpand = true;
|
||||
spoiler_button.halign = Align.END;
|
||||
spoiler_button.clicked.connect (() => revealer.set_reveal_child (!revealer.child_revealed));
|
||||
|
||||
|
||||
var spoiler_text = _("[ This post contains sensitive content ]");
|
||||
if (status.spoiler_text != null)
|
||||
spoiler_text = status.spoiler_text;
|
||||
content_spoiler = new RichLabel (spoiler_text);
|
||||
content_spoiler.wrap_words ();
|
||||
|
||||
|
||||
spoiler_box.add (content_spoiler);
|
||||
spoiler_box.add (spoiler_button);
|
||||
spoiler_box.show_all ();
|
||||
grid.attach (spoiler_box, 2, 3, 1, 1);
|
||||
}
|
||||
|
||||
if (status.get_formal ().attachments != null) {
|
||||
attachments.clear ();
|
||||
foreach (Attachment attachment in status.get_formal ().attachments)
|
||||
attachments.append (attachment);
|
||||
}
|
||||
|
||||
if (!is_notification && status.get_formal ().attachments != null)
|
||||
attachments.pack (status.get_formal ().attachments);
|
||||
else
|
||||
attachments.destroy ();
|
||||
|
||||
|
||||
destroy.connect (() => {
|
||||
avatar.show_default (AVATAR_SIZE);
|
||||
if (separator != null)
|
||||
separator.destroy ();
|
||||
});
|
||||
|
||||
|
||||
network.status_removed.connect (id => {
|
||||
if (id == status.id)
|
||||
destroy ();
|
||||
});
|
||||
|
||||
|
||||
rebind ();
|
||||
}
|
||||
|
||||
|
||||
public void highlight () {
|
||||
grid.get_style_context ().add_class ("card");
|
||||
grid.margin_bottom = 6;
|
||||
}
|
||||
|
||||
|
||||
public int get_avatar_size () {
|
||||
return AVATAR_SIZE * get_style_context ().get_scale ();
|
||||
}
|
||||
|
||||
|
||||
public void rebind () {
|
||||
var formal = status.get_formal ();
|
||||
|
||||
|
||||
title_user.set_label ("<b>%s</b>".printf ((formal.account.display_name)));
|
||||
title_acct.label = "@" + formal.account.acct;
|
||||
content_label.label = formal.content;
|
||||
content_label.mentions = formal.mentions;
|
||||
pin_indicator.visible = status.pinned;
|
||||
|
||||
|
||||
var datetime = parse_date_iso8601 (formal.created_at);
|
||||
title_date.label = Granite.DateTime.get_relative_datetime (datetime);
|
||||
|
||||
|
||||
reblogs.label = formal.reblogs_count.to_string ();
|
||||
favorites.label = formal.favourites_count.to_string ();
|
||||
replies.label = formal.replies_count.to_string ();
|
||||
|
||||
|
||||
reblog.sensitive = false;
|
||||
reblog.active = formal.reblogged;
|
||||
reblog.sensitive = true;
|
||||
favorite.sensitive = false;
|
||||
favorite.active = formal.favorited;
|
||||
favorite.sensitive = true;
|
||||
|
||||
|
||||
if (formal.visibility == StatusVisibility.DIRECT) {
|
||||
reblog.sensitive = false;
|
||||
reblog.icon.icon_name = formal.visibility.get_icon ();
|
||||
reblog.tooltip_text = _("This post can't be boosted");
|
||||
}
|
||||
|
||||
|
||||
network.load_avatar (formal.account.avatar, avatar, get_avatar_size ());
|
||||
}
|
||||
|
||||
public bool is_spoiler () {
|
||||
return this.status.get_formal ().spoiler_text != null || this.status.get_formal ().sensitive;
|
||||
}
|
||||
|
||||
|
||||
private GLib.DateTime? parse_date_iso8601 (string date) {
|
||||
var timeval = GLib.TimeVal ();
|
||||
if (timeval.from_iso8601 (date))
|
||||
return new GLib.DateTime.from_timeval_local (timeval);
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public bool open_account (EventButton ev) {
|
||||
if (ev.button == 8)
|
||||
return false;
|
||||
|
||||
|
||||
var view = new AccountView (status.get_formal ().account);
|
||||
window.open_view (view);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool open (EventButton ev) {
|
||||
if (ev.button == 8)
|
||||
return false;
|
||||
|
||||
|
||||
var formal = status.get_formal ();
|
||||
var view = new StatusView (formal);
|
||||
window.open_view (view);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool on_clicked (EventButton ev) {
|
||||
if (ev.button == 8)
|
||||
return false;
|
||||
|
||||
|
||||
if (ev.button == 3)
|
||||
return open_menu (ev.button, ev.time);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public virtual bool open_menu (uint button, uint32 time) {
|
||||
var menu = new Gtk.Menu ();
|
||||
|
||||
|
||||
var is_muted = status.muted;
|
||||
var is_pinned = status.pinned;
|
||||
|
||||
|
||||
var item_muting = new Gtk.MenuItem.with_label (is_muted ? _("Unmute Conversation") : _("Mute Conversation"));
|
||||
item_muting.activate.connect (() => status.set_muted (!is_muted));
|
||||
var item_open_link = new Gtk.MenuItem.with_label (_("Open in Browser"));
|
||||
@ -295,31 +293,31 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
||||
var sanitized = Html.remove_tags (status.get_formal ().content);
|
||||
Desktop.copy (sanitized);
|
||||
});
|
||||
|
||||
|
||||
if (this.status.is_owned ()) {
|
||||
var item_pin = new Gtk.MenuItem.with_label (is_pinned ? _("Unpin from Profile") : _("Pin on Profile"));
|
||||
item_pin.activate.connect (() => status.set_pinned (!is_pinned));
|
||||
menu.add (item_pin);
|
||||
|
||||
|
||||
var item_delete = new Gtk.MenuItem.with_label (_("Delete"));
|
||||
item_delete.activate.connect (() => status.poof ());
|
||||
menu.add (item_delete);
|
||||
|
||||
|
||||
var item_redraft = new Gtk.MenuItem.with_label (_("Redraft"));
|
||||
item_redraft.activate.connect (() => PostDialog.redraft (status.get_formal ()));
|
||||
menu.add (item_redraft);
|
||||
|
||||
|
||||
menu.add (new Gtk.SeparatorMenuItem ());
|
||||
}
|
||||
|
||||
|
||||
if (this.is_notification)
|
||||
menu.add (item_muting);
|
||||
|
||||
|
||||
menu.add (item_open_link);
|
||||
menu.add (new Gtk.SeparatorMenuItem ());
|
||||
menu.add (item_copy_link);
|
||||
menu.add (item_copy);
|
||||
|
||||
|
||||
menu.show_all ();
|
||||
menu.attach_widget = this;
|
||||
menu.popup_at_pointer ();
|
||||
|
Loading…
x
Reference in New Issue
Block a user