4 # This is SMaemoPy ("smpy"), the
5 # Python- and Maemo-ified Perlific SMS Sender
8 # / ____) | \ / | / ____)
10 # ( (___ | |``| | ( (___
11 # \___ \ | | | | \___ \
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
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
46 class PythonifiedSMSSender(object):
52 configfile = os.path.expanduser('~/.smpyrc')
54 self.config = shelve.open(configfile)
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
71 folder = gtk.MenuItem('Add contacts folder')
72 folder.connect('activate', self.activate_add_folder)
74 config = gtk.MenuItem('Configure smpy')
75 config.connect('activate', self.reconfigure)
77 menu.append(gtk.SeparatorMenuItem())
78 quit = gtk.MenuItem('Quit')
79 quit.connect('activate', gtk.main_quit)
81 self.SMSWindow.set_menu(menu)
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)
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)
111 self.message = gtk.TextView()
112 self.message.set_wrap_mode( gtk.WRAP_WORD)
114 frame.set_shadow_type( gtk.SHADOW_IN)
115 frame.add( self.message)
116 hb.pack_start( frame, True)
120 vb.pack_start( hb, False)
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)
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()
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
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()
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):
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)
185 self.send_button.set_sensitive(False)
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):
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)
212 for part in key.split():
213 if text.lower().find( part.lower()) == -1:
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()
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)
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())
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)
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)
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))
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])
284 def cute_type( self, type):
285 return type[0].upper() + type[1:].replace( '-', ' ')
287 def send_message( self, to, msg, really=True):
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']
298 yield 'Sending message to %s...' % to
301 b.select_form( name = 'myform')
303 yield 'Error logging in :('
307 yield 'Message sent :)'
309 if __name__ == '__main__':
310 sender = PythonifiedSMSSender()
311 gobject.threads_init()