/* jshint esnext:true */ /* * * Weather extension for GNOME Shell * - Displays a small weather information on the top panel. * - On click, gives a popup with details about the weather. * * Copyright (C) 2011 - 2013 * ecyrbe , * Timur Kristof , * Elad Alfassa , * Simon Legner , * Christian METZLER , * Mark Benjamin weather.gnome.Markie1@dfgh.net, * Mattia Meneguzzo odysseus@fedoraproject.org, * Meng Zhuo , * Jens Lody * Copyright (C) 2014 -2018 * Jens Lody , * * * This file is part of gnome-shell-extension-openweather. * * gnome-shell-extension-openweather is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * gnome-shell-extension-openweather is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gnome-shell-extension-openweather. If not, see . * */ const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Config = imports.misc.config; const DarkskyNet = Me.imports.darksky_net; const OpenweathermapOrg = Me.imports.openweathermap_org; const { Clutter, Gio, Gtk, GLib, GObject, Soup, St } = imports.gi; const Gettext = imports.gettext.domain('gnome-shell-extension-openweather'); const Lang = imports.lang; const Mainloop = imports.mainloop; const GnomeSession = imports.misc.gnomeSession; const Util = imports.misc.util; const _ = Gettext.gettext; const Main = imports.ui.main; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; // Settings const OPENWEATHER_SETTINGS_SCHEMA = 'org.gnome.shell.extensions.openweather'; const OPENWEATHER_DESKTOP_INTERFACE = 'org.gnome.desktop.interface'; const OPENWEATHER_PROVIDER_KEY = 'weather-provider'; const OPENWEATHER_UNIT_KEY = 'unit'; const OPENWEATHER_WIND_SPEED_UNIT_KEY = 'wind-speed-unit'; const OPENWEATHER_WIND_DIRECTION_KEY = 'wind-direction'; const OPENWEATHER_PRESSURE_UNIT_KEY = 'pressure-unit'; const OPENWEATHER_CITY_KEY = 'city'; const OPENWEATHER_ACTUAL_CITY_KEY = 'actual-city'; const OPENWEATHER_TRANSLATE_CONDITION_KEY = 'translate-condition'; const OPENWEATHER_USE_SYMBOLIC_ICONS_KEY = 'use-symbolic-icons'; const OPENWEATHER_USE_TEXT_ON_BUTTONS_KEY = 'use-text-on-buttons'; const OPENWEATHER_SHOW_TEXT_IN_PANEL_KEY = 'show-text-in-panel'; const OPENWEATHER_POSITION_IN_PANEL_KEY = 'position-in-panel'; const OPENWEATHER_MENU_ALIGNMENT_KEY = 'menu-alignment'; const OPENWEATHER_SHOW_COMMENT_IN_PANEL_KEY = 'show-comment-in-panel'; const OPENWEATHER_SHOW_COMMENT_IN_FORECAST_KEY = 'show-comment-in-forecast'; const OPENWEATHER_REFRESH_INTERVAL_CURRENT = 'refresh-interval-current'; const OPENWEATHER_REFRESH_INTERVAL_FORECAST = 'refresh-interval-forecast'; const OPENWEATHER_CENTER_FORECAST_KEY = 'center-forecast'; const OPENWEATHER_DAYS_FORECAST = 'days-forecast'; const OPENWEATHER_DECIMAL_PLACES = 'decimal-places'; const OPENWEATHER_USE_DEFAULT_OWM_API_KEY = 'use-default-owm-key'; const OPENWEATHER_OWM_API_KEY = 'appid'; const OPENWEATHER_OWM_DEFAULT_API_KEY = 'c93b4a667c8c9d1d1eb941621f899bb8'; const OPENWEATHER_FC_API_KEY = 'appid-fc'; const OPENWEATHER_LOC_TEXT_LEN = 'location-text-length' // Keep enums in sync with GSettings schemas const WeatherProvider = { DEFAULT: -1, OPENWEATHERMAP: 0, DARKSKY: 1 }; const WeatherUnits = { CELSIUS: 0, FAHRENHEIT: 1, KELVIN: 2, RANKINE: 3, REAUMUR: 4, ROEMER: 5, DELISLE: 6, NEWTON: 7 }; const WeatherWindSpeedUnits = { KPH: 0, MPH: 1, MPS: 2, KNOTS: 3, FPS: 4, BEAUFORT: 5 }; const WeatherPressureUnits = { HPA: 0, INHG: 1, BAR: 2, PA: 3, KPA: 4, ATM: 5, AT: 6, TORR: 7, PSI: 8, MMHG: 9, MBAR: 10 }; const WeatherPosition = { CENTER: 0, RIGHT: 1, LEFT: 2 }; const OPENWEATHER_CONV_MPS_IN_MPH = 2.23693629; const OPENWEATHER_CONV_MPS_IN_KPH = 3.6; const OPENWEATHER_CONV_MPS_IN_KNOTS = 1.94384449; const OPENWEATHER_CONV_MPS_IN_FPS = 3.2808399; let _httpSession; let _currentWeatherCache, _forecastWeatherCache; let _timeCacheCurrentWeather, _timeCacheForecastWeather; let OpenweatherMenuButton = GObject.registerClass( class OpenweatherMenuButton extends PanelMenu.Button { _init() { super._init(0, 'OpenweatherMenuButton', false); this.owmCityId = 0; // Get locale, needed for toLocaleString, workaround for gnome-shell 3.24 this.locale = GLib.get_language_names()[0]; if (this.locale == 'C') this.locale = 'en'; else if (this.locale.indexOf('_') != -1) this.locale = this.locale.split("_")[0]; // Create user-agent string from uuid and (if present) the version this.user_agent = Me.metadata.uuid; if (Me.metadata.version !== undefined && Me.metadata.version.toString().trim() !== '') { this.user_agent += '/'; this.user_agent += Me.metadata.version.toString(); } // add trailing space, so libsoup adds its own user-agent this.user_agent += ' '; this.oldProvider = this._weather_provider; this.oldUseDefaultOwmKey = this._use_default_owm_key; this.oldTranslateCondition = this._translate_condition; this.switchProvider(); // Load settings this.loadConfig(); // Label this._weatherInfo = new St.Label({ y_align: Clutter.ActorAlign.CENTER, text: _('...') }); this._weatherIcon = new St.Icon({ icon_name: 'view-refresh', style_class: 'system-status-icon openweather-icon ' + this.getIconType() }); this.checkAlignment(); // Putting the panel item together let topBox = new St.BoxLayout(); topBox.add_actor(this._weatherIcon); topBox.add_actor(this._weatherInfo); this.add_actor(topBox); this.checkPositionInPanel(); if (Main.panel._menus === undefined) Main.panel.menuManager.addMenu(this.menu); else Main.panel._menus.addMenu(this.menu); this._session = new GnomeSession.SessionManager(); // Current weather this._currentWeather = new St.Bin(); // Future weather this._futureWeather = new St.Bin(); // Putting the popup item together let _itemCurrent = new PopupMenu.PopupBaseMenuItem({ reactive: false }); let _itemFuture = new PopupMenu.PopupBaseMenuItem({ reactive: false }); _itemCurrent.actor.add_actor(this._currentWeather); _itemFuture.actor.add_actor(this._futureWeather); this.menu.addMenuItem(_itemCurrent); this._separatorItem = new PopupMenu.PopupSeparatorMenuItem(); this.menu.addMenuItem(this._separatorItem); this.menu.addMenuItem(_itemFuture); let item = new PopupMenu.PopupSeparatorMenuItem(); this.menu.addMenuItem(item); this._selectCity = new PopupMenu.PopupSubMenuMenuItem(""); this._selectCity.actor.set_height(0); this._selectCity._triangle.set_height(0); this._buttonMenu = new PopupMenu.PopupBaseMenuItem({ reactive: false, style_class: 'openweather-menu-button-container' }); this.rebuildButtonMenu(); this.menu.addMenuItem(this._buttonMenu); this.menu.addMenuItem(this._selectCity); this.rebuildSelectCityItem(); this._selectCity.menu.connect('open-state-changed', Lang.bind(this, function() { this._selectCity.remove_style_pseudo_class('open'); })); this.rebuildCurrentWeatherUi(); this.rebuildFutureWeatherUi(); this._idle = false; this._connected = false; this._network_monitor = Gio.network_monitor_get_default(); this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) { this._onStatusChanged(proxy.status); })); this._presence_connection = this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) { this._onStatusChanged(status); })); this.currentWeatherCache = _currentWeatherCache; this.forecastWeatherCache = _forecastWeatherCache; if (_timeCacheForecastWeather !== undefined) { let diff = Math.floor(new Date(new Date() - _timeCacheForecastWeather).getTime() / 1000); if (diff < this._refresh_interval_forecast) this.reloadWeatherForecast(this._refresh_interval_forecast - diff); } if (_timeCacheCurrentWeather !== undefined) { let diff = Math.floor(new Date(new Date() - _timeCacheCurrentWeather).getTime() / 1000); if (diff < this._refresh_interval_current) this.reloadWeatherCurrent(this._refresh_interval_current - diff); } this._network_monitor_connection = this._network_monitor.connect('network-changed', Lang.bind(this, this._onNetworkStateChanged)); this._checkConnectionState(); this.menu.connect('open-state-changed', Lang.bind(this, this.recalcLayout)); } _onStatusChanged(status) { this._idle = false; if (status == GnomeSession.PresenceStatus.IDLE) { this._idle = true; } } stop() { _forecastWeatherCache = this.forecastWeatherCache; _currentWeatherCache = this.currentWeatherCache; if (_httpSession !== undefined) _httpSession.abort(); _httpSession = undefined; if (this._timeoutCurrent) Mainloop.source_remove(this._timeoutCurrent); this._timeoutCurrent = undefined; if (this._timeoutForecast) Mainloop.source_remove(this._timeoutForecast); this._timeoutForecast = undefined; if (this._presence_connection) { this._presence.disconnectSignal(this._presence_connection); this._presence_connection = undefined; } if (this._network_monitor_connection) { this._network_monitor.disconnect(this._network_monitor_connection); this._network_monitor_connection = undefined; } if (this._timeoutCheckConnectionState) Mainloop.source_remove(this._timeoutCheckConnectionState); this._timeoutCheckConnectionState = undefined; if (this._settingsC) { this._settings.disconnect(this._settingsC); this._settingsC = undefined; } if (this._settingsInterfaceC) { this._settingsInterface.disconnect(this._settingsInterfaceC); this._settingsInterfaceC = undefined; } if (this._globalThemeChangedId) { let context = St.ThemeContext.get_for_stage(global.stage); context.disconnect(this._globalThemeChangedId); this._globalThemeChangedId = undefined; } } switchProvider() { if (this._weather_provider == WeatherProvider.DARKSKY) this.useDarkskyNet(); else this.useOpenweathermapOrg(); } useOpenweathermapOrg() { this.parseWeatherForecast = OpenweathermapOrg.parseWeatherForecast; this.parseWeatherCurrent = OpenweathermapOrg.parseWeatherCurrent; this.getWeatherIcon = OpenweathermapOrg.getWeatherIcon; this.refreshWeatherCurrent = OpenweathermapOrg.refreshWeatherCurrent; this.refreshWeatherForecast = OpenweathermapOrg.refreshWeatherForecast; this.weatherProvider = "OpenWeatherMap"; if (this._appid.toString().trim() === '') Main.notify("Openweather", _("Openweathermap.org does not work without an api-key.\nEither set the switch to use the extensions default key in the preferences dialog to on or register at https://openweathermap.org/appid and paste your personal key into the preferences dialog.")); } useDarkskyNet() { this.parseWeatherCurrent = DarkskyNet.parseWeatherCurrent; this.parseWeatherForecast = DarkskyNet.parseWeatherForecast; this.getWeatherIcon = DarkskyNet.getWeatherIcon; this.refreshWeatherCurrent = DarkskyNet.refreshWeatherCurrent; this.refreshWeatherForecast = function() {}; this.weatherProvider = "Dark Sky"; this.fc_locale = 'en'; if (this._translate_condition) { let fc_locales = [ 'ar', 'az', 'be', 'bs', 'cz', 'de', 'el', 'en', 'es', 'fr', 'hr', 'hu', 'id', 'it', 'is', 'kw', 'nb', 'nl', 'pl', 'pt', 'ru', 'sk', 'sr', 'sv', 'tet', 'tr', 'uk', 'x-pig-latin', 'zh', 'zh-tw' ]; if (fc_locales.indexOf(this.locale) != -1) this.fc_locale = this.locale; } if (this._appid_fc.toString().trim() === '') Main.notify("Openweather", _("Dark Sky does not work without an api-key.\nPlease register at https://darksky.net/dev/register and paste your personal key into the preferences dialog.")); } getWeatherProviderURL() { let url = ""; if (this._weather_provider == WeatherProvider.DARKSKY) { url = "https://darksky.net/"; url += this.extractCoord(this._city); } else { url = "https://openweathermap.org"; url += "/city/" + this.owmCityId; if (this._appid) url += "?APPID=" + this._appid; } return url; } loadConfig() { this._settings = ExtensionUtils.getSettings(OPENWEATHER_SETTINGS_SCHEMA); if (this._cities.length === 0) this._cities = "-8.5211767,179.1976747>Vaiaku, Tuvalu>-1"; this._settingsC = this._settings.connect("changed", Lang.bind(this, function() { if (this._cities.length === 0) this._cities = "-8.5211767,179.1976747>Vaiaku, Tuvalu>-1"; this.rebuildCurrentWeatherUi(); this.rebuildFutureWeatherUi(); if (this.providerChanged()) { this.switchProvider(); this.currentWeatherCache = undefined; this.forecastWeatherCache = undefined; } if (this.locationChanged()) { this.currentWeatherCache = undefined; this.forecastWeatherCache = undefined; } this.rebuildButtonMenu(); this.parseWeatherCurrent(); })); } loadConfigInterface() { this._settingsInterface = ExtensionUtils.getSettings(OPENWEATHER_DESKTOP_INTERFACE); this._settingsInterfaceC = this._settingsInterface.connect("changed", Lang.bind(this, function() { this.rebuildCurrentWeatherUi(); this.rebuildFutureWeatherUi(); if (this.providerChanged()) { this.switchProvider(); this.currentWeatherCache = undefined; this.forecastWeatherCache = undefined; } if (this.locationChanged()) { this.currentWeatherCache = undefined; this.forecastWeatherCache = undefined; } this.parseWeatherCurrent(); })); } _onNetworkStateChanged() { this._checkConnectionState(); } _checkConnectionState() { this._checkConnectionStateRetries = 3; this._oldConnected = this._connected; this._connected = false; this._checkConnectionStateWithRetries(1250); } _checkConnectionStateRetry() { if (this._checkConnectionStateRetries > 0) { let timeout; if (this._checkConnectionStateRetries == 3) timeout = 10000; else if (this._checkConnectionStateRetries == 2) timeout = 30000; else if (this._checkConnectionStateRetries == 1) timeout = 60000; this._checkConnectionStateRetries -= 1; this._checkConnectionStateWithRetries(timeout); } } _checkConnectionStateWithRetries(interval) { if (this._timeoutCheckConnectionState) { Mainloop.source_remove(this._timeoutCheckConnectionState); this._timeoutCheckConnectionState = undefined; } this._timeoutCheckConnectionState = Mainloop.timeout_add(interval, Lang.bind(this, function() { // Delete (undefine) the variable holding the timeout-id, otherwise we can get errors, if we try to delete // it manually, the timeout will be destroyed automatically if we return false. // We just fetch it for the rare case, where the connection chages or the extension will be stopped during // the timeout. this._timeoutCheckConnectionState = undefined; let url = this.getWeatherProviderURL(); let address = Gio.NetworkAddress.parse_uri(url, 80); let cancellable = Gio.Cancellable.new(); try { this._network_monitor.can_reach_async(address, cancellable, Lang.bind(this, this._asyncReadyCallback)); } catch (err) { let title = _("Can not connect to %s").format(url); log(title + '\n' + err.message); this._checkConnectionStateRetry(); } return false; })); } _asyncReadyCallback(nm, res) { try { this._connected = this._network_monitor.can_reach_finish(res); } catch (err) { let title = _("Can not connect to %s").format(this.getWeatherProviderURL()); log(title + '\n' + err.message); this._checkConnectionStateRetry(); return; } if (!this._oldConnected && this._connected) { let now = new Date(); if (_timeCacheCurrentWeather && (Math.floor(new Date(now - _timeCacheCurrentWeather).getTime() / 1000) > this._refresh_interval_current)) this.currentWeatherCache = undefined; if (_timeCacheForecastWeather && (Math.floor(new Date(now - _timeCacheForecastWeather).getTime() / 1000) > this._refresh_interval_forecast)) this.forecastWeatherCache = undefined; this.parseWeatherCurrent(); } } locationChanged() { let location = this.extractCoord(this._city); if (this.oldLocation != location) { return true; } return false; } providerChanged() { let provider = this._weather_provider; if (this.oldProvider != provider) { this.oldProvider = provider; return true; } if (provider == WeatherProvider.OPENWEATHERMAP) { let useDefaultOwmKey = this._use_default_owm_key; if (this.oldUseDefaultOwmKey != useDefaultOwmKey) { this.oldUseDefaultOwmKey = useDefaultOwmKey; return true; } } if (provider == WeatherProvider.DARKSKY) { let translateCondition = this._translate_condition; if (this.oldTranslateCondition != translateCondition) { this.oldTranslateCondition = translateCondition; return true; } } return false; } get _clockFormat() { if (!this._settingsInterface) this.loadConfigInterface(); return this._settingsInterface.get_string("clock-format"); } get _weather_provider() { if (!this._settings) this.loadConfig(); let provider = this.extractProvider(this._city); if (provider == WeatherProvider.DEFAULT) provider = this._settings.get_enum(OPENWEATHER_PROVIDER_KEY); return provider; } get _units() { if (!this._settings) this.loadConfig(); return this._settings.get_enum(OPENWEATHER_UNIT_KEY); } get _wind_speed_units() { if (!this._settings) this.loadConfig(); return this._settings.get_enum(OPENWEATHER_WIND_SPEED_UNIT_KEY); } get _wind_direction() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_WIND_DIRECTION_KEY); } get _pressure_units() { if (!this._settings) this.loadConfig(); return this._settings.get_enum(OPENWEATHER_PRESSURE_UNIT_KEY); } get _cities() { if (!this._settings) this.loadConfig(); return this._settings.get_string(OPENWEATHER_CITY_KEY); } set _cities(v) { if (!this._settings) this.loadConfig(); return this._settings.set_string(OPENWEATHER_CITY_KEY, v); } createButton(iconName, accessibleName) { let button; button = new St.Button({ reactive: true, can_focus: true, track_hover: true, accessible_name: accessibleName, style_class: 'message-list-clear-button button openweather-button-action' }); button.child = new St.Icon({ icon_name: iconName }); return button; } get _actual_city() { if (!this._settings) this.loadConfig(); var a = this._settings.get_int(OPENWEATHER_ACTUAL_CITY_KEY); var b = a; var cities = this._cities.split(" && "); if (typeof cities != "object") cities = [cities]; var l = cities.length - 1; if (a < 0) a = 0; if (l < 0) l = 0; if (a > l) a = l; return a; } set _actual_city(a) { if (!this._settings) this.loadConfig(); var cities = this._cities.split(" && "); if (typeof cities != "object") cities = [cities]; var l = cities.length - 1; if (a < 0) a = 0; if (l < 0) l = 0; if (a > l) a = l; this._settings.set_int(OPENWEATHER_ACTUAL_CITY_KEY, a); } get _city() { let cities = this._cities.split(" && "); if (cities && typeof cities == "string") cities = [cities]; if (!cities[0]) return ""; cities = cities[this._actual_city]; return cities; } get _translate_condition() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_TRANSLATE_CONDITION_KEY); } get _getIconType() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_USE_SYMBOLIC_ICONS_KEY) ? 1 : 0; } get _use_text_on_buttons() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_USE_TEXT_ON_BUTTONS_KEY) ? 1 : 0; } get _text_in_panel() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_SHOW_TEXT_IN_PANEL_KEY); } get _position_in_panel() { if (!this._settings) this.loadConfig(); return this._settings.get_enum(OPENWEATHER_POSITION_IN_PANEL_KEY); } get _menu_alignment() { if (!this._settings) this.loadConfig(); return this._settings.get_double(OPENWEATHER_MENU_ALIGNMENT_KEY); } get _comment_in_panel() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_SHOW_COMMENT_IN_PANEL_KEY); } get _comment_in_forecast() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_SHOW_COMMENT_IN_FORECAST_KEY); } get _refresh_interval_current() { if (!this._settings) this.loadConfig(); let v = this._settings.get_int(OPENWEATHER_REFRESH_INTERVAL_CURRENT); return ((v >= 600) ? v : 600); } get _refresh_interval_forecast() { if (!this._settings) this.loadConfig(); let v = this._settings.get_int(OPENWEATHER_REFRESH_INTERVAL_FORECAST); return ((v >= 600) ? v : 600); } get _loc_len_current() { if (!this._settings) this.loadConfig(); let v = this._settings.get_int(OPENWEATHER_LOC_TEXT_LEN); return ((v > 0) ? v : 0); } get _center_forecast() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_CENTER_FORECAST_KEY); } get _days_forecast() { if (!this._settings) this.loadConfig(); return this._settings.get_int(OPENWEATHER_DAYS_FORECAST); } get _decimal_places() { if (!this._settings) this.loadConfig(); return this._settings.get_int(OPENWEATHER_DECIMAL_PLACES); } get _appid() { if (!this._settings) this.loadConfig(); let key = ''; if (this._use_default_owm_key) key = OPENWEATHER_OWM_DEFAULT_API_KEY; else key = this._settings.get_string(OPENWEATHER_OWM_API_KEY); return (key.length == 32) ? key : ''; } get _use_default_owm_key() { if (!this._settings) this.loadConfig(); return this._settings.get_boolean(OPENWEATHER_USE_DEFAULT_OWM_API_KEY); } get _appid_fc() { if (!this._settings) this.loadConfig(); let key = this._settings.get_string(OPENWEATHER_FC_API_KEY); return (key.length == 32) ? key : ''; } rebuildButtonMenu() { if (this._buttonBox) { if (this._buttonBox1) { this._buttonBox1.destroy(); this._buttonBox1 = undefined; } if (this._buttonBox2) { this._buttonBox2.destroy(); this._buttonBox2 = undefined; } this._buttonMenu.removeActor(this._buttonBox); this._buttonBox.destroy(); this._buttonBox = undefined; } if (this._buttonBox1) { this._buttonBox1.destroy(); this._buttonBox1 = undefined; } if (this._buttonBox2) { this._buttonBox2.destroy(); this._buttonBox2 = undefined; } this._locationButton = this.createButton('find-location-symbolic', _("Locations")); if (this._use_text_on_buttons) this._locationButton.set_label(this._locationButton.get_accessible_name()); this._locationButton.connect('clicked', Lang.bind(this, function() { this._selectCity._setOpenState(!this._selectCity._getOpenState()); })); this._buttonBox1 = new St.BoxLayout({ style_class: 'openweather-button-box' }); this._buttonBox1.add_actor(this._locationButton); this._reloadButton = this.createButton('view-refresh-symbolic', _("Reload Weather Information")); if (this._use_text_on_buttons) this._reloadButton.set_label(this._reloadButton.get_accessible_name()); this._reloadButton.connect('clicked', Lang.bind(this, function() { this.currentWeatherCache = undefined; this.forecastWeatherCache = undefined; this.parseWeatherCurrent(); this.recalcLayout(); })); this._buttonBox1.add_actor(this._reloadButton); this._buttonBox2 = new St.BoxLayout({ style_class: 'openweather-button-box' }); this._urlButton = this.createButton('', _("Weather data provided by:") + (this._use_text_on_buttons ? "\n" : " ") + this.weatherProvider); this._urlButton.set_label(this._urlButton.get_accessible_name()); this._urlButton.connect('clicked', Lang.bind(this, function() { this.menu.actor.hide(); let url = this.getWeatherProviderURL(); try { Gtk.show_uri(null, url, global.get_current_time()); } catch (err) { let title = _("Can not open %s").format(url); Main.notifyError(title, err.message); } })); this._buttonBox2.add_actor(this._urlButton); this._prefsButton = this.createButton('preferences-system-symbolic', _("Weather Settings")); if (this._use_text_on_buttons) this._prefsButton.set_label(this._prefsButton.get_accessible_name()); this._prefsButton.connect('clicked', Lang.bind(this, this._onPreferencesActivate)); this._buttonBox2.add_actor(this._prefsButton); this._buttonMenu.actor.add_actor(this._buttonBox1); this._buttonMenu.actor.add_actor(this._buttonBox2); this._buttonBox1MinWidth = undefined; } rebuildSelectCityItem() { this._selectCity.menu.removeAll(); let item = null; let cities = this._cities; cities = cities.split(" && "); if (cities && typeof cities == "string") cities = [cities]; if (!cities[0]) return; for (let i = 0; cities.length > i; i++) { item = new PopupMenu.PopupMenuItem(this.extractLocation(cities[i])); item.location = i; if (i == this._actual_city) { item.setOrnament(PopupMenu.Ornament.DOT); } this._selectCity.menu.addMenuItem(item); // override the items default onActivate-handler, to keep the ui open while chosing the location item.activate = this._onActivate; } if (cities.length == 1) this._selectCity.actor.hide(); else this._selectCity.actor.show(); } _onActivate() { openweatherMenu._actual_city = this.location; } extractLocation() { if (!arguments[0]) return ""; if (arguments[0].search(">") == -1) return _("Invalid city"); return arguments[0].split(">")[1]; } extractCoord() { let coords = 0; if (arguments[0] && (arguments[0].search(">") != -1)) coords = arguments[0].split(">")[0].replace(' ', ''); if ((coords.search(",") == -1) || isNaN(coords.split(",")[0]) || isNaN(coords.split(",")[1])) { Main.notify("Openweather", _("Invalid location! Please try to recreate it.")); return 0; } return coords; } extractProvider() { if (!arguments[0]) return -1; if (arguments[0].split(">")[2] === undefined) return -1; if (isNaN(parseInt(arguments[0].split(">")[2]))) return -1; return parseInt(arguments[0].split(">")[2]); } _onPreferencesActivate() { this.menu.actor.hide(); if (typeof ExtensionUtils.openPrefs === 'function') { ExtensionUtils.openPrefs(); } else { Util.spawn([ "gnome-shell-extension-prefs", Me.uuid ]); } return 0; } recalcLayout() { if (!this.menu.isOpen) return; if (this._buttonBox1MinWidth === undefined) this._buttonBox1MinWidth = this._buttonBox1.get_width(); this._buttonBox1.set_width(Math.max(this._buttonBox1MinWidth, this._currentWeather.get_width() - this._buttonBox2.get_width())); if (this._forecastScrollBox !== undefined && this._forecastBox !== undefined && this._currentWeather !== undefined) { this._forecastScrollBox.set_width(Math.max(this._currentWeather.get_width(), (this._buttonBox1.get_width() + this._buttonBox2.get_width()))); this._forecastScrollBox.show(); if (this._forecastBox.get_preferred_width(this._forecastBox.get_height())[0] > this._currentWeather.get_width()) { this._forecastScrollBox.hscroll.margin_top = 10; this._forecastScrollBox.hscroll.show(); } else { this._forecastScrollBox.hscroll.margin_top = 0; this._forecastScrollBox.hscroll.hide(); } } } unit_to_unicode() { if (this._units == WeatherUnits.FAHRENHEIT) return _('\u00B0F'); else if (this._units == WeatherUnits.KELVIN) return _('K'); else if (this._units == WeatherUnits.RANKINE) return _('\u00B0Ra'); else if (this._units == WeatherUnits.REAUMUR) return _('\u00B0R\u00E9'); else if (this._units == WeatherUnits.ROEMER) return _('\u00B0R\u00F8'); else if (this._units == WeatherUnits.DELISLE) return _('\u00B0De'); else if (this._units == WeatherUnits.NEWTON) return _('\u00B0N'); else return _('\u00B0C'); } hasIcon(icon) { return Gtk.IconTheme.get_default().has_icon(icon); } toFahrenheit(t) { return ((Number(t) * 1.8) + 32).toFixed(this._decimal_places); } toKelvin(t) { return (Number(t) + 273.15).toFixed(this._decimal_places); } toRankine(t) { return ((Number(t) * 1.8) + 491.67).toFixed(this._decimal_places); } toReaumur(t) { return (Number(t) * 0.8).toFixed(this._decimal_places); } toRoemer(t) { return ((Number(t) * 21 / 40) + 7.5).toFixed(this._decimal_places); } toDelisle(t) { return ((100 - Number(t)) * 1.5).toFixed(this._decimal_places); } toNewton(t) { return (Number(t) - 0.33).toFixed(this._decimal_places); } toInHg(p /*, t*/ ) { return (p / 33.86530749).toFixed(this._decimal_places); } toBeaufort(w, t) { if (w < 0.3) return (!t) ? "0" : "(" + _("Calm") + ")"; else if (w >= 0.3 && w <= 1.5) return (!t) ? "1" : "(" + _("Light air") + ")"; else if (w > 1.5 && w <= 3.4) return (!t) ? "2" : "(" + _("Light breeze") + ")"; else if (w > 3.4 && w <= 5.4) return (!t) ? "3" : "(" + _("Gentle breeze") + ")"; else if (w > 5, 4 && w <= 7.9) return (!t) ? "4" : "(" + _("Moderate breeze") + ")"; else if (w > 7.9 && w <= 10.7) return (!t) ? "5" : "(" + _("Fresh breeze") + ")"; else if (w > 10.7 && w <= 13.8) return (!t) ? "6" : "(" + _("Strong breeze") + ")"; else if (w > 13.8 && w <= 17.1) return (!t) ? "7" : "(" + _("Moderate gale") + ")"; else if (w > 17.1 && w <= 20.7) return (!t) ? "8" : "(" + _("Fresh gale") + ")"; else if (w > 20.7 && w <= 24.4) return (!t) ? "9" : "(" + _("Strong gale") + ")"; else if (w > 24.4 && w <= 28.4) return (!t) ? "10" : "(" + _("Storm") + ")"; else if (w > 28.4 && w <= 32.6) return (!t) ? "11" : "(" + _("Violent storm") + ")"; else return (!t) ? "12" : "(" + _("Hurricane") + ")"; } getLocaleDay(abr) { let days = [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')]; return days[abr]; } getWindDirection(deg) { let arrows = ["\u2193", "\u2199", "\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198"]; let letters = [_('N'), _('NE'), _('E'), _('SE'), _('S'), _('SW'), _('W'), _('NW')]; let idx = Math.round(deg / 45) % arrows.length; return (this._wind_direction) ? arrows[idx] : letters[idx]; } getIconType() { if (this._getIconType) { return "openweather-symbolic"; } else { return "openweather-regular"; } } load_json_async(url, params, fun) { if (_httpSession === undefined) { _httpSession = new Soup.Session(); _httpSession.user_agent = this.user_agent; } else { // abort previous requests. _httpSession.abort(); } let message = Soup.form_request_new_from_hash('GET', url, params); _httpSession.queue_message(message, Lang.bind(this, function(_httpSession, message) { try { if (!message.response_body.data) { fun.call(this, 0); return; } let jp = JSON.parse(message.response_body.data); fun.call(this, jp); } catch (e) { fun.call(this, 0); return; } })); return; } checkAlignment() { let menuAlignment = 1.0 - (this._menu_alignment / 100); if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) menuAlignment = 1.0 - menuAlignment; this.menu._arrowAlignment=menuAlignment; } checkPositionInPanel() { if (this._old_position_in_panel == undefined || this._old_position_in_panel != this._position_in_panel) { this.get_parent().remove_actor(this); let children = null; switch (this._position_in_panel) { case WeatherPosition.LEFT: children = Main.panel._leftBox.get_children(); Main.panel._leftBox.insert_child_at_index(this, children.length); break; case WeatherPosition.CENTER: children = Main.panel._centerBox.get_children(); Main.panel._centerBox.insert_child_at_index(this, children.length); break; case WeatherPosition.RIGHT: children = Main.panel._rightBox.get_children(); Main.panel._rightBox.insert_child_at_index(this, 0); break; } this._old_position_in_panel = this._position_in_panel; } } formatPressure(pressure) { let pressure_unit = _('hPa'); switch (this._pressure_units) { case WeatherPressureUnits.INHG: pressure = this.toInHg(pressure); pressure_unit = _("inHg"); break; case WeatherPressureUnits.HPA: pressure = pressure.toFixed(this._decimal_places); pressure_unit = _("hPa"); break; case WeatherPressureUnits.BAR: pressure = (pressure / 1000).toFixed(this._decimal_places); pressure_unit = _("bar"); break; case WeatherPressureUnits.PA: pressure = (pressure * 100).toFixed(this._decimal_places); pressure_unit = _("Pa"); break; case WeatherPressureUnits.KPA: pressure = (pressure / 10).toFixed(this._decimal_places); pressure_unit = _("kPa"); break; case WeatherPressureUnits.ATM: pressure = (pressure * 0.000986923267).toFixed(this._decimal_places); pressure_unit = _("atm"); break; case WeatherPressureUnits.AT: pressure = (pressure * 0.00101971621298).toFixed(this._decimal_places); pressure_unit = _("at"); break; case WeatherPressureUnits.TORR: pressure = (pressure * 0.750061683).toFixed(this._decimal_places); pressure_unit = _("Torr"); break; case WeatherPressureUnits.PSI: pressure = (pressure * 0.0145037738).toFixed(this._decimal_places); pressure_unit = _("psi"); break; case WeatherPressureUnits.MMHG: pressure = (pressure * 0.750061683).toFixed(this._decimal_places); pressure_unit = _("mmHg"); break; case WeatherPressureUnits.MBAR: pressure = pressure.toFixed(this._decimal_places); pressure_unit = _("mbar"); break; } return parseFloat(pressure).toLocaleString(this.locale) + ' ' + pressure_unit; } formatTemperature(temperature) { switch (this._units) { case WeatherUnits.FAHRENHEIT: temperature = this.toFahrenheit(temperature); break; case WeatherUnits.CELSIUS: temperature = temperature.toFixed(this._decimal_places); break; case WeatherUnits.KELVIN: temperature = this.toKelvin(temperature); break; case WeatherUnits.RANKINE: temperature = this.toRankine(temperature); break; case WeatherUnits.REAUMUR: temperature = this.toReaumur(temperature); break; case WeatherUnits.ROEMER: temperature = this.toRoemer(temperature); break; case WeatherUnits.DELISLE: temperature = this.toDelisle(temperature); break; case WeatherUnits.NEWTON: temperature = this.toNewton(temperature); break; } return parseFloat(temperature).toLocaleString(this.locale).replace('-', '\u2212') + ' ' + this.unit_to_unicode(); } formatWind(speed, direction) { let unit = _('m/s'); switch (this._wind_speed_units) { case WeatherWindSpeedUnits.MPH: speed = (speed * OPENWEATHER_CONV_MPS_IN_MPH).toFixed(this._decimal_places); unit = _('mph'); break; case WeatherWindSpeedUnits.KPH: speed = (speed * OPENWEATHER_CONV_MPS_IN_KPH).toFixed(this._decimal_places); unit = _('km/h'); break; case WeatherWindSpeedUnits.MPS: speed = speed.toFixed(this._decimal_places); break; case WeatherWindSpeedUnits.KNOTS: speed = (speed * OPENWEATHER_CONV_MPS_IN_KNOTS).toFixed(this._decimal_places); unit = _('kn'); break; case WeatherWindSpeedUnits.FPS: speed = (speed * OPENWEATHER_CONV_MPS_IN_FPS).toFixed(this._decimal_places); unit = _('ft/s'); break; case WeatherWindSpeedUnits.BEAUFORT: speed = this.toBeaufort(speed); unit = this.toBeaufort(speed, true); break; } if (!speed) return '\u2013'; else if (speed === 0 || !direction) return parseFloat(speed).toLocaleString(this.locale) + ' ' + unit; else // i.e. speed > 0 && direction return direction + ' ' + parseFloat(speed).toLocaleString(this.locale) + ' ' + unit; } reloadWeatherCurrent(interval) { if (this._timeoutCurrent) { Mainloop.source_remove(this._timeoutCurrent); this._timeoutCurrent = undefined; } _timeCacheCurrentWeather = new Date(); this._timeoutCurrent = Mainloop.timeout_add_seconds(interval, Lang.bind(this, function() { // only invalidate cached data, if we can connect the weather-providers server if (this._connected && !this._idle) this.currentWeatherCache = undefined; this.parseWeatherCurrent(); return true; })); } reloadWeatherForecast(interval) { if (this._timeoutForecast) { Mainloop.source_remove(this._timeoutForecast); this._timeoutForecast = undefined; } _timeCacheForecastWeather = new Date(); this._timeoutForecast = Mainloop.timeout_add_seconds(interval, Lang.bind(this, function() { // only invalidate cached data, if we can connect the weather-providers server if (this._connected && !this._idle) this.forecastWeatherCache = undefined; this.parseWeatherForecast(); return true; })); } destroyCurrentWeather() { if (this._currentWeather.get_child() !== null) this._currentWeather.get_child().destroy(); } destroyFutureWeather() { if (this._futureWeather.get_child() !== null) this._futureWeather.get_child().destroy(); } rebuildCurrentWeatherUi() { this._weatherInfo.text = (' '); this._weatherIcon.icon_name = 'view-refresh'; this._weatherIcon.remove_style_class_name('openweather-regular'); this._weatherIcon.remove_style_class_name('openweather-symbolic'); this._weatherIcon.add_style_class_name(this.getIconType()); this.destroyCurrentWeather(); // This will hold the icon for the current weather this._currentWeatherIcon = new St.Icon({ icon_size: 72, icon_name: 'view-refresh', style_class: 'system-menu-action openweather-current-icon ' + this.getIconType() }); this._sunriseIcon = new St.Icon({ icon_size: 15, icon_name: 'weather-clear', style_class: 'openweather-sunrise-icon ' + this.getIconType() }); this._sunsetIcon = new St.Icon({ icon_size: 15, icon_name: 'weather-clear-night', style_class: 'openweather-sunset-icon ' + this.getIconType() }); this._buildIcon = new St.Icon({ icon_size: 15, icon_name: 'view-refresh', style_class: 'openweather-build-icon ' + this.getIconType() }); // The summary of the current weather this._currentWeatherSummary = new St.Label({ text: _('Loading ...'), style_class: 'openweather-current-summary' }); this._currentWeatherLocation = new St.Label({ text: _('Please wait') }); let bb = new St.BoxLayout({ vertical: true, style_class: 'system-menu-action openweather-current-summarybox' }); bb.add_actor(this._currentWeatherLocation); bb.add_actor(this._currentWeatherSummary); this._currentWeatherSunrise = new St.Label({ text: '-' }); this._currentWeatherSunset = new St.Label({ text: '-' }); this._currentWeatherBuild = new St.Label({ text: '-' }); let ab = new St.BoxLayout({ style_class: 'openweather-current-infobox' }); ab.add_actor(this._sunriseIcon); ab.add_actor(this._currentWeatherSunrise); ab.add_actor(this._sunsetIcon); ab.add_actor(this._currentWeatherSunset); ab.add_actor(this._buildIcon); ab.add_actor(this._currentWeatherBuild); bb.add_actor(ab); // Other labels this._currentWeatherCloudiness = new St.Label({ text: '...' }); this._currentWeatherHumidity = new St.Label({ text: '...' }); this._currentWeatherPressure = new St.Label({ text: '...' }); this._currentWeatherWind = new St.Label({ text: '...' }); let rb = new St.BoxLayout({ style_class: 'openweather-current-databox' }); let rb_captions = new St.BoxLayout({ vertical: true, style_class: 'popup-menu-item popup-status-menu-item openweather-current-databox-captions' }); let rb_values = new St.BoxLayout({ vertical: true, style_class: 'system-menu-action openweather-current-databox-values' }); rb.add_actor(rb_captions); rb.add_actor(rb_values); rb_captions.add_actor(new St.Label({ text: _('Cloudiness:') })); rb_values.add_actor(this._currentWeatherCloudiness); rb_captions.add_actor(new St.Label({ text: _('Humidity:') })); rb_values.add_actor(this._currentWeatherHumidity); rb_captions.add_actor(new St.Label({ text: _('Pressure:') })); rb_values.add_actor(this._currentWeatherPressure); rb_captions.add_actor(new St.Label({ text: _('Wind:') })); rb_values.add_actor(this._currentWeatherWind); let xb = new St.BoxLayout(); xb.add_actor(bb); xb.add_actor(rb); let box = new St.BoxLayout({ style_class: 'openweather-current-iconbox' }); box.add_actor(this._currentWeatherIcon); box.add_actor(xb); this._currentWeather.set_child(box); } scrollForecastBy(delta) { if (this._forecastScrollBox === undefined) return; this._forecastScrollBox.hscroll.adjustment.value += delta; } rebuildFutureWeatherUi(cnt) { this.destroyFutureWeather(); this._forecast = []; this._forecastBox = new St.BoxLayout({ x_align: this._center_forecast ? St.Align.END : St.Align.START, style_class: 'openweather-forecast-box' }); this._forecastScrollBox = new St.ScrollView({ style_class: 'openweather-forecasts' }); let pan = new Clutter.PanAction({ interpolate: true }); pan.connect('pan', Lang.bind(this, function(action) { let[dist, dx, dy] = action.get_motion_delta(0); this.scrollForecastBy(-1 * (dx / this._forecastScrollBox.width) * this._forecastScrollBox.hscroll.adjustment.page_size); return false; })); this._forecastScrollBox.add_action(pan); this._forecastScrollBox.connect('scroll-event', Lang.bind(this, this._onScroll)); this._forecastScrollBox.hscroll.connect('scroll-event', Lang.bind(this, this._onScroll)); this._forecastScrollBox.hscroll.margin_right = 25; this._forecastScrollBox.hscroll.margin_left = 25; this._forecastScrollBox.hscroll.hide(); this._forecastScrollBox.vscrollbar_policy = Gtk.PolicyType.NEVER; this._forecastScrollBox.hscrollbar_policy = Gtk.PolicyType.AUTOMATIC; this._forecastScrollBox.enable_mouse_scrolling = true; this._forecastScrollBox.hide(); this._futureWeather.set_child(this._forecastScrollBox); if (cnt === undefined) cnt = this._days_forecast; for (let i = 0; i < cnt; i++) { let forecastWeather = {}; forecastWeather.Icon = new St.Icon({ icon_size: 48, icon_name: 'view-refresh', style_class: 'system-menu-action openweather-forecast-icon ' + this.getIconType() }); forecastWeather.Day = new St.Label({ style_class: 'popup-menu-item popup-status-menu-item openweather-forecast-day' }); forecastWeather.Summary = new St.Label({ style_class: 'system-menu-action openweather-forecast-summary' }); forecastWeather.Summary.clutter_text.line_wrap = true; forecastWeather.Temperature = new St.Label({ style_class: 'system-menu-action openweather-forecast-temperature' }); let by = new St.BoxLayout({ vertical: true, style_class: 'openweather-forecast-databox' }); by.add_actor(forecastWeather.Day); if (this._comment_in_forecast) by.add_actor(forecastWeather.Summary); by.add_actor(forecastWeather.Temperature); let bb = new St.BoxLayout({ style_class: 'openweather-forecast-iconbox' }); bb.add_actor(forecastWeather.Icon); bb.add_actor(by); this._forecast[i] = forecastWeather; this._forecastBox.add_actor(bb); } this._forecastScrollBox.add_actor(this._forecastBox); } _onScroll(actor, event) { let dx = 0; let dy = 0; switch (event.get_scroll_direction()) { case Clutter.ScrollDirection.UP: case Clutter.ScrollDirection.RIGHT: dy = -1; break; case Clutter.ScrollDirection.DOWN: case Clutter.ScrollDirection.LEFT: dy = 1; break; default: return true; } this.scrollForecastBy(dy * this._forecastScrollBox.hscroll.adjustment.stepIncrement); return false; } }); let openweatherMenu; function init() { ExtensionUtils.initTranslations('gnome-shell-extension-openweather'); } function enable() { openweatherMenu = new OpenweatherMenuButton(); Main.panel.addToStatusArea('openweatherMenu', openweatherMenu); } function disable() { openweatherMenu.stop(); openweatherMenu.destroy(); }