comparison frontends/src/wix/main_window.py @ 223:86d249b6d9b7

Files reorganisation
author Goffi <goffi@goffi.org>
date Wed, 29 Dec 2010 01:06:29 +0100
parents frontends/wix/main_window.py@782319a64ac6
children fd9b7834d98a
comparison
equal deleted inserted replaced
222:3198bfd66daa 223:86d249b6d9b7
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 wix: a SAT frontend
6 Copyright (C) 2009, 2010 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 from quick_frontend.quick_chat_list import QuickChatList
24 from quick_frontend.quick_app import QuickApp
25 from quick_frontend.quick_contact_management import QuickContactManagement
26 import wx
27 from contact_list import ContactList
28 from chat import Chat
29 from param import Param
30 from xmlui import XMLUI
31 from gateways import GatewaysManager
32 from profile import Profile
33 from profile_manager import ProfileManager
34 import gobject
35 import os.path
36 import pdb
37 from tools.jid import JID
38 from logging import debug, info, warning, error
39 import constants
40
41 idCONNECT,\
42 idDISCONNECT,\
43 idEXIT,\
44 idABOUT,\
45 idPARAM,\
46 idADD_CONTACT,\
47 idREMOVE_CONTACT,\
48 idSHOW_PROFILE,\
49 idJOIN_ROOM,\
50 idFIND_GATEWAYS = range(10)
51
52 class ChatList(QuickChatList):
53 """This class manage the list of chat windows"""
54
55 def __init__(self, host):
56 QuickChatList.__init__(self, host)
57
58 def createChat(self, target):
59 return Chat(target, self.host)
60
61 class MainWindow(wx.Frame, QuickApp):
62 """main app window"""
63
64 def __init__(self):
65 wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500))
66 self.CM = QuickContactManagement() #FIXME: not the best place
67
68 #sizer
69 self.sizer = wx.BoxSizer(wx.VERTICAL)
70 self.SetSizer(self.sizer)
71
72 #Frame elements
73 self.contactList = ContactList(self, self)
74 self.contactList.registerActivatedCB(self.onContactActivated)
75 self.contactList.Hide()
76 self.sizer.Add(self.contactList, 1, flag=wx.EXPAND)
77
78 self.chat_wins=ChatList(self)
79 self.CreateStatusBar()
80
81 #ToolBar
82 self.tools=self.CreateToolBar()
83 self.statusBox = wx.ComboBox(self.tools, -1, "Online", choices=[status[1] for status in const_STATUS],
84 style=wx.CB_DROPDOWN | wx.CB_READONLY)
85 self.tools.AddControl(self.statusBox)
86 self.tools.AddSeparator()
87 self.statusTxt=wx.TextCtrl(self.tools, -1, style = wx.TE_PROCESS_ENTER)
88 self.tools.AddControl(self.statusTxt)
89 self.Bind(wx.EVT_COMBOBOX, self.onStatusChange, self.statusBox)
90 self.Bind(wx.EVT_TEXT_ENTER, self.onStatusChange, self.statusTxt)
91 self.tools.Disable()
92
93 #tray icon
94 ticon = wx.Icon(IMAGE_DIR+'/crystal/tray_icon.xpm', wx.BITMAP_TYPE_XPM)
95 self.tray_icon = wx.TaskBarIcon()
96 self.tray_icon.SetIcon(ticon, _("Wix jabber client"))
97 wx.EVT_TASKBAR_LEFT_UP(self.tray_icon, self.onTrayClick)
98
99
100 #events
101 self.Bind(wx.EVT_CLOSE, self.onClose, self)
102
103
104 QuickApp.__init__(self)
105
106 #menus
107 self.createMenus()
108 for i in range(self.menuBar.GetMenuCount()):
109 self.menuBar.EnableTop(i, False)
110
111 #profile panel
112 self.profile_pan = ProfileManager(self)
113 self.sizer.Add(self.profile_pan, 1, flag=wx.EXPAND)
114
115 self.postInit()
116
117 self.Show()
118
119 def plug_profile(self, profile_key='@DEFAULT@'):
120 """Hide profile panel then plug profile"""
121 debug (_('plugin profile %s' % profile_key))
122 self.profile_pan.Hide()
123 self.contactList.Show()
124 self.sizer.Layout()
125 for i in range(self.menuBar.GetMenuCount()):
126 self.menuBar.EnableTop(i, True)
127 super(MainWindow, self).plug_profile(profile_key)
128
129 def createMenus(self):
130 info(_("Creating menus"))
131 connectMenu = wx.Menu()
132 connectMenu.Append(idCONNECT, _("&Connect CTRL-c"),_(" Connect to the server"))
133 connectMenu.Append(idDISCONNECT, _("&Disconnect CTRL-d"),_(" Disconnect from the server"))
134 connectMenu.Append(idPARAM,_("&Parameters"),_(" Configure the program"))
135 connectMenu.AppendSeparator()
136 connectMenu.Append(idABOUT,_("A&bout"),_(" About %s") % APP_NAME)
137 connectMenu.Append(idEXIT,_("E&xit"),_(" Terminate the program"))
138 contactMenu = wx.Menu()
139 contactMenu.Append(idADD_CONTACT, _("&Add contact"),_(" Add a contact to your list"))
140 contactMenu.Append(idREMOVE_CONTACT, _("&Remove contact"),_(" Remove the selected contact from your list"))
141 contactMenu.AppendSeparator()
142 contactMenu.Append(idSHOW_PROFILE, _("&Show profile"), _(" Show contact's profile"))
143 communicationMenu = wx.Menu()
144 communicationMenu.Append(idJOIN_ROOM, _("&Join Room"),_(" Join a Multi-User Chat room"))
145 communicationMenu.Append(idFIND_GATEWAYS, _("&Find Gateways"),_(" Find gateways to legacy IM"))
146 self.menuBar = wx.MenuBar()
147 self.menuBar.Append(connectMenu,_("&General"))
148 self.menuBar.Append(contactMenu,_("&Contacts"))
149 self.menuBar.Append(communicationMenu,_("&Communication"))
150 self.SetMenuBar(self.menuBar)
151
152 #additionals menus
153 #FIXME: do this in a more generic way (in quickapp)
154 add_menus = self.bridge.getMenus()
155 for menu in add_menus:
156 category,item,type = menu
157 assert(type=="NORMAL") #TODO: manage other types
158 menu_idx = self.menuBar.FindMenu(category)
159 current_menu = None
160 if menu_idx == wx.NOT_FOUND:
161 #the menu is new, we create it
162 current_menu = wx.Menu()
163 self.menuBar.Append(current_menu, category)
164 else:
165 current_menu = self.menuBar.GetMenu(menu_idx)
166 assert(current_menu != None)
167 item_id = wx.NewId()
168 help_string = self.bridge.getMenuHelp(category, item, type)
169 current_menu.Append(item_id, item, help = help_string)
170 #now we register the event
171 def event_answer(e):
172 id = self.bridge.callMenu(category, item, type, self.profile)
173 self.current_action_ids.add(id)
174 wx.EVT_MENU(self, item_id, event_answer)
175
176
177 #events
178 wx.EVT_MENU(self, idCONNECT, self.onConnectRequest)
179 wx.EVT_MENU(self, idDISCONNECT, self.onDisconnectRequest)
180 wx.EVT_MENU(self, idPARAM, self.onParam)
181 wx.EVT_MENU(self, idABOUT, self.onAbout)
182 wx.EVT_MENU(self, idEXIT, self.onExit)
183 wx.EVT_MENU(self, idADD_CONTACT, self.onAddContact)
184 wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact)
185 wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile)
186 wx.EVT_MENU(self, idJOIN_ROOM, self.onJoinRoom)
187 wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways)
188
189
190 def newMessage(self, from_jid, msg, type, to_jid, profile):
191 QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
192
193 def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile):
194 super(MainWindow, self).roomJoined(room_id, room_service, room_nicks, user_nick, profile)
195
196 def showAlert(self, message):
197 # TODO: place this in a separate class
198 popup=wx.PopupWindow(self)
199 ### following code come from wxpython demo
200 popup.SetBackgroundColour("CADET BLUE")
201 st = wx.StaticText(popup, -1, message, pos=(10,10))
202 sz = st.GetBestSize()
203 popup.SetSize( (sz.width+20, sz.height+20) )
204 x=(wx.DisplaySize()[0]-popup.GetSize()[0])/2
205 popup.SetPosition((x,0))
206 popup.Show()
207 wx.CallLater(5000,popup.Destroy)
208
209 def showDialog(self, message, title="", type="info", answer_cb = None, answer_data = None):
210 if type == 'info':
211 flags = wx.OK | wx.ICON_INFORMATION
212 elif type == 'error':
213 flags = wx.OK | wx.ICON_ERROR
214 elif type == 'yes/no':
215 flags = wx.YES_NO | wx.ICON_QUESTION
216 else:
217 flags = wx.OK | wx.ICON_INFORMATION
218 error(_('unmanaged dialog type: %s'), type)
219 dlg = wx.MessageDialog(self, message, title, flags)
220 answer = dlg.ShowModal()
221 dlg.Destroy()
222 if answer_cb:
223 data = [answer_data] if answer_data else []
224 answer_cb(True if (answer == wx.ID_YES or answer == wx.ID_OK) else False, *data)
225
226 def setStatusOnline(self, online=True):
227 """enable/disable controls, must be called when local user online status change"""
228 if online:
229 self.SetStatusText(msgONLINE)
230 self.tools.Enable()
231 else:
232 self.SetStatusText(msgOFFLINE)
233 self.tools.Disable()
234 return
235
236 def askConfirmation(self, type, id, data):
237 #TODO: refactor this in QuickApp
238 debug (_("Confirmation asked"))
239 answer_data={}
240 if type == "FILE_TRANSFERT":
241 debug (_("File transfert confirmation asked"))
242 dlg = wx.MessageDialog(self, _("The contact %(jid)s wants to send you the file %(filename)s\nDo you accept ?") % {'jid':data["from"], 'filename':data["filename"]},
243 _('File Request'),
244 wx.YES_NO | wx.ICON_QUESTION
245 )
246 answer=dlg.ShowModal()
247 if answer==wx.ID_YES:
248 filename = wx.FileSelector(_("Where do you want to save the file ?"), flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
249 if filename:
250 answer_data["dest_path"] = filename
251 self.bridge.confirmationAnswer(id, True, answer_data)
252 self.waitProgress(id, _("File Transfer"), _("Copying %s") % os.path.basename(filename))
253 else:
254 answer = wx.ID_NO
255 if answer==wx.ID_NO:
256 self.bridge.confirmationAnswer(id, False, answer_data)
257
258 dlg.Destroy()
259
260 elif type == "YES/NO":
261 debug (_("Yes/No confirmation asked"))
262 dlg = wx.MessageDialog(self, data["message"],
263 _('Confirmation'),
264 wx.YES_NO | wx.ICON_QUESTION
265 )
266 answer=dlg.ShowModal()
267 if answer==wx.ID_YES:
268 self.bridge.confirmationAnswer(id, True, {})
269 if answer==wx.ID_NO:
270 self.bridge.confirmationAnswer(id, False, {})
271
272 dlg.Destroy()
273
274 def actionResult(self, type, id, data):
275 debug (_("actionResult: type = [%(type)s] id = [%(id)s] data = [%(data)s]") % {'type':type, 'id':id, 'data':data})
276 if not id in self.current_action_ids:
277 debug (_('unknown id, ignoring'))
278 return
279 if type == "SUPPRESS":
280 self.current_action_ids.remove(id)
281 elif type == "SUCCESS":
282 self.current_action_ids.remove(id)
283 dlg = wx.MessageDialog(self, data["message"],
284 _('Success'),
285 wx.OK | wx.ICON_INFORMATION
286 )
287 dlg.ShowModal()
288 dlg.Destroy()
289 elif type == "ERROR":
290 self.current_action_ids.remove(id)
291 dlg = wx.MessageDialog(self, data["message"],
292 _('Error'),
293 wx.OK | wx.ICON_ERROR
294 )
295 dlg.ShowModal()
296 dlg.Destroy()
297 elif type == "XMLUI":
298 self.current_action_ids.remove(id)
299 debug (_("XML user interface received"))
300 misc = {}
301 #FIXME FIXME FIXME: must clean all this crap !
302 title = _('Form')
303 if data['type'] == _('registration'):
304 title = _('Registration')
305 misc['target'] = data['target']
306 misc['action_back'] = self.bridge.gatewayRegister
307 XMLUI(self, title=title, xml_data = data['xml'], misc = misc)
308 elif type == "RESULT":
309 self.current_action_ids.remove(id)
310 if self.current_action_ids_cb.has_key(id):
311 callback = self.current_action_ids_cb[id]
312 del self.current_action_ids_cb[id]
313 callback(data)
314 elif type == "DICT_DICT":
315 self.current_action_ids.remove(id)
316 if self.current_action_ids_cb.has_key(id):
317 callback = self.current_action_ids_cb[id]
318 del self.current_action_ids_cb[id]
319 callback(data)
320 else:
321 error (_("FIXME FIXME FIXME: type [%s] not implemented") % type)
322 raise NotImplementedError
323
324
325
326 def progressCB(self, id, title, message):
327 data = self.bridge.getProgress(id)
328 if data:
329 if not self.pbar:
330 #first answer, we must construct the bar
331 self.pbar = wx.ProgressDialog(title, message, float(data['size']), None,
332 wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME)
333 self.pbar.finish_value = float(data['size'])
334
335 self.pbar.Update(int(data['position']))
336 elif self.pbar:
337 self.pbar.Update(self.pbar.finish_value)
338 return
339
340 wx.CallLater(10, self.progressCB, id, title, message)
341
342 def waitProgress (self, id, title, message):
343 self.pbar = None
344 wx.CallLater(10, self.progressCB, id, title, message)
345
346
347
348 ### events ###
349
350 def onContactActivated(self, jid):
351 debug (_("onContactActivated: %s"), jid)
352 if self.chat_wins[jid.short].IsShown():
353 self.chat_wins[jid.short].Hide()
354 else:
355 self.chat_wins[jid.short].Show()
356
357 def onConnectRequest(self, e):
358 self.bridge.connect(self.profile)
359
360 def onDisconnectRequest(self, e):
361 self.bridge.disconnect(self.profile)
362
363 def __updateStatus(self):
364 show = filter(lambda x:x[1] == self.statusBox.GetValue(), const_STATUS)[0][0]
365 status = self.statusTxt.GetValue()
366 self.bridge.setPresence(show=show, statuses={'default':status}, profile_key=self.profile) #FIXME: manage multilingual statuses
367
368 def onStatusChange(self, e):
369 debug(_("Status change request"))
370 self.__updateStatus()
371
372 def onParam(self, e):
373 debug(_("Param request"))
374 #xmlui = self.bridge.getParamsUI(self.profile)
375 #XMLUI(self, xml_data = xmlui)
376 param=Param(self)
377
378 def onAbout(self, e):
379 about = wx.AboutDialogInfo()
380 about.SetName(APP_NAME)
381 about.SetVersion (unicode(self.bridge.getVersion()))
382 about.SetCopyright(u"(C) 2009,2010 Jérôme Poisson aka Goffi")
383 about.SetDescription( _(u"%(name)s is a SàT (Salut à Toi) frontend\n"+
384 u"%(name)s is based on WxPython, and is the standard graphic interface of SàT") % {'name':APP_NAME})
385 about.SetWebSite(("http://www.goffi.org", "Goffi's non-hebdo (french)"))
386 about.SetDevelopers([ "Goffi (Jérôme Poisson)"])
387 try:
388 with open(LICENCE_PATH,"r") as licence:
389 about.SetLicence(''.join(licence.readlines()))
390 except:
391 pass
392
393 wx.AboutBox(about)
394
395 def onExit(self, e):
396 self.Close()
397
398 def onAddContact(self, e):
399 debug(_("Add contact request"))
400 dlg = wx.TextEntryDialog(
401 self, _('Please enter new contact JID'),
402 _('Adding a contact'), _('name@server.tld'))
403
404 if dlg.ShowModal() == wx.ID_OK:
405 jid=JID(dlg.GetValue())
406 if jid.is_valid():
407 self.bridge.addContact(jid.short, profile_key=self.profile)
408 else:
409 error (_("'%s' is an invalid JID !"), jid)
410 #TODO: notice the user
411
412 dlg.Destroy()
413
414 def onRemoveContact(self, e):
415 debug(_("Remove contact request"))
416 target = self.contactList.getSelection()
417 if not target:
418 dlg = wx.MessageDialog(self, _("You haven't selected any contact !"),
419 _('Error'),
420 wx.OK | wx.ICON_ERROR
421 )
422 dlg.ShowModal()
423 dlg.Destroy()
424 return
425
426 dlg = wx.MessageDialog(self, _("Are you sure you want to delete %s from your roster list ?") % target.short,
427 _('Contact suppression'),
428 wx.YES_NO | wx.ICON_QUESTION
429 )
430
431 if dlg.ShowModal() == wx.ID_YES:
432 info(_("Unsubscribing %s presence"), target.short)
433 self.bridge.delContact(target.short, profile_key=self.profile)
434
435 dlg.Destroy()
436
437 def onShowProfile(self, e):
438 debug(_("Show contact's profile request"))
439 target = self.contactList.getSelection()
440 if not target:
441 dlg = wx.MessageDialog(self, _("You haven't selected any contact !"),
442 _('Error'),
443 wx.OK | wx.ICON_ERROR
444 )
445 dlg.ShowModal()
446 dlg.Destroy()
447 return
448 id = self.bridge.getCard(target.short, profile_key=self.profile)
449 self.current_action_ids.add(id)
450 self.current_action_ids_cb[id] = self.onProfileReceived
451
452 def onProfileReceived(self, data):
453 """Called when a profile is received"""
454 debug (_('Profile received: [%s]') % data)
455 profile=Profile(self, data)
456
457 def onJoinRoom(self, e):
458 warning('FIXME: temporary menu, must be improved')
459 #TODO: a proper MUC room joining dialog with nickname etc
460 dlg = wx.TextEntryDialog(
461 self, _("Please enter MUC's JID"),
462 #_('Entering a MUC room'), 'test@conference.necton2.int')
463 _('Entering a MUC room'), 'room@muc_service.server.tld')
464 if dlg.ShowModal() == wx.ID_OK:
465 room_jid=JID(dlg.GetValue())
466 if room_jid.is_valid():
467 self.bridge.joinMUC(room_jid.domain, room_jid.node, self.profiles[self.profile]['whoami'].node, self.profile)
468 else:
469 error (_("'%s' is an invalid JID !"), room_jid)
470
471 def onFindGateways(self, e):
472 debug(_("Find Gateways request"))
473 id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile)
474 self.current_action_ids.add(id)
475 self.current_action_ids_cb[id] = self.onGatewaysFound
476
477 def onGatewaysFound(self, data):
478 """Called when SàT has found the server gateways"""
479 target = data['__private__']['target']
480 del data['__private__']
481 gatewayManager = GatewaysManager(self, data, server=target)
482
483 def onClose(self, e):
484 QuickApp.onExit(self)
485 info(_("Exiting..."))
486 for win in self.chat_wins:
487 self.chat_wins[win].Destroy()
488 e.Skip()
489
490 def onTrayClick(self, e):
491 debug(_("Tray Click"))
492 if self.IsShown():
493 self.Hide()
494 else:
495 self.Show()
496 self.Raise()
497 e.Skip()
498