Mercurial > libervia-backend
annotate frontends/sortilege_old/sortilege @ 297:c5554e2939dd
plugin XEP 0277: author for in request + author, updated management for out request
- a workaround is now used to parse "nick" tag (Jappix behaviour)
- author and updated can now be used in data when sendind microblog. Is no author is given, user jid is used, if no updated is given, current timestamp is used
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 18 Feb 2011 22:32:02 +0100 |
parents | f551e44adb25 |
children | 2a072735e459 |
rev | line source |
---|---|
0 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 """ | |
5 sortilege: a SAT frontend | |
57 | 6 Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org) |
0 | 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 | |
70 | 23 from quick_frontend.quick_app import QuickApp |
24 from quick_frontend.quick_chat_list import QuickChatList | |
25 from quick_frontend.quick_contact_list import QuickContactList | |
26 from quick_frontend.quick_contact_management import QuickContactManagement | |
0 | 27 import curses |
28 import pdb | |
29 from window import Window | |
30 from editbox import EditBox | |
31 from statusbar import StatusBar | |
32 from chat import Chat | |
33 from tools.jid import JID | |
34 import logging | |
35 from logging import debug, info, error | |
36 import locale | |
37 import sys, os | |
38 import gobject | |
39 import time | |
40 from curses import ascii | |
41 import locale | |
42 from signal import signal, SIGWINCH | |
43 import fcntl | |
44 import struct | |
45 import termios | |
46 from boxsizer import BoxSizer | |
47 | |
52 | 48 |
0 | 49 ### logging configuration FIXME: put this elsewhere ### |
50 logging.basicConfig(level=logging.CRITICAL, #TODO: configure it top put messages in a log file | |
51 format='%(message)s') | |
52 ### | |
53 | |
54 const_APP_NAME = "Sortilège" | |
55 const_CONTACT_WIDTH = 30 | |
56 | |
57 def ttysize(): | |
58 """This function return term size. | |
59 Comes from Donn Cave from python list mailing list""" | |
60 buf = 'abcdefgh' | |
61 buf = fcntl.ioctl(0, termios.TIOCGWINSZ, buf) | |
62 row, col, rpx, cpx = struct.unpack('hhhh', buf) | |
63 return row, col | |
64 | |
65 def C(k): | |
66 """return the value of Ctrl+key""" | |
67 return ord(ascii.ctrl(k)) | |
68 | |
69 class ChatList(QuickChatList): | |
70 """This class manage the list of chat windows""" | |
71 | |
72 def __init__(self, host): | |
73 QuickChatList.__init__(self, host) | |
74 self.sizer=host.sizer | |
75 | |
76 def createChat(self, name): | |
77 chat = Chat(name, self.host) | |
78 self.sizer.appendColum(0,chat) | |
79 self.sizer.update() | |
80 return chat | |
81 | |
82 class ContactList(Window, QuickContactList): | |
83 | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
84 def __init__(self, host, CM): |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
85 QuickContactList.__init__(self, CM) |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
86 self.host = host |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
87 self.jid_list = [] |
0 | 88 self.__index=0 #indicate which contact is selected (ie: where we are) |
70 | 89 Window.__init__(self, stdscr, stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0, True, _("Contact List"), code=code) |
0 | 90 |
91 def resize(self, height, width, y, x): | |
92 Window.resize(self, height, width, y, x) | |
93 self.update() | |
94 | |
95 def resizeAdapt(self): | |
96 """Adapt window size to stdscr size. | |
97 Must be called when stdscr is resized.""" | |
98 self.resize(stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0) | |
99 self.update() | |
100 | |
101 def registerEnterCB(self, CB): | |
102 self.__enterCB=CB | |
103 | |
52 | 104 def clear_contacts(self): |
105 """clear all the contact list""" | |
106 del self.jid_list[:] | |
107 self.__index = 0 | |
108 self.update() #FIXME: window is not updated correctly (contacts are still here until C-L) | |
109 | |
111
6c927140ba82
sortilege fix: it launches again now
Goffi <goffi@goffi.org>
parents:
70
diff
changeset
|
110 def replace(self, jid, groups=None): |
0 | 111 """add a contact to the list""" |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
112 name = self.CM.getAttr(jid,'name') |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
113 self.jid_list.append(jid.short) |
0 | 114 self.update() |
115 | |
116 def indexUp(self): | |
117 """increment select contact index""" | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
118 if self.__index < len(self.jid_list)-1: #we dont want to select a missing contact |
0 | 119 self.__index = self.__index + 1 |
120 self.update() | |
121 | |
122 def indexDown(self): | |
123 """decrement select contact index""" | |
124 if self.__index > 0: | |
125 self.__index = self.__index - 1 | |
126 self.update() | |
127 | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
128 def disconnect(self, jid): |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
129 """for now, we just remove the contact""" |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
130 self.remove(jid) |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
131 |
0 | 132 def remove(self, jid): |
133 """remove a contact from the list""" | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
134 self.jid_list.remove(jid.short) |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
135 if self.__index >= len(self.jid_list) and self.__index > 0: #if select index is out of border, we put it on the last contact |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
136 self.__index = len(self.jid_list)-1 |
0 | 137 self.update() |
138 | |
139 def update(self): | |
140 """redraw all the window""" | |
141 if self.isHidden(): | |
142 return | |
143 Window.update(self) | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
144 self.jid_list.sort() |
0 | 145 begin=0 if self.__index<self.rHeight else self.__index-self.rHeight+1 |
146 idx=0 | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
147 for item in self.jid_list[begin:self.rHeight+begin]: |
0 | 148 attr = curses.A_REVERSE if ( self.isActive() and (idx+begin) == self.__index ) else 0 |
149 centered = item.center(self.rWidth) ## it's nicer in the center :) | |
150 self.addYXStr(idx, 0, centered, attr) | |
151 idx = idx + 1 | |
152 | |
153 self.noutrefresh() | |
154 | |
155 def handleKey(self, k): | |
156 if k == curses.KEY_UP: | |
157 self.indexDown() | |
158 elif k == curses.KEY_DOWN: | |
159 self.indexUp() | |
160 elif k == ascii.NL: | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
161 if not self.jid_list: |
0 | 162 return |
163 try: | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
164 self.__enterCB(self.jid_list[self.__index]) |
0 | 165 except NameError: |
166 pass # TODO: thrown an error here | |
167 | |
168 class SortilegeApp(QuickApp): | |
169 | |
170 def __init__(self): | |
171 #debug(const_APP_NAME+" init...") | |
172 | |
173 ## unicode support ## | |
174 locale.setlocale(locale.LC_ALL, '') | |
175 global code | |
176 code = locale.getpreferredencoding() | |
177 self.code=code | |
178 | |
179 ## main loop setup ## | |
180 self.loop=gobject.MainLoop() | |
181 gobject.io_add_watch(0, gobject.IO_IN, self.loopCB) | |
182 | |
183 ## misc init stuff ## | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
184 self.CM = QuickContactManagement() |
0 | 185 self.listWins=[] |
186 self.chatParams={'timestamp':True, | |
187 'color':True, | |
188 'short_nick':False} | |
189 | |
190 def start(self): | |
191 curses.wrapper(self.start_curses) | |
192 | |
193 def start_curses(self, win): | |
194 global stdscr | |
195 stdscr = win | |
196 self.stdscr = stdscr | |
197 curses.raw() #we handle everything ourself | |
198 curses.curs_set(False) | |
199 stdscr.nodelay(True) | |
200 | |
201 ## colours ## | |
202 self.color(True) | |
203 | |
204 ## windows ## | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
205 self.contactList = ContactList(self, self.CM) |
0 | 206 self.editBar = EditBox(stdscr, "> ", self.code) |
207 self.editBar.activate(False) | |
208 self.statusBar = StatusBar(stdscr, self.code) | |
209 self.statusBar.hide(True) | |
210 self.addWin(self.contactList) | |
211 self.addWin(self.editBar) | |
212 self.addWin(self.statusBar) | |
213 self.sizer=BoxSizer(stdscr) | |
214 self.sizer.appendRow(self.contactList) | |
215 self.sizer.appendRow(self.statusBar) | |
216 self.sizer.appendRow(self.editBar) | |
217 self.currentChat=None | |
218 | |
219 self.contactList.registerEnterCB(self.onContactChoosed) | |
220 self.editBar.registerEnterCB(self.onTextEntered) | |
221 | |
222 self.chat_wins=ChatList(self) | |
223 | |
224 QuickApp.__init__(self) #XXX: yes it's an unusual place for the constructor of a parent class, but the init order is important | |
67
0e50dd3a234a
message sending bug fixes + sortilege update
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
225 self.plug_profile() |
0 | 226 |
227 signal (SIGWINCH, self.onResize) #we manage SIGWINCH ourselves, because the loop is not called otherwise | |
228 | |
229 #last but not least, we adapt windows' sizes | |
230 self.sizer.update() | |
231 self.editBar.replace_cur() | |
232 curses.doupdate() | |
233 | |
234 self.loop.run() | |
235 | |
236 def addWin(self, win): | |
237 self.listWins.append(win) | |
238 | |
239 def color(self, activate=True): | |
240 if activate: | |
70 | 241 debug (_("Activating colors")) |
0 | 242 curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK) |
243 curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) | |
244 else: | |
70 | 245 debug (_("Deactivating colors")) |
0 | 246 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) |
247 curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK) | |
248 | |
249 | |
250 def showChat(self, chat): | |
70 | 251 debug (_("show chat")) |
0 | 252 if self.currentChat: |
70 | 253 debug (_("hiding %s"), self.currentChat) |
0 | 254 self.chat_wins[self.currentChat].hide() |
255 self.currentChat=chat | |
70 | 256 debug (_("showing %s"), self.currentChat) |
0 | 257 self.chat_wins[self.currentChat].show() |
258 self.chat_wins[self.currentChat].update() | |
259 | |
260 | |
261 ### EVENTS ### | |
262 | |
263 def onContactChoosed(self, jid_txt): | |
264 """Called when a contact is selected in contact list.""" | |
265 jid=JID(jid_txt) | |
70 | 266 debug (_("contact choosed: %s"), jid) |
0 | 267 self.showChat(jid.short) |
268 self.statusBar.remove_item(jid.short) | |
269 if len(self.statusBar)==0: | |
270 self.statusBar.hide() | |
271 self.sizer.update() | |
272 | |
273 | |
274 def onTextEntered(self, text): | |
67
0e50dd3a234a
message sending bug fixes + sortilege update
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
275 jid=JID(self.profiles[self.profile]['whoami']) |
0e50dd3a234a
message sending bug fixes + sortilege update
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
276 self.bridge.sendMessage(self.currentChat, text, profile_key=self.profile) |
0 | 277 |
278 def showDialog(self, message, title, type="info"): | |
279 if type==question: | |
280 raise NotImplementedError | |
281 pass | |
282 | |
283 | |
67
0e50dd3a234a
message sending bug fixes + sortilege update
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
284 def presenceUpdate(self, jabber_id, show, priority, statuses, profile): |
0e50dd3a234a
message sending bug fixes + sortilege update
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
285 QuickApp.presenceUpdate(self, jabber_id, show, priority, statuses, profile) |
0 | 286 self.editBar.replace_cur() |
287 curses.doupdate() | |
288 | |
22
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
289 def askConfirmation(self, type, id, data): |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
290 #FIXME |
70 | 291 info (_("FIXME: askConfirmation not implemented")) |
22
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
292 |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
293 def actionResult(self, type, id, data): |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
294 #FIXME |
70 | 295 info (_("FIXME: actionResult not implemented")) |
22
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
296 |
67
0e50dd3a234a
message sending bug fixes + sortilege update
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
297 def newMessage(self, from_jid, msg, type, to_jid, profile): |
0e50dd3a234a
message sending bug fixes + sortilege update
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
298 QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile) |
0 | 299 sender=JID(from_jid) |
300 addr=JID(to_jid) | |
301 win = addr if sender.short == self.whoami.short else sender #FIXME: duplicate code with QuickApp | |
302 if (self.currentChat==None): | |
303 self.currentChat=win.short | |
304 self.showChat(win.short) | |
305 | |
306 # we show the window in the status bar | |
307 if not self.currentChat == win.short: | |
308 self.statusBar.add_item(win.short) | |
309 self.statusBar.show() | |
310 self.sizer.update() | |
311 self.statusBar.update() | |
312 | |
313 self.editBar.replace_cur() | |
314 curses.doupdate() | |
315 | |
316 def onResize(self, sig, stack): | |
317 """Called on SIGWINCH. | |
318 resize the screen and launch the loop""" | |
319 height, width = ttysize() | |
320 curses.resizeterm(height, width) | |
321 gobject.idle_add(self.callOnceLoop) | |
322 | |
323 def callOnceLoop(self): | |
324 """Call the loop and return false (for not being called again by gobject mainloop). | |
325 Usefull for calling loop when there is no input in stdin""" | |
326 self.loopCB() | |
327 return False | |
328 | |
329 def __key_handling(self, k): | |
330 """Handle key and transmit to active window.""" | |
331 | |
332 ### General keys, handled _everytime_ ### | |
333 if k == C('x'): | |
334 if os.getenv('TERM')=='screen': | |
335 os.system('screen -X remove') | |
336 else: | |
337 self.loop.quit() | |
338 | |
339 ## windows navigation | |
340 elif k == C('l') and not self.contactList.isHidden(): | |
341 """We go to the contact list""" | |
342 self.contactList.activate(not self.contactList.isActive()) | |
343 if self.currentChat: | |
344 self.editBar.activate(not self.contactList.isActive()) | |
345 | |
346 elif k == curses.KEY_F2: | |
347 self.contactList.hide(not self.contactList.isHidden()) | |
348 if self.contactList.isHidden(): | |
349 self.contactList.activate(False) #TODO: auto deactivation when hiding ? | |
350 if self.currentChat: | |
351 self.editBar.activate(True) | |
352 self.sizer.update() | |
353 | |
354 ## Chat Params ## | |
355 elif k == C('c'): | |
356 self.chatParams["color"] = not self.chatParams["color"] | |
357 self.color(self.chatParams["color"]) | |
358 elif k == C('t'): | |
359 self.chatParams["timestamp"] = not self.chatParams["timestamp"] | |
360 self.chat_wins[self.currentChat].update() | |
361 elif k == C('s'): | |
362 self.chatParams["short_nick"] = not self.chatParams["short_nick"] | |
363 self.chat_wins[self.currentChat].update() | |
364 | |
365 ## misc ## | |
366 elif k == curses.KEY_RESIZE: | |
367 stdscr.erase() | |
368 height, width = stdscr.getmaxyx() | |
369 if height<5 and width<35: | |
70 | 370 stdscr.addstr(_("Pleeeeasse, I can't even breathe !")) |
0 | 371 else: |
372 for win in self.listWins: | |
373 win.resizeAdapt() | |
374 for win in self.chat_wins.keys(): | |
375 self.chat_wins[win].resizeAdapt() | |
376 self.sizer.update() # FIXME: everything need to be managed by the sizer | |
377 | |
378 ## we now throw the key to win handlers ## | |
379 else: | |
380 for win in self.listWins: | |
381 if win.isActive(): | |
382 win.handleKey(k) | |
383 if self.currentChat: | |
384 self.chat_wins[self.currentChat].handleKey(k) | |
385 | |
386 def loopCB(self, source="", cb_condition=""): | |
387 """This callback is called by the main loop""" | |
388 #pressed = self.contactList.window.getch() | |
389 pressed = stdscr.getch() | |
390 if pressed != curses.ERR: | |
391 self.__key_handling(pressed) | |
392 self.editBar.replace_cur() | |
393 curses.doupdate() | |
394 | |
395 | |
396 return True | |
397 | |
398 | |
399 sat = SortilegeApp() | |
400 sat.start() |