Fullscreen support, UI fixes, reset improved
[smpy-maemo.git] / smpy
blobc5eae70a8370a9d36383d69b3c14b53dc7eb67c3
1 #!/usr/bin/python2.5
4 #             This is SMaemoPy ("smpy"), the
5 #        Python- and Maemo-ified Perlific SMS Sender
7 #               ____,    _    _     ____,  py 
8 #              / ____)  | \  / |   / ____)
9 #             ( (       |  \/  |  ( (  
10 #             ( (___    | |``| |  ( (___  
11 #              \___ \   | |  | |   \___ \ 
12 #                  ) )  (.)  (.)aemo   ) )
13 #              ____) )             ____) )
14 #             (,____/ pythonified (,____/
16 #  or:  Send SMS using a Hildon-based GUI using A1.net
18 #  Copyright 2007-2008 Thomas Perl <thp@perli.net>
19 #  Based on smpy and smp, Copyright 2006/2007 Thomas Perl
20 #  License: GNU General Public License v2 or later
23 #  Changes:
24 #   2008-01-07: Fork smpy, first port to Maemo Chinook
25 #   2008-01-10: Maemo Icon added
26 #   2008-01-11: GUI-Configurable, multiple contacts folders
27 #   2008-03-01: Some UI changes, see the Git history from now on
31 import gobject
33 import mechanize
35 import threading
36 import shelve
37 import os.path
38 import sys
40 import gtk
42 import hildon
43 import re
44 import glob
46 class PythonifiedSMSSender(object):
47     host = 'sms.a1.net'
48     SIZE = 90
50     def __init__( self):
51         self.username = ''
52         configfile = os.path.expanduser('~/.smpyrc')
53         try:
54             self.config = shelve.open(configfile)
55         except:
56             os.path.unlink(configfile)
57             self.config = shelve.open(configfile)
59         if not 'paths' in self.config:
60             self.config['paths'] = [os.path.expanduser('~/smpy-contacts')]
62         self.app = hildon.Program()
63         self.SMSWindow = hildon.Window()
64         self.app.add_window(self.SMSWindow)
65         self.SMSWindow.connect('destroy', self.close_down)
66         self.SMSWindow.connect('key-press-event', self.on_key_press)
67         self.SMSWindow.connect('window-state-event', self.on_window_state_change)
68         self.window_in_fullscreen = False
70         menu = gtk.Menu()
71         folder = gtk.MenuItem('Add contacts folder')
72         folder.connect('activate', self.activate_add_folder)
73         menu.append(folder)
74         config = gtk.MenuItem('Configure smpy')
75         config.connect('activate', self.reconfigure)
76         menu.append(config)        
77         menu.append(gtk.SeparatorMenuItem())
78         quit = gtk.MenuItem('Quit')
79         quit.connect('activate', gtk.main_quit)
80         menu.append(quit)
81         self.SMSWindow.set_menu(menu)
83         vb = gtk.VBox()
84         vb.set_spacing( 6)
85         vb.set_border_width( 12)
86         self.SMSWindow.add( vb)
88         self.title_label = gtk.Label('')
89         self.title_label.set_markup('<small><b>smpy :)</b></small>')
90         vb.pack_start(self.title_label,False)
92         hb = gtk.HBox()
93         hb.set_spacing(4)
94         vb.pack_start(hb,True)
95         self.iconview = gtk.IconView()
96         self.set_iconview_model()
97         self.iconview.set_pixbuf_column(2)
98         self.iconview.set_size_request(90, -1)
99         self.iconview.set_margin(0)
100         self.iconview.set_spacing(0)
101         self.iconview.set_row_spacing(2)
102         self.iconview.set_column_spacing(2)
103         self.iconview.connect('selection-changed', self.iconview_selection_changed)
104         sw = gtk.ScrolledWindow()
105         sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
106         hildon.hildon_helper_set_thumb_scrollbar(sw, True)
107         sw.add(self.iconview)
108         hb.pack_start(sw, False)
109         sw.show_all()
111         self.message = gtk.TextView()
112         self.message.set_wrap_mode( gtk.WRAP_WORD)
113         frame = gtk.Frame()
114         frame.set_shadow_type( gtk.SHADOW_IN)
115         frame.add( self.message)
116         hb.pack_start( frame, True)
118         hb = gtk.HBox()
119         hb.set_spacing( 6)
120         vb.pack_start( hb, False)
121         
122         self.status = gtk.Label('')
123         self.status.set_alignment( 0.0, 0.5)
124         hb.pack_start( self.status, True)
126         self.send_button = gtk.Button( label = 'Send SMS')
127         self.send_button.get_child().set_padding(20, 20)
128         self.send_button.connect( 'clicked', self.send_sms)
129         hb.pack_end( self.send_button, False)
131         self.reset_button = gtk.Button(label='Reset')
132         self.reset_button.get_child().set_padding(20, 20)
133         self.reset_button.connect('clicked', self.reset_all)
134         hb.pack_end(self.reset_button, False)
135         
136         self.SMSWindow.resize( 300, 250)
137         self.SMSWindow.move( 200, 200)
138         self.SMSWindow.set_title('Send SMS')
139         self.SMSWindow.show_all()
140         self.title_label.hide()
141         
142         if 'username' not in self.config or 'password' not in self.config or not self.config['username'] or not self.config['password']:
143             self.show_message('Please configure your account first using the menu.')
144             self.send_button.set_sensitive(False)
146         self.set_username('')
148     def on_window_state_change(self, widget, event, *args):
149         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
150             self.window_in_fullscreen = True
151         else:
152             self.window_in_fullscreen = False
154     def on_key_press(self, widget, event, *args):
155         if event.keyval == gtk.keysyms.F6:
156             if self.window_in_fullscreen:
157                 self.title_label.hide()
158                 self.SMSWindow.unfullscreen()
159             else:
160                 self.SMSWindow.fullscreen()
161                 self.title_label.show()
163     def set_username(self, username):
164         gobject.idle_add(self.send_button.set_sensitive, bool(username!=''))
165         self.username = username
167     def close_down(self, widget):
168         self.config.close()
169         gtk.main_quit()
170     
171     def reconfigure(self, widget=None):
172         dialog = hildon.LoginDialog(self.SMSWindow)
173         dialog.set_message('Enter your A1.net username and password.')
174         dialog.set_title('Login to A1.net')
175         if 'username' in self.config:
176             dialog.set_property('username', self.config['username'])
177         if 'password' in self.config:
178             dialog.set_property('password', self.config['password'])
179         if dialog.run() == gtk.RESPONSE_OK:
180             self.config['username'] = dialog.get_username()
181             self.config['password'] = dialog.get_password()
182             if self.config['username'] and self.config['password']:
183                 self.send_button.set_sensitive(True)
184         else:
185             self.send_button.set_sensitive(False)
186         dialog.destroy()
189     def activate_add_folder(self, menuitem):
190         folder = self.select_new_folder()
191         if folder is not None:
192             self.config['paths'] = self.config['paths'] + [folder]
193             print 'added: %s' % folder
194             self.set_iconview_model()
196     def set_iconview_model(self):
197         contact_list = []
198         for path in self.config['paths']:
199             contact_list = contact_list + glob.glob(path+'/*')
200         contacts_model = self.contacts_to_model(contact_list)
201         self.iconview.set_model(contacts_model)
203     def iconview_selection_changed(self, iconview):
204         model = self.iconview.get_model()
205         for selected in self.iconview.get_selected_items():
206             self.set_username(model.get_value(model.get_iter(selected), 1))
207             self.status.set_text(self.username+' selected')
209     def completion_match_func( self, completion, key, iter, column):
210         text = completion.get_model().get_value( iter, 0)
211         matched = True
212         for part in key.split():
213             if text.lower().find( part.lower()) == -1:
214                 matched = False
216         return matched
218     def select_new_folder(self):
219         fcd = hildon.FileChooserDialog(self.SMSWindow, 'select-folder')
220         fcd.set_title('Add contacts folder')
221         if fcd.run() == gtk.RESPONSE_OK:
222             result = fcd.get_filename()
223         else:
224             result = None
226         fcd.destroy()
227         return result
229     def match_selected( self, completion, model, iter):
230         self.number.set_text( model.get_value( iter, 1))
231         gobject.idle_add( self.message.grab_focus)
233     def show_message(self, text):
234         dlg = gtk.MessageDialog(self.SMSWindow, gtk.DIALOG_MODAL, 0, gtk.BUTTONS_OK, text)
235         dlg.run()
236         dlg.destroy()
238     def send_sms( self, widget):
239         self.send_button.set_sensitive(False)
240         self.message.set_sensitive(False)
241         self.iconview.set_sensitive(False)
242         threading.Thread( target = self.send_sms_thread).start()
244     def send_sms_thread( self):
245         buf = self.message.get_buffer()
246         message = buf.get_text( buf.get_start_iter(), buf.get_end_iter())
247         number = ''
248         model = self.iconview.get_model()
249         for selected in self.iconview.get_selected_items():
250             number = model.get_value(model.get_iter(selected), 0)
252         last_message = ':('
253         for message in self.send_message( number, message):
254             gobject.idle_add( lambda: self.status.set_text( message))
255             last_message = message
257         if last_message.find(':)') != -1:
258             gobject.idle_add(self.show_message, 'sms sent to %s\n(%s)'%(self.username,number))
259             gobject.idle_add(self.reset_all)
260         else:
261             gobject.idle_add(self.show_message, 'there has been an error')
263     def reset_all(self, widget=None):
264         self.iconview.unselect_all()
265         self.set_username('')
266         if widget is not None:
267             self.message.get_buffer().set_text('')
268         self.status.set_text('')
269         self.send_button.set_sensitive(True)
270         self.message.set_sensitive(True)
271         self.iconview.set_sensitive(True)
273     def contacts_to_model( self, contacts):
274         model = gtk.ListStore( str, str, gtk.gdk.Pixbuf)
275         for file in sorted(contacts, cmp=lambda x, y: cmp(os.path.basename(x), os.path.basename(y))):
276             (basename, ext) = os.path.splitext(os.path.basename(file))
277             s = ext.lower()
278             if s == '.png' or s == '.jpg':
279                 (caption, number) = re.match('(\w+) \(([^)]+)\)', basename).groups()
280                 pb = gtk.gdk.pixbuf_new_from_file_at_size(file, self.SIZE, self.SIZE)
281                 model.append([number, caption, pb])
282         return model
284     def cute_type( self, type):
285         return type[0].upper() + type[1:].replace( '-', ' ')
287     def send_message( self, to, msg, really=True):
288         if not really:
289             yield 'Sending disabled by code.'
290         b = mechanize.Browser()
291         yield 'Connecting to %s...' % self.host
292         b.open('http://%s/' % self.host)
293         yield 'Logging in as "%s"...' % self.config['username']
294         b.select_form( name = 'asmpform')
295         b['UserID'] = self.config['username']
296         b['Password'] = self.config['password']
297         b.submit()
298         yield 'Sending message to %s...' % to
299         
300         try:
301             b.select_form( name = 'myform')
302         except:
303             yield 'Error logging in :('
304         b['address'] = to
305         b['msg'] = msg
306         b.submit()
307         yield 'Message sent :)'
309 if __name__ == '__main__':
310     sender = PythonifiedSMSSender()
311     gobject.threads_init()
312     gtk.main()