1 /*
2  * Photos - access, organize and share your photos on GNOME
3  * Copyright © 2014 Pranav Kant
4  * Copyright © 2014 – 2019 Red Hat, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 
21 #include "config.h"
22 
23 #include <glib/gi18n.h>
24 
25 #include "photos-base-item.h"
26 #include "photos-delete-notification.h"
27 #include "photos-item-manager.h"
28 #include "photos-notification-manager.h"
29 #include "photos-search-context.h"
30 
31 
32 struct _PhotosDeleteNotification
33 {
34   GtkGrid parent_instance;
35   GList *items;
36   GtkWidget *ntfctn_mngr;
37   PhotosBaseManager *item_mngr;
38   guint timeout_id;
39 };
40 
41 enum
42 {
43   PROP_0,
44   PROP_ITEMS
45 };
46 
47 
48 G_DEFINE_TYPE (PhotosDeleteNotification, photos_delete_notification, GTK_TYPE_GRID);
49 
50 
51 enum
52 {
53   DELETE_TIMEOUT = 10 /* s */
54 };
55 
56 
57 static void
photos_delete_notification_remove_timeout(PhotosDeleteNotification * self)58 photos_delete_notification_remove_timeout (PhotosDeleteNotification *self)
59 {
60   if (self->timeout_id != 0)
61     {
62       g_source_remove (self->timeout_id);
63       self->timeout_id = 0;
64     }
65 }
66 
67 
68 static void
photos_delete_notification_destroy(PhotosDeleteNotification * self)69 photos_delete_notification_destroy (PhotosDeleteNotification *self)
70 {
71   photos_delete_notification_remove_timeout (self);
72   gtk_widget_destroy (GTK_WIDGET (self));
73 }
74 
75 
76 static void
photos_delete_notification_item_trash(GObject * source_object,GAsyncResult * res,gpointer user_data)77 photos_delete_notification_item_trash (GObject *source_object, GAsyncResult *res, gpointer user_data)
78 {
79   PhotosBaseItem *item = PHOTOS_BASE_ITEM (source_object);
80 
81   {
82     g_autoptr (GError) error = NULL;
83 
84     if (!photos_base_item_trash_finish (item, res, &error))
85       {
86         const gchar *uri;
87 
88         uri = photos_base_item_get_uri (item);
89         g_warning ("Unable to delete item at %s: %s", uri, error->message);
90       }
91   }
92 }
93 
94 
95 static void
photos_delete_notification_delete_items(PhotosDeleteNotification * self)96 photos_delete_notification_delete_items (PhotosDeleteNotification *self)
97 {
98   GList *l;
99 
100   for (l = self->items; l != NULL; l = l->next)
101     {
102       PhotosBaseItem *item = PHOTOS_BASE_ITEM (l->data);
103       photos_base_item_trash_async (item, NULL, photos_delete_notification_item_trash, NULL);
104     }
105 }
106 
107 
108 static gboolean
photos_delete_notification_timeout(gpointer user_data)109 photos_delete_notification_timeout (gpointer user_data)
110 {
111   PhotosDeleteNotification *self = PHOTOS_DELETE_NOTIFICATION (user_data);
112 
113   self->timeout_id = 0;
114   gtk_widget_destroy (GTK_WIDGET (self));
115   return G_SOURCE_REMOVE;
116 }
117 
118 
119 static void
photos_delete_notification_undo_clicked(PhotosDeleteNotification * self)120 photos_delete_notification_undo_clicked (PhotosDeleteNotification *self)
121 {
122   GList *l;
123 
124   for (l = self->items; l != NULL; l = l->next)
125     {
126       PhotosBaseItem *item = PHOTOS_BASE_ITEM (l->data);
127       photos_item_manager_unhide_item (PHOTOS_ITEM_MANAGER (self->item_mngr), item);
128     }
129 
130   g_list_free_full (self->items, g_object_unref);
131   self->items = NULL;
132 
133   photos_delete_notification_destroy (self);
134 }
135 
136 
137 static void
photos_delete_notification_constructed(GObject * object)138 photos_delete_notification_constructed (GObject *object)
139 {
140   PhotosDeleteNotification *self = PHOTOS_DELETE_NOTIFICATION (object);
141   GtkWidget *close;
142   GtkWidget *image;
143   GtkWidget *label;
144   GtkWidget *undo;
145   g_autofree gchar *msg = NULL;
146   guint length;
147 
148   G_OBJECT_CLASS (photos_delete_notification_parent_class)->constructed (object);
149 
150   length = g_list_length (self->items);
151   if (length == 1)
152     {
153       const gchar *name;
154 
155       name = photos_base_item_get_name_with_fallback (PHOTOS_BASE_ITEM (self->items->data));
156       msg = g_strdup_printf (_("“%s” deleted"), name);
157     }
158   else
159     msg = g_strdup_printf (ngettext ("%d item deleted", "%d items deleted", length), length);
160 
161   label = gtk_label_new (msg);
162   gtk_widget_set_halign (label, GTK_ALIGN_START);
163   gtk_widget_set_hexpand (label, TRUE);
164   gtk_container_add (GTK_CONTAINER (self), label);
165 
166   undo = gtk_button_new_with_label (_("Undo"));
167   gtk_widget_set_valign (undo, GTK_ALIGN_CENTER);
168   gtk_container_add (GTK_CONTAINER (self), undo);
169   g_signal_connect_swapped (undo, "clicked", G_CALLBACK (photos_delete_notification_undo_clicked), self);
170 
171   image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_INVALID);
172   gtk_widget_set_margin_bottom (image, 2);
173   gtk_widget_set_margin_top (image, 2);
174   gtk_image_set_pixel_size (GTK_IMAGE (image), 16);
175 
176   close = gtk_button_new ();
177   gtk_widget_set_valign (close, GTK_ALIGN_CENTER);
178   gtk_widget_set_focus_on_click (close, FALSE);
179   gtk_button_set_relief (GTK_BUTTON (close), GTK_RELIEF_NONE);
180   gtk_button_set_image (GTK_BUTTON (close), image);
181   gtk_container_add (GTK_CONTAINER (self), close);
182   g_signal_connect_swapped (close, "clicked", G_CALLBACK (gtk_widget_destroy), self);
183 
184   photos_notification_manager_add_notification (PHOTOS_NOTIFICATION_MANAGER (self->ntfctn_mngr),
185                                                 GTK_WIDGET (self));
186 
187   self->timeout_id = g_timeout_add_seconds (DELETE_TIMEOUT, photos_delete_notification_timeout, self);
188 }
189 
190 
191 static void
photos_delete_notification_dispose(GObject * object)192 photos_delete_notification_dispose (GObject *object)
193 {
194   PhotosDeleteNotification *self = PHOTOS_DELETE_NOTIFICATION (object);
195 
196   photos_delete_notification_delete_items (self);
197   photos_delete_notification_remove_timeout (self);
198 
199   g_list_free_full (self->items, g_object_unref);
200   self->items = NULL;
201 
202   g_clear_object (&self->ntfctn_mngr);
203   g_clear_object (&self->item_mngr);
204 
205   G_OBJECT_CLASS (photos_delete_notification_parent_class)->dispose (object);
206 }
207 
208 
209 static void
photos_delete_notification_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)210 photos_delete_notification_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
211 {
212   PhotosDeleteNotification *self = PHOTOS_DELETE_NOTIFICATION (object);
213 
214   switch (prop_id)
215     {
216     case PROP_ITEMS:
217       {
218         GList *items;
219 
220         items = (GList *) g_value_get_pointer (value);
221         self->items = g_list_copy_deep (items, (GCopyFunc) g_object_ref, NULL);
222         break;
223       }
224 
225     default:
226       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
227       break;
228     }
229 }
230 
231 
232 static void
photos_delete_notification_init(PhotosDeleteNotification * self)233 photos_delete_notification_init (PhotosDeleteNotification *self)
234 {
235   GApplication *app;
236   PhotosSearchContextState *state;
237 
238   app = g_application_get_default ();
239   state = photos_search_context_get_state (PHOTOS_SEARCH_CONTEXT (app));
240 
241   self->ntfctn_mngr = photos_notification_manager_dup_singleton ();
242   self->item_mngr = g_object_ref (state->item_mngr);
243 }
244 
245 
246 static void
photos_delete_notification_class_init(PhotosDeleteNotificationClass * class)247 photos_delete_notification_class_init (PhotosDeleteNotificationClass *class)
248 {
249   GObjectClass *object_class = G_OBJECT_CLASS (class);
250 
251   object_class->constructed = photos_delete_notification_constructed;
252   object_class->dispose = photos_delete_notification_dispose;
253   object_class->set_property = photos_delete_notification_set_property;
254 
255   g_object_class_install_property (object_class,
256                                    PROP_ITEMS,
257                                    g_param_spec_pointer ("items",
258                                                          "List of PhotosBaseItems",
259                                                          "List of items that are being deleted",
260                                                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
261 }
262 
263 
264 void
photos_delete_notification_new(GList * items)265 photos_delete_notification_new (GList *items)
266 {
267   g_return_if_fail (items != NULL);
268 
269   g_object_new (PHOTOS_TYPE_DELETE_NOTIFICATION,
270                 "column-spacing", 12,
271                 "items", items,
272                 "orientation", GTK_ORIENTATION_HORIZONTAL,
273                 NULL);
274 }
275