| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 | |
| 5 import string | |
| 6 import time | |
| 7 | |
| 8 import gtk | |
| 9 | |
| 10 from twisted.words.im.gtkcommon import GLADE_FILE, autoConnectMethods, InputOutp
utWindow, openGlade | |
| 11 | |
| 12 class ContactsList: | |
| 13 def __init__(self, chatui, xml): | |
| 14 self.xml = xml# openGlade(GLADE_FILE, root="ContactsWidget") | |
| 15 # self.widget = self.xml.get_widget("ContactsWidget") | |
| 16 self.people = [] | |
| 17 self.onlinePeople = [] | |
| 18 self.countOnline = 0 | |
| 19 autoConnectMethods(self) | |
| 20 self.selectedPerson = None | |
| 21 self.xml.get_widget("OnlineCount").set_text("Online: 0") | |
| 22 self.chat = chatui | |
| 23 | |
| 24 # Construct Menu for Account Selection | |
| 25 self.optionMenu = self.xml.get_widget("AccountsListPopup") | |
| 26 self.accountMenuItems = [] | |
| 27 self.currentAccount = None | |
| 28 | |
| 29 | |
| 30 def registerAccountClient(self, account): | |
| 31 print 'registering account client', self, account | |
| 32 self.accountMenuItems.append(account) | |
| 33 self._updateAccountMenu() | |
| 34 self.chat._accountmanager.refreshAccounts() | |
| 35 | |
| 36 def _updateAccountMenu(self): | |
| 37 # This seems to be necessary -- I don't understand gtk's handling of | |
| 38 # GtkOptionMenus | |
| 39 print 'updating account menu', self.accountMenuItems | |
| 40 self.accountMenu = gtk.GtkMenu() | |
| 41 for account in self.accountMenuItems: | |
| 42 i = gtk.GtkMenuItem(account.accountName) | |
| 43 i.connect('activate', self.on_AccountsListPopup_activate, account) | |
| 44 self.accountMenu.append(i) | |
| 45 if self.accountMenuItems: | |
| 46 print "setting default account to", self.accountMenuItems[0] | |
| 47 self.currentAccount = self.accountMenuItems[0] | |
| 48 self.accountMenu.show_all() | |
| 49 self.optionMenu.set_menu(self.accountMenu) | |
| 50 | |
| 51 def on_AccountsListPopup_activate(self, w, account): | |
| 52 print 'setting current account', account | |
| 53 self.currentAccount = account | |
| 54 | |
| 55 def on_AddContactButton_clicked(self, b): | |
| 56 self.currentAccount.addContact( | |
| 57 self.xml.get_widget("ContactNameEntry").get_text()) | |
| 58 | |
| 59 def unregisterAccountClient(self,account): | |
| 60 print 'unregistering account client', self, account | |
| 61 self.accountMenuItems.remove(account) | |
| 62 self._updateAccountMenu() | |
| 63 | |
| 64 def setContactStatus(self, person): | |
| 65 if person not in self.people: | |
| 66 self.people.append(person) | |
| 67 self.refreshContactsLists() | |
| 68 | |
| 69 def on_OnlineContactsTree_select_row(self, w, row, column, event): | |
| 70 self.selectedPerson = self.onlinePeople[row] | |
| 71 entry = self.xml.get_widget("ContactNameEntry") | |
| 72 entry.set_text(self.selectedPerson.name) | |
| 73 self.currentAccount = self.selectedPerson.account.client | |
| 74 idx = self.accountMenuItems.index(self.currentAccount) | |
| 75 self.accountMenu.set_active(idx) | |
| 76 self.optionMenu.remove_menu() | |
| 77 self.optionMenu.set_menu(self.accountMenu) | |
| 78 | |
| 79 def on_PlainSendIM_clicked(self, b): | |
| 80 self.chat.getConversation( | |
| 81 self.currentAccount.getPerson( | |
| 82 self.xml.get_widget("ContactNameEntry").get_text())) | |
| 83 ## if self.selectedPerson: | |
| 84 ## c = self.chat.getConversation(self.selectedPerson) | |
| 85 | |
| 86 def on_PlainJoinChat_clicked(self, b): | |
| 87 ## GroupJoinWindow(self.chat) | |
| 88 name = self.xml.get_widget("ContactNameEntry").get_text() | |
| 89 self.currentAccount.joinGroup(name) | |
| 90 | |
| 91 def refreshContactsLists(self): | |
| 92 # HIDEOUSLY inefficient | |
| 93 online = self.xml.get_widget("OnlineContactsTree") | |
| 94 offline = self.xml.get_widget("OfflineContactsList") | |
| 95 online.freeze() | |
| 96 offline.freeze() | |
| 97 online.clear() | |
| 98 offline.clear() | |
| 99 self.countOnline = 0 | |
| 100 self.onlinePeople = [] | |
| 101 self.people.sort(lambda x, y: cmp(x.name, y.name)) | |
| 102 for person in self.people: | |
| 103 if person.isOnline(): | |
| 104 self.onlinePeople.append(person) | |
| 105 online.append([person.name, str(person.getStatus()), | |
| 106 person.getIdleTime(), | |
| 107 person.account.accountName]) | |
| 108 self.countOnline = self.countOnline + 1 | |
| 109 offline.append([person.name, person.account.accountName, | |
| 110 'Aliasing Not Implemented', 'Groups Not Implemented'
]) | |
| 111 self.xml.get_widget("OnlineCount").set_text("Online: %d" % self.countOnl
ine) | |
| 112 online.thaw() | |
| 113 offline.thaw() | |
| 114 | |
| 115 | |
| 116 | |
| 117 def colorhash(name): | |
| 118 h = hash(name) | |
| 119 l = [0x5555ff, | |
| 120 0x55aa55, | |
| 121 0x55aaff, | |
| 122 0xff5555, | |
| 123 0xff55ff, | |
| 124 0xffaa55] | |
| 125 index = l[h % len(l)] | |
| 126 return '%06.x' % (abs(hash(name)) & index) | |
| 127 | |
| 128 | |
| 129 def _msgDisplay(output, name, text, color, isEmote): | |
| 130 text = string.replace(text, '\n', '\n\t') | |
| 131 ins = output.insert | |
| 132 ins(None, color, None, "[ %s ] " % time.strftime("%H:%M:%S")) | |
| 133 if isEmote: | |
| 134 ins(None, color, None, "* %s " % name) | |
| 135 ins(None, None, None, "%s\n" % text) | |
| 136 else: | |
| 137 ins(None, color, None, "<%s> " % name) | |
| 138 ins(None, None, None, "%s\n" % text) | |
| 139 | |
| 140 | |
| 141 class Conversation(InputOutputWindow): | |
| 142 """GUI representation of a conversation. | |
| 143 """ | |
| 144 def __init__(self, person): | |
| 145 InputOutputWindow.__init__(self, | |
| 146 "ConversationWidget", | |
| 147 "ConversationMessageEntry", | |
| 148 "ConversationOutput") | |
| 149 self.person = person | |
| 150 alloc_color = self.output.get_colormap().alloc | |
| 151 self.personColor = alloc_color("#%s" % colorhash(person.name)) | |
| 152 self.myColor = alloc_color("#0000ff") | |
| 153 print "allocated my color %s and person color %s" % ( | |
| 154 self.myColor, self.personColor) | |
| 155 | |
| 156 def getTitle(self): | |
| 157 return "Conversation - %s (%s)" % (self.person.name, | |
| 158 self.person.account.accountName) | |
| 159 | |
| 160 # Chat Interface Implementation | |
| 161 | |
| 162 def sendText(self, text): | |
| 163 metadata = None | |
| 164 if text[:4] == "/me ": | |
| 165 text = text[4:] | |
| 166 metadata = {"style": "emote"} | |
| 167 self.person.sendMessage(text, metadata).addCallback(self._cbTextSent, te
xt, metadata) | |
| 168 | |
| 169 def showMessage(self, text, metadata=None): | |
| 170 _msgDisplay(self.output, self.person.name, text, self.personColor, | |
| 171 (metadata and metadata.get("style", None) == "emote")) | |
| 172 | |
| 173 # Internal | |
| 174 | |
| 175 def _cbTextSent(self, result, text, metadata=None): | |
| 176 _msgDisplay(self.output, self.person.account.client.name, | |
| 177 text, self.myColor, | |
| 178 (metadata and metadata.get("style", None) == "emote")) | |
| 179 | |
| 180 class GroupConversation(InputOutputWindow): | |
| 181 def __init__(self, group): | |
| 182 InputOutputWindow.__init__(self, | |
| 183 "GroupChatBox", | |
| 184 "GroupInput", | |
| 185 "GroupOutput") | |
| 186 self.group = group | |
| 187 self.members = [] | |
| 188 self.membersHidden = 0 | |
| 189 self._colorcache = {} | |
| 190 alloc_color = self.output.get_colormap().alloc | |
| 191 self.myColor = alloc_color("#0000ff") | |
| 192 # TODO: we shouldn't be getting group conversations randomly without | |
| 193 # names, but irc autojoin appears broken. | |
| 194 self.xml.get_widget("NickLabel").set_text( | |
| 195 getattr(self.group.account.client,"name","(no name)")) | |
| 196 participantList = self.xml.get_widget("ParticipantList") | |
| 197 groupBox = self.xml.get_widget("GroupActionsBox") | |
| 198 for method in group.getGroupCommands(): | |
| 199 b = gtk.GtkButton(method.__name__) | |
| 200 b.connect("clicked", self._doGroupAction, method) | |
| 201 groupBox.add(b) | |
| 202 self.setTopic("No Topic Yet", "No Topic Author Yet") | |
| 203 | |
| 204 # GTK Event Handlers | |
| 205 | |
| 206 def on_HideButton_clicked(self, b): | |
| 207 self.membersHidden = not self.membersHidden | |
| 208 self.xml.get_widget("GroupHPaned").set_position(self.membersHidden and -
1 or 20000) | |
| 209 | |
| 210 def on_LeaveButton_clicked(self, b): | |
| 211 self.win.destroy() | |
| 212 self.group.leave() | |
| 213 | |
| 214 def on_AddContactButton_clicked(self, b): | |
| 215 lw = self.xml.get_widget("ParticipantList") | |
| 216 | |
| 217 if lw.selection: | |
| 218 self.group.client.addContact(self.members[lw.selection[0]]) | |
| 219 | |
| 220 def on_TopicEntry_focus_out_event(self, w, e): | |
| 221 self.xml.get_widget("TopicEntry").set_text(self._topic) | |
| 222 | |
| 223 def on_TopicEntry_activate(self, e): | |
| 224 print "ACTIVATING TOPIC!!" | |
| 225 self.group.setTopic(e.get_text()) | |
| 226 # self.xml.get_widget("TopicEntry").set_editable(0) | |
| 227 self.xml.get_widget("GroupInput").grab_focus() | |
| 228 | |
| 229 def on_ParticipantList_unselect_row(self, w, row, column, event): | |
| 230 print 'row unselected' | |
| 231 personBox = self.xml.get_widget("PersonActionsBox") | |
| 232 for child in personBox.children(): | |
| 233 personBox.remove(child) | |
| 234 | |
| 235 def on_ParticipantList_select_row(self, w, row, column, event): | |
| 236 self.selectedPerson = self.group.account.client.getPerson(self.members[r
ow]) | |
| 237 print 'selected', self.selectedPerson | |
| 238 personBox = self.xml.get_widget("PersonActionsBox") | |
| 239 personFrame = self.xml.get_widget("PersonFrame") | |
| 240 # clear out old buttons | |
| 241 for child in personBox.children(): | |
| 242 personBox.remove(child) | |
| 243 personFrame.set_label("Person: %s" % self.selectedPerson.name) | |
| 244 for method in self.selectedPerson.getPersonCommands(): | |
| 245 b = gtk.GtkButton(method.__name__) | |
| 246 b.connect("clicked", self._doPersonAction, method) | |
| 247 personBox.add(b) | |
| 248 b.show() | |
| 249 for method in self.group.getTargetCommands(self.selectedPerson): | |
| 250 b = gtk.GtkButton(method.__name__) | |
| 251 b.connect("clicked", self._doTargetAction, method, | |
| 252 self.selectedPerson) | |
| 253 personBox.add(b) | |
| 254 b.show() | |
| 255 | |
| 256 def _doGroupAction(self, evt, method): | |
| 257 method() | |
| 258 | |
| 259 def _doPersonAction(self, evt, method): | |
| 260 method() | |
| 261 | |
| 262 def _doTargetAction(self, evt, method, person): | |
| 263 method(person) | |
| 264 | |
| 265 def hidden(self, w): | |
| 266 InputOutputWindow.hidden(self, w) | |
| 267 self.group.leave() | |
| 268 | |
| 269 def getTitle(self): | |
| 270 return "Group Conversation - " + self.group.name | |
| 271 | |
| 272 # Chat Interface Implementation | |
| 273 def sendText(self, text): | |
| 274 metadata = None | |
| 275 if text[:4] == "/me ": | |
| 276 text = text[4:] | |
| 277 metadata = {"style": "emote"} | |
| 278 self.group.sendGroupMessage(text, metadata).addCallback(self._cbTextSent
, text, metadata=metadata) | |
| 279 | |
| 280 def showGroupMessage(self, sender, text, metadata=None): | |
| 281 _msgDisplay(self.output, sender, text, self._cacheColorHash(sender), | |
| 282 (metadata and metadata.get("style", None) == "emote")) | |
| 283 | |
| 284 | |
| 285 def setGroupMembers(self, members): | |
| 286 self.members = members | |
| 287 self.refreshMemberList() | |
| 288 | |
| 289 def setTopic(self, topic, author): | |
| 290 self._topic = topic | |
| 291 self._topicAuthor = author | |
| 292 self.xml.get_widget("TopicEntry").set_text(topic) | |
| 293 self.xml.get_widget("AuthorLabel").set_text(author) | |
| 294 | |
| 295 def memberJoined(self, member): | |
| 296 self.members.append(member) | |
| 297 self.output.insert_defaults("> %s joined <\n" % member) | |
| 298 self.refreshMemberList() | |
| 299 | |
| 300 def memberChangedNick(self, member, newnick): | |
| 301 self.members.remove(member) | |
| 302 self.members.append(newnick) | |
| 303 self.output.insert_defaults("> %s becomes %s <\n" % (member, newnick)) | |
| 304 self.refreshMemberList() | |
| 305 | |
| 306 def memberLeft(self, member): | |
| 307 self.members.remove(member) | |
| 308 self.output.insert_defaults("> %s left <\n" % member) | |
| 309 self.refreshMemberList() | |
| 310 | |
| 311 | |
| 312 | |
| 313 # Tab Completion | |
| 314 | |
| 315 def tabComplete(self, word): | |
| 316 """InputOutputWindow calls me when tab is pressed.""" | |
| 317 if not word: | |
| 318 return [] | |
| 319 potentialMatches = [] | |
| 320 for nick in self.members: | |
| 321 if string.lower(nick[:len(word)]) == string.lower(word): | |
| 322 potentialMatches.append(nick + ": ") #colon is a nick-specific t
hing | |
| 323 return potentialMatches | |
| 324 | |
| 325 # Internal | |
| 326 | |
| 327 def _cbTextSent(self, result, text, metadata=None): | |
| 328 _msgDisplay(self.output, self.group.account.client.name, | |
| 329 text, self.myColor, | |
| 330 (metadata and metadata.get("style", None) == "emote")) | |
| 331 | |
| 332 def refreshMemberList(self): | |
| 333 pl = self.xml.get_widget("ParticipantList") | |
| 334 pl.freeze() | |
| 335 pl.clear() | |
| 336 self.members.sort(lambda x,y: cmp(string.lower(x), string.lower(y))) | |
| 337 for member in self.members: | |
| 338 pl.append([member]) | |
| 339 pl.thaw() | |
| 340 | |
| 341 def _cacheColorHash(self, name): | |
| 342 if self._colorcache.has_key(name): | |
| 343 return self._colorcache[name] | |
| 344 else: | |
| 345 alloc_color = self.output.get_colormap().alloc | |
| 346 c = alloc_color('#%s' % colorhash(name)) | |
| 347 self._colorcache[name] = c | |
| 348 return c | |
| 349 | |
| 350 | |
| 351 | |
| 352 | |
| 353 class GtkChatClientUI: | |
| 354 def __init__(self,xml): | |
| 355 self.conversations = {} # cache of all direct windows | |
| 356 self.groupConversations = {} # cache of all group windows | |
| 357 self.personCache = {} # keys are (name, account) | |
| 358 self.groupCache = {} # cache of all groups | |
| 359 self.theContactsList = ContactsList(self,xml) | |
| 360 self.onlineAccounts = [] # list of message sources currently online | |
| 361 | |
| 362 def registerAccountClient(self,account): | |
| 363 print 'registering account client' | |
| 364 self.onlineAccounts.append(account) | |
| 365 self.getContactsList().registerAccountClient(account) | |
| 366 | |
| 367 def unregisterAccountClient(self,account): | |
| 368 print 'unregistering account client' | |
| 369 self.onlineAccounts.remove(account) | |
| 370 self.getContactsList().unregisterAccountClient(account) | |
| 371 | |
| 372 def contactsListClose(self, w, evt): | |
| 373 return gtk.TRUE | |
| 374 | |
| 375 def getContactsList(self): | |
| 376 return self.theContactsList | |
| 377 | |
| 378 def getConversation(self, person): | |
| 379 conv = self.conversations.get(person) | |
| 380 if not conv: | |
| 381 conv = Conversation(person) | |
| 382 self.conversations[person] = conv | |
| 383 conv.show() | |
| 384 return conv | |
| 385 | |
| 386 def getGroupConversation(self, group, stayHidden=0): | |
| 387 conv = self.groupConversations.get(group) | |
| 388 if not conv: | |
| 389 conv = GroupConversation(group) | |
| 390 self.groupConversations[group] = conv | |
| 391 if not stayHidden: | |
| 392 conv.show() | |
| 393 else: | |
| 394 conv.hide() | |
| 395 return conv | |
| 396 | |
| 397 # ??? Why doesn't this inherit the basechat.ChatUI implementation? | |
| 398 | |
| 399 def getPerson(self, name, client): | |
| 400 account = client.account | |
| 401 p = self.personCache.get((name, account)) | |
| 402 if not p: | |
| 403 p = account.getPerson(name) | |
| 404 self.personCache[name, account] = p | |
| 405 return p | |
| 406 | |
| 407 def getGroup(self, name, client): | |
| 408 account = client.account | |
| 409 g = self.groupCache.get((name, account)) | |
| 410 if g is None: | |
| 411 g = account.getGroup(name) | |
| 412 self.groupCache[name, account] = g | |
| 413 return g | |
| OLD | NEW |