2
* Copyright 2014 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 3, as published
6
* by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranties of
10
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
* PURPOSE. See the GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License along
14
* with this program. If not, see <http://www.gnu.org/licenses/>.
17
* Charles Kerr <charles.kerr@canonical.com>
20
#include <transfer/dbus-shared.h>
21
#include <transfer/controller.h>
22
#include <transfer/view-gmenu.h>
24
#include <core/connection.h>
26
#include <glib/gi18n.h>
29
#include <algorithm> // std::sort()
42
* \brief GActionGroup wrapper that routes action callbacks to the Controller
48
GActions(const std::shared_ptr<Model>& model,
49
const std::shared_ptr<Controller>& controller):
50
m_action_group(g_simple_action_group_new()),
51
m_controller(controller)
55
const GActionEntry entries[] = {
56
{ "activate-transfer", on_tap, "s", nullptr },
57
{ "cancel-transfer", on_cancel, "s", nullptr },
58
{ "pause-transfer", on_pause, "s", nullptr },
59
{ "resume-transfer", on_resume, "s", nullptr },
60
{ "open-transfer", on_open, "s", nullptr },
61
{ "open-app-transfer", on_open_app, "s", nullptr },
62
{ "resume-all", on_resume_all },
63
{ "pause-all", on_pause_all },
64
{ "clear-all", on_clear_all }
67
auto gam = G_ACTION_MAP(m_action_group);
68
g_action_map_add_action_entries(gam,
70
G_N_ELEMENTS(entries),
73
// add the header actions
74
auto v = create_default_header_state();
75
auto a = g_simple_action_new_stateful("phone-header", nullptr, v);
76
g_action_map_add_action(gam, G_ACTION(a));
78
// add the transfer-states dictionary
79
v = create_transfer_states();
80
a = g_simple_action_new_stateful("transfer-states", nullptr, v);
81
g_action_map_add_action(gam, G_ACTION(a));
84
void set_model(const std::shared_ptr<Model>& model)
86
auto& c = m_connections;
89
if ((m_model = model))
91
auto updater = [this](const Transfer::Id&) {update_soon();};
92
c.insert(m_model->added().connect(updater));
93
c.insert(m_model->changed().connect(updater));
94
c.insert(m_model->removed().connect(updater));
102
g_source_remove (m_update_tag);
103
g_clear_object(&m_action_group);
106
GActionGroup* action_group() const
108
return G_ACTION_GROUP(m_action_group);
119
if (m_update_tag == 0)
120
m_update_tag = g_timeout_add_seconds(1, update_timeout, this);
123
static gboolean update_timeout(gpointer gself)
125
auto self = static_cast<GActions*>(gself);
126
self->m_update_tag = 0;
128
return G_SOURCE_REMOVE;
133
g_action_group_change_action_state(action_group(),
135
create_transfer_states());
138
GVariant* create_transfer_states()
141
g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
143
for (const auto& transfer : m_model->get_all())
145
auto state = create_transfer_state(transfer);
146
g_variant_builder_add(&b, "{sv}", transfer->id.c_str(), state);
149
return g_variant_builder_end(&b);
152
GVariant* create_transfer_state(const std::shared_ptr<Transfer>& transfer)
155
g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
157
g_variant_builder_add(&b, "{sv}", "percent",
158
g_variant_new_double(transfer->progress));
159
if (transfer->seconds_left >= 0)
161
g_variant_builder_add(&b, "{sv}", "seconds-left",
162
g_variant_new_int32(transfer->seconds_left));
165
g_variant_builder_add(&b, "{sv}", "state", g_variant_new_int32(transfer->state));
167
return g_variant_builder_end(&b);
171
**** ACTION CALLBACKS
174
std::shared_ptr<Controller>& controller()
179
static void on_tap(GSimpleAction*, GVariant* vuid, gpointer gself)
181
const auto uid = g_variant_get_string(vuid, nullptr);
182
static_cast<GActions*>(gself)->controller()->tap(uid);
185
static void on_cancel(GSimpleAction*, GVariant* vuid, gpointer gself)
187
const auto uid = g_variant_get_string(vuid, nullptr);
188
static_cast<GActions*>(gself)->controller()->cancel(uid);
191
static void on_pause(GSimpleAction*, GVariant* vuid, gpointer gself)
193
const auto uid = g_variant_get_string(vuid, nullptr);
194
static_cast<GActions*>(gself)->controller()->pause(uid);
197
static void on_resume(GSimpleAction*, GVariant* vuid, gpointer gself)
199
const auto uid = g_variant_get_string(vuid, nullptr);
200
static_cast<GActions*>(gself)->controller()->resume(uid);
203
static void on_open(GSimpleAction*, GVariant* vuid, gpointer gself)
205
const auto uid = g_variant_get_string(vuid, nullptr);
206
static_cast<GActions*>(gself)->controller()->open(uid);
209
static void on_open_app(GSimpleAction*, GVariant* vuid, gpointer gself)
211
const auto uid = g_variant_get_string(vuid, nullptr);
212
static_cast<GActions*>(gself)->controller()->open_app(uid);
215
static void on_resume_all(GSimpleAction*, GVariant*, gpointer gself)
217
static_cast<GActions*>(gself)->controller()->resume_all();
220
static void on_clear_all(GSimpleAction*, GVariant*, gpointer gself)
222
static_cast<GActions*>(gself)->controller()->clear_all();
225
static void on_pause_all(GSimpleAction*, GVariant*, gpointer gself)
227
static_cast<GActions*>(gself)->controller()->pause_all();
230
GVariant* create_default_header_state()
233
g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
234
g_variant_builder_add(&b, "{sv}", "accessible-desc",
235
g_variant_new_string("accessible-desc"));
236
g_variant_builder_add(&b, "{sv}", "label", g_variant_new_string("label"));
237
g_variant_builder_add(&b, "{sv}", "title", g_variant_new_string("title"));
238
g_variant_builder_add(&b, "{sv}", "visible", g_variant_new_boolean(true));
239
return g_variant_builder_end(&b);
246
GSimpleActionGroup* m_action_group = nullptr;
247
std::shared_ptr<Model> m_model;
248
std::shared_ptr<Controller> m_controller;
249
std::set<core::ScopedConnection> m_connections;
250
guint m_update_tag = 0;
252
// we've got raw pointers in here, so disable copying
253
GActions(const GActions&) =delete;
254
GActions& operator=(const GActions&) =delete;
262
* \brief A menu for a specific profile; eg, Desktop or Phone.
268
enum Profile { DESKTOP, PHONE, NUM_PROFILES };
269
enum Section { ONGOING, SUCCESSFUL, NUM_SECTIONS };
271
const char* name() const { return m_name; }
272
GMenuModel* menu_model() { return G_MENU_MODEL(m_menu); }
274
Menu(const char* name_in,
275
const std::shared_ptr<Model>& model,
276
const std::shared_ptr<GActions>& gactions):
280
// initialize the menu
283
update_section(ONGOING);
284
update_section(SUCCESSFUL);
289
if (m_update_menu_tag)
290
g_source_remove(m_update_menu_tag);
291
if (m_update_header_tag)
292
g_source_remove(m_update_header_tag);
293
g_clear_object(&m_menu);
296
void set_model (const std::shared_ptr<Model>& model)
298
auto& c = m_connections;
301
if ((m_model = model))
303
auto updater = [this](const Transfer::Id&) {
304
update_header_soon();
307
c.insert(m_model->added().connect(updater));
308
c.insert(m_model->changed().connect(updater));
309
c.insert(m_model->removed().connect(updater));
319
g_assert(m_submenu == nullptr);
321
m_submenu = g_menu_new();
323
// build placeholders for the sections
324
for(int i=0; i<NUM_SECTIONS; i++)
326
auto item = g_menu_item_new(nullptr, nullptr);
327
g_menu_append_item(m_submenu, item);
328
g_object_unref(item);
331
// add submenu to the header
332
const auto detailed_action = (std::string{"indicator."} + name()) + "-header";
333
auto header = g_menu_item_new(nullptr, detailed_action.c_str());
334
g_menu_item_set_attribute(header, "x-canonical-type", "s",
335
"com.canonical.indicator.root");
336
g_menu_item_set_submenu(header, G_MENU_MODEL(m_submenu));
337
g_object_unref(m_submenu);
339
// add header to the menu
340
m_menu = g_menu_new();
341
g_menu_append_item(m_menu, header);
342
g_object_unref(header);
349
void update_header_soon()
351
if (m_update_header_tag == 0)
352
m_update_header_tag = g_timeout_add(100, update_header_now, this);
354
static gboolean update_header_now (gpointer gself)
356
auto* self = static_cast<Menu*>(gself);
357
self->m_update_header_tag = 0;
358
self->update_header();
359
return G_SOURCE_REMOVE;
364
auto action_name = g_strdup_printf("%s-header", name());
365
auto state = create_header_state();
366
g_action_group_change_action_state(m_gactions->action_group(), action_name, state);
370
// FIXME: see fixme comment for create_header_label()
371
GVariant* create_header_icon()
373
return create_image_missing_icon();
376
// FIXME: this information is supposed to be given the user via an icon.
377
// since the icons haven't been drawn yet, use a placeholder label instead.
378
GVariant* create_header_label() const
380
int n_in_progress = 0;
384
for (const auto& transfer : m_model->get_all())
386
switch (transfer->state)
388
case Transfer::RUNNING:
389
case Transfer::HASHING:
390
case Transfer::PROCESSING:
394
case Transfer::PAUSED:
398
case Transfer::ERROR:
402
case Transfer::QUEUED:
403
case Transfer::CANCELED:
404
case Transfer::FINISHED:
410
if (n_in_progress > 0)
411
str = g_strdup_printf ("%d active", n_in_progress);
412
else if (n_paused > 0)
413
str = g_strdup_printf ("%d paused", n_paused);
414
else if (n_failed > 0)
415
str = g_strdup_printf ("%d failed", n_failed);
417
str = g_strdup_printf ("idle");
419
return g_variant_new_take_string(str);
422
GVariant* create_header_state()
424
auto title_v = g_variant_new_string(_("Transfers"));
427
g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
428
g_variant_builder_add(&b, "{sv}", "title", title_v);
429
g_variant_builder_add(&b, "{sv}", "icon", create_header_icon());
430
g_variant_builder_add(&b, "{sv}", "label", create_header_label());
431
g_variant_builder_add(&b, "{sv}", "accessible-desc", title_v);
432
g_variant_builder_add(&b, "{sv}", "visible", g_variant_new_boolean(true));
433
return g_variant_builder_end (&b);
440
void update_menu_soon()
442
if (m_update_menu_tag == 0)
443
m_update_menu_tag = g_timeout_add_seconds (1, update_menu_now, this);
445
static gboolean update_menu_now (gpointer gself)
447
auto* self = static_cast<Menu*>(gself);
448
self->m_update_menu_tag = 0;
449
self->update_section(ONGOING);
450
self->update_section(SUCCESSFUL);
451
return G_SOURCE_REMOVE;
454
void update_section(Section section)
461
model = create_ongoing_transfers_section();
465
model = create_successful_transfers_section();
475
g_menu_remove(m_submenu, section);
476
g_menu_insert_section(m_submenu, section, nullptr, model);
477
g_object_unref(model);
481
GMenuModel* create_ongoing_transfers_section()
483
auto menu = g_menu_new();
485
// build a list of the ongoing transfers
486
std::vector<std::shared_ptr<Transfer>> transfers;
487
for (const auto& transfer : m_model->get_all())
488
if (transfer->state != Transfer::FINISHED)
489
transfers.push_back (transfer);
491
// soft them in reverse chronological order s.t.
492
// the most recent transfer is displayed first
493
auto compare = [](const std::shared_ptr<Transfer>& a,
494
const std::shared_ptr<Transfer>& b){
495
return a->time_started > b->time_started;
497
std::sort(transfers.begin(), transfers.end(), compare);
499
// add the bulk actions menuitem ("Resume all" or "Pause all")
500
int n_can_resume = 0;
502
for (const auto& t : transfers)
506
else if (t->can_pause())
509
if (n_can_resume > 0)
510
append_bulk_action_menuitem(menu, _("Resume all"), "indicator.resume-all");
511
else if (n_can_pause > 0)
512
append_bulk_action_menuitem(menu, _("Pause all"), "indicator.pause-all");
515
for (const auto& t : transfers)
516
append_transfer_menuitem(menu, t);
518
return G_MENU_MODEL(menu);
521
GMenuModel* create_successful_transfers_section()
523
auto menu = g_menu_new();
525
// build a list of the successful transfers
526
std::vector<std::shared_ptr<Transfer>> transfers;
527
for (const auto& transfer : m_model->get_all())
528
if (transfer->state == Transfer::FINISHED)
529
transfers.push_back (transfer);
531
// as per spec, sort s.t. most recent transfer is first
532
auto compare = [](const std::shared_ptr<Transfer>& a,
533
const std::shared_ptr<Transfer>& b){
534
return a->time_started > b->time_started;
536
std::sort(transfers.begin(), transfers.end(), compare);
538
// as per spec, limit the list to 10 items
539
constexpr int max_items = 10;
540
if (transfers.size() > max_items)
541
transfers.erase(transfers.begin()+max_items, transfers.end());
543
// if there are any successful transfers, show the 'Clear all' button
544
if (!transfers.empty())
545
append_bulk_action_menuitem(menu, _("Clear all"), "indicator.clear-all");
548
for (const auto& t : transfers)
549
append_transfer_menuitem(menu, t);
551
return G_MENU_MODEL(menu);
554
void append_transfer_menuitem(GMenu* menu,
555
const std::shared_ptr<Transfer>& transfer)
557
auto menuitem = create_transfer_menuitem(transfer);
558
g_menu_append_item(menu, menuitem);
559
g_object_unref(menuitem);
562
GMenuItem* create_transfer_menuitem(const std::shared_ptr<Transfer>& transfer)
564
const auto& id = transfer->id.c_str();
566
GMenuItem* menu_item;
568
if (!transfer->title.empty())
570
menu_item = g_menu_item_new (transfer->title.c_str(), nullptr);
574
char* size = g_format_size (transfer->total_size);
575
char* label = g_strdup_printf(_("Unknown Download (%s)"), size);
576
menu_item = g_menu_item_new (label, nullptr);
581
g_menu_item_set_attribute (menu_item, "x-canonical-type",
582
"s", "com.canonical.indicator.transfer");
583
g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON,
584
create_transfer_icon(transfer));
585
g_menu_item_set_attribute (menu_item, "x-canonical-uid", "s", id);
586
g_menu_item_set_action_and_target_value (menu_item,
587
"indicator.activate-transfer",
588
g_variant_new_string(id));
589
return G_MENU_ITEM(menu_item);
592
GVariant* create_transfer_icon(const std::shared_ptr<Transfer>&)// transfer)
594
//FIXME: this is a placeholder
595
return create_image_missing_icon();
598
void append_bulk_action_menuitem(GMenu* menu,
600
const char* detailed_action)
602
auto menuitem = create_bulk_action_menuitem(label, detailed_action);
603
g_menu_append_item(menu, menuitem);
604
g_object_unref(menuitem);
607
GMenuItem* create_bulk_action_menuitem(const char* label,
608
const char* detailed_action)
610
auto menu_item = g_menu_item_new(label, detailed_action);
611
const char * type = "com.canonical.indicator.transfer-bulk-action";
612
g_menu_item_set_attribute (menu_item, "x-canonical-type", "s", type);
620
// FIXME: this is a placeholder.
621
// remove it when we have real icons for (a) the header and (b) the menuitems
622
GVariant* create_image_missing_icon()
624
auto icon = g_themed_icon_new("image-missing");
625
auto v = g_icon_serialize(icon);
626
g_object_unref(icon);
634
std::set<core::ScopedConnection> m_connections;
635
GMenu* m_menu = nullptr;
636
const char* const m_name;
638
std::shared_ptr<Model> m_model;
639
std::shared_ptr<GActions> m_gactions;
640
GMenu* m_submenu = nullptr;
642
guint m_update_menu_tag = 0;
643
guint m_update_header_tag = 0;
645
// we've got raw pointers in here, so disable copying
646
Menu(const Menu&) =delete;
647
Menu& operator=(const Menu&) =delete;
655
* \brief Exports actions and gmenus to the DBus
665
if (m_bus != nullptr)
667
for(const auto& id : m_exported_menu_ids)
668
g_dbus_connection_unexport_menu_model(m_bus, id);
670
if (m_exported_actions_id)
671
g_dbus_connection_unexport_action_group(m_bus,
672
m_exported_actions_id);
676
g_bus_unown_name(m_own_id);
678
g_clear_object(&m_bus);
681
core::Signal<> name_lost;
683
void publish(const std::shared_ptr<GActions>& gactions,
684
const std::vector<std::shared_ptr<Menu>>& menus)
686
m_gactions = gactions;
688
m_own_id = g_bus_own_name(G_BUS_TYPE_SESSION,
690
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
704
static void on_bus_acquired(GDBusConnection* connection,
708
g_debug("bus acquired: %s", name);
709
static_cast<Exporter*>(gself)->on_bus_acquired(connection, name);
712
void on_bus_acquired(GDBusConnection* connection, const gchar* /*name*/)
714
m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(connection)));
716
// export the actions
717
GError * error = nullptr;
718
auto id = g_dbus_connection_export_action_group(m_bus,
720
m_gactions->action_group(),
724
m_exported_actions_id = id;
728
g_warning("cannot export action group: %s", error->message);
729
g_clear_error(&error);
733
for(auto& menu : m_menus)
735
const auto path = std::string(BUS_PATH) + "/" + menu->name();
736
id = g_dbus_connection_export_menu_model(m_bus,
742
m_exported_menu_ids.insert(id);
746
if (error != nullptr)
747
g_warning("cannot export %s menu: %s", menu->name(), error->message);
749
g_clear_error(&error);
758
static void on_name_lost(GDBusConnection* connection,
762
g_debug("name lost: %s", name);
763
static_cast<Exporter*>(gthis)->on_name_lost(connection, name);
766
void on_name_lost(GDBusConnection* /*connection*/, const gchar* /*name*/)
775
std::set<guint> m_exported_menu_ids;
777
guint m_exported_actions_id = 0;
778
GDBusConnection * m_bus = nullptr;
779
std::shared_ptr<GActions> m_gactions;
780
std::vector<std::shared_ptr<Menu>> m_menus;
782
// we've got raw pointers and gsignal tags in here, so disable copying
783
Exporter(const Exporter&) =delete;
784
Exporter& operator=(const Exporter&) =delete;
787
} // anonymous namespace
793
class GMenuView::Impl
797
Impl (const std::shared_ptr<Model>& model,
798
const std::shared_ptr<Controller>& controller):
800
m_controller(controller),
801
m_gactions(new GActions(model, controller)),
802
m_exporter(new Exporter)
805
for(int i=0; i<Menu::NUM_PROFILES; i++)
806
m_menus.push_back(create_menu_for_profile(Menu::Profile(i)));
808
m_exporter->publish(m_gactions, m_menus);
815
void set_model(const std::shared_ptr<Model>& model)
819
for(const auto& menu : m_menus)
820
menu->set_model(model);
823
const core::Signal<>& name_lost() { return m_exporter->name_lost; }
827
std::shared_ptr<Menu> create_menu_for_profile(Menu::Profile profile)
829
// only one design, so for now everything uses the phone menu
830
constexpr static const char* profile_names[] = { "desktop", "phone" };
831
std::shared_ptr<Menu> m(new Menu(profile_names[profile],
837
std::shared_ptr<Model> m_model;
838
std::shared_ptr<Controller> m_controller;
839
std::shared_ptr<GActions> m_gactions;
840
std::vector<std::shared_ptr<Menu>> m_menus;
841
std::shared_ptr<Exporter> m_exporter;
848
GMenuView::GMenuView(const std::shared_ptr<Model>& model,
849
const std::shared_ptr<Controller>& controller):
850
p(new Impl(model, controller))
854
GMenuView::~GMenuView()
858
void GMenuView::set_model(const std::shared_ptr<Model>& model)
863
const core::Signal<>& GMenuView::name_lost() const
865
return p->name_lost();
872
} // namespace transfer
873
} // namespace indicator