~ps-jenkins/indicator-transfer/ubuntu-vivid-proposed

« back to all changes in this revision

Viewing changes to src/view-gmenu.cpp

  • Committer: Ted Gould
  • Author(s): Charles Kerr
  • Date: 2014-06-18 19:26:56 UTC
  • mfrom: (1.2.20 indicator-transfer)
  • Revision ID: ted@gould.cx-20140618192656-ufycr3k7g5w0en4u
This sets up the code layout, menu, indicator, unit tests, code coverage rules, etc... what you'd expect from an indicator.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2014 Canonical Ltd.
 
3
 *
 
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.
 
7
 *
 
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.
 
12
 *
 
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/>.
 
15
 *
 
16
 * Authors:
 
17
 *   Charles Kerr <charles.kerr@canonical.com>
 
18
 */
 
19
 
 
20
#include <transfer/dbus-shared.h>
 
21
#include <transfer/controller.h>
 
22
#include <transfer/view-gmenu.h>
 
23
 
 
24
#include <core/connection.h>
 
25
 
 
26
#include <glib/gi18n.h>
 
27
#include <gio/gio.h>
 
28
 
 
29
#include <algorithm> // std::sort()
 
30
 
 
31
namespace unity {
 
32
namespace indicator {
 
33
namespace transfer {
 
34
 
 
35
/****
 
36
*****
 
37
****/
 
38
 
 
39
namespace {
 
40
 
 
41
/**
 
42
 * \brief GActionGroup wrapper that routes action callbacks to the Controller
 
43
 */
 
44
class GActions
 
45
{
 
46
public:
 
47
 
 
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)
 
52
  {
 
53
    set_model(model);
 
54
 
 
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 }
 
65
    };
 
66
 
 
67
    auto gam = G_ACTION_MAP(m_action_group);
 
68
    g_action_map_add_action_entries(gam,
 
69
                                    entries,
 
70
                                    G_N_ELEMENTS(entries),
 
71
                                    this);
 
72
 
 
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));
 
77
 
 
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));
 
82
  }
 
83
 
 
84
  void set_model(const std::shared_ptr<Model>& model)
 
85
  {
 
86
    auto& c = m_connections;
 
87
    c.clear();
 
88
 
 
89
    if ((m_model = model))
 
90
      {
 
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));
 
95
        update_soon();
 
96
      }
 
97
  }
 
98
 
 
99
  ~GActions()
 
100
  {
 
101
    if (m_update_tag)
 
102
      g_source_remove (m_update_tag);
 
103
    g_clear_object(&m_action_group);
 
104
  }
 
105
 
 
106
  GActionGroup* action_group() const
 
107
  {
 
108
    return G_ACTION_GROUP(m_action_group);
 
109
  }
 
110
 
 
111
private:
 
112
 
 
113
  /***
 
114
  ****  TRANSFER STATES
 
115
  ***/
 
116
 
 
117
  void update_soon()
 
118
  {
 
119
    if (m_update_tag == 0)
 
120
      m_update_tag = g_timeout_add_seconds(1, update_timeout, this);
 
121
  }
 
122
 
 
123
  static gboolean update_timeout(gpointer gself)
 
124
  {
 
125
    auto self = static_cast<GActions*>(gself);
 
126
    self->m_update_tag = 0;
 
127
    self->update();
 
128
    return G_SOURCE_REMOVE;
 
129
  }
 
130
 
 
131
  void update()
 
132
  {
 
133
    g_action_group_change_action_state(action_group(),
 
134
                                       "transfer-states",
 
135
                                       create_transfer_states());
 
136
  }
 
137
 
 
138
  GVariant* create_transfer_states()
 
139
  {
 
140
    GVariantBuilder b;
 
141
    g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
 
142
 
 
143
    for (const auto& transfer : m_model->get_all())
 
144
      {
 
145
        auto state = create_transfer_state(transfer);
 
146
        g_variant_builder_add(&b, "{sv}", transfer->id.c_str(), state);
 
147
      }
 
148
 
 
149
    return g_variant_builder_end(&b);
 
150
  }
 
151
 
 
152
  GVariant* create_transfer_state(const std::shared_ptr<Transfer>& transfer)
 
153
  {
 
154
    GVariantBuilder b;
 
155
    g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
 
156
 
 
157
    g_variant_builder_add(&b, "{sv}", "percent",
 
158
                          g_variant_new_double(transfer->progress));
 
159
    if (transfer->seconds_left >= 0)
 
160
      {
 
161
        g_variant_builder_add(&b, "{sv}", "seconds-left",
 
162
                              g_variant_new_int32(transfer->seconds_left));
 
163
      }
 
164
 
 
165
    g_variant_builder_add(&b, "{sv}", "state", g_variant_new_int32(transfer->state));
 
166
 
 
167
    return g_variant_builder_end(&b);
 
168
  }
 
169
 
 
170
  /***
 
171
  ****  ACTION CALLBACKS
 
172
  ***/
 
173
 
 
174
  std::shared_ptr<Controller>& controller()
 
175
  {
 
176
    return m_controller;
 
177
  }
 
178
 
 
179
  static void on_tap(GSimpleAction*, GVariant* vuid, gpointer gself)
 
180
  {
 
181
    const auto uid = g_variant_get_string(vuid, nullptr);
 
182
    static_cast<GActions*>(gself)->controller()->tap(uid);
 
183
  }
 
184
 
 
185
  static void on_cancel(GSimpleAction*, GVariant* vuid, gpointer gself)
 
186
  {
 
187
    const auto uid = g_variant_get_string(vuid, nullptr);
 
188
    static_cast<GActions*>(gself)->controller()->cancel(uid);
 
189
  }
 
190
 
 
191
  static void on_pause(GSimpleAction*, GVariant* vuid, gpointer gself)
 
192
  {
 
193
    const auto uid = g_variant_get_string(vuid, nullptr);
 
194
    static_cast<GActions*>(gself)->controller()->pause(uid);
 
195
  }
 
196
 
 
197
  static void on_resume(GSimpleAction*, GVariant* vuid, gpointer gself)
 
198
  {
 
199
    const auto uid = g_variant_get_string(vuid, nullptr);
 
200
    static_cast<GActions*>(gself)->controller()->resume(uid);
 
201
  }
 
202
 
 
203
  static void on_open(GSimpleAction*, GVariant* vuid, gpointer gself)
 
204
  {
 
205
    const auto uid = g_variant_get_string(vuid, nullptr);
 
206
    static_cast<GActions*>(gself)->controller()->open(uid);
 
207
  }
 
208
 
 
209
  static void on_open_app(GSimpleAction*, GVariant* vuid, gpointer gself)
 
210
  {
 
211
    const auto uid = g_variant_get_string(vuid, nullptr);
 
212
    static_cast<GActions*>(gself)->controller()->open_app(uid);
 
213
  }
 
214
 
 
215
  static void on_resume_all(GSimpleAction*, GVariant*, gpointer gself)
 
216
  {
 
217
    static_cast<GActions*>(gself)->controller()->resume_all();
 
218
  }
 
219
 
 
220
  static void on_clear_all(GSimpleAction*, GVariant*, gpointer gself)
 
221
  {
 
222
    static_cast<GActions*>(gself)->controller()->clear_all();
 
223
  }
 
224
 
 
225
  static void on_pause_all(GSimpleAction*, GVariant*, gpointer gself)
 
226
  {
 
227
    static_cast<GActions*>(gself)->controller()->pause_all();
 
228
  }
 
229
 
 
230
  GVariant* create_default_header_state()
 
231
  {
 
232
    GVariantBuilder b;
 
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);
 
240
  }
 
241
 
 
242
  /***
 
243
  ****
 
244
  ***/
 
245
 
 
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;
 
251
 
 
252
  // we've got raw pointers in here, so disable copying
 
253
  GActions(const GActions&) =delete;
 
254
  GActions& operator=(const GActions&) =delete;
 
255
};
 
256
 
 
257
/***
 
258
****
 
259
***/
 
260
 
 
261
/**
 
262
 * \brief A menu for a specific profile; eg, Desktop or Phone.
 
263
 */
 
264
class Menu
 
265
{
 
266
public:
 
267
 
 
268
  enum Profile { DESKTOP, PHONE, NUM_PROFILES };
 
269
  enum Section { ONGOING, SUCCESSFUL, NUM_SECTIONS };
 
270
 
 
271
  const char* name() const { return m_name; }
 
272
  GMenuModel* menu_model() { return G_MENU_MODEL(m_menu); }
 
273
 
 
274
  Menu(const char* name_in,
 
275
       const std::shared_ptr<Model>& model,
 
276
       const std::shared_ptr<GActions>& gactions):
 
277
    m_name{name_in},
 
278
    m_gactions{gactions}
 
279
  {
 
280
    // initialize the menu
 
281
    create_gmenu();
 
282
    set_model(model);
 
283
    update_section(ONGOING);
 
284
    update_section(SUCCESSFUL);
 
285
  }
 
286
 
 
287
  virtual ~Menu()
 
288
  {
 
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);
 
294
  }
 
295
 
 
296
  void set_model (const std::shared_ptr<Model>& model)
 
297
  {
 
298
    auto& c = m_connections;
 
299
    c.clear();
 
300
 
 
301
    if ((m_model = model))
 
302
      {
 
303
        auto updater = [this](const Transfer::Id&) {
 
304
          update_header_soon();
 
305
          update_menu_soon();
 
306
        };
 
307
        c.insert(m_model->added().connect(updater));
 
308
        c.insert(m_model->changed().connect(updater));
 
309
        c.insert(m_model->removed().connect(updater));
 
310
      }
 
311
 
 
312
    update_header();
 
313
  }
 
314
 
 
315
private:
 
316
 
 
317
  void create_gmenu()
 
318
  {
 
319
    g_assert(m_submenu == nullptr);
 
320
 
 
321
    m_submenu = g_menu_new();
 
322
 
 
323
    // build placeholders for the sections
 
324
    for(int i=0; i<NUM_SECTIONS; i++)
 
325
    {
 
326
      auto item = g_menu_item_new(nullptr, nullptr);
 
327
      g_menu_append_item(m_submenu, item);
 
328
      g_object_unref(item);
 
329
    }
 
330
 
 
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);
 
338
 
 
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);
 
343
  }
 
344
 
 
345
  /***
 
346
  ****  HEADER
 
347
  ***/
 
348
 
 
349
  void update_header_soon()
 
350
  {
 
351
    if (m_update_header_tag == 0)
 
352
      m_update_header_tag = g_timeout_add(100, update_header_now, this);
 
353
  }
 
354
  static gboolean update_header_now (gpointer gself)
 
355
  {
 
356
    auto* self = static_cast<Menu*>(gself);
 
357
    self->m_update_header_tag = 0;
 
358
    self->update_header();
 
359
    return G_SOURCE_REMOVE;
 
360
  }
 
361
 
 
362
  void update_header()
 
363
  {
 
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);
 
367
    g_free(action_name);
 
368
  }
 
369
 
 
370
  // FIXME: see fixme comment for create_header_label()
 
371
  GVariant* create_header_icon()
 
372
  {
 
373
    return create_image_missing_icon();
 
374
  }
 
375
 
 
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
 
379
  {
 
380
    int n_in_progress = 0;
 
381
    int n_failed = 0;
 
382
    int n_paused = 0;
 
383
 
 
384
    for (const auto& transfer : m_model->get_all())
 
385
      {
 
386
        switch (transfer->state)
 
387
          {
 
388
            case Transfer::RUNNING:
 
389
            case Transfer::HASHING:
 
390
            case Transfer::PROCESSING:
 
391
              ++n_in_progress;
 
392
              break;
 
393
 
 
394
            case Transfer::PAUSED:
 
395
              ++n_paused;
 
396
              break;
 
397
 
 
398
            case Transfer::ERROR:
 
399
              ++n_failed;
 
400
              break;
 
401
 
 
402
            case Transfer::QUEUED:
 
403
            case Transfer::CANCELED:
 
404
            case Transfer::FINISHED:
 
405
              break;
 
406
          }
 
407
      }
 
408
 
 
409
    char* str;
 
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);
 
416
    else
 
417
      str = g_strdup_printf ("idle");
 
418
 
 
419
    return g_variant_new_take_string(str);
 
420
  }
 
421
 
 
422
  GVariant* create_header_state()
 
423
  {
 
424
    auto title_v = g_variant_new_string(_("Transfers"));
 
425
 
 
426
    GVariantBuilder b;
 
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);
 
434
  }
 
435
 
 
436
  /***
 
437
  ****  MENU
 
438
  ***/
 
439
 
 
440
  void update_menu_soon()
 
441
  {
 
442
    if (m_update_menu_tag == 0)
 
443
      m_update_menu_tag = g_timeout_add_seconds (1, update_menu_now, this);
 
444
  }
 
445
  static gboolean update_menu_now (gpointer gself)
 
446
  {
 
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;
 
452
  }
 
453
 
 
454
  void update_section(Section section)
 
455
  {
 
456
    GMenuModel * model;
 
457
 
 
458
    switch (section)
 
459
      {
 
460
        case ONGOING:
 
461
          model = create_ongoing_transfers_section();
 
462
          break;
 
463
 
 
464
        case SUCCESSFUL:
 
465
          model = create_successful_transfers_section();
 
466
          break;
 
467
 
 
468
        case NUM_SECTIONS:
 
469
          model = nullptr;
 
470
          g_warn_if_reached();
 
471
      }
 
472
 
 
473
    if (model)
 
474
      {
 
475
        g_menu_remove(m_submenu, section);
 
476
        g_menu_insert_section(m_submenu, section, nullptr, model);
 
477
        g_object_unref(model);
 
478
      }
 
479
  }
 
480
 
 
481
  GMenuModel* create_ongoing_transfers_section()
 
482
  {
 
483
    auto menu = g_menu_new();
 
484
 
 
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);
 
490
 
 
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;
 
496
    };
 
497
    std::sort(transfers.begin(), transfers.end(), compare);
 
498
 
 
499
    // add the bulk actions menuitem ("Resume all" or "Pause all")
 
500
    int n_can_resume = 0;
 
501
    int n_can_pause = 0;
 
502
    for (const auto& t : transfers)
 
503
      {
 
504
        if (t->can_resume())
 
505
          ++n_can_resume;
 
506
        else if (t->can_pause())
 
507
          ++n_can_pause;
 
508
      }
 
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");
 
513
 
 
514
    // add the transfers
 
515
    for (const auto& t : transfers)
 
516
      append_transfer_menuitem(menu, t);
 
517
 
 
518
    return G_MENU_MODEL(menu);
 
519
  }
 
520
 
 
521
  GMenuModel* create_successful_transfers_section()
 
522
  {
 
523
    auto menu = g_menu_new();
 
524
 
 
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);
 
530
 
 
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;
 
535
    };
 
536
    std::sort(transfers.begin(), transfers.end(), compare);
 
537
 
 
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());
 
542
 
 
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");
 
546
 
 
547
    // add the transfers
 
548
    for (const auto& t : transfers)
 
549
      append_transfer_menuitem(menu, t);
 
550
 
 
551
    return G_MENU_MODEL(menu);
 
552
  }
 
553
 
 
554
  void append_transfer_menuitem(GMenu* menu,
 
555
                                const std::shared_ptr<Transfer>& transfer)
 
556
  {
 
557
    auto menuitem = create_transfer_menuitem(transfer);
 
558
    g_menu_append_item(menu, menuitem);
 
559
    g_object_unref(menuitem);
 
560
  }
 
561
 
 
562
  GMenuItem* create_transfer_menuitem(const std::shared_ptr<Transfer>& transfer)
 
563
  {
 
564
    const auto& id = transfer->id.c_str();
 
565
 
 
566
    GMenuItem* menu_item;
 
567
 
 
568
    if (!transfer->title.empty())
 
569
      {
 
570
        menu_item = g_menu_item_new (transfer->title.c_str(), nullptr);
 
571
      }
 
572
    else
 
573
      {
 
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);
 
577
        g_free(label);
 
578
        g_free(size);
 
579
      }
 
580
 
 
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);
 
590
  }
 
591
 
 
592
  GVariant* create_transfer_icon(const std::shared_ptr<Transfer>&)// transfer)
 
593
  {
 
594
    //FIXME: this is a placeholder
 
595
    return create_image_missing_icon();
 
596
  }
 
597
 
 
598
  void append_bulk_action_menuitem(GMenu* menu,
 
599
                                   const char* label,
 
600
                                   const char* detailed_action)
 
601
  {
 
602
    auto menuitem = create_bulk_action_menuitem(label, detailed_action);
 
603
    g_menu_append_item(menu, menuitem);
 
604
    g_object_unref(menuitem);
 
605
  }
 
606
 
 
607
  GMenuItem* create_bulk_action_menuitem(const char* label,
 
608
                                         const char* detailed_action)
 
609
  {
 
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);
 
613
    return menu_item;
 
614
  }
 
615
 
 
616
  /***
 
617
  ****
 
618
  ***/
 
619
 
 
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()
 
623
  {
 
624
    auto icon = g_themed_icon_new("image-missing");
 
625
    auto v = g_icon_serialize(icon);
 
626
    g_object_unref(icon);
 
627
    return v;
 
628
  }
 
629
 
 
630
  /***
 
631
  ****
 
632
  ***/
 
633
 
 
634
  std::set<core::ScopedConnection> m_connections;
 
635
  GMenu* m_menu = nullptr;
 
636
  const char* const m_name;
 
637
 
 
638
  std::shared_ptr<Model> m_model;
 
639
  std::shared_ptr<GActions> m_gactions;
 
640
  GMenu* m_submenu = nullptr;
 
641
 
 
642
  guint m_update_menu_tag = 0;
 
643
  guint m_update_header_tag = 0;
 
644
 
 
645
  // we've got raw pointers in here, so disable copying
 
646
  Menu(const Menu&) =delete;
 
647
  Menu& operator=(const Menu&) =delete;
 
648
};
 
649
 
 
650
/***
 
651
****
 
652
***/
 
653
 
 
654
/**
 
655
 * \brief Exports actions and gmenus to the DBus
 
656
 */
 
657
class Exporter
 
658
{
 
659
public:
 
660
 
 
661
  Exporter(){}
 
662
 
 
663
  ~Exporter()
 
664
  {
 
665
    if (m_bus != nullptr)
 
666
      {
 
667
        for(const auto& id : m_exported_menu_ids)
 
668
          g_dbus_connection_unexport_menu_model(m_bus, id);
 
669
 
 
670
        if (m_exported_actions_id)
 
671
          g_dbus_connection_unexport_action_group(m_bus,
 
672
                                                  m_exported_actions_id);
 
673
    }
 
674
 
 
675
    if (m_own_id)
 
676
      g_bus_unown_name(m_own_id);
 
677
 
 
678
    g_clear_object(&m_bus);
 
679
  }
 
680
 
 
681
  core::Signal<> name_lost;
 
682
 
 
683
  void publish(const std::shared_ptr<GActions>& gactions,
 
684
               const std::vector<std::shared_ptr<Menu>>& menus)
 
685
  {
 
686
    m_gactions = gactions;
 
687
    m_menus = menus;
 
688
    m_own_id = g_bus_own_name(G_BUS_TYPE_SESSION,
 
689
                              BUS_NAME,
 
690
                              G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
 
691
                              on_bus_acquired,
 
692
                              nullptr,
 
693
                              on_name_lost,
 
694
                              this,
 
695
                              nullptr);
 
696
  }
 
697
 
 
698
private:
 
699
 
 
700
  /***
 
701
  ****
 
702
  ***/
 
703
 
 
704
  static void on_bus_acquired(GDBusConnection* connection,
 
705
                                        const gchar* name,
 
706
                                        gpointer gself)
 
707
  {
 
708
    g_debug("bus acquired: %s", name);
 
709
    static_cast<Exporter*>(gself)->on_bus_acquired(connection, name);
 
710
  }
 
711
 
 
712
  void on_bus_acquired(GDBusConnection* connection, const gchar* /*name*/)
 
713
  {
 
714
    m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(connection)));
 
715
 
 
716
    // export the actions
 
717
    GError * error = nullptr;
 
718
    auto id = g_dbus_connection_export_action_group(m_bus,
 
719
                                                    BUS_PATH,
 
720
                                                    m_gactions->action_group(),
 
721
                                                    &error);
 
722
    if (id)
 
723
      {
 
724
        m_exported_actions_id = id;
 
725
      }
 
726
    else
 
727
      {
 
728
        g_warning("cannot export action group: %s", error->message);
 
729
        g_clear_error(&error);
 
730
      }
 
731
 
 
732
    // export the menus
 
733
    for(auto& menu : m_menus)
 
734
      {
 
735
        const auto path = std::string(BUS_PATH) + "/" + menu->name();
 
736
        id = g_dbus_connection_export_menu_model(m_bus,
 
737
                                                 path.c_str(),
 
738
                                                 menu->menu_model(),
 
739
                                                 &error);
 
740
        if (id)
 
741
          {
 
742
            m_exported_menu_ids.insert(id);
 
743
          }
 
744
        else
 
745
          {
 
746
            if (error != nullptr)
 
747
                g_warning("cannot export %s menu: %s", menu->name(), error->message);
 
748
 
 
749
            g_clear_error(&error);
 
750
          }
 
751
      }
 
752
  }
 
753
 
 
754
  /***
 
755
  ****
 
756
  ***/
 
757
 
 
758
  static void on_name_lost(GDBusConnection* connection,
 
759
                           const gchar* name,
 
760
                           gpointer gthis)
 
761
  {
 
762
    g_debug("name lost: %s", name);
 
763
    static_cast<Exporter*>(gthis)->on_name_lost(connection, name);
 
764
  }
 
765
 
 
766
  void on_name_lost(GDBusConnection* /*connection*/, const gchar* /*name*/)
 
767
  {
 
768
    name_lost();
 
769
  }
 
770
 
 
771
  /***
 
772
  ****
 
773
  ***/
 
774
 
 
775
  std::set<guint> m_exported_menu_ids;
 
776
  guint m_own_id = 0;
 
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;
 
781
 
 
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;
 
785
};
 
786
 
 
787
} // anonymous namespace
 
788
 
 
789
/***
 
790
****
 
791
***/
 
792
 
 
793
class GMenuView::Impl
 
794
{
 
795
public:
 
796
 
 
797
  Impl (const std::shared_ptr<Model>& model,
 
798
        const std::shared_ptr<Controller>& controller):
 
799
    m_model(model),
 
800
    m_controller(controller),
 
801
    m_gactions(new GActions(model, controller)),
 
802
    m_exporter(new Exporter)
 
803
  {
 
804
    // create the Menus
 
805
    for(int i=0; i<Menu::NUM_PROFILES; i++)
 
806
      m_menus.push_back(create_menu_for_profile(Menu::Profile(i)));
 
807
  
 
808
    m_exporter->publish(m_gactions, m_menus);
 
809
  }
 
810
 
 
811
  ~Impl()
 
812
  {
 
813
  }
 
814
 
 
815
  void set_model(const std::shared_ptr<Model>& model)
 
816
  {
 
817
    m_model = model;
 
818
 
 
819
    for(const auto& menu : m_menus)
 
820
      menu->set_model(model);
 
821
  }
 
822
 
 
823
  const core::Signal<>& name_lost() { return m_exporter->name_lost; }
 
824
 
 
825
private:
 
826
 
 
827
  std::shared_ptr<Menu> create_menu_for_profile(Menu::Profile profile)
 
828
  {
 
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],
 
832
                                     m_model,
 
833
                                     m_gactions));
 
834
    return m;
 
835
  }
 
836
 
 
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;
 
842
};
 
843
 
 
844
/***
 
845
****
 
846
***/
 
847
 
 
848
GMenuView::GMenuView(const std::shared_ptr<Model>& model,
 
849
                 const std::shared_ptr<Controller>& controller):
 
850
  p(new Impl(model, controller))
 
851
{
 
852
}
 
853
 
 
854
GMenuView::~GMenuView()
 
855
{
 
856
}
 
857
 
 
858
void GMenuView::set_model(const std::shared_ptr<Model>& model)
 
859
{
 
860
  p->set_model(model);
 
861
}
 
862
 
 
863
const core::Signal<>& GMenuView::name_lost() const
 
864
{
 
865
  return p->name_lost();
 
866
}
 
867
 
 
868
/***
 
869
****
 
870
***/
 
871
 
 
872
} // namespace transfer
 
873
} // namespace indicator
 
874
} // namespace unity
 
875