// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; const GnomeDesktop = imports.gi.GnomeDesktop; const GObject = imports.gi.GObject; const Lang = imports.lang; const Mainloop = imports.mainloop; const Cairo = imports.cairo; const Clutter = imports.gi.Clutter; const Shell = imports.gi.Shell; const St = imports.gi.St; const Atk = imports.gi.Atk; const Params = imports.misc.params; const Util = imports.misc.util; const Main = imports.ui.main; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const Calendar = imports.ui.calendar; function _onVertSepRepaint(area) { let cr = area.get_context(); let themeNode = area.get_theme_node(); let [width, height] = area.get_surface_size(); let stippleColor = themeNode.get_color('-stipple-color'); let stippleWidth = themeNode.get_length('-stipple-width'); let x = Math.floor(width/2) + 0.5; cr.moveTo(x, 0); cr.lineTo(x, height); Clutter.cairo_set_source_color(cr, stippleColor); cr.setDash([1, 3], 1); // Hard-code for now cr.setLineWidth(stippleWidth); cr.stroke(); cr.$dispose(); } const DateMenuButton = new Lang.Class({ Name: 'DateMenuButton', Extends: PanelMenu.Button, _init: function() { let item; let hbox; let vbox; let menuAlignment = 0.25; if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) menuAlignment = 1.0 - menuAlignment; this.parent(menuAlignment); this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER }); this.actor.label_actor = this._clockDisplay; this.actor.add_actor(this._clockDisplay); this.actor.add_style_class_name ('clock-display'); hbox = new St.BoxLayout({ name: 'calendarArea' }); this.menu.box.add_child(hbox); // Fill up the first column vbox = new St.BoxLayout({vertical: true}); hbox.add(vbox); // Date // 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. this._date = new St.Button({ style_class: 'datemenu-date-label', reactive: false }); this._date.connect('clicked', Lang.bind(this, function() { this._calendar.setDate(new Date(), false); })); vbox.add(this._date, { x_fill: false }); this._eventList = new Calendar.EventsList(); this._calendar = new Calendar.Calendar(); this._calendar.connect('selected-date-changed', Lang.bind(this, function(calendar, date) { // we know this._eventList is defined here, because selected-data-changed // only gets emitted when the user clicks a date in the calendar, // and the calender makes those dates unclickable when instantiated with // a null event source this._eventList.setDate(date); // Make the button reactive only if the selected date is not the current date. this._date.can_focus = this._date.reactive = !this._isToday(date) })); vbox.add(this._calendar.actor); let separator = new PopupMenu.PopupSeparatorMenuItem(); vbox.add(separator.actor, { y_align: St.Align.END, expand: true, y_fill: false }); this._openCalendarItem = new PopupMenu.PopupMenuItem(_("Open Calendar")); this._openCalendarItem.connect('activate', Lang.bind(this, this._onOpenCalendarActivate)); vbox.add(this._openCalendarItem.actor, {y_align: St.Align.END, expand: true, y_fill: false}); this._openClocksItem = new PopupMenu.PopupMenuItem(_("Open Clocks")); this._openClocksItem.connect('activate', Lang.bind(this, this._onOpenClocksActivate)); vbox.add(this._openClocksItem.actor, {y_align: St.Align.END, expand: true, y_fill: false}); Shell.AppSystem.get_default().connect('installed-changed', Lang.bind(this, this._appInstalledChanged)); item = this.menu.addSettingsAction(_("Date & Time Settings"), 'gnome-datetime-panel.desktop'); if (item) { item.actor.show_on_set_parent = false; item.actor.reparent(vbox); this._dateAndTimeSeparator = separator; } this._separator = new St.DrawingArea({ style_class: 'calendar-vertical-separator', pseudo_class: 'highlighted' }); this._separator.connect('repaint', Lang.bind(this, _onVertSepRepaint)); hbox.add(this._separator); // Fill up the second column hbox.add(this._eventList.actor, { expand: true, y_fill: false, y_align: St.Align.START }); // Whenever the menu is opened, select today this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { if (isOpen) { let now = new Date(); this._calendar.setDate(now); /* Translators: This is the date format to use when the calendar popup is * shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM"). */ let dateFormat = _("%A %B %e, %Y"); this._date.set_label(now.toLocaleFormat(dateFormat)); } })); // Done with hbox for calendar and event list this._clock = new GnomeDesktop.WallClock(); this._clock.bind_property('clock', this._clockDisplay, 'text', GObject.BindingFlags.SYNC_CREATE); Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); this._sessionUpdated(); }, _isToday: function(date) { let now = new Date(); return now.getYear() == date.getYear() && now.getMonth() == date.getMonth() && now.getDay() == date.getDay(); }, _appInstalledChanged: function() { this._calendarApp = undefined; this._updateEventsVisibility(); }, _updateEventsVisibility: function() { let visible = this._eventSource.hasCalendars; this._openCalendarItem.actor.visible = visible && (this._getCalendarApp() != null); this._openClocksItem.actor.visible = visible && (this._getClockApp() != null); this._separator.visible = visible; this._eventList.actor.visible = visible; if (visible) { let alignment = 0.25; if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) alignment = 1.0 - alignment; this.menu._arrowAlignment = alignment; } else { this.menu._arrowAlignment = 0.5; } }, _setEventSource: function(eventSource) { if (this._eventSource) this._eventSource.destroy(); this._calendar.setEventSource(eventSource); this._eventList.setEventSource(eventSource); this._eventSource = eventSource; this._eventSource.connect('notify::has-calendars', Lang.bind(this, function() { this._updateEventsVisibility(); })); }, _sessionUpdated: function() { let eventSource; let showEvents = Main.sessionMode.showCalendarEvents; if (showEvents) { eventSource = new Calendar.DBusEventSource(); } else { eventSource = new Calendar.EmptyEventSource(); } this._setEventSource(eventSource); this._updateEventsVisibility(); // This needs to be handled manually, as the code to // autohide separators doesn't work across the vbox this._dateAndTimeSeparator.actor.visible = Main.sessionMode.allowSettings; }, _getCalendarApp: function() { if (this._calendarApp !== undefined) return this._calendarApp; let apps = Gio.AppInfo.get_recommended_for_type('text/calendar'); if (apps && (apps.length > 0)) { let app = Gio.AppInfo.get_default_for_type('text/calendar', false); let defaultInRecommended = apps.some(function(a) { return a.equal(app); }); this._calendarApp = defaultInRecommended ? app : apps[0]; } else { this._calendarApp = null; } return this._calendarApp; }, _getClockApp: function() { return Shell.AppSystem.get_default().lookup_app('org.gnome.clocks.desktop'); }, _onOpenCalendarActivate: function() { this.menu.close(); let app = this._getCalendarApp(); if (app.get_id() == 'evolution.desktop') app = Gio.DesktopAppInfo.new('evolution-calendar.desktop'); app.launch([], global.create_app_launch_context(0, -1)); }, _onOpenClocksActivate: function() { this.menu.close(); let app = this._getClockApp(); app.activate(); } });