''' Accounts GUI For displaying accounts (IM, email, and social) in the Preference window. ''' from __future__ import with_statement import wx from contextlib import contextmanager from gui.uberwidgets.umenu import UMenu from .anylists import AnyRow, AnyList from .accountdialog import AccountPrefsDialog from . import skin from common import profile, StateMixin import digsbyprofile import common from logging import getLogger; log = getLogger('accountslist'); info = log.info from .toolbox.refreshtimer import refreshtimer class AccountRow(AnyRow): def __init__(self, *a, **k): # self.ChildPaints = Delegate() # for cleartext AnyRow.__init__(self, *a, **k) # def _paint(self, e): # unused_dc = AnyRow._paint(self, e) # return unused_dc # self.ChildPaints(dc) def ConstructMore(self): # Extra component--the edit hyperlink edit = self.edit = wx.HyperlinkCtrl(self, -1, _('Edit'), '#') edit.Hide() edit.Bind(wx.EVT_HYPERLINK, lambda e: self.on_edit()) remove = self.remove = wx.HyperlinkCtrl(self, -1, _('Remove'), '#') remove.Hide() remove.Bind(wx.EVT_HYPERLINK, lambda e: self.on_delete()) edit.HoverColour = edit.VisitedColour = edit.ForegroundColour remove.HoverColour = remove.VisitedColour = remove.ForegroundColour def LayoutMore(self, sizer): sizer.AddStretchSpacer() sizer.Add(self.edit, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) sizer.Add(self.remove, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 6) class AccountList(AnyList): 'Base class for IM, Email, and Social account lists.' def __init__(self, parent, accounts, row_control, edit_buttons, velocity=None): AnyList.__init__(self, parent, accounts, row_control = row_control, edit_buttons = edit_buttons, velocity=velocity, ) self.show_selected = False Bind = self.Bind Bind(wx.EVT_LISTBOX_DCLICK, self.on_doubleclick) Bind(wx.EVT_LIST_ITEM_FOCUSED,self.OnHoveredChanged) @contextmanager def create_account_dialog(self, protocol_name): with AccountPrefsDialog.create_new(self.Top, protocol_name = protocol_name) as diag: diag.CenterOnParent() yield diag def on_doubleclick(self, e): try: row = self.rows[e.Int] except IndexError: return None else: row.on_edit() def on_edit(self, account): profile.account_manager.edit(account) def OnHoveredChanged(self,e): row = self.GetRow(e.Int) if row: if row.IsHovered(): row.edit.Show() row.remove.Show() row.Layout() row.Refresh() else: row.edit.Hide() row.remove.Hide() row.Layout() def on_data_changed(self, *args): oldrowcount = len(self.rows) AnyList.on_data_changed(self, *args) newrowcount = len(self.rows) # when adding a new account, scroll to the bottom if oldrowcount < newrowcount: wx.CallAfter(self.Scroll, 0, self.VirtualSize.height) link_states = ('Hover', 'Normal', 'Visited') class IMAccountRow(AccountRow): 'One row in the accounts list.' icon_cache = {} def __init__(self, *a, **k): refreshtimer().Register(self) AccountRow.__init__(self, *a, **k) @property def popup(self): if not hasattr(self,'_menu') or not self._menu: self._menu = menu = UMenu(self) else: menu = self._menu menu.RemoveAllItems() menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) menu.AddItem(_('&Remove'), callback = lambda: self.on_delete()) menu.AddSep() if self.data.connection: common.actions.menu(self, self.data.connection, menu) common.actions.menu(self, self.data, menu) else: menu.AddItem(_('&Connect'), callback = lambda: self.data.connect()) return menu def PopulateControls(self, account): # Name label self.text = self.acct_text(account) # Checkbox # if we're not connected or disconnected, we must be somewhere in the middle, # so don't alter the checkbox conn = account.connection if conn is None and account.offline_reason != StateMixin.Reasons.WILL_RECONNECT: self.checkbox.Value = False elif conn is not None and conn.state == conn.Statuses.ONLINE: self.checkbox.Value = True else: self.checkbox.Set3StateValue(wx.CHK_UNDETERMINED) def acct_text(self, account = None): if account is None: account = self.data text = account.name note = profile.account_manager.state_desc(account) if note: text = text + (' (%s)' % note) return text @property def image(self): return skin.get('serviceicons.%s' % self.data.protocol).PIL.Resized(24).WXB @property def online_bitmap(self): ''' Returns a red or green light depending on if this account is connected. ''' conn = self.data.connection state = 'Online' if conn and conn.is_connected else 'Offline' return skin.get(state + '.icon') def on_close(self, *a,**k): refreshtimer().UnRegister(self) AccountRow.on_close(self, *a, **k) def Refresh(self, *a, **k): self.text = self.acct_text() AccountRow.Refresh(self, *a, **k) class IMAccountsList(AccountList): 'The accounts list.' def __init__(self, parent, accounts, edit_buttons = None): AccountList.__init__(self, parent, accounts, row_control = IMAccountRow, edit_buttons = edit_buttons, velocity = 150, ) # Clicking an account's checkbox toggles its connected state Bind = self.Bind Bind(wx.EVT_CHECKBOX, self.on_account_checked) # And double clicking an element pops up the details dialog # Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_selected) # Bind(wx.EVT_LIST_ITEM_DESELECTED, self.on_deselected) Bind(wx.EVT_KILL_FOCUS, self.on_kill_focus) def on_kill_focus(self, e): self.Layout() self.Refresh() def on_account_checked(self, e): 'A checkbox next to one of the accounts has been clicked.' row = e.EventObject.Parent account = row.data if row.IsChecked(): call = account.connect row.checkbox.Set3StateValue(wx.CHK_UNDETERMINED) else: call = lambda: profile.account_manager.cancel_reconnect(account) row.checkbox.SetValue(False) wx.CallAfter(call) def OnNew(self, e = None): 'Called when the plus button above this list is clicked.' self.addmenu.PopupMenu() def OnDelete(self, acct): 'Called when the minus button above this list is clicked.' # acct = self.GetDataObject(self.Selection) if not acct: return # Display a confirmation dialog. message = _('Are you sure you want to delete account "{name}"?').format(name=acct.name) caption = _('Delete Account') style = wx.ICON_QUESTION | wx.YES_NO parent = self import jabber if acct.protocol_class() == jabber.protocol: from gui.protocols.jabbergui import JabberDeleteConfirmBox msgbox = JabberDeleteConfirmBox(acct, message, parent, title=caption) else: msgbox = wx.MessageDialog(parent, message, caption, style) try: if msgbox.ShowModal() == wx.ID_YES: profile.remove_account(acct) finally: msgbox.Destroy() def add_account(self, protocol_name): # protocolinfo = digsbyprofile.protocols[protocol_name] with self.create_account_dialog(protocol_name) as diag: unusedres = diag.ShowModal() if diag.ReturnCode == wx.ID_SAVE: # this results in a network operation that may fail... profile.add_account(**diag.info()) # but for now, just show the account self.on_data_changed() @property def addmenu(self): try: return self.add_popup except AttributeError: self.add_popup = menu = UMenu(self) protocols = common.protocolmeta.protocols improtocols = common.protocolmeta.improtocols for p in improtocols.keys(): menu.AddItem(protocols[p].name, callback = lambda p=p: self.add_account(p), bitmap = skin.get("serviceicons." + p, None)) return menu # ---------------- class SocialRow(AccountRow): checkbox_border = 3 row_height = 20 image_offset = (6,0) def PopulateControls(self, account): self.text = account.display_name self.checkbox.Value = bool(account.enabled) @property def image(self): img = skin.get("serviceicons." + self.data.protocol, None) return img.Resized(16) if img else None @property def popup(self): if not hasattr(self,'_menu') or not self._menu: self._menu = menu = UMenu(self) else: menu = self._menu menu.RemoveAllItems() menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) menu.AddItem(_('&Remove'), callback = lambda: self.on_delete()) if self.data.enabled: menu.AddSep() common.actions.menu(self, self.data, menu) return menu class SocialList(AccountList): def __init__(self, parent, accounts, edit_buttons = None): AccountList.__init__(self, parent, accounts, row_control = SocialRow, edit_buttons = edit_buttons, velocity = 100, ) self.Bind(wx.EVT_CHECKBOX, self.on_account_checked) def on_account_checked(self, e): account = e.EventObject.Parent.data checked = e.EventObject.Value account.setnotify('enabled', checked) wx.CallAfter(account.update_info) def OnDelete(self, acct): 'Called when the minus button above this list is clicked.' if acct is None: return # Display a confirmation dialog. message = _('Are you sure you want to delete social network account "{name}"?').format(name=acct.name) caption = _('Delete Social Network Account') style = wx.ICON_QUESTION | wx.YES_NO parent = self if wx.YES == wx.MessageBox(message, caption, style, parent): log.info('removing account %r' % acct) profile.remove_social_account(acct) def OnNew(self, e = None): 'Called when the plus button above this list is clicked.' self.addmenu.PopupMenu() def add_account(self, protocol_name): with self.create_account_dialog(protocol_name) as diag: unusedres = diag.ShowModal() if diag.ReturnCode == wx.ID_SAVE: acct = profile.add_social_account(**diag.info()) on_create = getattr(acct, 'onCreate', None) #CamelCase for GUI code if on_create is not None: on_create() @property def addmenu(self): try: return self.add_popup except AttributeError: self.add_popup = menu = UMenu(self) protocols = common.protocolmeta.protocols socialprotocols = common.protocolmeta.socialprotocols for p in socialprotocols.keys(): menu.AddItem(protocols[p].name, callback = lambda p=p: self.add_account(p), bitmap = skin.get('serviceicons.' + p)) return menu # ---------------- class EmailRow(AccountRow): checkbox_border = 3 row_height = 20 image_offset = (6, 0) def PopulateControls(self, account): self.text = account.display_name self.checkbox.Value = bool(account.enabled) @property def image(self): icon = getattr(self.data, 'icon', None) if icon is not None: icon = icon.Resized(16) return icon @property def popup(self): if not hasattr(self,'_menu') or not self._menu: self._menu = menu = UMenu(self) else: menu = self._menu menu.RemoveAllItems() menu.AddItem(_('&Edit'), callback = lambda: self.on_edit()) menu.AddItem(_('&Remove'), callback = lambda: self.on_delete()) from common.emailaccount import EmailAccount if self.data.enabled: menu.AddSep() common.actions.menu(self, self.data, menu, cls = EmailAccount) return menu class EmailList(AccountList): 'Email accounts list.' def __init__(self, parent, accounts, edit_buttons = None): AccountList.__init__(self, parent, accounts, row_control = EmailRow, edit_buttons = edit_buttons, velocity = 100, ) self.Bind(wx.EVT_CHECKBOX, self.on_account_checked) def on_account_checked(self, e): eo = e.EventObject account, checked = eo.Parent.data, eo.Value account.setnotifyif('enabled', checked) wx.CallAfter(account.update_info) def OnDelete(self, acct): 'Called when the minus button above this list is clicked.' # acct = self.GetDataObject(self.Selection) if acct is None: return # Display a confirmation dialog. message = _('Are you sure you want to delete email account "{account_name}"?').format(account_name=acct.name) caption = _('Delete Email Account') style = wx.ICON_QUESTION | wx.YES_NO parent = self if wx.MessageBox(message, caption, style, parent) == wx.YES: profile.remove_email_account(acct) def OnNew(self, e = None): 'Called when the plus button above this list is clicked.' self.addmenu.PopupMenu() def add_account(self, protocol_name): with self.create_account_dialog(protocol_name) as diag: unusedres = diag.ShowModal() if diag.ReturnCode == wx.ID_SAVE: info = diag.info() common.profile.add_email_account(**info) import hooks hooks.notify('digsby.email.new_account', parent = self.Top, **info) @property def addmenu(self): try: return self.add_popup except AttributeError: self.add_popup = menu = UMenu(self) protocols = common.protocolmeta.protocols emailprotocols = common.protocolmeta.emailprotocols for p in emailprotocols.keys(): menu.AddItem(protocols[p].name, callback = lambda p=p: self.add_account(p), bitmap = skin.get('serviceicons.' + p, None)) return menu