comparison frontends/wix/main_window.py @ 0:c4bc297b82f0

sat: - first public release, initial commit
author goffi@necton2
date Sat, 29 Aug 2009 13:34:59 +0200
parents
children a06a151fc31f
comparison
equal deleted inserted replaced
-1:000000000000 0:c4bc297b82f0
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 wix: a SAT frontend
6 Copyright (C) 2009 Jérôme Poisson (goffi@goffi.org)
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21
22
23 import wx
24 from chat import Chat
25 from param import Param
26 import gobject
27 import os.path
28 import pdb
29 from tools.jid import JID
30 from logging import debug, info, error
31 from quick_frontend.quick_chat_list import QuickChatList
32 from quick_frontend.quick_contact_list import QuickContactList
33 from quick_frontend.quick_app import QuickApp
34
35
36 msgOFFLINE = "offline"
37 msgONLINE = "online"
38 idCONNECT = 1
39 idEXIT = 2
40 idPARAM = 3
41 idADD_CONTACT = 4
42 idREMOVE_CONTACT = 5
43 const_DEFAULT_GROUP = "Unclassed"
44 const_STATUS = {"Online":"",
45 "Want to discuss":"chat",
46 "AFK":"away",
47 "Do Not Disturb":"dnd",
48 "Away":"xa"}
49
50 class ChatList(QuickChatList):
51 """This class manage the list of chat windows"""
52
53 def __init__(self, host):
54 QuickChatList.__init__(self, host)
55
56 def createChat(self, name):
57 return Chat(name, self.host)
58
59
60
61 class ContactList(wx.TreeCtrl, QuickContactList):
62 """Customized control to manage contacts."""
63
64 def __init__(self, parent):
65 wx.TreeCtrl.__init__(self, parent, style = wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS)
66 QuickContactList.__init__(self)
67 self.jid_ids={}
68 self.groups={}
69 self.root=self.AddRoot("")
70 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.onActivated, self)
71
72 #icons
73 isz = (16,16)
74 il = wx.ImageList(isz[0], isz[1])
75 self.icon_online = il.Add(wx.ArtProvider_GetBitmap(wx.ART_TICK_MARK, wx.ART_OTHER, isz))
76 self.icon_unavailable = il.Add(wx.ArtProvider_GetBitmap(wx.ART_CROSS_MARK, wx.ART_OTHER, isz))
77 self.AssignImageList(il)
78
79 self.__addNode(const_DEFAULT_GROUP)
80
81 def __addNode(self, label):
82 """Add an item container"""
83 ret=self.AppendItem(self.root, label)
84 self.SetPyData(ret, "[node]")
85 self.SetItemBold(ret)
86 self.groups[label]=ret
87
88 def replace(self, jid, name="", show="", status="", group=""):
89 debug("status = %s show = %s",status, show)
90 if not self.jid_ids.has_key(jid):
91 self.add(jid, name, show, status, group)
92 else:
93 debug ("updating %s",jid)
94 self.__presentItem(jid, name, show, status, group)
95
96 def __presentItem(self, jid, name, show, status, group):
97 """Make a nice presentation of the contact in the list."""
98 id=self.jid_ids[jid]
99 label= "%s [%s] \n %s" % ((name or jid), (show or "online"), status)
100 self.SetItemText(id, label)
101
102 # icon
103 if not show or show=="chat":
104 self.SetItemImage(id, self.icon_online)
105 else:
106 self.SetItemImage(id, self.icon_unavailable)
107
108 #colour
109 if not show:
110 self.SetItemTextColour(id, wx.BLACK)
111 elif show=="chat":
112 self.SetItemTextColour(id, wx.GREEN)
113 elif show=="away":
114 self.SetItemTextColour(id, wx.BLUE)
115 else:
116 self.SetItemTextColour(id, wx.RED)
117
118 def add(self, jid, name="", show="", status="", group=""):
119 """add a contact to the list"""
120 debug ("adding %s",jid)
121 dest_group=group or const_DEFAULT_GROUP
122 if not self.groups.has_key(dest_group):
123 self.__addNode(dest_group)
124 self.jid_ids[jid]=self.AppendItem(self.groups[dest_group], "")
125 self.__presentItem(jid, name, show, status, group)
126 self.SetPyData(self.jid_ids[jid], "[contact]"+jid)
127 self.EnsureVisible(self.jid_ids[jid])
128 self.Refresh() #FIXME: Best way ?
129
130 def remove(self, jid):
131 """remove a contact from the list"""
132 debug ("removing %s",jid)
133 self.Delete(self.jid_ids[jid])
134 del self.jid_ids[jid]
135 self.Refresh() #FIXME: Best way ?
136
137 def onActivated(self, event):
138 """Called when a contact is clicked or activated with keyboard."""
139 if self.GetPyData(event.GetItem()).startswith("[contact]"):
140 self.onActivatedCB(self.GetPyData(event.GetItem())[9:])
141 else:
142 event.Skip()
143
144 def getSelection(self):
145 """Return the selected contact, or an empty string if there is not"""
146 data = self.GetPyData(self.GetSelection())
147 if not data or not data.startswith("[contact]"):
148 return ""
149 return JID(data[9:])
150
151 def registerActivatedCB(self, cb):
152 """Register a callback with manage contact activation."""
153 self.onActivatedCB=cb
154
155 class MainWindow(wx.Frame, QuickApp):
156 """main app window"""
157
158 def __init__(self):
159 wx.Frame.__init__(self,None, title="SAT Wix", size=(400,200))
160
161
162 #Frame elements
163 self.contactList = ContactList(self)
164 self.contactList.registerActivatedCB(self.onContactActivated)
165 self.chat_wins=ChatList(self)
166 self.CreateStatusBar()
167 self.SetStatusText(msgOFFLINE)
168 self.createMenus()
169
170 #ToolBar
171 self.tools=self.CreateToolBar()
172 self.statusBox = wx.ComboBox(self.tools, -1, "Online", choices=const_STATUS.keys(),
173 style=wx.CB_DROPDOWN | wx.CB_READONLY)
174 self.tools.AddControl(self.statusBox)
175 self.tools.AddSeparator()
176 self.statusTxt=wx.TextCtrl(self.tools, -1, style = wx.TE_PROCESS_ENTER)
177 self.tools.AddControl(self.statusTxt)
178 self.Bind(wx.EVT_COMBOBOX, self.onStatusChange, self.statusBox)
179 self.Bind(wx.EVT_TEXT_ENTER, self.onStatusChange, self.statusTxt)
180 self.tools.Disable()
181
182 #events
183 self.Bind(wx.EVT_CLOSE, self.onClose, self)
184
185 QuickApp.__init__(self)
186
187 self.Show()
188
189 def createMenus(self):
190 info("Creating menus")
191 connectMenu = wx.Menu()
192 connectMenu.Append(idCONNECT, "&Connect CTRL-c"," Connect to the server")
193 connectMenu.Append(idPARAM,"&Parameters"," Configure the program")
194 connectMenu.AppendSeparator()
195 connectMenu.Append(idEXIT,"E&xit"," Terminate the program")
196 contactMenu = wx.Menu()
197 contactMenu.Append(idADD_CONTACT, "&Add contact"," Add a contact to your list")
198 contactMenu.Append(idREMOVE_CONTACT, "&Remove contact"," Remove the selected contact from your list")
199 menuBar = wx.MenuBar()
200 menuBar.Append(connectMenu,"&General")
201 menuBar.Append(contactMenu,"&Contacts")
202 self.SetMenuBar(menuBar)
203
204 #events
205 wx.EVT_MENU(self, idCONNECT, self.onConnectRequest)
206 wx.EVT_MENU(self, idPARAM, self.onParam)
207 wx.EVT_MENU(self, idEXIT, self.onExit)
208 wx.EVT_MENU(self, idADD_CONTACT, self.onAddContact)
209 wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact)
210
211
212 def newMessage(self, from_jid, msg, type, to_jid):
213 QuickApp.newMessage(self, from_jid, msg, type, to_jid)
214
215 def showAlert(self, message):
216 # TODO: place this in a separate class
217 popup=wx.PopupWindow(self)
218 ### following code come from wxpython demo
219 popup.SetBackgroundColour("CADET BLUE")
220 st = wx.StaticText(popup, -1, message, pos=(10,10))
221 sz = st.GetBestSize()
222 popup.SetSize( (sz.width+20, sz.height+20) )
223 x=(wx.DisplaySize()[0]-popup.GetSize()[0])/2
224 popup.SetPosition((x,0))
225 popup.Show()
226 wx.CallLater(5000,popup.Destroy)
227
228 def showDialog(self, message, title="", type="info"):
229 if type == 'info':
230 flags = wx.OK | wx.ICON_INFORMATION
231 elif type == 'error':
232 flags = wx.OK | wx.ICON_ERROR
233 elif type == 'question':
234 flags = wx.OK | wx.ICON_QUESTION
235 else:
236 flags = wx.OK | wx.ICON_INFORMATION
237 dlg = wx.MessageDialog(self, message, title, flags)
238 answer = dlg.ShowModal()
239 dlg.Destroy()
240 return True if (answer == wx.ID_YES or answer == wx.ID_OK) else False
241
242 def setStatusOnline(self, online=True):
243 """enable/disable controls, must be called when local user online status change"""
244 if online:
245 self.SetStatusText(msgONLINE)
246 self.tools.Enable()
247 else:
248 self.SetStatusText(msgOFFLINE)
249 self.tools.Disable()
250 return
251
252
253 def presenceUpdate(self, jabber_id, type, show, status, priority):
254 QuickApp.presenceUpdate(self, jabber_id, type, show, status, priority)
255
256 def askConfirmation(self, type, id, data):
257 #TODO: refactor this in QuickApp
258 debug ("Confirmation asked")
259 answer_data={}
260 if type == "FILE_TRANSFERT":
261 debug ("File transfert confirmation asked")
262 dlg = wx.MessageDialog(self, "The contact %s wants to send you the file %s\nDo you accept ?" % (data["from"], data["filename"]),
263 'File Request',
264 wx.YES_NO | wx.ICON_QUESTION
265 )
266 answer=dlg.ShowModal()
267 if answer==wx.ID_YES:
268 filename = wx.FileSelector("Where do you want to save the file ?", flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
269 if filename:
270 answer_data["dest_path"] = filename
271 self.bridge.confirmationAnswer(id, True, answer_data)
272 self.waitProgress(id, "File Transfer", "Copying %s" % os.path.basename(filename))
273 else:
274 answer = wx.ID_NO
275 if answer==wx.ID_NO:
276 self.bridge.confirmationAnswer(id, False, answer_data)
277
278 dlg.Destroy()
279
280
281
282
283 def progressCB(self, id, title, message):
284 data = self.bridge.getProgress(id)
285 if data:
286 if not data['position']:
287 data['position'] = '0'
288 if not self.pbar:
289 #first answer, we must construct the bar
290 self.pbar = wx.ProgressDialog(title, message, int(data['size']), None,
291 wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME)
292 self.pbar.finish_value = int(data['size'])
293
294 self.pbar.Update(int(data['position']))
295 elif self.pbar:
296 self.pbar.Update(self.pbar.finish_value)
297 return
298
299 wx.CallLater(10, self.progressCB, id, title, message)
300
301 def waitProgress (self, id, title, message):
302 self.pbar = None
303 wx.CallLater(10, self.progressCB, id, title, message)
304
305
306
307 ### events ###
308
309 def onContactActivated(self, jid):
310 debug ("onContactActivated: %s", jid)
311 if self.chat_wins[jid].IsShown():
312 self.chat_wins[jid].Hide()
313 else:
314 self.chat_wins[jid].Show()
315
316 def onConnectRequest(self, e):
317 self.bridge.connect()
318
319 def __updateStatus(self):
320 show = const_STATUS[self.statusBox.GetValue()]
321 status = self.statusTxt.GetValue()
322 self.bridge.setPresence(show=show, status=status)
323
324 def onStatusChange(self, e):
325 debug("Status change request")
326 self.__updateStatus()
327
328 def onParam(self, e):
329 debug("Param request")
330 param=Param(self.bridge.setParam, self.bridge.getParam, self.bridge.getParams, self.bridge.getParamsCategories)
331
332 def onExit(self, e):
333 self.Close()
334
335 def onAddContact(self, e):
336 debug("Add contact request")
337 dlg = wx.TextEntryDialog(
338 self, 'Please enter new contact JID',
339 'Adding a contact', 'name@server.ext')
340
341 if dlg.ShowModal() == wx.ID_OK:
342 jid=JID(dlg.GetValue())
343 if jid.is_valid():
344 self.bridge.addContact(jid.short)
345 else:
346 error ("'%s' is an invalid JID !", jid)
347 #TODO: notice the user
348
349 dlg.Destroy()
350
351 def onRemoveContact(self, e):
352 debug("Remove contact request")
353 target = self.contactList.getSelection()
354 if not target:
355 dlg = wx.MessageDialog(self, "You haven't selected any contact !",
356 'Error',
357 wx.OK | wx.ICON_ERROR
358 )
359 dlg.ShowModal()
360 dlg.Destroy()
361 return
362
363 dlg = wx.MessageDialog(self, "Are you sure you want to delete %s from your roster list ?" % target.short,
364 'Contact suppression',
365 wx.YES_NO | wx.ICON_QUESTION
366 )
367
368 if dlg.ShowModal() == wx.ID_YES:
369 info("Unsubsribing %s presence", target.short)
370 self.bridge.delContact(target.short)
371
372 dlg.Destroy()
373
374 def onClose(self, e):
375 info("Exiting...")
376 e.Skip()
377