2011-09-28 09:16:26 -04:00
|
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
|
/* exported DateMenuButton */
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
2023-06-08 00:52:46 -04:00
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
|
|
|
const Gio = imports.gi.Gio;
|
|
|
|
|
const GLib = imports.gi.GLib;
|
|
|
|
|
const GnomeDesktop = imports.gi.GnomeDesktop;
|
|
|
|
|
const GObject = imports.gi.GObject;
|
|
|
|
|
const GWeather = imports.gi.GWeather;
|
|
|
|
|
const Pango = imports.gi.Pango;
|
|
|
|
|
const Shell = imports.gi.Shell;
|
|
|
|
|
const St = imports.gi.St;
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
|
const PanelMenu = imports.ui.panelMenu;
|
|
|
|
|
const Calendar = imports.ui.calendar;
|
2017-02-24 07:15:39 -05:00
|
|
|
|
const Weather = imports.misc.weather;
|
2023-07-07 20:58:11 -04:00
|
|
|
|
const DateUtils = imports.misc.dateUtils;
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
2023-07-07 20:58:11 -04:00
|
|
|
|
const {formatDateWithCFormatString, formatTime} = imports.misc.dateUtils;
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const {loadInterfaceXML} = imports.misc.fileUtils;
|
2019-07-31 19:24:13 -04:00
|
|
|
|
|
2022-02-07 09:14:06 -05:00
|
|
|
|
const NC_ = (context, str) => `${context}\u0004${str}`;
|
2020-05-14 16:24:36 -04:00
|
|
|
|
const T_ = Shell.util_translate_time_string;
|
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
const MAX_FORECASTS = 5;
|
2022-02-07 06:24:51 -05:00
|
|
|
|
const EN_CHAR = '\u2013';
|
2018-12-13 08:45:22 -05:00
|
|
|
|
|
2019-07-31 19:24:13 -04:00
|
|
|
|
const ClocksIntegrationIface = loadInterfaceXML('org.gnome.Shell.ClocksIntegration');
|
|
|
|
|
const ClocksProxy = Gio.DBusProxy.makeProxyWrapper(ClocksIntegrationIface);
|
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
*
|
|
|
|
|
* @param {Date} date a Date
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
2015-02-13 10:25:21 -05:00
|
|
|
|
function _isToday(date) {
|
|
|
|
|
let now = new Date();
|
2023-07-07 16:12:49 -04:00
|
|
|
|
return now.getFullYear() === date.getFullYear() &&
|
|
|
|
|
now.getMonth() === date.getMonth() &&
|
|
|
|
|
now.getDate() === date.getDate();
|
2015-02-13 10:25:21 -05:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
*
|
|
|
|
|
* @param {GLib.DateTime} datetime a GLib.DateTime
|
|
|
|
|
* @returns {Date}
|
|
|
|
|
*/
|
2019-09-02 15:18:54 -04:00
|
|
|
|
function _gDateTimeToDate(datetime) {
|
|
|
|
|
return new Date(datetime.to_unix() * 1000 + datetime.get_microsecond() / 1000);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
var TodayButton = GObject.registerClass(
|
|
|
|
|
class TodayButton extends St.Button {
|
|
|
|
|
_init(calendar) {
|
2014-12-05 10:03:59 -05:00
|
|
|
|
// Having the ability to go to the current date if the user is already
|
|
|
|
|
// on the current date can be confusing. So don't make the button reactive
|
|
|
|
|
// until the selected date changes.
|
2019-07-16 05:24:13 -04:00
|
|
|
|
super._init({
|
2019-02-12 09:02:09 -05:00
|
|
|
|
style_class: 'datemenu-today-button',
|
|
|
|
|
x_expand: true,
|
|
|
|
|
can_focus: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
reactive: false,
|
2019-02-12 09:02:09 -05:00
|
|
|
|
});
|
2014-12-05 10:03:59 -05:00
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const hbox = new St.BoxLayout({vertical: true});
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.add_actor(hbox);
|
2014-12-05 10:03:59 -05:00
|
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
|
this._dayLabel = new St.Label({
|
|
|
|
|
style_class: 'day-label',
|
|
|
|
|
x_align: Clutter.ActorAlign.START,
|
|
|
|
|
});
|
2014-12-05 10:03:59 -05:00
|
|
|
|
hbox.add_actor(this._dayLabel);
|
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
this._dateLabel = new St.Label({style_class: 'date-label'});
|
2014-12-05 10:03:59 -05:00
|
|
|
|
hbox.add_actor(this._dateLabel);
|
|
|
|
|
|
|
|
|
|
this._calendar = calendar;
|
2019-09-02 15:18:54 -04:00
|
|
|
|
this._calendar.connect('selected-date-changed', (_calendar, datetime) => {
|
2017-10-30 20:38:18 -04:00
|
|
|
|
// Make the button reactive only if the selected date is not the
|
|
|
|
|
// current date.
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.reactive = !_isToday(_gDateTimeToDate(datetime));
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2014-12-05 10:03:59 -05:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_clicked() {
|
|
|
|
|
this._calendar.setDate(new Date(), false);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
setDate(date) {
|
2023-07-07 20:58:11 -04:00
|
|
|
|
this._dayLabel.set_text(formatDateWithCFormatString(date, '%A'));
|
2014-12-05 10:03:59 -05:00
|
|
|
|
|
|
|
|
|
/* Translators: This is the date format to use when the calendar popup is
|
2018-10-15 10:43:36 -04:00
|
|
|
|
* shown - it is shown just below the time in the top bar (e.g.,
|
|
|
|
|
* "Tue 9:29 AM"). The string itself should become a full date, e.g.,
|
|
|
|
|
* "February 17 2015".
|
2014-12-05 10:03:59 -05:00
|
|
|
|
*/
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const dateFormat = Shell.util_translate_time_string(N_('%B %-d %Y'));
|
2023-07-07 20:58:11 -04:00
|
|
|
|
this._dateLabel.set_text(formatDateWithCFormatString(date, dateFormat));
|
2014-12-05 10:03:59 -05:00
|
|
|
|
|
|
|
|
|
/* Translators: This is the accessible name of the date button shown
|
|
|
|
|
* below the time in the shell; it should combine the weekday and the
|
|
|
|
|
* date, e.g. "Tuesday February 17 2015".
|
|
|
|
|
*/
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const dateAccessibleNameFormat = Shell.util_translate_time_string(N_('%A %B %e %Y'));
|
2023-07-07 20:58:11 -04:00
|
|
|
|
this.accessible_name = formatDateWithCFormatString(date, dateAccessibleNameFormat);
|
2014-12-05 10:03:59 -05:00
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2020-05-14 16:24:36 -04:00
|
|
|
|
var EventsSection = GObject.registerClass(
|
|
|
|
|
class EventsSection extends St.Button {
|
|
|
|
|
_init() {
|
|
|
|
|
super._init({
|
|
|
|
|
style_class: 'events-button',
|
|
|
|
|
can_focus: true,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
child: new St.BoxLayout({
|
|
|
|
|
style_class: 'events-box',
|
|
|
|
|
vertical: true,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this._startDate = null;
|
|
|
|
|
this._endDate = null;
|
|
|
|
|
|
|
|
|
|
this._eventSource = null;
|
|
|
|
|
this._calendarApp = null;
|
|
|
|
|
|
|
|
|
|
this._title = new St.Label({
|
|
|
|
|
style_class: 'events-title',
|
|
|
|
|
});
|
|
|
|
|
this.child.add_child(this._title);
|
|
|
|
|
|
|
|
|
|
this._eventsList = new St.BoxLayout({
|
|
|
|
|
style_class: 'events-list',
|
|
|
|
|
vertical: true,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
});
|
|
|
|
|
this.child.add_child(this._eventsList);
|
|
|
|
|
|
|
|
|
|
this._appSys = Shell.AppSystem.get_default();
|
|
|
|
|
this._appSys.connect('installed-changed',
|
|
|
|
|
this._appInstalledChanged.bind(this));
|
|
|
|
|
this._appInstalledChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setDate(date) {
|
2021-11-04 07:42:52 -04:00
|
|
|
|
this._startDate =
|
|
|
|
|
new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
|
|
|
this._endDate =
|
|
|
|
|
new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
|
2020-05-14 16:24:36 -04:00
|
|
|
|
|
|
|
|
|
this._updateTitle();
|
|
|
|
|
this._reloadEvents();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setEventSource(eventSource) {
|
|
|
|
|
if (!(eventSource instanceof Calendar.EventSourceBase))
|
|
|
|
|
throw new Error('Event source is not valid type');
|
|
|
|
|
|
|
|
|
|
this._eventSource = eventSource;
|
|
|
|
|
this._eventSource.connect('changed', this._reloadEvents.bind(this));
|
|
|
|
|
this._eventSource.connect('notify::has-calendars',
|
|
|
|
|
this._sync.bind(this));
|
|
|
|
|
this._sync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updateTitle() {
|
|
|
|
|
/* Translators: Shown on calendar heading when selected day occurs on current year */
|
|
|
|
|
const sameYearFormat = T_(NC_('calendar heading', '%B %-d'));
|
|
|
|
|
|
|
|
|
|
/* Translators: Shown on calendar heading when selected day occurs on different year */
|
|
|
|
|
const otherYearFormat = T_(NC_('calendar heading', '%B %-d %Y'));
|
|
|
|
|
|
|
|
|
|
const timeSpanDay = GLib.TIME_SPAN_DAY / 1000;
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
2021-11-04 07:42:52 -04:00
|
|
|
|
if (this._startDate <= now && now < this._endDate)
|
2020-05-14 16:24:36 -04:00
|
|
|
|
this._title.text = _('Today');
|
2021-11-04 07:42:52 -04:00
|
|
|
|
else if (this._endDate <= now && now - this._endDate < timeSpanDay)
|
2020-05-14 16:24:36 -04:00
|
|
|
|
this._title.text = _('Yesterday');
|
2021-11-04 07:42:52 -04:00
|
|
|
|
else if (this._startDate > now && this._startDate - now <= timeSpanDay)
|
2020-05-14 16:24:36 -04:00
|
|
|
|
this._title.text = _('Tomorrow');
|
|
|
|
|
else if (this._startDate.getFullYear() === now.getFullYear())
|
2023-07-07 20:58:11 -04:00
|
|
|
|
this._title.text = formatDateWithCFormatString(this._startDate, sameYearFormat);
|
2020-05-14 16:24:36 -04:00
|
|
|
|
else
|
2023-07-07 20:58:11 -04:00
|
|
|
|
this._title.text = formatDateWithCFormatString(this._startDate, otherYearFormat);
|
2020-05-14 16:24:36 -04:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-07 06:24:51 -05:00
|
|
|
|
_isAtMidnight(eventTime) {
|
|
|
|
|
return eventTime.getHours() === 0 && eventTime.getMinutes() === 0 && eventTime.getSeconds() === 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-14 16:24:36 -04:00
|
|
|
|
_formatEventTime(event) {
|
2022-02-07 06:24:51 -05:00
|
|
|
|
const eventStart = event.date;
|
2022-02-15 05:21:48 -05:00
|
|
|
|
let eventEnd = event.end;
|
2022-02-07 06:24:51 -05:00
|
|
|
|
|
2021-11-04 07:48:26 -04:00
|
|
|
|
const allDay =
|
2022-02-07 06:24:51 -05:00
|
|
|
|
eventStart.getTime() === this._startDate.getTime() && eventEnd.getTime() === this._endDate.getTime();
|
|
|
|
|
|
|
|
|
|
const startsBeforeToday = eventStart < this._startDate;
|
|
|
|
|
const endsAfterToday = eventEnd > this._endDate;
|
|
|
|
|
|
2023-07-07 20:58:11 -04:00
|
|
|
|
const startTimeOnly = formatTime(eventStart, {timeOnly: true});
|
|
|
|
|
const endTimeOnly = formatTime(eventEnd, {timeOnly: true});
|
2022-02-07 06:24:51 -05:00
|
|
|
|
|
|
|
|
|
const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
|
2020-05-14 16:24:36 -04:00
|
|
|
|
|
|
|
|
|
let title;
|
|
|
|
|
if (allDay) {
|
|
|
|
|
/* Translators: Shown in calendar event list for all day events
|
|
|
|
|
* Keep it short, best if you can use less then 10 characters
|
|
|
|
|
*/
|
|
|
|
|
title = C_('event list time', 'All Day');
|
2022-02-07 06:24:51 -05:00
|
|
|
|
} else if (startsBeforeToday || endsAfterToday) {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const thisYear = now.getFullYear();
|
2020-05-14 16:24:36 -04:00
|
|
|
|
|
2022-02-07 06:24:51 -05:00
|
|
|
|
const startsAtMidnight = this._isAtMidnight(eventStart);
|
|
|
|
|
const endsAtMidnight = this._isAtMidnight(eventEnd);
|
|
|
|
|
|
|
|
|
|
const startYear = eventStart.getFullYear();
|
|
|
|
|
|
2022-02-15 05:21:48 -05:00
|
|
|
|
if (endsAtMidnight) {
|
|
|
|
|
eventEnd = new Date(eventEnd);
|
2022-02-07 06:24:51 -05:00
|
|
|
|
eventEnd.setDate(eventEnd.getDate() - 1);
|
2022-02-15 05:21:48 -05:00
|
|
|
|
}
|
2022-02-07 06:24:51 -05:00
|
|
|
|
|
|
|
|
|
const endYear = eventEnd.getFullYear();
|
|
|
|
|
|
|
|
|
|
let format;
|
|
|
|
|
if (startYear === thisYear && thisYear === endYear)
|
|
|
|
|
/* Translators: Shown in calendar event list as the start/end of events
|
|
|
|
|
* that only show day and month
|
|
|
|
|
*/
|
2022-03-19 16:15:50 -04:00
|
|
|
|
format = T_(N_('%m/%d'));
|
2020-05-14 16:24:36 -04:00
|
|
|
|
else
|
2022-02-07 06:24:51 -05:00
|
|
|
|
format = '%x';
|
|
|
|
|
|
2023-07-07 20:58:11 -04:00
|
|
|
|
const startDateOnly = formatDateWithCFormatString(eventStart, format);
|
|
|
|
|
const endDateOnly = formatDateWithCFormatString(eventEnd, format);
|
2022-02-07 06:24:51 -05:00
|
|
|
|
|
|
|
|
|
if (startsAtMidnight && endsAtMidnight)
|
|
|
|
|
title = `${rtl ? endDateOnly : startDateOnly} ${EN_CHAR} ${rtl ? startDateOnly : endDateOnly}`;
|
|
|
|
|
else if (rtl)
|
|
|
|
|
title = `${endTimeOnly} ${endDateOnly} ${EN_CHAR} ${startTimeOnly} ${startDateOnly}`;
|
2020-05-14 16:24:36 -04:00
|
|
|
|
else
|
2022-02-07 06:24:51 -05:00
|
|
|
|
title = `${startDateOnly} ${startTimeOnly} ${EN_CHAR} ${endDateOnly} ${endTimeOnly}`;
|
|
|
|
|
} else if (eventStart === eventEnd) {
|
|
|
|
|
title = startTimeOnly;
|
|
|
|
|
} else {
|
|
|
|
|
title = `${rtl ? endTimeOnly : startTimeOnly} ${EN_CHAR} ${rtl ? startTimeOnly : endTimeOnly}`;
|
2020-05-14 16:24:36 -04:00
|
|
|
|
}
|
2022-02-07 06:24:51 -05:00
|
|
|
|
|
2020-05-14 16:24:36 -04:00
|
|
|
|
return title;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_reloadEvents() {
|
|
|
|
|
if (this._eventSource.isLoading || this._reloading)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._reloading = true;
|
|
|
|
|
|
|
|
|
|
[...this._eventsList].forEach(c => c.destroy());
|
|
|
|
|
|
|
|
|
|
const events =
|
|
|
|
|
this._eventSource.getEvents(this._startDate, this._endDate);
|
|
|
|
|
|
|
|
|
|
for (let event of events) {
|
|
|
|
|
const box = new St.BoxLayout({
|
|
|
|
|
style_class: 'event-box',
|
|
|
|
|
vertical: true,
|
|
|
|
|
});
|
|
|
|
|
box.add(new St.Label({
|
|
|
|
|
text: event.summary,
|
|
|
|
|
style_class: 'event-summary',
|
|
|
|
|
}));
|
|
|
|
|
box.add(new St.Label({
|
|
|
|
|
text: this._formatEventTime(event),
|
|
|
|
|
style_class: 'event-time',
|
|
|
|
|
}));
|
|
|
|
|
this._eventsList.add_child(box);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._eventsList.get_n_children() === 0) {
|
|
|
|
|
const placeholder = new St.Label({
|
|
|
|
|
text: _('No Events'),
|
|
|
|
|
style_class: 'event-placeholder',
|
|
|
|
|
});
|
|
|
|
|
this._eventsList.add_child(placeholder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._reloading = false;
|
|
|
|
|
this._sync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_clicked() {
|
|
|
|
|
Main.overview.hide();
|
|
|
|
|
Main.panel.closeCalendar();
|
|
|
|
|
|
2022-09-07 18:14:24 -04:00
|
|
|
|
const appInfo = this._calendarApp;
|
|
|
|
|
const context = global.create_app_launch_context(0, -1);
|
|
|
|
|
if (appInfo.get_id() === 'org.gnome.Evolution.desktop')
|
|
|
|
|
appInfo.launch_action('calendar', context);
|
|
|
|
|
else
|
|
|
|
|
appInfo.launch([], context);
|
2020-05-14 16:24:36 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_appInstalledChanged() {
|
|
|
|
|
const apps = Gio.AppInfo.get_recommended_for_type('text/calendar');
|
|
|
|
|
if (apps && (apps.length > 0)) {
|
|
|
|
|
const app = Gio.AppInfo.get_default_for_type('text/calendar', false);
|
|
|
|
|
const defaultInRecommended = apps.some(a => a.equal(app));
|
|
|
|
|
this._calendarApp = defaultInRecommended ? app : apps[0];
|
|
|
|
|
} else {
|
|
|
|
|
this._calendarApp = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this._sync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_sync() {
|
|
|
|
|
this.visible = this._eventSource && this._eventSource.hasCalendars;
|
|
|
|
|
this.reactive = this._calendarApp !== null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
var WorldClocksSection = GObject.registerClass(
|
|
|
|
|
class WorldClocksSection extends St.Button {
|
|
|
|
|
_init() {
|
|
|
|
|
super._init({
|
|
|
|
|
style_class: 'world-clocks-button',
|
2019-10-21 14:44:00 -04:00
|
|
|
|
can_focus: true,
|
|
|
|
|
x_expand: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2015-02-06 18:47:27 -05:00
|
|
|
|
this._clock = new GnomeDesktop.WallClock();
|
|
|
|
|
this._clockNotifyId = 0;
|
2020-05-14 18:04:12 -04:00
|
|
|
|
this._tzNotifyId = 0;
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
|
|
|
|
this._locations = [];
|
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const layout = new Clutter.GridLayout({orientation: Clutter.Orientation.VERTICAL});
|
2020-03-29 17:51:13 -04:00
|
|
|
|
this._grid = new St.Widget({
|
|
|
|
|
style_class: 'world-clocks-grid',
|
|
|
|
|
x_expand: true,
|
|
|
|
|
layout_manager: layout,
|
|
|
|
|
});
|
2015-02-06 18:47:27 -05:00
|
|
|
|
layout.hookup_style(this._grid);
|
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.child = this._grid;
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2019-07-31 19:24:13 -04:00
|
|
|
|
this._clocksApp = null;
|
|
|
|
|
this._clocksProxy = new ClocksProxy(
|
|
|
|
|
Gio.DBus.session,
|
|
|
|
|
'org.gnome.clocks',
|
|
|
|
|
'/org/gnome/clocks',
|
|
|
|
|
this._onProxyReady.bind(this),
|
|
|
|
|
null /* cancellable */,
|
|
|
|
|
Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
|
|
|
|
|
|
|
|
|
|
this._settings = new Gio.Settings({
|
2019-08-20 17:43:54 -04:00
|
|
|
|
schema_id: 'org.gnome.shell.world-clocks',
|
2019-07-31 19:24:13 -04:00
|
|
|
|
});
|
|
|
|
|
this._settings.connect('changed', this._clocksChanged.bind(this));
|
|
|
|
|
this._clocksChanged();
|
|
|
|
|
|
|
|
|
|
this._appSystem = Shell.AppSystem.get_default();
|
|
|
|
|
this._appSystem.connect('installed-changed',
|
|
|
|
|
this._sync.bind(this));
|
2015-02-06 18:47:27 -05:00
|
|
|
|
this._sync();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_clicked() {
|
|
|
|
|
if (this._clocksApp)
|
|
|
|
|
this._clocksApp.activate();
|
|
|
|
|
|
|
|
|
|
Main.overview.hide();
|
|
|
|
|
Main.panel.closeCalendar();
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_sync() {
|
2019-07-31 19:24:13 -04:00
|
|
|
|
this._clocksApp = this._appSystem.lookup_app('org.gnome.clocks.desktop');
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.visible = this._clocksApp != null;
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2019-07-31 19:24:13 -04:00
|
|
|
|
_clocksChanged() {
|
2015-02-06 18:47:27 -05:00
|
|
|
|
this._grid.destroy_all_children();
|
|
|
|
|
this._locations = [];
|
|
|
|
|
|
|
|
|
|
let world = GWeather.Location.get_world();
|
2022-08-10 05:56:14 -04:00
|
|
|
|
let clocks = this._settings.get_value('locations').deepUnpack();
|
2015-02-06 18:47:27 -05:00
|
|
|
|
for (let i = 0; i < clocks.length; i++) {
|
2019-07-31 19:24:13 -04:00
|
|
|
|
let l = world.deserialize(clocks[i]);
|
2019-04-28 06:33:41 -04:00
|
|
|
|
if (l && l.get_timezone() != null)
|
2023-07-07 16:12:49 -04:00
|
|
|
|
this._locations.push({location: l});
|
2015-02-06 18:47:27 -05:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 21:18:08 -05:00
|
|
|
|
const unixtime = GLib.DateTime.new_now_local().to_unix();
|
2017-10-30 20:38:18 -04:00
|
|
|
|
this._locations.sort((a, b) => {
|
2022-01-04 21:18:08 -05:00
|
|
|
|
const tzA = a.location.get_timezone();
|
|
|
|
|
const tzB = b.location.get_timezone();
|
|
|
|
|
const intA = tzA.find_interval(GLib.TimeType.STANDARD, unixtime);
|
2022-02-18 08:26:23 -05:00
|
|
|
|
const intB = tzB.find_interval(GLib.TimeType.STANDARD, unixtime);
|
2022-01-04 21:18:08 -05:00
|
|
|
|
return tzA.get_offset(intA) - tzB.get_offset(intB);
|
2015-02-06 18:47:27 -05:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let layout = this._grid.layout_manager;
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const title = this._locations.length === 0
|
|
|
|
|
? _('Add world clocks…')
|
|
|
|
|
: _('World Clocks');
|
2020-03-29 17:51:13 -04:00
|
|
|
|
const header = new St.Label({
|
|
|
|
|
style_class: 'world-clocks-header',
|
|
|
|
|
x_align: Clutter.ActorAlign.START,
|
|
|
|
|
text: title,
|
|
|
|
|
});
|
2022-03-11 05:10:24 -05:00
|
|
|
|
if (this._grid.text_direction === Clutter.TextDirection.RTL)
|
|
|
|
|
layout.attach(header, 2, 0, 1, 1);
|
|
|
|
|
else
|
|
|
|
|
layout.attach(header, 0, 0, 2, 1);
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.label_actor = header;
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
|
|
|
|
for (let i = 0; i < this._locations.length; i++) {
|
|
|
|
|
let l = this._locations[i].location;
|
|
|
|
|
|
2019-04-11 20:10:04 -04:00
|
|
|
|
let name = l.get_city_name() || l.get_name();
|
2020-03-29 17:51:13 -04:00
|
|
|
|
const label = new St.Label({
|
|
|
|
|
style_class: 'world-clocks-city',
|
|
|
|
|
text: name,
|
|
|
|
|
x_align: Clutter.ActorAlign.START,
|
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
});
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const time = new St.Label({style_class: 'world-clocks-time'});
|
2018-12-13 11:44:28 -05:00
|
|
|
|
|
2020-03-19 15:48:12 -04:00
|
|
|
|
const tz = new St.Label({
|
|
|
|
|
style_class: 'world-clocks-timezone',
|
|
|
|
|
x_align: Clutter.ActorAlign.END,
|
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
});
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2020-03-13 10:37:42 -04:00
|
|
|
|
time.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
|
tz.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
if (this._grid.text_direction === Clutter.TextDirection.RTL) {
|
2018-12-13 11:44:28 -05:00
|
|
|
|
layout.attach(tz, 0, i + 1, 1, 1);
|
|
|
|
|
layout.attach(time, 1, i + 1, 1, 1);
|
|
|
|
|
layout.attach(label, 2, i + 1, 1, 1);
|
2015-02-06 18:47:27 -05:00
|
|
|
|
} else {
|
|
|
|
|
layout.attach(label, 0, i + 1, 1, 1);
|
|
|
|
|
layout.attach(time, 1, i + 1, 1, 1);
|
2018-12-13 11:44:28 -05:00
|
|
|
|
layout.attach(tz, 2, i + 1, 1, 1);
|
2015-02-06 18:47:27 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-14 18:04:12 -04:00
|
|
|
|
this._locations[i].timeLabel = time;
|
|
|
|
|
this._locations[i].tzLabel = tz;
|
2015-02-06 18:47:27 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this._grid.get_n_children() > 1) {
|
2019-08-19 20:51:42 -04:00
|
|
|
|
if (!this._clockNotifyId) {
|
2015-02-06 18:47:27 -05:00
|
|
|
|
this._clockNotifyId =
|
2020-05-14 18:04:12 -04:00
|
|
|
|
this._clock.connect('notify::clock', this._updateTimeLabels.bind(this));
|
2019-08-19 20:51:42 -04:00
|
|
|
|
}
|
2020-05-14 18:04:12 -04:00
|
|
|
|
if (!this._tzNotifyId) {
|
|
|
|
|
this._tzNotifyId =
|
|
|
|
|
this._clock.connect('notify::timezone', this._updateTimezoneLabels.bind(this));
|
|
|
|
|
}
|
|
|
|
|
this._updateTimeLabels();
|
|
|
|
|
this._updateTimezoneLabels();
|
2015-02-06 18:47:27 -05:00
|
|
|
|
} else {
|
|
|
|
|
if (this._clockNotifyId)
|
|
|
|
|
this._clock.disconnect(this._clockNotifyId);
|
|
|
|
|
this._clockNotifyId = 0;
|
2020-05-14 18:04:12 -04:00
|
|
|
|
|
|
|
|
|
if (this._tzNotifyId)
|
|
|
|
|
this._clock.disconnect(this._tzNotifyId);
|
|
|
|
|
this._tzNotifyId = 0;
|
2015-02-06 18:47:27 -05:00
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2020-05-14 18:04:12 -04:00
|
|
|
|
_getTimezoneOffsetAtLocation(location) {
|
2022-01-04 21:18:08 -05:00
|
|
|
|
const tz = location.get_timezone();
|
2020-05-14 18:04:12 -04:00
|
|
|
|
const localOffset = GLib.DateTime.new_now_local().get_utc_offset();
|
2022-01-04 21:18:08 -05:00
|
|
|
|
const utcOffset = GLib.DateTime.new_now(tz).get_utc_offset();
|
2020-05-14 18:04:12 -04:00
|
|
|
|
const offsetCurrentTz = utcOffset - localOffset;
|
2022-03-21 14:24:25 -04:00
|
|
|
|
const offsetHours =
|
|
|
|
|
Math.floor(Math.abs(offsetCurrentTz) / GLib.TIME_SPAN_HOUR);
|
2020-05-14 18:04:12 -04:00
|
|
|
|
const offsetMinutes =
|
|
|
|
|
(Math.abs(offsetCurrentTz) % GLib.TIME_SPAN_HOUR) /
|
|
|
|
|
GLib.TIME_SPAN_MINUTE;
|
|
|
|
|
|
|
|
|
|
const prefix = offsetCurrentTz >= 0 ? '+' : '-';
|
|
|
|
|
const text = offsetMinutes === 0
|
2022-02-07 09:14:06 -05:00
|
|
|
|
? `${prefix}${offsetHours}`
|
|
|
|
|
: `${prefix}${offsetHours}\u2236${offsetMinutes}`;
|
2020-05-14 18:04:12 -04:00
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updateTimeLabels() {
|
2015-02-06 18:47:27 -05:00
|
|
|
|
for (let i = 0; i < this._locations.length; i++) {
|
|
|
|
|
let l = this._locations[i];
|
2022-01-04 21:18:08 -05:00
|
|
|
|
const now = GLib.DateTime.new_now(l.location.get_timezone());
|
2023-07-07 20:58:11 -04:00
|
|
|
|
l.timeLabel.text = formatTime(now, {timeOnly: true});
|
2020-05-14 18:04:12 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updateTimezoneLabels() {
|
|
|
|
|
for (let i = 0; i < this._locations.length; i++) {
|
|
|
|
|
let l = this._locations[i];
|
|
|
|
|
l.tzLabel.text = this._getTimezoneOffsetAtLocation(l.location);
|
2015-02-06 18:47:27 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-31 19:24:13 -04:00
|
|
|
|
|
|
|
|
|
_onProxyReady(proxy, error) {
|
|
|
|
|
if (error) {
|
2022-02-07 09:14:06 -05:00
|
|
|
|
log(`Failed to create GNOME Clocks proxy: ${error}`);
|
2019-07-31 19:24:13 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._clocksProxy.connect('g-properties-changed',
|
|
|
|
|
this._onClocksPropertiesChanged.bind(this));
|
|
|
|
|
this._onClocksPropertiesChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onClocksPropertiesChanged() {
|
|
|
|
|
if (this._clocksProxy.g_name_owner == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._settings.set_value('locations',
|
|
|
|
|
new GLib.Variant('av', this._clocksProxy.Locations));
|
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var WeatherSection = GObject.registerClass(
|
|
|
|
|
class WeatherSection extends St.Button {
|
|
|
|
|
_init() {
|
|
|
|
|
super._init({
|
|
|
|
|
style_class: 'weather-button',
|
2019-10-21 14:44:00 -04:00
|
|
|
|
can_focus: true,
|
|
|
|
|
x_expand: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2015-02-06 18:47:27 -05:00
|
|
|
|
|
2017-02-24 07:15:39 -05:00
|
|
|
|
this._weatherClient = new Weather.WeatherClient();
|
|
|
|
|
|
2019-10-17 17:40:24 -04:00
|
|
|
|
let box = new St.BoxLayout({
|
|
|
|
|
style_class: 'weather-box',
|
|
|
|
|
vertical: true,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
});
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.child = box;
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
let titleBox = new St.BoxLayout({style_class: 'weather-header-box'});
|
2020-04-05 03:39:02 -04:00
|
|
|
|
this._titleLabel = new St.Label({
|
2019-11-22 09:41:19 -05:00
|
|
|
|
style_class: 'weather-header',
|
|
|
|
|
x_align: Clutter.ActorAlign.START,
|
|
|
|
|
x_expand: true,
|
2019-11-20 15:48:03 -05:00
|
|
|
|
y_align: Clutter.ActorAlign.END,
|
2020-04-05 03:39:02 -04:00
|
|
|
|
});
|
|
|
|
|
titleBox.add_child(this._titleLabel);
|
2019-01-11 11:42:22 -05:00
|
|
|
|
box.add_child(titleBox);
|
|
|
|
|
|
2019-11-22 09:41:19 -05:00
|
|
|
|
this._titleLocation = new St.Label({
|
|
|
|
|
style_class: 'weather-header location',
|
|
|
|
|
x_align: Clutter.ActorAlign.END,
|
2019-11-20 15:48:03 -05:00
|
|
|
|
y_align: Clutter.ActorAlign.END,
|
2019-11-22 09:41:19 -05:00
|
|
|
|
});
|
2019-01-11 11:42:22 -05:00
|
|
|
|
titleBox.add_child(this._titleLocation);
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
let layout = new Clutter.GridLayout({orientation: Clutter.Orientation.VERTICAL});
|
2019-11-22 09:41:19 -05:00
|
|
|
|
this._forecastGrid = new St.Widget({
|
|
|
|
|
style_class: 'weather-grid',
|
|
|
|
|
layout_manager: layout,
|
|
|
|
|
});
|
2018-12-13 08:45:22 -05:00
|
|
|
|
layout.hookup_style(this._forecastGrid);
|
|
|
|
|
box.add_child(this._forecastGrid);
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2017-12-01 19:27:35 -05:00
|
|
|
|
this._weatherClient.connect('changed', this._sync.bind(this));
|
2017-02-24 07:15:39 -05:00
|
|
|
|
this._sync();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
|
vfunc_map() {
|
|
|
|
|
this._weatherClient.update();
|
|
|
|
|
super.vfunc_map();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vfunc_clicked() {
|
|
|
|
|
this._weatherClient.activateApp();
|
|
|
|
|
|
|
|
|
|
Main.overview.hide();
|
|
|
|
|
Main.panel.closeCalendar();
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
_getInfos() {
|
2019-11-22 16:44:42 -05:00
|
|
|
|
let forecasts = this._weatherClient.info.get_forecast_list();
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2019-11-20 17:23:50 -05:00
|
|
|
|
let now = GLib.DateTime.new_now_local();
|
2019-11-22 16:44:42 -05:00
|
|
|
|
let current = GLib.DateTime.new_from_unix_local(0);
|
|
|
|
|
let infos = [];
|
2017-02-24 07:15:39 -05:00
|
|
|
|
for (let i = 0; i < forecasts.length; i++) {
|
2019-11-22 17:27:47 -05:00
|
|
|
|
const [valid, timestamp] = forecasts[i].get_value_update();
|
|
|
|
|
if (!valid || timestamp === 0)
|
|
|
|
|
continue; // 0 means 'never updated'
|
|
|
|
|
|
2019-11-20 17:23:50 -05:00
|
|
|
|
const datetime = GLib.DateTime.new_from_unix_local(timestamp);
|
|
|
|
|
if (now.difference(datetime) > 0)
|
|
|
|
|
continue; // Ignore earlier forecasts
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2019-11-22 16:44:42 -05:00
|
|
|
|
if (datetime.difference(current) < GLib.TIME_SPAN_HOUR)
|
2018-12-13 08:45:22 -05:00
|
|
|
|
continue; // Enforce a minimum interval of 1h
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
if (infos.push(forecasts[i]) === MAX_FORECASTS)
|
2018-12-13 08:45:22 -05:00
|
|
|
|
break; // Use a maximum of five forecasts
|
2019-11-22 16:44:42 -05:00
|
|
|
|
|
|
|
|
|
current = datetime;
|
2017-02-24 07:15:39 -05:00
|
|
|
|
}
|
2018-12-13 08:45:22 -05:00
|
|
|
|
return infos;
|
|
|
|
|
}
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
_addForecasts() {
|
|
|
|
|
let layout = this._forecastGrid.layout_manager;
|
|
|
|
|
|
|
|
|
|
let infos = this._getInfos();
|
2023-07-07 16:12:49 -04:00
|
|
|
|
if (this._forecastGrid.text_direction === Clutter.TextDirection.RTL)
|
2018-12-13 08:45:22 -05:00
|
|
|
|
infos.reverse();
|
|
|
|
|
|
|
|
|
|
let col = 0;
|
|
|
|
|
infos.forEach(fc => {
|
2019-11-22 17:27:47 -05:00
|
|
|
|
const [valid_, timestamp] = fc.get_value_update();
|
2023-07-07 20:58:11 -04:00
|
|
|
|
let timeStr = formatTime(new Date(timestamp * 1000), {
|
2019-08-20 17:43:54 -04:00
|
|
|
|
timeOnly: true,
|
2019-11-19 16:15:34 -05:00
|
|
|
|
ampm: false,
|
2018-12-13 08:45:22 -05:00
|
|
|
|
});
|
2019-12-18 18:45:17 -05:00
|
|
|
|
const [, tempValue] = fc.get_value_temp(GWeather.TemperatureUnit.DEFAULT);
|
2020-12-20 17:11:26 -05:00
|
|
|
|
const tempPrefix = Math.round(tempValue) >= 0 ? ' ' : '';
|
2018-12-13 08:45:22 -05:00
|
|
|
|
|
2019-12-18 18:43:12 -05:00
|
|
|
|
let time = new St.Label({
|
|
|
|
|
style_class: 'weather-forecast-time',
|
|
|
|
|
text: timeStr,
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
});
|
2019-11-22 09:41:19 -05:00
|
|
|
|
let icon = new St.Icon({
|
|
|
|
|
style_class: 'weather-forecast-icon',
|
|
|
|
|
icon_name: fc.get_symbolic_icon_name(),
|
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
});
|
|
|
|
|
let temp = new St.Label({
|
|
|
|
|
style_class: 'weather-forecast-temp',
|
2022-02-07 09:14:06 -05:00
|
|
|
|
text: `${tempPrefix}${Math.round(tempValue)}°`,
|
2019-11-22 09:41:19 -05:00
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
|
});
|
2018-12-13 08:45:22 -05:00
|
|
|
|
|
2019-11-20 17:15:55 -05:00
|
|
|
|
temp.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
|
time.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
|
|
2019-12-18 18:43:12 -05:00
|
|
|
|
layout.attach(time, col, 0, 1, 1);
|
|
|
|
|
layout.attach(icon, col, 1, 1, 1);
|
|
|
|
|
layout.attach(temp, col, 2, 1, 1);
|
2018-12-13 08:45:22 -05:00
|
|
|
|
col++;
|
2017-02-25 12:25:07 -05:00
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
_setStatusLabel(text) {
|
|
|
|
|
let layout = this._forecastGrid.layout_manager;
|
2023-07-07 16:12:49 -04:00
|
|
|
|
let label = new St.Label({text});
|
2018-12-13 08:45:22 -05:00
|
|
|
|
layout.attach(label, 0, 0, 1, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-27 21:54:14 -04:00
|
|
|
|
_findBestLocationName(loc) {
|
|
|
|
|
const locName = loc.get_name();
|
|
|
|
|
|
|
|
|
|
if (loc.get_level() === GWeather.LocationLevel.CITY ||
|
|
|
|
|
!loc.has_coords())
|
|
|
|
|
return locName;
|
|
|
|
|
|
|
|
|
|
const world = GWeather.Location.get_world();
|
|
|
|
|
const city = world.find_nearest_city(...loc.get_coords());
|
|
|
|
|
const cityName = city.get_name();
|
|
|
|
|
|
|
|
|
|
return locName.includes(cityName) ? cityName : locName;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
_updateForecasts() {
|
|
|
|
|
this._forecastGrid.destroy_all_children();
|
|
|
|
|
|
2020-04-05 03:39:02 -04:00
|
|
|
|
if (!this._weatherClient.hasLocation)
|
2018-12-13 08:45:22 -05:00
|
|
|
|
return;
|
2017-03-19 09:42:35 -04:00
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
const {info} = this._weatherClient;
|
2020-03-27 21:54:14 -04:00
|
|
|
|
this._titleLocation.text = this._findBestLocationName(info.location);
|
2019-01-11 11:42:22 -05:00
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
if (this._weatherClient.loading) {
|
2023-07-07 16:12:49 -04:00
|
|
|
|
this._setStatusLabel(_('Loading…'));
|
2018-12-13 08:45:22 -05:00
|
|
|
|
return;
|
|
|
|
|
}
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
if (info.is_valid()) {
|
|
|
|
|
this._addForecasts();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
|
|
|
|
if (info.network_error())
|
2023-07-07 16:12:49 -04:00
|
|
|
|
this._setStatusLabel(_('Go online for weather information'));
|
2018-12-13 08:45:22 -05:00
|
|
|
|
else
|
2023-07-07 16:12:49 -04:00
|
|
|
|
this._setStatusLabel(_('Weather information is currently unavailable'));
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_sync() {
|
2019-07-16 05:24:13 -04:00
|
|
|
|
this.visible = this._weatherClient.available;
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
if (!this.visible)
|
2017-02-24 07:15:39 -05:00
|
|
|
|
return;
|
|
|
|
|
|
2020-04-05 03:39:02 -04:00
|
|
|
|
if (this._weatherClient.hasLocation)
|
|
|
|
|
this._titleLabel.text = _('Weather');
|
|
|
|
|
else
|
|
|
|
|
this._titleLabel.text = _('Select weather location…');
|
|
|
|
|
|
|
|
|
|
this._forecastGrid.visible = this._weatherClient.hasLocation;
|
2019-01-11 11:42:22 -05:00
|
|
|
|
this._titleLocation.visible = this._weatherClient.hasLocation;
|
|
|
|
|
|
2018-12-13 08:45:22 -05:00
|
|
|
|
this._updateForecasts();
|
2017-02-24 07:15:39 -05:00
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2017-02-24 07:15:39 -05:00
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
var MessagesIndicator = GObject.registerClass(
|
|
|
|
|
class MessagesIndicator extends St.Icon {
|
|
|
|
|
_init() {
|
|
|
|
|
super._init({
|
|
|
|
|
icon_size: 16,
|
|
|
|
|
visible: false,
|
|
|
|
|
y_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
|
|
|
|
this._sources = [];
|
2020-01-17 11:26:08 -05:00
|
|
|
|
this._count = 0;
|
|
|
|
|
|
|
|
|
|
this._settings = new Gio.Settings({
|
|
|
|
|
schema_id: 'org.gnome.desktop.notifications',
|
|
|
|
|
});
|
|
|
|
|
this._settings.connect('changed::show-banners', this._sync.bind(this));
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2017-12-01 19:27:35 -05:00
|
|
|
|
Main.messageTray.connect('source-added', this._onSourceAdded.bind(this));
|
|
|
|
|
Main.messageTray.connect('source-removed', this._onSourceRemoved.bind(this));
|
|
|
|
|
Main.messageTray.connect('queue-changed', this._updateCount.bind(this));
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
|
|
|
|
let sources = Main.messageTray.getSources();
|
2019-01-27 19:42:00 -05:00
|
|
|
|
sources.forEach(source => this._onSourceAdded(null, source));
|
2020-01-17 11:26:08 -05:00
|
|
|
|
|
2020-02-20 13:57:20 -05:00
|
|
|
|
this._sync();
|
|
|
|
|
|
2020-01-17 11:26:08 -05:00
|
|
|
|
this.connect('destroy', () => {
|
|
|
|
|
this._settings.run_dispose();
|
|
|
|
|
this._settings = null;
|
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onSourceAdded(tray, source) {
|
2019-05-13 17:32:31 -04:00
|
|
|
|
source.connect('notify::count', this._updateCount.bind(this));
|
2015-02-13 21:17:16 -05:00
|
|
|
|
this._sources.push(source);
|
|
|
|
|
this._updateCount();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_onSourceRemoved(tray, source) {
|
2015-02-13 21:17:16 -05:00
|
|
|
|
this._sources.splice(this._sources.indexOf(source), 1);
|
|
|
|
|
this._updateCount();
|
2017-10-30 21:19:44 -04:00
|
|
|
|
}
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_updateCount() {
|
2015-02-13 21:17:16 -05:00
|
|
|
|
let count = 0;
|
2019-08-19 16:20:35 -04:00
|
|
|
|
this._sources.forEach(source => (count += source.unseenCount));
|
2020-01-17 11:26:08 -05:00
|
|
|
|
this._count = count - Main.messageTray.queueCount;
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2020-01-17 11:26:08 -05:00
|
|
|
|
this._sync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_sync() {
|
|
|
|
|
let doNotDisturb = !this._settings.get_boolean('show-banners');
|
|
|
|
|
this.icon_name = doNotDisturb
|
|
|
|
|
? 'notifications-disabled-symbolic'
|
|
|
|
|
: 'message-indicator-symbolic';
|
|
|
|
|
this.visible = doNotDisturb || this._count > 0;
|
2015-02-13 21:17:16 -05:00
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
|
});
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2017-10-30 21:23:39 -04:00
|
|
|
|
var FreezableBinLayout = GObject.registerClass(
|
|
|
|
|
class FreezableBinLayout extends Clutter.BinLayout {
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_init() {
|
2017-10-30 21:23:39 -04:00
|
|
|
|
super._init();
|
2015-02-13 10:25:21 -05:00
|
|
|
|
|
|
|
|
|
this._frozen = false;
|
|
|
|
|
this._savedWidth = [NaN, NaN];
|
|
|
|
|
this._savedHeight = [NaN, NaN];
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2015-02-13 10:25:21 -05:00
|
|
|
|
|
|
|
|
|
set frozen(v) {
|
2023-07-07 16:12:49 -04:00
|
|
|
|
if (this._frozen === v)
|
2015-02-13 10:25:21 -05:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this._frozen = v;
|
|
|
|
|
if (!this._frozen)
|
|
|
|
|
this.layout_changed();
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2015-02-13 10:25:21 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
vfunc_get_preferred_width(container, forHeight) {
|
2015-02-13 10:25:21 -05:00
|
|
|
|
if (!this._frozen || this._savedWidth.some(isNaN))
|
2017-10-30 21:23:39 -04:00
|
|
|
|
return super.vfunc_get_preferred_width(container, forHeight);
|
2015-02-13 10:25:21 -05:00
|
|
|
|
return this._savedWidth;
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2015-02-13 10:25:21 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
vfunc_get_preferred_height(container, forWidth) {
|
2015-02-13 10:25:21 -05:00
|
|
|
|
if (!this._frozen || this._savedHeight.some(isNaN))
|
2017-10-30 21:23:39 -04:00
|
|
|
|
return super.vfunc_get_preferred_height(container, forWidth);
|
2015-02-13 10:25:21 -05:00
|
|
|
|
return this._savedHeight;
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2017-02-26 08:37:51 -05:00
|
|
|
|
|
2020-05-09 15:30:26 -04:00
|
|
|
|
vfunc_allocate(container, allocation) {
|
|
|
|
|
super.vfunc_allocate(container, allocation);
|
2017-02-26 08:37:51 -05:00
|
|
|
|
|
|
|
|
|
let [width, height] = allocation.get_size();
|
|
|
|
|
this._savedWidth = [width, width];
|
|
|
|
|
this._savedHeight = [height, height];
|
2015-02-13 10:25:21 -05:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2017-10-30 21:23:39 -04:00
|
|
|
|
var CalendarColumnLayout = GObject.registerClass(
|
|
|
|
|
class CalendarColumnLayout extends Clutter.BoxLayout {
|
2020-09-23 22:46:47 -04:00
|
|
|
|
_init(actors) {
|
2023-07-07 16:12:49 -04:00
|
|
|
|
super._init({orientation: Clutter.Orientation.VERTICAL});
|
2020-09-23 22:46:47 -04:00
|
|
|
|
this._colActors = actors;
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2017-02-24 08:56:47 -05:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
vfunc_get_preferred_width(container, forHeight) {
|
2020-09-23 22:46:47 -04:00
|
|
|
|
const actors =
|
|
|
|
|
this._colActors.filter(a => a.get_parent() === container);
|
|
|
|
|
if (actors.length === 0)
|
2017-10-30 21:23:39 -04:00
|
|
|
|
return super.vfunc_get_preferred_width(container, forHeight);
|
2020-09-23 22:46:47 -04:00
|
|
|
|
return actors.reduce(([minAcc, natAcc], child) => {
|
|
|
|
|
const [min, nat] = child.get_preferred_width(forHeight);
|
|
|
|
|
return [Math.max(minAcc, min), Math.max(natAcc, nat)];
|
|
|
|
|
}, [0, 0]);
|
2017-02-24 08:56:47 -05:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2017-10-30 21:23:39 -04:00
|
|
|
|
var DateMenuButton = GObject.registerClass(
|
|
|
|
|
class DateMenuButton extends PanelMenu.Button {
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_init() {
|
2011-01-28 16:35:46 -05:00
|
|
|
|
let hbox;
|
|
|
|
|
|
2020-02-20 14:11:16 -05:00
|
|
|
|
super._init(0.5);
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
this._clockDisplay = new St.Label({style_class: 'clock'});
|
2020-02-20 14:10:54 -05:00
|
|
|
|
this._clockDisplay.clutter_text.y_align = Clutter.ActorAlign.CENTER;
|
2020-06-22 08:41:58 -04:00
|
|
|
|
this._clockDisplay.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
2020-02-20 14:10:54 -05:00
|
|
|
|
|
2015-02-13 21:17:16 -05:00
|
|
|
|
this._indicator = new MessagesIndicator();
|
|
|
|
|
|
2020-02-18 19:52:59 -05:00
|
|
|
|
const indicatorPad = new St.Widget();
|
|
|
|
|
this._indicator.bind_property('visible',
|
|
|
|
|
indicatorPad, 'visible',
|
|
|
|
|
GObject.BindingFlags.SYNC_CREATE);
|
|
|
|
|
indicatorPad.add_constraint(new Clutter.BindConstraint({
|
|
|
|
|
source: this._indicator,
|
|
|
|
|
coordinate: Clutter.BindCoordinate.SIZE,
|
|
|
|
|
}));
|
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
let box = new St.BoxLayout({style_class: 'clock-display-box'});
|
2020-02-18 19:52:59 -05:00
|
|
|
|
box.add_actor(indicatorPad);
|
2015-02-13 21:17:16 -05:00
|
|
|
|
box.add_actor(this._clockDisplay);
|
2019-07-16 05:24:13 -04:00
|
|
|
|
box.add_actor(this._indicator);
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2019-04-09 19:17:51 -04:00
|
|
|
|
this.label_actor = this._clockDisplay;
|
|
|
|
|
this.add_actor(box);
|
2019-08-19 13:55:49 -04:00
|
|
|
|
this.add_style_class_name('clock-display');
|
2015-02-13 21:17:16 -05:00
|
|
|
|
|
2015-02-13 10:25:21 -05:00
|
|
|
|
let layout = new FreezableBinLayout();
|
2023-07-07 16:12:49 -04:00
|
|
|
|
let bin = new St.Widget({layout_manager: layout});
|
2017-06-17 12:14:20 -04:00
|
|
|
|
// For some minimal compatibility with PopupMenuItem
|
|
|
|
|
bin._delegate = this;
|
2015-02-13 10:25:21 -05:00
|
|
|
|
this.menu.box.add_child(bin);
|
|
|
|
|
|
2023-07-07 16:12:49 -04:00
|
|
|
|
hbox = new St.BoxLayout({name: 'calendarArea'});
|
2015-02-13 10:25:21 -05:00
|
|
|
|
bin.add_actor(hbox);
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
2015-01-29 23:56:28 -05:00
|
|
|
|
this._calendar = new Calendar.Calendar();
|
2019-09-02 15:18:54 -04:00
|
|
|
|
this._calendar.connect('selected-date-changed', (_calendar, datetime) => {
|
|
|
|
|
let date = _gDateTimeToDate(datetime);
|
|
|
|
|
layout.frozen = !_isToday(date);
|
2020-05-14 16:24:36 -04:00
|
|
|
|
this._eventsItem.setDate(date);
|
2019-09-02 15:18:54 -04:00
|
|
|
|
});
|
2020-09-23 22:46:47 -04:00
|
|
|
|
this._date = new TodayButton(this._calendar);
|
2015-01-29 23:56:28 -05:00
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
|
this.menu.connect('open-state-changed', (menu, isOpen) => {
|
2015-02-13 17:53:18 -05:00
|
|
|
|
// Whenever the menu is opened, select today
|
2015-01-29 23:56:28 -05:00
|
|
|
|
if (isOpen) {
|
|
|
|
|
let now = new Date();
|
|
|
|
|
this._calendar.setDate(now);
|
2014-12-05 10:03:59 -05:00
|
|
|
|
this._date.setDate(now);
|
2020-05-14 16:24:36 -04:00
|
|
|
|
this._eventsItem.setDate(now);
|
2015-01-29 23:56:28 -05:00
|
|
|
|
}
|
2017-10-30 20:38:18 -04:00
|
|
|
|
});
|
2015-01-29 23:56:28 -05:00
|
|
|
|
|
2011-01-28 16:35:46 -05:00
|
|
|
|
// Fill up the first column
|
2016-02-15 06:02:31 -05:00
|
|
|
|
this._messageList = new Calendar.CalendarMessageList();
|
2019-10-21 14:44:00 -04:00
|
|
|
|
hbox.add_child(this._messageList);
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
2015-01-29 23:56:28 -05:00
|
|
|
|
// Fill up the second column
|
2020-09-23 22:46:47 -04:00
|
|
|
|
const boxLayout = new CalendarColumnLayout([this._calendar, this._date]);
|
2020-03-29 17:51:13 -04:00
|
|
|
|
const vbox = new St.Widget({
|
|
|
|
|
style_class: 'datemenu-calendar-column',
|
|
|
|
|
layout_manager: boxLayout,
|
|
|
|
|
});
|
2017-02-24 08:56:47 -05:00
|
|
|
|
boxLayout.hookup_style(vbox);
|
2011-01-28 16:35:46 -05:00
|
|
|
|
hbox.add(vbox);
|
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
|
vbox.add_actor(this._date);
|
|
|
|
|
vbox.add_actor(this._calendar);
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
|
this._displaysSection = new St.ScrollView({
|
|
|
|
|
style_class: 'datemenu-displays-section vfade',
|
|
|
|
|
x_expand: true,
|
|
|
|
|
overlay_scrollbars: true,
|
|
|
|
|
});
|
2020-02-16 15:57:34 -05:00
|
|
|
|
this._displaysSection.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL);
|
2015-03-02 19:27:43 -05:00
|
|
|
|
vbox.add_actor(this._displaysSection);
|
2012-12-21 16:54:20 -05:00
|
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
|
const displaysBox = new St.BoxLayout({
|
|
|
|
|
vertical: true,
|
|
|
|
|
x_expand: true,
|
|
|
|
|
style_class: 'datemenu-displays-box',
|
|
|
|
|
});
|
2015-03-02 19:27:43 -05:00
|
|
|
|
this._displaysSection.add_actor(displaysBox);
|
2012-12-15 05:30:03 -05:00
|
|
|
|
|
2020-05-14 16:24:36 -04:00
|
|
|
|
this._eventsItem = new EventsSection();
|
|
|
|
|
displaysBox.add_child(this._eventsItem);
|
|
|
|
|
|
2015-02-06 18:47:27 -05:00
|
|
|
|
this._clocksItem = new WorldClocksSection();
|
2019-10-21 14:44:00 -04:00
|
|
|
|
displaysBox.add_child(this._clocksItem);
|
2012-12-15 05:30:03 -05:00
|
|
|
|
|
2017-02-24 07:15:39 -05:00
|
|
|
|
this._weatherItem = new WeatherSection();
|
2019-10-21 14:44:00 -04:00
|
|
|
|
displaysBox.add_child(this._weatherItem);
|
2011-01-28 16:35:46 -05:00
|
|
|
|
|
|
|
|
|
// Done with hbox for calendar and event list
|
|
|
|
|
|
2011-08-21 03:31:16 -04:00
|
|
|
|
this._clock = new GnomeDesktop.WallClock();
|
2014-10-01 17:28:14 -04:00
|
|
|
|
this._clock.bind_property('clock', this._clockDisplay, 'text', GObject.BindingFlags.SYNC_CREATE);
|
2017-12-01 19:27:35 -05:00
|
|
|
|
this._clock.connect('notify::timezone', this._updateTimeZone.bind(this));
|
2012-09-01 08:42:53 -04:00
|
|
|
|
|
2017-12-01 19:27:35 -05:00
|
|
|
|
Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
|
2012-09-01 08:42:53 -04:00
|
|
|
|
this._sessionUpdated();
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2012-09-01 08:42:53 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_getEventSource() {
|
2012-03-21 03:59:39 -04:00
|
|
|
|
return new Calendar.DBusEventSource();
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2012-03-21 03:59:39 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_setEventSource(eventSource) {
|
2013-03-04 11:04:28 -05:00
|
|
|
|
if (this._eventSource)
|
|
|
|
|
this._eventSource.destroy();
|
|
|
|
|
|
2012-09-01 08:42:53 -04:00
|
|
|
|
this._calendar.setEventSource(eventSource);
|
2020-05-14 16:24:36 -04:00
|
|
|
|
this._eventsItem.setEventSource(eventSource);
|
2013-03-04 11:04:28 -05:00
|
|
|
|
|
|
|
|
|
this._eventSource = eventSource;
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2012-09-01 08:42:53 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_updateTimeZone() {
|
2023-07-07 20:58:11 -04:00
|
|
|
|
DateUtils.clearCachedLocalTimeZone();
|
2015-08-13 18:57:49 -04:00
|
|
|
|
|
|
|
|
|
this._calendar.updateTimeZone();
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2015-08-13 18:57:49 -04:00
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
|
_sessionUpdated() {
|
2012-09-01 08:42:53 -04:00
|
|
|
|
let eventSource;
|
|
|
|
|
let showEvents = Main.sessionMode.showCalendarEvents;
|
2019-08-19 20:51:42 -04:00
|
|
|
|
if (showEvents)
|
2012-03-21 03:59:39 -04:00
|
|
|
|
eventSource = this._getEventSource();
|
2019-08-19 20:51:42 -04:00
|
|
|
|
else
|
2013-03-04 11:04:28 -05:00
|
|
|
|
eventSource = new Calendar.EmptyEventSource();
|
2019-08-19 20:51:42 -04:00
|
|
|
|
|
2012-09-01 08:42:53 -04:00
|
|
|
|
this._setEventSource(eventSource);
|
2015-03-02 19:27:43 -05:00
|
|
|
|
|
|
|
|
|
// Displays are not actually expected to launch Settings when activated
|
|
|
|
|
// but the corresponding app (clocks, weather); however we can consider
|
|
|
|
|
// that display-specific settings, so re-use "allowSettings" here ...
|
|
|
|
|
this._displaysSection.visible = Main.sessionMode.allowSettings;
|
2017-10-30 21:23:39 -04:00
|
|
|
|
}
|
2011-11-20 09:38:48 -05:00
|
|
|
|
});
|