Created
April 29, 2016 05:30
-
-
Save arzzen/7925e31c5355035a6161b5059232ec35 to your computer and use it in GitHub Desktop.
Cinnamon weather applet - file applet.sj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//"use strict" | |
//---------------------------------- | |
// imports | |
//---------------------------------- | |
/** | |
* /usr/share/gjs-1.0/ | |
* /usr/share/gnome-js/ | |
*/ | |
const Cairo = imports.cairo | |
const Lang = imports.lang | |
// http://developer.gnome.org/glib/unstable/glib-The-Main-Event-Loop.html | |
const Mainloop = imports.mainloop | |
/** | |
* /usr/share/gjs-1.0/overrides/ | |
* /usr/share/gir-1.0/ | |
* /usr/lib/cinnamon/ | |
*/ | |
const Gio = imports.gi.Gio | |
const Gtk = imports.gi.Gtk | |
const Json = imports.gi.Json | |
// http://developer.gnome.org/libsoup/stable/libsoup-client-howto.html | |
const Soup = imports.gi.Soup | |
// http://developer.gnome.org/st/stable/ | |
const St = imports.gi.St | |
/** | |
* /usr/share/cinnamon/js/ | |
*/ | |
const Applet = imports.ui.applet | |
const Config = imports.misc.config | |
const PopupMenu = imports.ui.popupMenu | |
const Settings = imports.ui.settings | |
const Util = imports.misc.util | |
//---------------------------------------------------------------------- | |
// | |
// Constants | |
// | |
//---------------------------------------------------------------------- | |
const UUID = "weather@mockturtl" | |
const APPLET_ICON = "view-refresh-symbolic" | |
const CMD_SETTINGS = "cinnamon-settings applets " + UUID | |
const WOEID_URL = "http://woeid.rosselliot.co.nz" | |
const CMD_WOEID_LOOKUP = "xdg-open " + WOEID_URL | |
// Conversion Factors | |
const WEATHER_CONV_MPH_IN_MPS = 2.23693629 | |
const WEATHER_CONV_KPH_IN_MPS = 3.6 | |
const WEATHER_CONV_KNOTS_IN_MPS = 1.94384449 | |
// Magic strings | |
const BLANK = ' ' | |
const ELLIPSIS = '...' | |
const EN_DASH = '\u2013' | |
// Query | |
const QUERY_PARAMS = '?format=json&q=select ' | |
const QUERY_TABLE = 'weather.forecast' | |
const QUERY_VIEW = '*' | |
const QUERY_URL = 'http://query.yahooapis.com/v1/public/yql' + QUERY_PARAMS + QUERY_VIEW + ' from ' + QUERY_TABLE | |
// Schema keys | |
const WEATHER_CITY_KEY = 'locationLabelOverride' | |
const WEATHER_REFRESH_INTERVAL = 'refreshInterval' | |
const WEATHER_SHOW_COMMENT_IN_PANEL_KEY = 'showCommentInPanel' | |
const WEATHER_SHOW_SUNRISE_KEY = 'showSunrise' | |
const WEATHER_SHOW_24HOURS_KEY = 'show24Hours' | |
const WEATHER_SHOW_FIVEDAY_FORECAST_KEY = 'showFivedayForecast' | |
const WEATHER_SHOW_TEXT_IN_PANEL_KEY = 'showTextInPanel' | |
const WEATHER_TRANSLATE_CONDITION_KEY = 'translateCondition' | |
const WEATHER_TEMPERATURE_UNIT_KEY = 'temperatureUnit' | |
const WEATHER_PRESSURE_UNIT_KEY = 'pressureUnit' | |
const WEATHER_USE_SYMBOLIC_ICONS_KEY = 'useSymbolicIcons' | |
const WEATHER_WIND_SPEED_UNIT_KEY = 'windSpeedUnit' | |
const WEATHER_WOEID_KEY = 'woeid' | |
const KEYS = [ | |
WEATHER_TEMPERATURE_UNIT_KEY, | |
WEATHER_WIND_SPEED_UNIT_KEY, | |
WEATHER_CITY_KEY, | |
WEATHER_WOEID_KEY, | |
WEATHER_TRANSLATE_CONDITION_KEY, | |
WEATHER_SHOW_TEXT_IN_PANEL_KEY, | |
WEATHER_SHOW_COMMENT_IN_PANEL_KEY, | |
WEATHER_SHOW_SUNRISE_KEY, | |
WEATHER_SHOW_24HOURS_KEY, | |
WEATHER_SHOW_FIVEDAY_FORECAST_KEY, | |
WEATHER_REFRESH_INTERVAL, | |
WEATHER_PRESSURE_UNIT_KEY | |
] | |
// Signals | |
const SIGNAL_CHANGED = 'changed::' | |
const SIGNAL_CLICKED = 'clicked' | |
const SIGNAL_REPAINT = 'repaint' | |
// stylesheet.css | |
const STYLE_LOCATION_LINK = 'weather-current-location-link' | |
const STYLE_SUMMARYBOX = 'weather-current-summarybox' | |
const STYLE_SUMMARY = 'weather-current-summary' | |
const STYLE_DATABOX = 'weather-current-databox' | |
const STYLE_ICON = 'weather-current-icon' | |
const STYLE_ICONBOX = 'weather-current-iconbox' | |
const STYLE_DATABOX_CAPTIONS = 'weather-current-databox-captions' | |
const STYLE_ASTRONOMY = 'weather-current-astronomy' | |
const STYLE_FORECAST_ICON = 'weather-forecast-icon' | |
const STYLE_FORECAST_DATABOX = 'weather-forecast-databox' | |
const STYLE_FORECAST_DAY = 'weather-forecast-day' | |
const STYLE_CONFIG = 'weather-config' | |
const STYLE_DATABOX_VALUES = 'weather-current-databox-values' | |
const STYLE_FORECAST_SUMMARY = 'weather-forecast-summary' | |
const STYLE_FORECAST_TEMPERATURE = 'weather-forecast-temperature' | |
const STYLE_FORECAST_BOX = 'weather-forecast-box' | |
const STYLE_PANEL_BUTTON = 'panel-button' | |
const STYLE_POPUP_SEPARATOR_MENU_ITEM = 'popup-separator-menu-item' | |
const STYLE_CURRENT = 'current' | |
const STYLE_FORECAST = 'forecast' | |
const STYLE_WEATHER_MENU = 'weather-menu' | |
const WeatherUnits = { | |
CELSIUS: 'celsius', | |
FAHRENHEIT: 'fahrenheit' | |
} | |
const WeatherWindSpeedUnits = { | |
KPH: 'kph', | |
MPH: 'mph', | |
MPS: 'm/s', | |
KNOTS: 'knots' | |
} | |
const WeatherPressureResponseUnits = { | |
MBAR: 'mb', | |
PSI: 'in' | |
} | |
const WeatherPressureUnits = { | |
MBAR: 'mbar', | |
MMHG: 'mm Hg', | |
INHG: 'in Hg', | |
PA: 'Pa', | |
KPA: 'kPa', | |
PSI: 'psi', | |
ATM: 'atm', | |
AT: 'at' | |
} | |
// Pressure conversion factors | |
const WEATHER_CONV_KPA_IN_MBAR = 0.1 | |
const WEATHER_CONV_PA_IN_MBAR = 100 | |
const WEATHER_CONV_MMHG_IN_MBAR = 750.0615613e-3 | |
const WEATHER_CONV_INHG_IN_MBAR = 2.952998307e-2 | |
const WEATHER_CONV_AT_IN_MBAR = 1.019716213e-3 | |
const WEATHER_CONV_ATM_IN_MBAR = 0.986923169e-3 | |
const WEATHER_CONV_PSI_IN_MBAR = 14.5037738e-3 | |
const WEATHER_CONV_MBAR_IN_INHG = 3.3863886667e+1 | |
const WEATHER_CONV_KPA_IN_INHG = 3.386389 | |
const WEATHER_CONV_PA_IN_INHG = 3.386389e+3 | |
const WEATHER_CONV_MMHG_IN_INHG = 25.4 | |
const WEATHER_CONV_PSI_IN_INHG = 491.154152e-3 | |
const WEATHER_CONV_AT_IN_INHG = 34.531554e-3 | |
const WEATHER_CONV_ATM_IN_INHG = 33.421054e-3 | |
//---------------------------------------------------------------------- | |
// | |
// Soup | |
// | |
//---------------------------------------------------------------------- | |
// Soup session (see https://bugzilla.gnome.org/show_bug.cgi?id=661323#c64) | |
const _httpSession = new Soup.SessionAsync() | |
Soup.Session.prototype.add_feature.call(_httpSession, new Soup.ProxyResolverDefault()) | |
//---------------------------------------------------------------------- | |
// | |
// Logging | |
// | |
//---------------------------------------------------------------------- | |
function log(message) { | |
global.log(UUID + "#" + log.caller.name + ": " + message) | |
} | |
function logError(error) { | |
global.logError(UUID + "#" + logError.caller.name + ": " + error) | |
} | |
//---------------------------------------------------------------- | |
// | |
// l10n | |
// | |
//---------------------------------------------------------------------- | |
const GLib = imports.gi.GLib | |
const Gettext = imports.gettext | |
Gettext.bindtextdomain(UUID, GLib.get_home_dir() + "/.local/share/locale") | |
function _(str) { | |
return Gettext.dgettext(UUID, str) | |
} | |
//---------------------------------------------------------------------- | |
// | |
// MyApplet | |
// | |
//---------------------------------------------------------------------- | |
function MyApplet(metadata, orientation, panelHeight, instanceId) { | |
this.settings = new Settings.AppletSettings(this, UUID, instanceId) | |
this._init(orientation, panelHeight, instanceId) | |
} | |
MyApplet.prototype = { | |
__proto__: Applet.TextIconApplet.prototype | |
, refreshAndRebuild: function refreshAndRebuild() { | |
this.refreshWeather(false) | |
this.rebuild() | |
} | |
, dumpKeys: function dumpKeys() { | |
for (let k in KEYS) { | |
let key = KEYS[k] | |
let keyProp = "_" + key | |
log(keyProp + "=" + this[keyProp]) | |
} | |
} | |
, woeidLookup: function() { | |
Util.spawnCommandLine(CMD_WOEID_LOOKUP) | |
} | |
// Override Methods: TextIconApplet | |
, _init: function _init(orientation, panelHeight, instanceId) { | |
Applet.TextIconApplet.prototype._init.call(this, orientation, panelHeight, instanceId) | |
// Interface: TextIconApplet | |
this.set_applet_icon_name(APPLET_ICON) | |
this.set_applet_label(_("...")) | |
this.set_applet_tooltip(_("Click to open")) | |
// PopupMenu | |
this.menuManager = new PopupMenu.PopupMenuManager(this) | |
this.menu = new Applet.AppletPopupMenu(this, orientation) | |
this.menu.actor.add_style_class_name(STYLE_WEATHER_MENU) | |
this.menuManager.addMenu(this.menu) | |
//---------------------------------- | |
// bind settings | |
//---------------------------------- | |
for (let k in KEYS) { | |
let key = KEYS[k] | |
let keyProp = "_" + key | |
this.settings.bindProperty(Settings.BindingDirection.IN, key, keyProp, | |
this.refreshAndRebuild, null) | |
} | |
//log("bound settings") | |
this.updateIconType() | |
this.settings.connect(SIGNAL_CHANGED + WEATHER_USE_SYMBOLIC_ICONS_KEY, Lang.bind(this, function() { | |
this.updateIconType() | |
this._applet_icon.icon_type = this._icon_type | |
this._currentWeatherIcon.icon_type = this._icon_type | |
let daysToShow = this._showFivedayForecast ? 5 : 2 | |
for (let i = 0; i < daysToShow; i++) { | |
this._forecast[i].Icon.icon_type = this._icon_type | |
} | |
this.refreshWeather(false) | |
})) | |
// configuration via context menu is automatically provided in Cinnamon 2.0+ | |
let cinnamonVersion = Config.PACKAGE_VERSION.split('.') | |
let majorVersion = parseInt(cinnamonVersion[0]) | |
//log("cinnamonVersion=" + cinnamonVersion + "; majorVersion=" + majorVersion) | |
// for Cinnamon 1.x, build a menu item | |
if (majorVersion < 2) { | |
let itemLabel = _("Settings") | |
let settingsMenuItem = new Applet.MenuItem(itemLabel, Gtk.STOCK_EDIT, Lang.bind(this, function() { | |
Util.spawnCommandLine(CMD_SETTINGS) | |
})) | |
this._applet_context_menu.addMenuItem(settingsMenuItem) | |
} | |
//------------------------------ | |
// render graphics container | |
//------------------------------ | |
// build menu | |
let mainBox = new St.BoxLayout({ vertical: true }) | |
this.menu.addActor(mainBox) | |
// today's forecast | |
this._currentWeather = new St.Bin({ style_class: STYLE_CURRENT }) | |
mainBox.add_actor(this._currentWeather) | |
// horizontal rule | |
this._separatorArea = new St.DrawingArea({ style_class: STYLE_POPUP_SEPARATOR_MENU_ITEM }) | |
this._separatorArea.width = 200 | |
this._separatorArea.connect(SIGNAL_REPAINT, Lang.bind(this, this._onSeparatorAreaRepaint)) | |
mainBox.add_actor(this._separatorArea) | |
// tomorrow's forecast | |
this._futureWeather = new St.Bin({ style_class: STYLE_FORECAST }) | |
mainBox.add_actor(this._futureWeather) | |
this.rebuild() | |
//------------------------------ | |
// run | |
//------------------------------ | |
Mainloop.timeout_add_seconds(3, Lang.bind(this, function mainloopTimeout() { | |
this.refreshWeather(true) | |
})) | |
} | |
// Override Methods: Applet | |
, on_applet_clicked: function on_applet_clicked(event) { | |
this.menu.toggle() | |
} | |
, _onSeparatorAreaRepaint: function onSeparatorAreaRepaint(area) { | |
let cr = area.get_context() | |
let themeNode = area.get_theme_node() | |
let [width, height] = area.get_surface_size() | |
let margin = themeNode.get_length('-margin-horizontal') | |
let gradientHeight = themeNode.get_length('-gradient-height') | |
let startColor = themeNode.get_color('-gradient-start') | |
let endColor = themeNode.get_color('-gradient-end') | |
let gradientWidth = (width - margin * 2) | |
let gradientOffset = (height - gradientHeight) / 2 | |
let pattern = new Cairo.LinearGradient(margin, gradientOffset, width - margin, gradientOffset + gradientHeight) | |
pattern.addColorStopRGBA(0, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255) | |
pattern.addColorStopRGBA(0.5, endColor.red / 255, endColor.green / 255, endColor.blue / 255, endColor.alpha / 255) | |
pattern.addColorStopRGBA(1, startColor.red / 255, startColor.green / 255, startColor.blue / 255, startColor.alpha / 255) | |
cr.setSource(pattern) | |
cr.rectangle(margin, gradientOffset, gradientWidth, gradientHeight) | |
cr.fill() | |
} | |
//---------------------------------------------------------------------- | |
// | |
// Methods | |
// | |
//---------------------------------------------------------------------- | |
, updateIconType: function updateIconType() { | |
this._icon_type = this.settings.getValue(WEATHER_USE_SYMBOLIC_ICONS_KEY) ? | |
St.IconType.SYMBOLIC : | |
St.IconType.FULLCOLOR | |
} | |
, loadJsonAsync: function loadJsonAsync(url, callback) { | |
let context = this | |
let message = Soup.Message.new('GET', url) | |
_httpSession.queue_message(message, function soupQueue(session, message) { | |
let jp = new Json.Parser() | |
jp.load_from_data(message.response_body.data, -1) | |
callback.call(context, jp.get_root().get_object()) | |
}) | |
} | |
, parseDay: function(abr) { | |
let yahoo_days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] | |
for (let i = 0; i < yahoo_days.length; i++) { | |
if (yahoo_days[i].substr(0, abr.length) == abr.toLowerCase()) { | |
return i | |
} | |
} | |
return 0 | |
} | |
, refreshWeather: function refreshWeather(recurse) { | |
//log("recurse=" + recurse) | |
//this.dumpKeys() | |
this.loadJsonAsync(this.weatherUrl(), function(json) { | |
try { | |
let weather = json.get_object_member('query').get_object_member('results').get_object_member('channel') | |
let weather_c = weather.get_object_member('item').get_object_member('condition') | |
let forecast = weather.get_object_member('item').get_array_member('forecast').get_elements() | |
let location = weather.get_object_member('location').get_string_member('city') | |
if (this.nonempty(this._locationLabelOverride)) | |
location = this._locationLabelOverride | |
// Refresh current weather | |
let comment = weather_c.get_string_member('text') | |
if (this._translateCondition) | |
comment = this.weatherCondition(weather_c.get_string_member('code')) | |
let humidity = weather.get_object_member('atmosphere').get_string_member('humidity') + ' %' | |
let pressure = weather.get_object_member('atmosphere').get_string_member('pressure') | |
//let pressure_unit = weather.get_object_member('units').get_string_member('pressure') | |
//log('pressure: ' + pressure + ' ' + pressure_unit) | |
let sunrise = weather.get_object_member('astronomy').get_string_member('sunrise') | |
let sunset = weather.get_object_member('astronomy').get_string_member('sunset') | |
let temperature = weather_c.get_string_member('temp') | |
let wind = weather.get_object_member('wind').get_string_member('speed') | |
let wind_chill = weather.get_object_member('wind').get_string_member('chill') | |
let wind_direction = this.compassDirection(weather.get_object_member('wind').get_string_member('direction')) | |
//let wind_unit = weather.get_object_member('units').get_string_member('speed') | |
let iconname = this.weatherIconSafely(weather_c.get_string_member('code')) | |
this._currentWeatherIcon.icon_name = iconname | |
this._icon_type == St.IconType.SYMBOLIC ? | |
this.set_applet_icon_symbolic_name(iconname) : | |
this.set_applet_icon_name(iconname) | |
if (this._showTextInPanel) { | |
if (this._showCommentInPanel) { | |
this.set_applet_label(comment + ' ' + temperature + ' ' + this.unitToUnicode()) | |
} else { | |
this.set_applet_label(temperature + ' ' + this.unitToUnicode()) | |
} | |
} else { | |
this.set_applet_label('') | |
} | |
this._currentWeatherSummary.text = comment | |
this._currentWeatherTemperature.text = temperature + ' ' + this.unitToUnicode() | |
this._currentWeatherHumidity.text = humidity | |
// Override wind units with our preference | |
// Need to consider what units the Yahoo API has returned it in | |
switch (this._windSpeedUnit) { | |
case WeatherWindSpeedUnits.KPH: | |
// Round to whole units | |
if (this._temperatureUnit == WeatherUnits.FAHRENHEIT) { | |
wind = Math.round (wind / WEATHER_CONV_MPH_IN_MPS * WEATHER_CONV_KPH_IN_MPS) | |
} | |
// Otherwise no conversion needed - already in correct units | |
break | |
case WeatherWindSpeedUnits.MPH: | |
// Round to whole units | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
wind = Math.round (wind / WEATHER_CONV_KPH_IN_MPS * WEATHER_CONV_MPH_IN_MPS) | |
} | |
// Otherwise no conversion needed - already in correct units | |
break | |
case WeatherWindSpeedUnits.MPS: | |
// Precision to one decimal place as 1 m/s is quite a large unit | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
wind = Math.round ((wind / WEATHER_CONV_KPH_IN_MPS) * 10)/ 10 | |
} else { | |
wind = Math.round ((wind / WEATHER_CONV_MPH_IN_MPS) * 10)/ 10 | |
} | |
break | |
case WeatherWindSpeedUnits.KNOTS: | |
// Round to whole units | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
wind = Math.round (wind / WEATHER_CONV_KPH_IN_MPS * WEATHER_CONV_KNOTS_IN_MPS) | |
} else { | |
wind = Math.round (wind / WEATHER_CONV_MPH_IN_MPS * WEATHER_CONV_KNOTS_IN_MPS) | |
} | |
break | |
} | |
this._currentWeatherWind.text = (wind_direction ? wind_direction + ' ' : '') + wind + ' ' + _(this._windSpeedUnit) | |
// Override wind chill units with our preference | |
// Yahoo API always returns Fahrenheit | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
wind_chill = Math.round((wind_chill - 32) / 1.8) | |
} | |
this._currentWeatherWindChill.text = wind_chill + ' ' + this.unitToUnicode() | |
// Override pressure units with our preference | |
// Need to consider what units the Yahoo API has returned it in | |
switch (this._pressureUnit) { | |
case WeatherPressureUnits.MBAR: | |
if (this._temperatureUnit == WeatherUnits.FAHRENHEIT) { | |
pressure = pressure * WEATHER_CONV_MBAR_IN_INHG | |
} | |
// Otherwise no conversion needed - already in correct units | |
pressure = parseFloat(pressure).toFixed(2) | |
break | |
case WeatherPressureUnits.INHG: | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
pressure = pressure * WEATHER_CONV_INHG_IN_MBAR | |
} | |
// Otherwise no conversion needed - already in correct units | |
pressure = parseFloat(pressure).toFixed(2) | |
break | |
case WeatherPressureUnits.PSI: | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
pressure = pressure * WEATHER_CONV_PSI_IN_MBAR | |
} else { | |
pressure = pressure * WEATHER_CONV_PSI_IN_INHG | |
} | |
pressure = parseFloat(pressure).toFixed(3) | |
break | |
case WeatherPressureUnits.MMHG: | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
pressure = pressure * WEATHER_CONV_MMHG_IN_MBAR | |
} else { | |
pressure = pressure * WEATHER_CONV_MMHG_IN_INHG | |
} | |
pressure = Math.round(pressure) | |
break | |
case WeatherPressureUnits.AT: | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
pressure = pressure * WEATHER_CONV_AT_IN_MBAR | |
} else { | |
pressure = pressure * WEATHER_CONV_AT_IN_INHG | |
} | |
pressure = pressure.toFixed(4) | |
break | |
case WeatherPressureUnits.ATM: | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
pressure = pressure * WEATHER_CONV_ATM_IN_MBAR | |
} else { | |
pressure = pressure * WEATHER_CONV_ATM_IN_INHG | |
} | |
pressure = pressure.toFixed(4) | |
break | |
case WeatherPressureUnits.PA: | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
pressure = pressure * WEATHER_CONV_PA_IN_MBAR | |
} else { | |
pressure = pressure * WEATHER_CONV_PA_IN_INHG | |
} | |
pressure = Math.round(pressure) | |
break | |
case WeatherPressureUnits.KPA: | |
if (this._temperatureUnit == WeatherUnits.CELSIUS) { | |
pressure = pressure * WEATHER_CONV_KPA_IN_MBAR | |
} else { | |
pressure = pressure * WEATHER_CONV_KPA_IN_INHG | |
} | |
pressure = parseFloat(pressure).toFixed(2) | |
break | |
} | |
this._currentWeatherPressure.text = pressure + ' ' + _(this._pressureUnit) | |
// location is a button | |
this._currentWeatherLocation.url = weather.get_string_member('link') | |
this._currentWeatherLocation.label = _(location) | |
// gettext can't see these inline | |
let sunriseText = _('Sunrise') | |
let sunsetText = _('Sunset') | |
let sunriseTime = this._show24Hours ? (this.convertTo24(sunrise)) : sunrise | |
let sunsetTime = this._show24Hours ? (this.convertTo24(sunset)) : sunset | |
this._currentWeatherSunrise.text = this._showSunrise ? (sunriseText + ': ' + sunriseTime ) : '' | |
this._currentWeatherSunset.text = this._showSunrise ? (sunsetText + ': ' + sunsetTime) : '' | |
// Refresh forecast | |
let daysToShow = this._showFivedayForecast ? 5 : 2 | |
for (let i = 0; i < daysToShow; i++) { | |
let forecastUi = this._forecast[i] | |
let forecastData = forecast[i].get_object() | |
let code = forecastData.get_string_member('code') | |
let t_low = forecastData.get_string_member('low') | |
let t_high = forecastData.get_string_member('high') | |
let comment = forecastData.get_string_member('text') | |
if (this._translateCondition) | |
comment = this.weatherCondition(code) | |
forecastUi.Day.text = this.localeDay(forecastData.get_string_member('day')) | |
forecastUi.Temperature.text = t_low + ' ' + '\u002F' + ' ' + t_high + ' ' + this.unitToUnicode() | |
forecastUi.Summary.text = comment | |
forecastUi.Icon.icon_name = this.weatherIconSafely(code) | |
} | |
} catch(error) { | |
logError(error) | |
} | |
}) | |
if (recurse) { | |
Mainloop.timeout_add_seconds(this._refreshInterval * 60, Lang.bind(this, function() { | |
this.refreshWeather(true) | |
})) | |
} | |
} | |
, convertTo24: function convertTo24(timeStr) { | |
let s = timeStr.indexOf(':') | |
let t = timeStr.indexOf(' ') | |
let n = timeStr.length | |
let hh = timeStr.substr(0, s) | |
let mm = timeStr.substring(s + 1, t) | |
if (parseInt(hh) < 10) // pad | |
hh = '0' + hh | |
let beforeNoon = timeStr.substr(n - 2).toLowerCase() == 'am' | |
if (beforeNoon) { | |
if (hh == '12') // 12 AM -> 00 | |
hh = '00' | |
return hh + ':' + mm | |
} | |
if (hh == '12') // 12 PM -> ok | |
return hh + ':' + mm | |
return (parseInt(hh) + 12).toString() + ':' + mm | |
} | |
, destroyCurrentWeather: function destroyCurrentWeather() { | |
if (this._currentWeather.get_child() != null) | |
this._currentWeather.get_child().destroy() | |
} | |
, destroyFutureWeather: function destroyFutureWeather() { | |
if (this._futureWeather.get_child() != null) | |
this._futureWeather.get_child().destroy() | |
} | |
, showLoadingUi: function showLoadingUi() { | |
this.destroyCurrentWeather() | |
this.destroyFutureWeather() | |
this._currentWeather.set_child(new St.Label({ text: _('Loading current weather ...') })) | |
this._futureWeather.set_child(new St.Label({ text: _('Loading future weather ...') })) | |
} | |
, rebuild: function rebuild() { | |
this.showLoadingUi() | |
this.rebuildCurrentWeatherUi() | |
this.rebuildFutureWeatherUi() | |
} | |
, rebuildCurrentWeatherUi: function rebuildCurrentWeatherUi() { | |
this.destroyCurrentWeather() | |
// This will hold the icon for the current weather | |
this._currentWeatherIcon = new St.Icon({ | |
icon_type: this._icon_type, | |
icon_size: 64, | |
icon_name: APPLET_ICON, | |
style_class: STYLE_ICON | |
}) | |
// The summary of the current weather | |
this._currentWeatherSummary = new St.Label({ | |
text: _('Loading ...'), | |
style_class: STYLE_SUMMARY | |
}) | |
this._currentWeatherLocation = new St.Button({ | |
reactive: true, | |
label: _('Refresh') | |
}) | |
this._currentWeatherLocation.style_class = STYLE_LOCATION_LINK | |
// link to the details page | |
this._currentWeatherLocation.connect(SIGNAL_CLICKED, Lang.bind(this, function() { | |
if (this._currentWeatherLocation.url == null) { | |
this.refreshWeather(false) | |
} else { | |
Gio.app_info_launch_default_for_uri( | |
this._currentWeatherLocation.url, | |
global.create_app_launch_context() | |
) | |
} | |
})) | |
let bb = new St.BoxLayout({ | |
vertical: true, | |
style_class: STYLE_SUMMARYBOX | |
}) | |
bb.add_actor(this._currentWeatherLocation) | |
bb.add_actor(this._currentWeatherSummary) | |
let textOb = { text: ELLIPSIS } | |
this._currentWeatherSunrise = new St.Label(textOb) | |
this._currentWeatherSunset = new St.Label(textOb) | |
let ab = new St.BoxLayout({ | |
style_class: STYLE_ASTRONOMY | |
}) | |
ab.add_actor(this._currentWeatherSunrise) | |
let ab_spacerlabel = new St.Label({ text: BLANK }) | |
ab.add_actor(ab_spacerlabel) | |
ab.add_actor(this._currentWeatherSunset) | |
let bb_spacerlabel = new St.Label({ text: BLANK }) | |
bb.add_actor(bb_spacerlabel) | |
bb.add_actor(ab) | |
// Other labels | |
this._currentWeatherTemperature = new St.Label(textOb) | |
this._currentWeatherHumidity = new St.Label(textOb) | |
this._currentWeatherPressure = new St.Label(textOb) | |
this._currentWeatherWind = new St.Label(textOb) | |
this._currentWeatherWindChill = new St.Label(textOb) | |
let rb = new St.BoxLayout({ | |
style_class: STYLE_DATABOX | |
}) | |
let rb_captions = new St.BoxLayout({ | |
vertical: true, | |
style_class: STYLE_DATABOX_CAPTIONS | |
}) | |
let rb_values = new St.BoxLayout({ | |
vertical: true, | |
style_class: STYLE_DATABOX_VALUES | |
}) | |
rb.add_actor(rb_captions) | |
rb.add_actor(rb_values) | |
rb_captions.add_actor(new St.Label({text: _('Temperature:')})) | |
rb_values.add_actor(this._currentWeatherTemperature) | |
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) | |
rb_captions.add_actor(new St.Label({text: _('Wind Chill:')})) | |
rb_values.add_actor(this._currentWeatherWindChill) | |
let xb = new St.BoxLayout() | |
xb.add_actor(bb) | |
xb.add_actor(rb) | |
let box = new St.BoxLayout({ | |
style_class: STYLE_ICONBOX | |
}) | |
box.add_actor(this._currentWeatherIcon) | |
box.add_actor(xb) | |
this._currentWeather.set_child(box) | |
} | |
, rebuildFutureWeatherUi: function rebuildFutureWeatherUi() { | |
this.destroyFutureWeather() | |
this._forecast = [] | |
this._forecastBox = new St.BoxLayout() | |
this._futureWeather.set_child(this._forecastBox) | |
let daysToShow = this._showFivedayForecast ? 5 : 2 | |
for (let i = 0; i < daysToShow; i++) { | |
let forecastWeather = {} | |
forecastWeather.Icon = new St.Icon({ | |
icon_type: this._icon_type, | |
icon_size: 48, | |
icon_name: APPLET_ICON, | |
style_class: STYLE_FORECAST_ICON | |
}) | |
forecastWeather.Day = new St.Label({ | |
style_class: STYLE_FORECAST_DAY | |
}) | |
forecastWeather.Summary = new St.Label({ | |
style_class: STYLE_FORECAST_SUMMARY | |
}) | |
forecastWeather.Temperature = new St.Label({ | |
style_class: STYLE_FORECAST_TEMPERATURE | |
}) | |
let by = new St.BoxLayout({ | |
vertical: true, | |
style_class: STYLE_FORECAST_DATABOX | |
}) | |
by.add_actor(forecastWeather.Day) | |
by.add_actor(forecastWeather.Summary) | |
by.add_actor(forecastWeather.Temperature) | |
let bb = new St.BoxLayout({ | |
style_class: STYLE_FORECAST_BOX | |
}) | |
bb.add_actor(forecastWeather.Icon) | |
bb.add_actor(by) | |
this._forecast[i] = forecastWeather | |
this._forecastBox.add_actor(bb) | |
} | |
} | |
//---------------------------------------------------------------------- | |
// | |
// Properties | |
// | |
//---------------------------------------------------------------------- | |
, unitToUrl: function() { | |
return this._temperatureUnit == WeatherUnits.FAHRENHEIT ? 'f' : 'c' | |
} | |
, unitToUnicode: function() { | |
return this._temperatureUnit == WeatherUnits.FAHRENHEIT ? '\u2109' : '\u2103' | |
} | |
, weatherUrl: function weatherUrl() { | |
let output = QUERY_URL + ' where woeid="' + this._woeid + '" and u="' + this.unitToUrl() + '"' | |
//let output = QUERY_URL + this._woeid + '_' + this.unitToUrl() + '.xml"' | |
return output | |
} | |
, weatherIcon: function(code) { | |
/* see http://developer.yahoo.com/weather/#codetable */ | |
/* fallback icons are: weather-clear-night weather-clear weather-few-clouds-night weather-few-clouds weather-fog weather-overcast weather-severe-alert weather-showers weather-showers-scattered weather-snow weather-storm */ | |
switch (parseInt(code, 10)) { | |
case 0:/* tornado */ | |
return ['weather-severe-alert'] | |
case 1:/* tropical storm */ | |
return ['weather-severe-alert'] | |
case 2:/* hurricane */ | |
return ['weather-severe-alert'] | |
case 3:/* severe thunderstorms */ | |
return ['weather-severe-alert'] | |
case 4:/* thunderstorms */ | |
return ['weather-storm'] | |
case 5:/* mixed rain and snow */ | |
return ['weather-snow-rain', 'weather-snow'] | |
case 6:/* mixed rain and sleet */ | |
return ['weather-snow-rain', 'weather-snow'] | |
case 7:/* mixed snow and sleet */ | |
return ['weather-snow'] | |
case 8:/* freezing drizzle */ | |
return ['weather-freezing-rain', 'weather-showers'] | |
case 9:/* drizzle */ | |
return ['weather-fog'] | |
case 10:/* freezing rain */ | |
return ['weather-freezing-rain', 'weather-showers'] | |
case 11:/* showers */ | |
return ['weather-showers'] | |
case 12:/* showers */ | |
return ['weather-showers'] | |
case 13:/* snow flurries */ | |
return ['weather-snow'] | |
case 14:/* light snow showers */ | |
return ['weather-snow'] | |
case 15:/* blowing snow */ | |
return ['weather-snow'] | |
case 16:/* snow */ | |
return ['weather-snow'] | |
case 17:/* hail */ | |
return ['weather-snow'] | |
case 18:/* sleet */ | |
return ['weather-snow'] | |
case 19:/* dust */ | |
return ['weather-fog'] | |
case 20:/* foggy */ | |
return ['weather-fog'] | |
case 21:/* haze */ | |
return ['weather-fog'] | |
case 22:/* smoky */ | |
return ['weather-fog'] | |
case 23:/* blustery */ | |
return ['weather-few-clouds'] | |
case 24:/* windy */ | |
return ['weather-few-clouds'] | |
case 25:/* cold */ | |
return ['weather-few-clouds'] | |
case 26:/* cloudy */ | |
return ['weather-overcast'] | |
case 27:/* mostly cloudy (night) */ | |
return ['weather-clouds-night', 'weather-few-clouds-night'] | |
case 28:/* mostly cloudy (day) */ | |
return ['weather-clouds', 'weather-overcast'] | |
case 29:/* partly cloudy (night) */ | |
return ['weather-few-clouds-night'] | |
case 30:/* partly cloudy (day) */ | |
return ['weather-few-clouds'] | |
case 31:/* clear (night) */ | |
return ['weather-clear-night'] | |
case 32:/* sunny */ | |
return ['weather-clear'] | |
case 33:/* fair (night) */ | |
return ['weather-clear-night'] | |
case 34:/* fair (day) */ | |
return ['weather-clear'] | |
case 35:/* mixed rain and hail */ | |
return ['weather-snow-rain', 'weather-showers'] | |
case 36:/* hot */ | |
return ['weather-clear'] | |
case 37:/* isolated thunderstorms */ | |
return ['weather-storm'] | |
case 38:/* scattered thunderstorms */ | |
return ['weather-storm'] | |
case 39:/* http://developer.yahoo.com/forum/YDN-Documentation/Yahoo-Weather-API-Wrong-Condition-Code/1290534174000-1122fc3d-da6d-34a2-9fb9-d0863e6c5bc6 */ | |
case 40:/* scattered showers */ | |
return ['weather-showers-scattered', 'weather-showers'] | |
case 41:/* heavy snow */ | |
return ['weather-snow'] | |
case 42:/* scattered snow showers */ | |
return ['weather-snow'] | |
case 43:/* heavy snow */ | |
return ['weather-snow'] | |
case 44:/* partly cloudy */ | |
return ['weather-few-clouds'] | |
case 45:/* thundershowers */ | |
return ['weather-storm'] | |
case 46:/* snow showers */ | |
return ['weather-snow'] | |
case 47:/* isolated thundershowers */ | |
return ['weather-storm'] | |
case 3200:/* not available */ | |
default: | |
return ['weather-severe-alert'] | |
} | |
} | |
, weatherIconSafely: function(code) { | |
let iconname = this.weatherIcon(code) | |
for (let i = 0; i < iconname.length; i++) { | |
if (this.hasIcon(iconname[i])) | |
return iconname[i] | |
} | |
return 'weather-severe-alert' | |
} | |
, hasIcon: function(icon) { | |
return Gtk.IconTheme.get_default().has_icon(icon + (this._icon_type == St.IconType.SYMBOLIC ? '-symbolic' : '')) | |
} | |
, nonempty: function(str) { | |
return (str != null && str.length > 0) | |
} | |
, weatherCondition: function(code) { | |
switch (parseInt(code, 10)){ | |
case 0:/* tornado */ | |
return _('Tornado') | |
case 1:/* tropical storm */ | |
return _('Tropical storm') | |
case 2:/* hurricane */ | |
return _('Hurricane') | |
case 3:/* severe thunderstorms */ | |
return _('Severe thunderstorms') | |
case 4:/* thunderstorms */ | |
return _('Thunderstorms') | |
case 5:/* mixed rain and snow */ | |
return _('Mixed rain and snow') | |
case 6:/* mixed rain and sleet */ | |
return _('Mixed rain and sleet') | |
case 7:/* mixed snow and sleet */ | |
return _('Mixed snow and sleet') | |
case 8:/* freezing drizzle */ | |
return _('Freezing drizzle') | |
case 9:/* drizzle */ | |
return _('Drizzle') | |
case 10:/* freezing rain */ | |
return _('Freezing rain') | |
case 11:/* showers */ | |
return _('Showers') | |
case 12:/* showers */ | |
return _('Showers') | |
case 13:/* snow flurries */ | |
return _('Snow flurries') | |
case 14:/* light snow showers */ | |
return _('Light snow showers') | |
case 15:/* blowing snow */ | |
return _('Blowing snow') | |
case 16:/* snow */ | |
return _('Snow') | |
case 17:/* hail */ | |
return _('Hail') | |
case 18:/* sleet */ | |
return _('Sleet') | |
case 19:/* dust */ | |
return _('Dust') | |
case 20:/* foggy */ | |
return _('Foggy') | |
case 21:/* haze */ | |
return _('Haze') | |
case 22:/* smoky */ | |
return _('Smoky') | |
case 23:/* blustery */ | |
return _('Blustery') | |
case 24:/* windy */ | |
return _('Windy') | |
case 25:/* cold */ | |
return _('Cold') | |
case 26:/* cloudy */ | |
return _('Cloudy') | |
case 27:/* mostly cloudy (night) */ | |
case 28:/* mostly cloudy (day) */ | |
return _('Mostly cloudy') | |
case 29:/* partly cloudy (night) */ | |
case 30:/* partly cloudy (day) */ | |
return _('Partly cloudy') | |
case 31:/* clear (night) */ | |
return _('Clear') | |
case 32:/* sunny */ | |
return _('Sunny') | |
case 33:/* fair (night) */ | |
case 34:/* fair (day) */ | |
return _('Fair') | |
case 35:/* mixed rain and hail */ | |
return _('Mixed rain and hail') | |
case 36:/* hot */ | |
return _('Hot') | |
case 37:/* isolated thunderstorms */ | |
return _('Isolated thunderstorms') | |
case 38:/* scattered thunderstorms */ | |
case 39:/* scattered thunderstorms */ | |
return _('Scattered thunderstorms') | |
case 40:/* scattered showers */ | |
return _('Scattered showers') | |
case 41:/* heavy snow */ | |
return _('Heavy snow') | |
case 42:/* scattered snow showers */ | |
return _('Scattered snow showers') | |
case 43:/* heavy snow */ | |
return _('Heavy snow') | |
case 44:/* partly cloudy */ | |
return _('Partly cloudy') | |
case 45:/* thundershowers */ | |
return _('Thundershowers') | |
case 46:/* snow showers */ | |
return _('Snow showers') | |
case 47:/* isolated thundershowers */ | |
return _('Isolated thundershowers') | |
case 3200:/* not available */ | |
default: | |
return _('Not available') | |
} | |
} | |
, localeDay: function(abr) { | |
let days = [_('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')] | |
return days[this.parseDay(abr)] | |
} | |
, compassDirection: function(deg) { | |
let directions = [_('N'), _('NE'), _('E'), _('SE'), _('S'), _('SW'), _('W'), _('NW')] | |
return directions[Math.round(deg / 45) % directions.length] | |
} | |
} | |
//---------------------------------------------------------------------- | |
// | |
// Entry point | |
// | |
//---------------------------------------------------------------------- | |
function main(metadata, orientation, panelHeight, instanceId) { | |
//log("v" + metadata.version + ", cinnamon " + Config.PACKAGE_VERSION) | |
return new MyApplet(metadata, orientation, panelHeight, instanceId) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment