~ubuntu-branches/ubuntu/trusty/indicator-power/trusty-proposed

« back to all changes in this revision

Viewing changes to src/device.c

  • Committer: Package Import Robot
  • Author(s): Ubuntu daily release, Charles Kerr
  • Date: 2014-03-14 14:14:07 UTC
  • mfrom: (1.1.45)
  • Revision ID: package-import@ubuntu.com-20140314141407-jme2gnrojkg37tgz
Tags: 12.10.6+14.04.20140314-0ubuntu1
[ Charles Kerr ]
Change the implementation of the title text / title accessible text
/ menuitem text to follow the spec changes in
https://wiki.ubuntu.com/Power?action=diff&rev2=44&rev1=43#Title and
update the tests accordingly. (LP: #1234458)

Show diffs side-by-side

added added

removed removed

Lines of Context:
37
37
  gchar * object_path;
38
38
  gdouble percentage;
39
39
  time_t time;
 
40
 
 
41
  /* Timestamp of when when we first noticed that upower couldn't estimate
 
42
     the time-remaining field for this device, or 0 if not applicable.
 
43
     This is used when generating the time-remaining string. */
 
44
  GTimer * inestimable;
40
45
};
41
46
 
42
47
#define INDICATOR_POWER_DEVICE_GET_PRIVATE(o) (INDICATOR_POWER_DEVICE(o)->priv)
136
141
static void
137
142
indicator_power_device_dispose (GObject *object)
138
143
{
 
144
  IndicatorPowerDevice * self = INDICATOR_POWER_DEVICE(object);
 
145
  IndicatorPowerDevicePrivate * p = self->priv;
 
146
 
 
147
  g_clear_pointer (&p->inestimable, g_timer_destroy);
 
148
 
139
149
  G_OBJECT_CLASS (indicator_power_device_parent_class)->dispose (object);
140
150
}
141
151
 
192
202
set_property (GObject * o, guint prop_id, const GValue * value, GParamSpec * pspec)
193
203
{
194
204
  IndicatorPowerDevice * self = INDICATOR_POWER_DEVICE(o);
195
 
  IndicatorPowerDevicePrivate * priv = self->priv;
 
205
  IndicatorPowerDevicePrivate * p = self->priv;
196
206
 
197
207
  switch (prop_id)
198
208
    {
199
209
      case PROP_KIND:
200
 
        priv->kind = g_value_get_int (value);
 
210
        p->kind = g_value_get_int (value);
201
211
        break;
202
212
 
203
213
      case PROP_STATE:
204
 
        priv->state = g_value_get_int (value);
 
214
        p->state = g_value_get_int (value);
205
215
        break;
206
216
 
207
217
      case PROP_OBJECT_PATH:
208
 
        g_free (priv->object_path);
209
 
        priv->object_path = g_value_dup_string (value);
 
218
        g_free (p->object_path);
 
219
        p->object_path = g_value_dup_string (value);
210
220
        break;
211
221
 
212
222
      case PROP_PERCENTAGE:
213
 
        priv->percentage = g_value_get_double (value);
 
223
        p->percentage = g_value_get_double (value);
214
224
        break;
215
225
 
216
226
      case PROP_TIME:
217
 
        priv->time = g_value_get_uint64(value);
 
227
        p->time = g_value_get_uint64(value);
218
228
        break;
219
229
 
220
230
      default:
221
231
        G_OBJECT_WARN_INVALID_PROPERTY_ID(o, prop_id, pspec);
222
232
        break;
223
233
    }
 
234
 
 
235
  /**
 
236
   * Check to see if the time-remaining value is estimable.
 
237
   * When it first becomes inestimable, kick off a timer because
 
238
   * we need to track that to generate the appropriate title text.
 
239
   */
 
240
 
 
241
  const gboolean is_inestimable = (p->time == 0)
 
242
                               && (p->state != UP_DEVICE_STATE_FULLY_CHARGED)
 
243
                               && (p->percentage > 0);
 
244
 
 
245
  if (!is_inestimable)
 
246
    {
 
247
      g_clear_pointer (&p->inestimable, g_timer_destroy);
 
248
    }
 
249
  else if (p->inestimable == NULL)
 
250
    {
 
251
      p->inestimable = g_timer_new ();
 
252
    }
224
253
}
225
254
 
226
255
/***
442
471
****
443
472
***/
444
473
 
445
 
/* Format time remaining for reading ("H:MM") and speech ("H hours, MM minutes") */
446
 
static void
447
 
get_timestring (guint64   time_secs,
448
 
                gchar   **readable_timestring,
449
 
                gchar   **accessible_timestring)
450
 
{
451
 
  gint  hours;
452
 
  gint  minutes;
453
 
 
454
 
  /* Add 0.5 to do rounding */
455
 
  minutes = (int) ( ( time_secs / 60.0 ) + 0.5 );
456
 
 
457
 
  if (minutes == 0)
458
 
    {
459
 
      *readable_timestring = g_strdup (_("Unknown time"));
460
 
      *accessible_timestring = g_strdup (_("Unknown time"));
461
 
 
462
 
      return;
463
 
    }
464
 
 
465
 
  if (minutes < 60)
466
 
    {
467
 
      *readable_timestring = g_strdup_printf ("0:%.2i", minutes);
468
 
      *accessible_timestring = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%i minute",
469
 
                                              "%i minutes",
470
 
                                              minutes), minutes);
471
 
      return;
472
 
    }
473
 
 
474
 
  hours = minutes / 60;
475
 
  minutes = minutes % 60;
476
 
 
477
 
  *readable_timestring = g_strdup_printf ("%i:%.2i", hours, minutes);
478
 
 
479
 
  if (minutes == 0)
480
 
    {
481
 
      *accessible_timestring = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
482
 
                                              "%i hour",
483
 
                                              "%i hours",
484
 
                                              hours), hours);
485
 
    }
486
 
  else
487
 
    {
488
 
      /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes"
489
 
       * Swap order with "%2$s %2$i %1$s %1$i if needed */
490
 
      *accessible_timestring = g_strdup_printf (_("%i %s %i %s"),
491
 
                                              hours, g_dngettext (GETTEXT_PACKAGE, "hour", "hours", hours),
492
 
                                              minutes, g_dngettext (GETTEXT_PACKAGE, "minute", "minutes", minutes));
493
 
    }
494
 
}
495
 
 
496
474
static const gchar *
497
475
device_kind_to_localised_string (UpDeviceKind kind)
498
476
{
555
533
  return text;
556
534
}
557
535
 
558
 
static char *
559
 
join_strings (const char * name, const char * time, const char * percent)
560
 
{
561
 
  char * str;
562
 
  const gboolean have_name = name && *name;
563
 
  const gboolean have_time = time && *time;
564
 
  const gboolean have_percent = percent && *percent;
565
 
 
566
 
  if (have_name && have_time && have_percent)
567
 
    str = g_strdup_printf (_("%s (%s, %s)"), name, time, percent);
568
 
  else if (have_name && have_time)
569
 
    str = g_strdup_printf (_("%s (%s)"), name, time);
570
 
  else if (have_name && have_percent)
571
 
    str = g_strdup_printf (_("%s (%s)"), name, percent);
572
 
  else if (have_name)
573
 
    str = g_strdup (name);
574
 
  else if (have_time && have_percent)
575
 
    str = g_strdup_printf (_("(%s, %s)"), time, percent);
576
 
  else if (have_time)
577
 
    str = g_strdup_printf (_("(%s)"), time);
578
 
  else if (have_percent)
579
 
    str = g_strdup_printf (_("(%s)"), percent);
580
 
  else
581
 
    str = g_strdup ("");
582
 
 
583
 
  return str;
584
 
}
585
 
 
586
 
static void
587
 
indicator_power_device_get_text (const IndicatorPowerDevice * device,
588
 
                                 gboolean show_time_in_header,
589
 
                                 gboolean show_percentage_in_header,
590
 
                                 gchar ** header,
591
 
                                 gchar ** label,
592
 
                                 gchar ** a11y)
593
 
{
594
 
  if (!INDICATOR_IS_POWER_DEVICE(device))
595
 
    {
596
 
      if (a11y != NULL) *a11y = NULL;
597
 
      if (label != NULL) *label = NULL;
598
 
      if (header != NULL) *header = NULL;
599
 
      g_warning ("%s: %p is not an IndicatorPowerDevice", G_STRFUNC, device);
600
 
      return;
601
 
    }
602
 
 
603
 
  const time_t time = indicator_power_device_get_time (device);
604
 
  const UpDeviceState state = indicator_power_device_get_state (device);
605
 
  const UpDeviceKind kind = indicator_power_device_get_kind (device);
606
 
  const gchar * device_name = device_kind_to_localised_string (kind);
607
 
  const gdouble percentage = indicator_power_device_get_percentage (device);
608
 
  char pctstr[32] = { '\0' };
609
 
  g_snprintf (pctstr, sizeof(pctstr), "%.0lf%%", percentage);
610
 
 
611
 
  GString * terse_time = g_string_new (NULL);
612
 
  GString * verbose_time = g_string_new (NULL);
613
 
  GString * accessible_time = g_string_new (NULL);
614
 
 
615
 
  if (time > 0)
616
 
    {
617
 
      char * readable_timestr = NULL;
618
 
      char * accessible_timestr = NULL;
619
 
      get_timestring (time, &readable_timestr, &accessible_timestr);
620
 
 
621
 
      if (state == UP_DEVICE_STATE_CHARGING)
622
 
        {
623
 
          g_string_assign (terse_time, readable_timestr);
624
 
          g_string_printf (verbose_time, _("%s to charge"), readable_timestr);
625
 
          g_string_printf (accessible_time, _("%s to charge"), accessible_timestr);
626
 
        }
627
 
      else if ((state == UP_DEVICE_STATE_DISCHARGING) && (time <= (60*60*24)))
628
 
        {
629
 
          g_string_assign (terse_time, readable_timestr);
630
 
          g_string_printf (verbose_time, _("%s left"), readable_timestr);
631
 
          g_string_printf (accessible_time, _("%s left"), accessible_timestr);
632
 
        }
 
536
/**
 
537
 * The '''brief time-remaining string''' for a component should be:
 
538
 *  * the time remaining for it to empty or fully charge,
 
539
 *    if estimable, in H:MM format; otherwise
 
540
 *  * “estimating…” if the time remaining has been inestimable for
 
541
 *    less than 30 seconds; otherwise
 
542
 *  * “unknown” if the time remaining has been inestimable for
 
543
 *    between 30 seconds and one minute; otherwise
 
544
 *  * the empty string.
 
545
 */
 
546
static char *
 
547
get_brief_time_remaining (const IndicatorPowerDevice * device)
 
548
{
 
549
  gchar * str = NULL;
 
550
  const IndicatorPowerDevicePrivate * p = device->priv;
 
551
 
 
552
  if (p->time > 0)
 
553
    {
 
554
      int minutes = p->time / 60;
 
555
      const int hours = minutes / 60;
 
556
      minutes %= 60;
 
557
 
 
558
      str = g_strdup_printf("%0d:%02d", hours, minutes);
 
559
    }
 
560
  else if (p->inestimable != NULL)
 
561
    {
 
562
      const double elapsed = g_timer_elapsed (p->inestimable, NULL);
 
563
 
 
564
      if (elapsed < 30)
 
565
        {
 
566
          str = g_strdup_printf (_("estimating…"));
 
567
        }
 
568
      else if (elapsed < 60)
 
569
        {
 
570
          str = g_strdup_printf (_("unknown"));
 
571
        }
 
572
    }
 
573
 
 
574
  return str;
 
575
}
 
576
 
 
577
/**
 
578
 * The '''expanded time-remaining string''' for a component should
 
579
 * be the same as the brief time-remaining string, except that if
 
580
 * the time is estimable:
 
581
 *  * if the component is charging, it should be “H:MM to charge”
 
582
 *  * if the component is discharging, it should be “H:MM left”.
 
583
 */
 
584
static char*
 
585
get_expanded_time_remaining (const IndicatorPowerDevice * device)
 
586
{
 
587
  char * str = NULL;
 
588
  const IndicatorPowerDevicePrivate * p = device->priv;
 
589
 
 
590
  if (p->time && ((p->state == UP_DEVICE_STATE_CHARGING) || (p->state == UP_DEVICE_STATE_DISCHARGING)))
 
591
    {
 
592
      int minutes = p->time / 60;
 
593
      const int hours = minutes / 60;
 
594
      minutes %= 60;
 
595
 
 
596
      if (p->state == UP_DEVICE_STATE_CHARGING)
 
597
        {
 
598
          str = g_strdup_printf (_("%0d:%02d to charge"), hours, minutes);
 
599
        }
 
600
      else // discharging
 
601
        {
 
602
          str = g_strdup_printf (_("%0d:%02d left"), hours, minutes);
 
603
        }
 
604
    }
 
605
  else
 
606
    {
 
607
      str = get_brief_time_remaining (device);
 
608
    }
 
609
 
 
610
  return str;
 
611
}
 
612
 
 
613
/**
 
614
 * The '''accessible time-remaining string''' for a component
 
615
 * should be the same as the expanded time-remaining string,
 
616
 * except the H:MM time should be rendered as “''H'' hours ''M'' minutes”,
 
617
 * or just as “''M'' minutes” if the time is less than one hour.
 
618
 */
 
619
static char *
 
620
get_accessible_time_remaining (const IndicatorPowerDevice * device)
 
621
{
 
622
  char * str = NULL;
 
623
  const IndicatorPowerDevicePrivate * p = device->priv;
 
624
 
 
625
  if (p->time && ((p->state == UP_DEVICE_STATE_CHARGING) || (p->state == UP_DEVICE_STATE_DISCHARGING)))
 
626
    {
 
627
      int minutes = p->time / 60;
 
628
      const int hours = minutes / 60;
 
629
      minutes %= 60;
 
630
 
 
631
      if (p->state == UP_DEVICE_STATE_CHARGING)
 
632
        {
 
633
          if (hours > 0)
 
634
            str = g_strdup_printf (_("%d %s %d %s to charge"),
 
635
                        hours, g_dngettext (NULL, "hour", "hours", hours),
 
636
                        minutes, g_dngettext (NULL, "minute", "minutes", minutes));
 
637
         else
 
638
            str = g_strdup_printf (_("%d %s to charge"),
 
639
                        minutes, g_dngettext (NULL, "minute", "minutes", minutes));
 
640
        }
 
641
      else // discharging
 
642
        {
 
643
          if (hours > 0)
 
644
            str = g_strdup_printf (_("%d %s %d %s left"),
 
645
                        hours, g_dngettext (NULL, "hour", "hours", hours),
 
646
                        minutes, g_dngettext (NULL, "minute", "minutes", minutes));
 
647
         else
 
648
            str = g_strdup_printf (_("%d %s left"),
 
649
                        minutes, g_dngettext (NULL, "minute", "minutes", minutes));
 
650
        }
 
651
    }
 
652
  else
 
653
    {
 
654
      str = get_brief_time_remaining (device);
 
655
    }
 
656
 
 
657
  return str;
 
658
}
 
659
 
 
660
/**
 
661
 * The time is relevant for a device if either (a) the component is charging,
 
662
 * or (b) the component is discharging and the estimated time is less than
 
663
 * 24 hours. (A time greater than 24 hours is probably a mistaken calculation.)
 
664
 */
 
665
static gboolean
 
666
time_is_relevant (const IndicatorPowerDevice * device)
 
667
{
 
668
  const IndicatorPowerDevicePrivate * p = device->priv;
 
669
 
 
670
  if (p->state == UP_DEVICE_STATE_CHARGING)
 
671
    return TRUE;
 
672
 
 
673
  if ((p->state == UP_DEVICE_STATE_DISCHARGING) && (p->time<(24*60*60)))
 
674
    return TRUE;
 
675
 
 
676
  return FALSE;
 
677
}
 
678
 
 
679
/**
 
680
 * The menu item for each chargeable component should consist of ...
 
681
 * Text representing the name of the component (“Battery”, “Mouse”,
 
682
 * “UPS”, “Alejandra’s iPod”, etc) and the charge status in brackets:
 
683
 *
 
684
 *  * “X (charged)” if it is fully charged and not discharging;
 
685
 *  * “X (expanded time-remaining string)” if it is charging,
 
686
 *    or discharging with less than 24 hours left;
 
687
 *  * “X” if it is discharging with 24 hours or more left. 
 
688
 *
 
689
 * The accessible label for the menu item should be the same as the
 
690
 * visible label, except with the accessible time-remaining string
 
691
 * instead of the expanded time-remaining string. 
 
692
 */
 
693
static char *
 
694
get_menuitem_text (const IndicatorPowerDevice * device,
 
695
                   gboolean                     accessible)
 
696
{
 
697
  char * str = NULL;
 
698
  const IndicatorPowerDevicePrivate * p = device->priv;
 
699
  const char * kind_str = device_kind_to_localised_string (p->kind);
 
700
 
 
701
  if (p->state == UP_DEVICE_STATE_FULLY_CHARGED)
 
702
    {
 
703
      str = g_strdup_printf (_("%s (charged)"), kind_str);
 
704
    }
 
705
  else
 
706
    {
 
707
      char * time_str = NULL;
 
708
 
 
709
      if (time_is_relevant (device))
 
710
        {
 
711
          if (accessible)
 
712
            time_str = get_accessible_time_remaining (device);
 
713
          else
 
714
            time_str = get_expanded_time_remaining (device);
 
715
        }
 
716
 
 
717
      if (time_str && *time_str)
 
718
        str = g_strdup_printf (_("%s (%s)"), kind_str, time_str);
633
719
      else
634
 
        {
635
 
          /* if there's more than 24 hours remaining, we don't show it */
636
 
        }
637
 
 
638
 
      g_free (readable_timestr);
639
 
      g_free (accessible_timestr);
640
 
    }
641
 
  else if (state == UP_DEVICE_STATE_FULLY_CHARGED)
642
 
    {
643
 
      g_string_assign (verbose_time,    _("charged"));
644
 
      g_string_assign (accessible_time, _("charged"));
645
 
    }
646
 
  else if (percentage > 0)
647
 
    {
648
 
      g_string_assign (terse_time,      _("estimating…"));
649
 
      g_string_assign (verbose_time,    _("estimating…"));
650
 
      g_string_assign (accessible_time, _("estimating…"));
 
720
        str = g_strdup (kind_str);
 
721
 
 
722
      g_free (time_str);
 
723
    }
 
724
 
 
725
  return str;
 
726
}
 
727
 
 
728
char *
 
729
indicator_power_device_get_readable_text (const IndicatorPowerDevice * device)
 
730
{
 
731
  g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL);
 
732
 
 
733
  return get_menuitem_text (device, FALSE);
 
734
}
 
735
 
 
736
char *
 
737
indicator_power_device_get_accessible_text (const IndicatorPowerDevice * device)
 
738
{
 
739
  g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL);
 
740
 
 
741
  return get_menuitem_text (device, TRUE);
 
742
}
 
743
 
 
744
/**
 
745
 * If the time is relevant and/or “Show Percentage in Menu Bar” is checked,
 
746
 * the icon should be followed by brackets.
 
747
 *
 
748
 * If the time is relevant, the brackets should contain the time-remaining
 
749
 * string for that component.
 
750
 *
 
751
 * If “Show Percentage in Menu Bar” is checked (as it should not be by default),
 
752
 * the brackets should contain the percentage charge for that device.
 
753
 *
 
754
 * If both conditions are true, the time and percentage should be separated by a space. 
 
755
 */
 
756
char*
 
757
indicator_power_device_get_readable_title (const IndicatorPowerDevice * device,
 
758
                                           gboolean                     want_time,
 
759
                                           gboolean                     want_percent)
 
760
{
 
761
  char * str = NULL;
 
762
  char * time_str = NULL;
 
763
  const IndicatorPowerDevicePrivate * p;
 
764
 
 
765
  g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL);
 
766
 
 
767
  p = device->priv;
 
768
 
 
769
  // if we can't provide time-remaining, turn off the time flag
 
770
  if (want_time && !time_is_relevant (device))
 
771
    want_time = FALSE;
 
772
 
 
773
  // if we can't provide percent, turn off the percent flag
 
774
  if (p->percentage < 0.01)
 
775
    want_percent = FALSE;
 
776
 
 
777
  // try to build the time-remaining string
 
778
  if (want_time)
 
779
    {
 
780
      time_str = get_brief_time_remaining (device);
 
781
      want_time = time_str && *time_str;
 
782
    }
 
783
 
 
784
  if (want_time && want_percent)
 
785
    {
 
786
      str = g_strdup_printf (_("(%s, %.0lf%%)"), time_str, p->percentage);
 
787
    }
 
788
  else if (want_time)
 
789
    {
 
790
      str = g_strdup_printf (_("(%s)"), time_str);
 
791
    }
 
792
  else if (want_percent)
 
793
    {
 
794
      str = g_strdup_printf (_("(%.0lf%%)"), p->percentage);
651
795
    }
652
796
  else
653
797
    {
654
 
      *pctstr = '\0';
655
 
 
656
 
      if (kind != UP_DEVICE_KIND_LINE_POWER)
657
 
        {
658
 
          g_string_assign (verbose_time,    _("not present"));
659
 
          g_string_assign (accessible_time, _("not present"));
660
 
        }
 
798
      str = NULL;
661
799
    }
662
800
 
663
 
  if (header != NULL)
664
 
    *header = join_strings (NULL,
665
 
                            show_time_in_header ? terse_time->str : "",
666
 
                            show_percentage_in_header ? pctstr : "");
667
 
 
668
 
  if (label != NULL)
669
 
    *label  = join_strings (device_name,
670
 
                            verbose_time->str,
671
 
                            NULL);
672
 
 
673
 
  if (a11y != NULL)
674
 
    *a11y   = join_strings (device_name,
675
 
                            accessible_time->str,
676
 
                            pctstr);
677
 
 
678
 
  g_string_free (terse_time, TRUE);
679
 
  g_string_free (verbose_time, TRUE);
680
 
  g_string_free (accessible_time, TRUE);
681
 
}
682
 
 
683
 
gchar *
684
 
indicator_power_device_get_label (const IndicatorPowerDevice * device)
685
 
{
686
 
  gchar * label = NULL;
687
 
  indicator_power_device_get_text (device, FALSE, FALSE,
688
 
                                   NULL, &label, NULL);
689
 
  return label;
690
 
}
691
 
 
692
 
void
693
 
indicator_power_device_get_header (const IndicatorPowerDevice * device,
694
 
                                   gboolean                     show_time,
695
 
                                   gboolean                     show_percentage,
696
 
                                   gchar                     ** header,
697
 
                                   gchar                     ** a11y)
698
 
{
699
 
  indicator_power_device_get_text (device, show_time, show_percentage,
700
 
                                   header, NULL, a11y);
 
801
  g_free (time_str);
 
802
  return str;
 
803
}
 
804
 
 
805
/**
 
806
 * Regardless, the accessible name for the whole menu title should be the same
 
807
 * as the accessible name for that thing’s component inside the menu itself. 
 
808
 */
 
809
char *
 
810
indicator_power_device_get_accessible_title (const IndicatorPowerDevice * device,
 
811
                                             gboolean                     want_time G_GNUC_UNUSED,
 
812
                                             gboolean                     want_percent G_GNUC_UNUSED)
 
813
{
 
814
  g_return_val_if_fail (INDICATOR_IS_POWER_DEVICE(device), NULL);
 
815
 
 
816
  return indicator_power_device_get_accessible_text (device);
701
817
}
702
818
 
703
819
/***