Simplify RadarWindow.delete_profile
[wifi-radar.git] / wifiradar / gui / g2 / __init__.py
blob9d488ac33c0a344eb09d8fe2476e80deefa44501
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
4 # gui/g2/__init__.py - collection of classes for main UI with PyGTK
6 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
8 # Copyright (C) 2004-2005 Ahmad Baitalmal <ahmad@baitalmal.com>
9 # Copyright (C) 2005 Nicolas Brouard <nicolas.brouard@mandrake.org>
10 # Copyright (C) 2005-2009 Brian Elliott Finley <brian@thefinleys.com>
11 # Copyright (C) 2006 David Decotigny <com.d2@free.fr>
12 # Copyright (C) 2006 Simon Gerber <gesimu@gmail.com>
13 # Copyright (C) 2006-2007 Joey Hurst <jhurst@lucubrate.org>
14 # Copyright (C) 2012 Anari Jalakas <anari.jalakas@gmail.com>
15 # Copyright (C) 2006, 2009 Ante Karamatic <ivoks@ubuntu.com>
16 # Copyright (C) 2009-2010,2014 Sean Robinson <seankrobinson@gmail.com>
17 # Copyright (C) 2010 Prokhor Shuchalov <p@shuchalov.ru>
19 # This program is free software; you can redistribute it and/or modify
20 # it under the terms of the GNU General Public License as published by
21 # the Free Software Foundation; version 2 of the License.
23 # This program is distributed in the hope that it will be useful,
24 # but WITHOUT ANY WARRANTY; without even the implied warranty of
25 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 # GNU General Public License in LICENSE.GPL for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software
30 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 import errno
35 import logging
36 import sys
38 import glib
39 import gtk
41 import wifiradar.connections as connections
42 import wifiradar.misc as misc
43 from wifiradar.pubsub import Message
44 from . import prefs
45 from . import profile as profile_ed
46 from . import transients
48 # create a logger
49 logger = logging.getLogger(__name__)
52 # Create a bunch of icons from files in the package.
53 known_profile_icon = gtk.gdk.pixbuf_new_from_file("pixmaps/known_profile.png")
54 unknown_profile_icon = gtk.gdk.pixbuf_new_from_file("pixmaps/unknown_profile.png")
55 signal_none_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_none.xpm")
56 signal_low_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_low.xpm")
57 signal_barely_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_barely.xpm")
58 signal_ok_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_ok.xpm")
59 signal_best_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_best.xpm")
61 def pixbuf_from_known(known):
62 """ Return a :class:`gtk.gdk.Pixbuf` icon to represent :data:`known`.
63 Any true :data:`known` value returns the icon showing previous
64 familiarity.
65 """
66 if known:
67 return known_profile_icon
68 return unknown_profile_icon
70 def pixbuf_from_signal(signal):
71 """ Return a :class:`gtk.gdk.Pixbuf` icon to indicate the :data:`signal`
72 level. :data:`signal` is as reported by iwlist (may be arbitrary
73 scale in 0-100 or -X dBm)
74 """
75 signal = int(signal)
76 # Shift signal up by 80 to convert dBm scale to arbitrary scale.
77 if signal < 0:
78 signal = signal + 80
79 # Find an icon...
80 if signal < 3:
81 return signal_none_pb
82 elif signal < 12:
83 return signal_low_pb
84 elif signal < 20:
85 return signal_barely_pb
86 elif signal < 35:
87 return signal_ok_pb
88 elif signal >= 35:
89 return signal_best_pb
92 class RadarWindow:
93 def __init__(self, msg_pipe):
94 """ Create a new RadarWindow wanting to communicate through
95 :data:`msg_pipe`, a :class:`multiprocessing.Connection`.
96 """
97 self.access_points = {}
98 self.msg_pipe = msg_pipe
100 gtk.gdk.threads_init()
102 self.icon = gtk.gdk.pixbuf_new_from_file("pixmaps/wifi-radar.png")
104 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL)
105 self.window.set_icon(self.icon)
106 self.window.set_border_width(10)
107 self.window.set_size_request(550, 300)
108 self.window.set_title("WiFi Radar")
109 self.window.connect('delete_event', self.delete_event)
110 # let's create all our widgets
111 self.current_network = gtk.Label()
112 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
113 self.current_network.show()
114 self.close_button = gtk.Button("Close", gtk.STOCK_CLOSE)
115 self.close_button.show()
116 self.close_button.connect('clicked', self.delete_event, None)
117 self.about_button = gtk.Button("About", gtk.STOCK_ABOUT)
118 self.about_button.show()
119 self.about_button.connect('clicked', self.show_about_info, None)
120 self.preferences_button = gtk.Button("Preferences", gtk.STOCK_PREFERENCES)
121 self.preferences_button.show()
122 self.preferences_button.connect('clicked', self.edit_preferences, None)
123 # essid bssid known_icon known available wep_icon signal_level mode protocol channel
124 self.pstore = gtk.ListStore(str, str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str)
125 self.plist = gtk.TreeView(self.pstore)
126 # The icons column, known and encryption
127 self.pix_cell = gtk.CellRendererPixbuf()
128 self.wep_cell = gtk.CellRendererPixbuf()
129 self.icons_cell = gtk.CellRendererText()
130 self.icons_col = gtk.TreeViewColumn()
131 self.icons_col.pack_start(self.pix_cell, False)
132 self.icons_col.pack_start(self.wep_cell, False)
133 self.icons_col.add_attribute(self.pix_cell, 'pixbuf', 2)
134 self.icons_col.add_attribute(self.wep_cell, 'stock-id', 5)
135 self.plist.append_column(self.icons_col)
136 # The AP column
137 self.ap_cell = gtk.CellRendererText()
138 self.ap_col = gtk.TreeViewColumn("Access Point")
139 self.ap_col.pack_start(self.ap_cell, True)
140 self.ap_col.set_cell_data_func(self.ap_cell, self._set_ap_col_value)
141 self.plist.append_column(self.ap_col)
142 # The signal column
143 self.sig_cell = gtk.CellRendererPixbuf()
144 self.signal_col = gtk.TreeViewColumn("Signal")
145 self.signal_col.pack_start(self.sig_cell, True)
146 self.signal_col.add_attribute(self.sig_cell, 'pixbuf', 6)
147 self.plist.append_column(self.signal_col)
148 # The mode column
149 self.mode_cell = gtk.CellRendererText()
150 self.mode_col = gtk.TreeViewColumn("Mode")
151 self.mode_col.pack_start(self.mode_cell, True)
152 self.mode_col.add_attribute(self.mode_cell, 'text', 7)
153 self.plist.append_column(self.mode_col)
154 # The protocol column
155 self.prot_cell = gtk.CellRendererText()
156 self.protocol_col = gtk.TreeViewColumn("802.11")
157 self.protocol_col.pack_start(self.prot_cell, True)
158 self.protocol_col.add_attribute(self.prot_cell, 'text', 8)
159 self.plist.append_column(self.protocol_col)
160 # The channel column
161 self.channel_cell = gtk.CellRendererText()
162 self.channel_col = gtk.TreeViewColumn("Channel")
163 self.channel_col.pack_start(self.channel_cell, True)
164 self.channel_col.add_attribute(self.channel_cell, 'text', 9)
165 self.plist.append_column(self.channel_col)
166 # DnD Ordering
167 self.plist.set_reorderable(True)
168 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
169 self.pstore.connect('row-deleted', self.update_auto_profile_order)
170 # enable/disable buttons based on the selected network
171 self.selected_network = self.plist.get_selection()
172 self.selected_network.connect('changed', self.on_network_selection, None)
173 # the list scroll bar
174 sb = gtk.VScrollbar(self.plist.get_vadjustment())
175 sb.show()
176 self.plist.show()
177 # Add New button
178 self.new_button = gtk.Button("_New")
179 self.new_button.connect('clicked', self.create_new_profile, misc.get_new_profile())
180 self.new_button.show()
181 # Add Configure button
182 self.edit_button = gtk.Button("C_onfigure")
183 self.edit_button.connect('clicked', self.edit_profile, None)
184 self.edit_button.show()
185 self.edit_button.set_sensitive(False)
186 # Add Delete button
187 self.delete_button = gtk.Button("_Delete")
188 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
189 self.delete_button.show()
190 self.delete_button.set_sensitive(False)
191 # Add Connect button
192 self.connect_button = gtk.Button("Co_nnect")
193 self.connect_button.connect('clicked', self.connect_profile, None)
194 # Add Disconnect button
195 self.disconnect_button = gtk.Button("D_isconnect")
196 self.disconnect_button.connect('clicked', self.disconnect_profile, None)
197 # lets add our widgets
198 rows = gtk.VBox(False, 3)
199 net_list = gtk.HBox(False, 0)
200 listcols = gtk.HBox(False, 0)
201 prows = gtk.VBox(False, 0)
202 # lets start packing
203 # the network list
204 net_list.pack_start(self.plist, True, True, 0)
205 net_list.pack_start(sb, False, False, 0)
206 # the rows level
207 rows.pack_start(net_list , True, True, 0)
208 rows.pack_start(self.current_network, False, True, 0)
209 # the list columns
210 listcols.pack_start(rows, True, True, 0)
211 listcols.pack_start(prows, False, False, 5)
212 # the list buttons
213 prows.pack_start(self.new_button, False, False, 2)
214 prows.pack_start(self.edit_button, False, False, 2)
215 prows.pack_start(self.delete_button, False, False, 2)
216 prows.pack_end(self.connect_button, False, False, 2)
217 prows.pack_end(self.disconnect_button, False, False, 2)
219 self.window.action_area.pack_start(self.about_button)
220 self.window.action_area.pack_start(self.preferences_button)
221 self.window.action_area.pack_start(self.close_button)
223 rows.show()
224 prows.show()
225 listcols.show()
226 self.window.vbox.add(listcols)
227 self.window.vbox.set_spacing(3)
228 self.window.show_all()
230 # Now, immediately hide these two. The proper one will be
231 # displayed later, based on interface state. -BEF-
232 self.disconnect_button.hide()
233 self.connect_button.hide()
234 self.connect_button.set_sensitive(False)
236 # set up status window for later use
237 self.status_window = transients.StatusWindow(self)
238 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
240 self._running = True
241 # Check for incoming messages every 25 ms, a.k.a. 40 Hz.
242 glib.timeout_add(25, self.run)
244 gtk.main()
246 def run(self):
247 """ Watch for incoming messages.
249 if self.msg_pipe.poll():
250 try:
251 msg = self.msg_pipe.recv()
252 except (EOFError, IOError) as e:
253 # This is bad, really bad.
254 logger.critical('read on closed ' +
255 'Pipe ({}), failing...'.format(rfd))
256 raise misc.PipeError(e)
257 else:
258 self._check_message(msg)
259 return self._running
261 def _check_message(self, msg):
262 """ Process incoming messages.
264 if msg.topic == 'EXIT':
265 self.delete_event()
266 elif msg.topic == 'CONFIG-UPDATE':
267 # Replace configuration manager with the one in msg.details.
268 self.config = msg.details
269 elif msg.topic == 'PROFILE-UPDATE':
270 self.update_plist_items(msg.details)
271 elif msg.topic == 'PROFILE-UNLIST':
272 self.remove_profile(msg.details)
273 elif msg.topic == 'PREFS-EDIT':
274 self.edit_preferences()
275 else:
276 logger.warning('unrecognized Message: "{}"'.format(msg))
278 def destroy(self, widget=None):
279 """ Quit the Gtk event loop. :data:`widget` is the widget
280 sending the signal, but it is ignored.
282 if self.status_window:
283 self.status_window.destroy()
284 gtk.main_quit()
286 def delete_event(self, widget=None, data=None):
287 """ Shutdown the application. :data:`widget` is the widget sending
288 the signal and :data:`data` is a list of arbitrary arguments,
289 both are ignored. Always returns False to not propigate the
290 signal which called :func:`delete_event`.
292 self._running = False
293 self.msg_pipe.send(Message('EXIT', ''))
294 self.msg_pipe.close()
295 self.window.hide()
296 # process GTK events so that window hides more quickly
297 if sys.modules.has_key("gtk"):
298 while gtk.events_pending():
299 gtk.main_iteration(False)
300 self.destroy()
301 return False
303 def update_network_info(self, profile=None, ip=None):
304 """ Update the current ip and essid shown to the user.
306 if (profile is None) and (ip is None):
307 self.current_network.set_text("Not Connected.")
308 else:
309 self.current_network.set_text('Connected to {}\nIP Address {}'.format(profile, ip))
311 def update_connect_buttons(self, connected=False):
312 """ Set the state of connect/disconnect buttons to reflect the
313 current connected state.
315 if connected:
316 self.connect_button.hide()
317 self.disconnect_button.show()
318 else:
319 self.disconnect_button.hide()
320 self.connect_button.show()
322 def update_plist_items(self, profile):
323 """ Updates the display of :data:`profile`.
325 if profile['roaming']:
326 prow_iter = self.get_row_by_ap(profile['essid'])
327 else:
328 prow_iter = self.get_row_by_ap(profile['essid'], profile['bssid'])
330 if prow_iter is None:
331 # the AP is not in the list of APs on the screen
332 self.add_profile(profile)
333 else:
334 # the AP is in the list of APs on the screen
335 wep = None
336 if profile['encrypted']:
337 wep = gtk.STOCK_DIALOG_AUTHENTICATION
338 # Update the Gtk objects.
339 self.pstore.set_value(prow_iter, 2, pixbuf_from_known(profile['known']))
340 self.pstore.set_value(prow_iter, 3, profile['known'])
341 self.pstore.set_value(prow_iter, 4, profile['available'])
342 self.pstore.set_value(prow_iter, 5, wep)
343 self.pstore.set_value(prow_iter, 6, pixbuf_from_signal(profile['signal']))
344 self.pstore.set_value(prow_iter, 7, profile['mode'])
345 self.pstore.set_value(prow_iter, 8, profile['protocol'])
346 self.pstore.set_value(prow_iter, 9, profile['channel'])
348 def _set_ap_col_value(self, column, cell, model, iter):
349 """ Set the text attribute of :data:`column` to the first two
350 :data:`model` values joined by a newline. This is for
351 displaying the :data:`essid` and :data:`bssid` in a single
352 cell column.
354 essid = model.get_value(iter, 0)
355 bssid = model.get_value(iter, 1)
356 cell.set_property('text', '\n'.join([essid, bssid]))
358 def get_row_by_ap(self, essid, bssid=' Multiple APs'):
359 """ Returns a :class:`gtk.TreeIter` for the row which holds
360 :data:`essid` and :data:`bssid`.
362 :data:`bssid` is optional. If not given, :func:`get_row_by_ap`
363 will try to match a roaming profile with the given :data:`essid`.
365 If no match is found, it returns None.
367 for row in self.pstore:
368 if (row[0] == essid) and (row[1] == bssid):
369 return row.iter
370 return None
372 def on_network_selection(self, widget=None, data=None):
373 """ Enable/disable buttons based on the selected network.
374 :data:`widget` is the widget sending the signal and :data:`data`
375 is a list of arbitrary arguments, both are ignored.
377 store, selected_iter = self.selected_network.get_selected()
378 if selected_iter is None:
379 # No row is selected, disable all buttons except New.
380 # This occurs after a drag-and-drop.
381 self.edit_button.set_sensitive(False)
382 self.delete_button.set_sensitive(False)
383 self.connect_button.set_sensitive(False)
384 else:
385 # One row is selected, so enable or disable buttons.
386 self.connect_button.set_sensitive(True)
387 if store.get_value(selected_iter, 3):
388 # Known profile.
389 self.edit_button.set_sensitive(True)
390 self.delete_button.set_sensitive(True)
391 else:
392 # Unknown profile.
393 self.edit_button.set_sensitive(True)
394 self.delete_button.set_sensitive(False)
396 def show_about_info(self, widget=None, data=None):
397 """ Handle the life-cycle of the About dialog. :data:`widget` is
398 the widget sending the signal and :data:`data` is a list of
399 arbitrary arguments, both are ignored.
401 about = transients.AboutDialog()
402 about.run()
403 about.destroy()
405 def edit_preferences(self, widget=None, data=None):
406 """ Handle the life-cycle of the preferences dialog. :data:`widget`
407 is the widget sending the signal and :data:`data` is a list of
408 arbitrary arguments, both are ignored.
410 # get raw strings from config file
411 self.config.raw = True
412 prefs_editor = prefs.PreferencesEditor(self, self.config)
413 response = prefs_editor.run()
414 if response == int(gtk.RESPONSE_ACCEPT):
415 prefs_editor.save()
416 prefs_editor.destroy()
417 # get cooked strings from config file
418 self.config.raw = False
420 def add_profile(self, profile):
421 """ Add :data:`profile` to the list of APs shown to the user.
423 if profile['roaming']:
424 profile['bssid'] = ' Multiple APs'
426 wep = None
427 if profile['encrypted']:
428 wep = gtk.STOCK_DIALOG_AUTHENTICATION
430 self.pstore.append([profile['essid'], profile['bssid'],
431 known_profile_icon, profile['known'], profile['available'],
432 wep, signal_none_pb, profile['mode'], profile['protocol'],
433 profile['channel']])
435 def remove_profile(self, profile):
436 """ Remove :data:`profile` from the list of APs shown to the user.
438 if profile['roaming']:
439 prow_iter = self.get_row_by_ap(profile['essid'])
440 else:
441 prow_iter = self.get_row_by_ap(profile['essid'], profile['bssid'])
442 if prow_iter is not None:
443 self.pstore.remove(prow_iter)
445 def create_new_profile(self, widget=None, profile=None, data=None):
446 """ Respond to a user request to create a new AP profile.
447 :data:`widget` is the widget sending the signal. :data:profile`
448 is an AP profile to use as the basis for the new profile. It
449 is likely empty or mostly empty. :data:`data` is a list of
450 arbitrary arguments. :data:`widget` and "data"`data` are both
451 ignored.
453 The order of parameters is important. Because when this method
454 is called from a signal handler, :data:`widget` is always the
455 first argument.
457 profile_editor = profile_ed.ProfileEditor(self, profile)
458 try:
459 profile = profile_editor.run()
460 except ValueError:
461 self.msg_pipe.send(Message('ERROR', 'Cannot save empty ESSID'))
462 else:
463 if profile:
464 self.msg_pipe.send(Message('PROFILE-UPDATE', profile))
465 finally:
466 profile_editor.destroy()
468 def request_profile_edit(self, widget=None, data=None):
469 """ Respond to a request to edit an AP profile. Edit the selected
470 AP profile, if it is known. Otherwise, create a new profile
471 with the selected ESSID and BSSID. :data:`widget` is the widget
472 sending the signal and :data:`data` is a list of arbitrary
473 arguments, both are ignored.
475 store, selected_iter = self.plist.get_selection().get_selected()
476 if selected_iter is None:
477 # No AP is selected
478 return
480 essid = self.pstore.get_value(selected_iter, 0)
481 bssid = self.pstore.get_value(selected_iter, 1)
483 if bssid == ' Multiple APs':
484 # AP list says this is a roaming profile
485 apname = misc.make_section_name(essid, '')
486 else:
487 # AP list says this is NOT a roaming profile
488 apname = misc.make_section_name(essid, bssid)
489 profile = self.config.get_profile(apname)
490 if profile:
491 # A profile was found in the config file
492 profile['bssid'] = self.access_points[apname]['bssid']
493 profile_editor = profile_ed.ProfileEditor(self, profile)
494 try:
495 # try editing the profile
496 edited_profile = profile_editor.run()
497 except ValueError:
498 error_dlg = transients.ErrorDialog(profile_editor.dialog, "Cannot save empty ESSID")
499 del error_dlg
500 return False
501 finally:
502 # Always remove profile editor window from screen
503 profile_editor.destroy()
504 if edited_profile:
505 # A profile was returned by the editor
506 old_index = None
507 row = None
508 if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
509 # ESSID, BSSID, or roaming was changed in profile editor
510 self.msg_pipe.send(Message('SCAN-STOP', ''))
511 if profile['roaming']:
512 # The old profile was a roaming profile
513 old_ap = misc.make_section_name(profile['essid'], '')
514 else:
515 # The old profile was NOT a roaming profile
516 old_ap = misc.make_section_name(profile['essid'], profile['bssid'])
517 # Find where old profile was in auto order
518 old_index = self.config.auto_profile_order.index(old_ap)
519 # Remove old profile and get its place in AP list
520 row = self.delete_profile(old_ap)
521 self.msg_pipe.send(Message('PROFILE-REMOVE', old_ap))
522 # Add AP to the list displayed to user
523 self.msg_pipe.send(Message('PROFILE-UPDATE', edited_profile))
524 self.msg_pipe.send(Message('SCAN-START', ''))
525 if edited_profile['roaming']:
526 # New profile is a roaming profile
527 apname = misc.make_section_name(edited_profile['essid'], '')
528 ap_display = edited_profile['essid'] + "\n" + ' Multiple APs'
529 # Remove all other profiles that match the new profile ESSID
530 while True:
531 prow_iter = self.get_row_by_ap(edited_profile['essid'])
532 if prow_iter:
533 self.pstore.remove(prow_iter)
534 else:
535 break
536 else:
537 # New profile is NOT a roaming profile
538 apname = misc.make_section_name(edited_profile['essid'], edited_profile['bssid'])
539 ap_display = edited_profile['essid'] + "\n" + edited_profile['bssid']
540 # Insert the new profile in the same position as the one being replaced
541 if old_index is not None:
542 # Old profile was in auto order list
543 self.config.auto_profile_order.insert(old_index, apname)
544 if (( row is not None) and (self.pstore.iter_is_valid(row)) ):
545 self.pstore.insert_before(row, [ap_display, None, None, None, None, None, None, None, None])
546 self.access_points[apname] = edited_profile
547 self.config.set_section(apname, edited_profile)
548 try:
549 # Save updated profile to config file
550 self.config.write()
551 except IOError as e:
552 if e.errno == errno.ENOENT:
553 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
554 del error_dlg
555 else:
556 raise e
557 else:
558 # The AP does not already have a profile
559 profile = misc.get_new_profile()
560 profile['essid'] = self.pstore.get_value(selected_iter, 0)
561 profile['bssid'] = self.pstore.get_value(selected_iter, 1)
562 self.create_new_profile(widget, profile, data)
564 def delete_profile(self, apname):
565 """ Delete the profile associated with :data:`apname` (i.e. make
566 the profile unknown).
568 self.msg_pipe.send(Message('PROFILE-REMOVE', apname))
570 def delete_profile_with_check(self, widget=None, data=None):
571 """ Respond to a request to delete an AP profile (i.e. make the
572 profile unknown) Check with the user first. :data:`widget`
573 is the widget sending the signal and :data:`data` is a list of
574 arbitrary arguments, both are ignored.
576 store, selected_iter = self.plist.get_selection().get_selected()
577 if selected_iter is None:
578 return
580 essid = self.pstore.get_value(selected_iter, 0)
581 bssid = self.pstore.get_value(selected_iter, 1)
582 if bssid == ' Multiple APs':
583 apname = misc.make_section_name(essid, '')
584 else:
585 apname = misc.make_section_name(essid, bssid)
586 profile = self.config.get_profile(apname)
587 if profile['roaming']:
588 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s profile?" % (essid, ))
589 else:
590 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid))
591 known = store.get_value(selected_iter, 3)
592 if not known: return
593 res = dlg.run()
594 dlg.destroy()
595 del dlg
596 if res == gtk.RESPONSE_NO:
597 return
598 self.delete_profile(apname)
600 def connect_profile(self, widget, profile, data=None):
601 """ Respond to a request to connect to an AP.
603 Parameters:
605 'widget' -- gtk.Widget - The widget sending the event.
607 'profile' -- dictionary - The AP profile to which to connect.
609 'data' -- tuple - list of arbitrary arguments (not used)
611 Returns:
613 nothing
615 store, selected_iter = self.plist.get_selection().get_selected()
616 if selected_iter is None:
617 return
618 essid = self.pstore.get_value(selected_iter, 0)
619 bssid = self.pstore.get_value(selected_iter, 1)
620 known = store.get_value(selected_iter, 3)
621 if not known:
622 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "This network does not have a profile configured.\n\nWould you like to create one now?")
623 res = dlg.run()
624 dlg.destroy()
625 del dlg
626 if res == gtk.RESPONSE_NO:
627 return
628 profile = misc.get_new_profile()
629 profile['essid'] = essid
630 profile['bssid'] = bssid
631 if not self.create_new_profile(widget, profile, data):
632 return
633 else:
634 # Check for roaming profile.
635 ap_name = misc.make_section_name(essid, '')
636 profile = self.config.get_profile(ap_name)
637 if not profile:
638 # Check for normal profile.
639 ap_name = misc.make_section_name(essid, bssid)
640 profile = self.config.get_profile(ap_name)
641 if not profile:
642 # No configured profile
643 return
644 profile['bssid'] = self.access_points[ap_name]['bssid']
645 profile['channel'] = self.access_points[ap_name]['channel']
646 self.msg_pipe.send(Message('CONNECT', profile))
648 def disconnect_profile(self, widget=None, data=None):
649 """ Respond to a request to disconnect by sending a message to
650 ConnectionManager. :data:`widget` is the widget sending the
651 signal and :data:`data` is a list of arbitrary arguments, both
652 are ignored.
654 self.msg_pipe.send(Message('DISCONNECT', ''))
655 if data == "cancel":
656 self.status_window.update_message("Canceling connection...")
657 if sys.modules.has_key("gtk"):
658 while gtk.events_pending():
659 gtk.main_iteration(False)
661 def profile_order_updater(self, model, path, iter, auto_profile_order):
664 if model.get_value(iter, 3) is True:
665 essid = self.pstore.get_value(selected_iter, 0)
666 bssid = self.pstore.get_value(selected_iter, 1)
667 if bssid == ' Multiple APs':
668 bssid = ''
669 apname = misc.make_section_name(essid, bssid)
670 auto_profile_order.append(apname)
672 def update_auto_profile_order(self, widget=None, data=None, data2=None):
673 """ Update the config file auto profile order from the on-screen
674 order. :data:`widget` is the widget sending the signal and
675 :data:`data` and :data:`data2` is a list of arbitrary arguments,
676 all are ignored.
678 # recreate the auto_profile_order
679 auto_profile_order = []
680 self.pstore.foreach(self.profile_order_updater, auto_profile_order)
681 self.config.auto_profile_order = auto_profile_order
682 try:
683 self.config.write()
684 except IOError as e:
685 if e.errno == errno.ENOENT:
686 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
687 del error_dlg
688 else:
689 raise e
692 # Make so we can be imported
693 if __name__ == "__main__":
694 pass