Skip to content

Instantly share code, notes, and snippets.

@ltddev
Created February 13, 2015 15:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ltddev/2ce6a942f5198fbf2de8 to your computer and use it in GitHub Desktop.
Save ltddev/2ce6a942f5198fbf2de8 to your computer and use it in GitHub Desktop.
Clock Gadget
# coding: utf-8
import ui, speech, console
svgInHtml='''
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400" width="400" height="400" version="1.0">
<defs>
<linearGradient id="a" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" style="stop-color:#777799"/>
<stop offset="100%" style="stop-color:#ffffff"/>
</linearGradient>
<linearGradient id="b" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" style="stop-color:#ffffff"/>
<stop offset="25%" style="stop-color:#b6b6cc"/>
<stop offset="40%" style="stop-color:#515177"/>
<stop offset="48%" style="stop-color:#ffffff"/>
<stop offset="56%" style="stop-color:#ffffff"/>
<stop offset="75%" style="stop-color:#8b8baa"/>
<stop offset="98%" style="stop-color:#efeff4"/>
<stop offset="100%" style="stop-color:#fbfbfc"/>
</linearGradient>
<linearGradient id="c" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" style="stop-color:#ffffff"/>
<stop offset="100%" style="stop-color:#777799"/>
</linearGradient>
<radialGradient id="d" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" style="stop-color:#ffffff"/>
<stop offset="40%" style="stop-color:#ffffff"/>
<stop offset="70%" style="stop-color:#e6e6ee"/>
<stop offset="92%" style="stop-color:#b6b6cc"/>
<stop offset="100%" style="stop-color:#636388"/>
</radialGradient>
<radialGradient id="e" cx="50%" cy="150%" r="200%" fx="50%" fy="150%">
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:0"/>
<stop offset="59%" style="stop-color:#ffffff;stop-opacity:0"/>
<stop offset="60%" style="stop-color:#ffffff;stop-opacity:0.6"/>
<stop offset="70%" style="stop-color:#ffffff;stop-opacity:0.3"/>
<stop offset="100%" style="stop-color:#ffffff;stop-opacity:0.0"/>
</radialGradient>
</defs>
<g transform="translate(200 200)">
<circle cx="0" cy="0" r="200" fill="#cecedd"/>
<circle cx="0" cy="0" r="196" stroke="url(#a)" stroke-width="5" fill="url(#b)"/>
<circle cx="0" cy="0" r="170" stroke="url(#c)" stroke-width="4" fill="url(#d)"/>
<circle cx="0" cy="0" r="172" stroke="#ffffff" stroke-width="0.5" fill="none"/>
<circle cx="0" cy="0" r="193.5" stroke="#ffffff" stroke-width="0.5" fill="none"/>
<g id="O">
<polygon points="4,155 4,130 -4,130 -4,155" style="fill:#777799;stroke:#313155;stroke-width:1"/>
<polygon points="4,-155 4,-130 -4,-130 -4,-155" style="fill:#777799;stroke:#313155;stroke-width:1"/>
</g>
<g transform="rotate(30)"><use xlink:href="#O"/></g>
<g transform="rotate(60)"><use xlink:href="#O"/></g>
<g transform="rotate(90)"><use xlink:href="#O"/></g>
<g transform="rotate(120)"><use xlink:href="#O"/></g>
<g transform="rotate(150)"><use xlink:href="#O"/></g>
<polygon id="h" points="6,-80 6,18 -6,18 -6,-80" style="fill:#232344">
<animateTransform id="ht" attributeType="xml" attributeName="transform" type="rotate" from="000" to="000" begin="0" dur="86400s" repeatCount="indefinite"/>
</polygon>
<polygon id="m" points="3.5,-140 3.5,23 -3.5,23 -3.5,-140" style="fill:#232344">
<animateTransform id="mt" attributeType="xml" attributeName="transform" type="rotate" from="000" to="000" begin="0" dur="3600s" repeatCount="indefinite"/>
</polygon>
<polygon id="s" points="2,-143 2,25 -2,25 -2,-143" style="fill:#232344">
<animateTransform id="st" attributeType="xml" attributeName="transform" type="rotate" from="000" to="000" begin="0" dur="60s" repeatCount="indefinite"/>
</polygon>
<circle cx="0" cy="0" r="163" fill="url(#e)"/>
</g>
<script type="text/javascript"><![CDATA[
var d = new Date();
var s = d.getSeconds();
var m = d.getMinutes() + s/60;
var h = (d.getHours() % 12) + m/60 + s/3600;
document.getElementById('st').setAttribute('from',s*6);
document.getElementById('mt').setAttribute('from',m*6);
document.getElementById('ht').setAttribute('from',h*30);
document.getElementById('st').setAttribute('to',360+s*6);
document.getElementById('mt').setAttribute('to',360+m*6);
document.getElementById('ht').setAttribute('to',360+h*30);
]]></script>
</svg>
'''
announce1 = "The current local time"
announce2 = "is"
numbersZeroToTwenty = 'zero,one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve,thirteen,fourteen,fifteen,sixteen,seventeen,eighteen,nineteen,twenty'.split(',')
thirty = "thirty"
forty = "forty"
fifty = "fifty"
am = "A.M"
pm = "P.M"
ampm = ""
oclock = "oclock"
# Turn off tracing when not required for simple debug/trace
traceFlag = True
speechDelay = 0.05
locale = "en-US"
#==============================================================================
class ClockGadget:
def __init__(self):
view = ui.load_view()
# Set announce duration to a default of once per every 15 minutes.
#view['rootview']['announceFreqTextfield'].text = '15'
#view['rootview']['announceFreqSlider'].value = .25 # every 1/4 hour.
# cache away the current location (if location services on for Pythonista)
self.locationString = self.createCurrentLocationString()
if traceFlag: print 'location string in __init__ is ->>' + self.locationString
webview = view['rootview']['webview']
# Place the analog clock in a location that is sensible for a "gadget"
# and disable touch or multitouch gestures to prevent unwannted resizing
# or movement to the underlying clock itself which has no need to do so.
webview.height = 20
webview.width = 10
webview.frame= 15,25,120,125
webview.multitouch_enabled = False
webview.touch_enabled = False
if traceFlag: print 'Presenting view now.'
view.present('sidebar')
if traceFlag: print 'Loading html with embedded svg now.'
webview.load_html(svgInHtml)
#==============================================================================
def speaknowButtonTapped(self, sender):
if sender.name == 'speakTimeButton':
# Get the say seconds preference
isSaySeconds = sender.superview['speakSecondsSwitch'].value
if traceFlag: print 'isSaySeconds current value now->>' + str(isSaySeconds)
timeMessage = self.getCurrentLocalTimeAnnouncement(isSaySeconds)
if traceFlag: print 'Time message to announce ->>' + timeMessage
speech.say(timeMessage,locale, speechDelay)
#=============================================================================
def switchTapped(self,sender):
if sender.name == 'speakSecondsSwitch':
if traceFlag: print "speakSecondsSwitch tapped...current value is ->>" + str(sender.value)
#==============================================================================
def createCurrentLocationString(self):
import location
location.start_updates()
coordinates = location.get_location()
addressDictionaries = location.reverse_geocode(coordinates)
mycity = addressDictionaries[0]['City']
mycountry = addressDictionaries[0]['Country']
# if we can't get city and country not much point to continue
if mycountry == None or mycity == None:
return ""
locationString = "in " + mycity + " " + mycountry
location.stop_updates()
if traceFlag: print 'Returning location string ->>' + locationString
return locationString
#==============================================================================
def getCurrentLocalTimeAnnouncement(self, isSaySeconds):
from datetime import datetime
##todo maybe get time and pass in from another function since calc twice?
now = datetime.now().time()
nowHour = now.hour
nowMinutes = now.minute
nowSeconds = now.second
# Get the current local hour
hourString = self.getCurrentHourString(nowHour)
if traceFlag: print "hourString is -->" + hourString
# Get the local current minute
minuteString = self.getCurrentMinutesString(nowMinutes)
if traceFlag: print 'minuteString is ->>' + minuteString
# If it is right on the hour, add "o'clock"; if not, bad English grammar to use it in this context.
if nowMinutes == 0:
hourString = hourString + " " + oclock
# If minutes less than 10 US english adds ' oh'begore saying minutes, eg. 'Two oh one'for 2:01, but make sure not to append 'owe' if its right on the hour. That is, when 'oclock' never 'owe' preventing 'nine oclock owe p.m.' phrases.
if nowMinutes > 0 and nowMinutes < 10:
minuteString = "owe " + minuteString # Use 'owe' which is real word not to confuse speech synth.
# Not using 24 hour system but instead a 12 hour one which uses a.m and p.m
ampm = am if nowHour < 12 else pm
announcement = ""
seconds = "seconds"
if isSaySeconds:
# English grammar, if seconds is 1, agrrement rules say use singular.
if nowSeconds == 1: seconds = "second"
announcement = announce1 + " " + self.locationString + " " +announce2 + " " + hourString + " " + minuteString + " " + ampm + " " + str(nowSeconds) + " " + seconds + " "
else:
announcement = announce1 + " " + self.locationString + " " + announce2 + " " + hourString + " " + minuteString + " " + ampm
if traceFlag: print announcement
return announcement
#==============================================================================
def getCurrentHourString(self, nowHour):
if traceFlag: print "nowHour parameter to function ->>" + str(nowHour)
hourString = ""
assert -1 < nowHour < 24, 'Error: Invalid hour {}.'.format(nowHour)
if traceFlag: print "Hour string to return ->>" + hourString
return numbersZeroToTwenty[nowHour % 12 or 12]
#==============================================================================
def getCurrentMinutesString(self, nowMinutes):
minuteString = ""
# Handles minutes between 1 and 20
if nowMinutes > 0 and nowMinutes < 21:
minuteString = numbersZeroToTwenty[nowMinutes]
# Handles minutes between 21 and 29
if nowMinutes > 20 and nowMinutes < 30:
if nowMinutes == 21:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[1]
elif nowMinutes == 22:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[2]
elif nowMinutes == 23:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[3]
elif nowMinutes == 24:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[4]
elif nowMinutes == 25:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[5]
elif nowMinutes == 26:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[6]
elif nowMinutes == 27:
minuteString = numbersZeroToTwenty[20] + " " + numbersZeroToTwenty[7]
elif nowMinutes == 28:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[8]
elif nowMinutes == 29:
minuteString = numbersZeroToTwenty[20] + " " + numbersOneToTwenty[9]
# Handle 30 minutes
if nowMinutes == 30:
minuteString = thirty
# Handles minutes between 31 and 39
if nowMinutes > 30 and nowMinutes < 40:
if nowMinutes == 31:
minuteString = thirty + " " + numbersZeroToTwenty[1]
elif nowMinutes == 32:
minuteString = thirty + " " + numbersZeroToTwenty[2]
elif nowMinutes == 33:
minuteString = thirty + " " + numbersZeroToTwenty[3]
elif nowMinutes == 34:
minuteString = thirty + " " + numbersZeroToTwenty[4]
elif nowMinutes == 35:
minuteString = thirty + " " + numbersZeroToTwenty[5]
elif nowMinutes == 36:
minuteString = thirty + " " + numbersZeroToTwenty[6]
elif nowMinutes == 37:
minuteString = thirty + " " + numbersZeroToTwenty[7]
elif nowMinutes == 38:
minuteString = thirty + " " + numbersZeroToTwenty[8]
elif nowMinutes == 39:
minuteString = thirty + " " + numbersZeroToTwenty[9]
# Handles 40 minutes
if nowMinutes == 40:
minuteString = forty
if nowMinutes > 40 and nowMinutes < 50:
if nowMinutes == 41:
minuteString = forty + " " + numbersZeroToTwenty[1]
elif nowMinutes == 42:
minuteString = forty + " " + numbersZeroToTwenty[2]
elif nowMinutes == 43:
minuteString = forty + " " + numbersZeroToTwenty[3]
elif nowMinutes == 44:
minuteString = forty + " " + numbersZeroToTwenty[4]
elif nowMinutes == 45:
minuteString = forty + " " + numbersZeroToTwenty[5]
elif nowMinutes == 46:
minuteString = forty + " " + numbersZeroToTwenty[6]
elif nowMinutes == 47:
minuteString = forty + " " + numbersZeroToTwenty[7]
elif nowMinutes == 48:
minuteString = forty + " " + numbersZeroToTwenty[8]
elif nowMinutes == 49:
minuteString = forty + " " + numbersZeroToTwenty[9]
# Handle 50 minutes
if nowMinutes == 50:
minuteString = fifty
# Handle minutes between 51 and 59
if nowMinutes > 50 and nowMinutes < 60:
if nowMinutes == 51:
minuteString = fifty + " " + numbersZeroToTwenty[1]
elif nowMinutes == 52:
minuteString = fifty + " " + numbersZeroToTwenty[2]
elif nowMinutes == 53:
minuteString = fifty + " " + numbersZeroToTwenty[3]
elif nowMinutes == 54:
minuteString = fifty + " " + numbersZeroToTwenty[4]
elif nowMinutes == 55:
minuteString = fifty + " " + numbersZeroToTwenty[5]
elif nowMinutes == 56:
minuteString = fifty + " " + numbersZeroToTwenty[6]
elif nowMinutes == 57:
minuteString = fifty + " " + numbersZeroToTwenty[7]
elif nowMinutes == 58:
minuteString = fifty + " " + numbersZeroToTwenty[8]
elif nowMinutes == 59:
minuteString = fifty + " " + numbersZeroToTwenty[9]
if traceFlag: print "Minute string to return ->>" + minuteString
return minuteString
#==============================================================================
if traceFlag: console.clear()
ClockGadget()
[{"class":"View","attributes":{"name":"clock_gadget","tint_color":"RGBA(0.000000,0.478000,1.000000,1.000000)","background_color":"RGBA(1.000000,1.000000,1.000000,1.000000)","enabled":true,"border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","flex":""},"frame":"{{0, 0}, {125, 233}}","nodes":[{"class":"View","attributes":{"enabled":true,"flex":"","name":"rootview","border_width":1,"alpha":0,"border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","uuid":"F9CA3888-3F34-4DCA-9C86-DDB5EEE7C867"},"frame":"{{0, 0}, {127, 230}}","nodes":[{"class":"WebView","attributes":{"name":"webview","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","uuid":"266E1FE4-C52A-4785-B092-498E9C313A96","enabled":true,"scales_to_fit":true,"flex":"WH"},"frame":"{{6, 6}, {115, 94}}","nodes":[]},{"class":"Button","attributes":{"font_size":11,"enabled":true,"flex":"","font_bold":false,"name":"speakTimeButton","uuid":"ADC2750C-A6F5-4311-8138-F368D068F9E9","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","border_width":1,"action":"self.speaknowButtonTapped","image_name":"ionicons-ios7-mic-32","title":"Say Now "},"frame":"{{12, 180}, {100.5, 35}}","nodes":[]},{"class":"Switch","attributes":{"enabled":true,"flex":"","name":"speakSecondsSwitch","value":false,"action":"self.switchTapped","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","uuid":"42117A61-0D45-465F-9711-4AEA39422352"},"frame":"{{12, 141}, {51, 31}}","nodes":[]},{"class":"Label","attributes":{"font_size":10,"enabled":true,"text":"Speak Seconds","flex":"","name":"speakSecondsLabel","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","text_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","alignment":"left","uuid":"039FAE80-19F0-4D9E-9BC1-EEAEFD306EE6"},"frame":"{{12, 123.5}, {80.5, 20}}","nodes":[]}]}]}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment