Mercurial > libervia-backend
annotate frontends/sortilege/sortilege @ 57:a5b5fb5fc9fd
updated README and copyright note
- fixed images path in README
- added year 2010 in all copyright notes
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 10 Jan 2010 17:51:56 +1100 |
parents | 6455fb62ff83 |
children | 0e50dd3a234a |
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 | |
23 import curses | |
24 import pdb | |
25 from window import Window | |
26 from editbox import EditBox | |
27 from statusbar import StatusBar | |
28 from chat import Chat | |
29 from tools.jid import JID | |
30 import logging | |
31 from logging import debug, info, error | |
32 import locale | |
33 import sys, os | |
34 import gobject | |
35 import time | |
36 from curses import ascii | |
37 import locale | |
38 from signal import signal, SIGWINCH | |
39 import fcntl | |
40 import struct | |
41 import termios | |
42 from boxsizer import BoxSizer | |
43 from quick_frontend.quick_chat_list import QuickChatList | |
44 from quick_frontend.quick_contact_list import QuickContactList | |
45 from quick_frontend.quick_app import QuickApp | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
46 from quick_frontend.quick_contact_management import QuickContactManagement |
0 | 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) |
89 Window.__init__(self, stdscr, stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0, True, "Contact List", code=code) | |
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 | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
110 def replace(self, jid): |
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 | |
225 | |
226 signal (SIGWINCH, self.onResize) #we manage SIGWINCH ourselves, because the loop is not called otherwise | |
227 | |
228 #last but not least, we adapt windows' sizes | |
229 self.sizer.update() | |
230 self.editBar.replace_cur() | |
231 curses.doupdate() | |
232 | |
233 self.loop.run() | |
234 | |
235 def addWin(self, win): | |
236 self.listWins.append(win) | |
237 | |
238 def color(self, activate=True): | |
239 if activate: | |
240 debug ("Activation des couleurs") | |
241 curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK) | |
242 curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) | |
243 else: | |
244 debug ("Desactivation des couleurs") | |
245 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) | |
246 curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK) | |
247 | |
248 | |
249 def showChat(self, chat): | |
250 debug ("show chat") | |
251 if self.currentChat: | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
252 debug ("hiding %s", self.currentChat) |
0 | 253 self.chat_wins[self.currentChat].hide() |
254 self.currentChat=chat | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
255 debug ("showing %s", self.currentChat) |
0 | 256 self.chat_wins[self.currentChat].show() |
257 self.chat_wins[self.currentChat].update() | |
258 | |
259 | |
260 ### EVENTS ### | |
261 | |
262 def onContactChoosed(self, jid_txt): | |
263 """Called when a contact is selected in contact list.""" | |
264 jid=JID(jid_txt) | |
52 | 265 debug ("contact choosed: %s", jid) |
0 | 266 self.showChat(jid.short) |
267 self.statusBar.remove_item(jid.short) | |
268 if len(self.statusBar)==0: | |
269 self.statusBar.hide() | |
270 self.sizer.update() | |
271 | |
272 | |
273 def onTextEntered(self, text): | |
274 jid=JID(self.whoami) | |
275 self.bridge.sendMessage(self.currentChat, text) | |
276 | |
277 def showDialog(self, message, title, type="info"): | |
278 if type==question: | |
279 raise NotImplementedError | |
280 pass | |
281 | |
282 | |
51
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
283 def presenceUpdate(self, jabber_id, show, priority, statuses): |
8c67ea98ab91
frontend improved to take into account new SàT features
Goffi <goffi@goffi.org>
parents:
32
diff
changeset
|
284 QuickApp.presenceUpdate(self, jabber_id, show, priority, statuses) |
0 | 285 self.editBar.replace_cur() |
286 curses.doupdate() | |
287 | |
22
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
288 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
|
289 #FIXME |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
290 info ("FIXME: askConfirmation not implemented") |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
291 |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
292 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
|
293 #FIXME |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
294 info ("FIXME: actionResult not implemented") |
bb72c29f3432
added action cb mechanism for buttons. Tested with a temporary new user registration button.
Goffi <goffi@goffi.org>
parents:
0
diff
changeset
|
295 |
0 | 296 def newMessage(self, from_jid, msg, type, to_jid): |
297 QuickApp.newMessage(self, from_jid, msg, type, to_jid) | |
298 sender=JID(from_jid) | |
299 addr=JID(to_jid) | |
300 win = addr if sender.short == self.whoami.short else sender #FIXME: duplicate code with QuickApp | |
301 if (self.currentChat==None): | |
302 self.currentChat=win.short | |
303 self.showChat(win.short) | |
304 | |
305 # we show the window in the status bar | |
306 if not self.currentChat == win.short: | |
307 self.statusBar.add_item(win.short) | |
308 self.statusBar.show() | |
309 self.sizer.update() | |
310 self.statusBar.update() | |
311 | |
312 self.editBar.replace_cur() | |
313 curses.doupdate() | |
314 | |
315 def onResize(self, sig, stack): | |
316 """Called on SIGWINCH. | |
317 resize the screen and launch the loop""" | |
318 height, width = ttysize() | |
319 curses.resizeterm(height, width) | |
320 gobject.idle_add(self.callOnceLoop) | |
321 | |
322 def callOnceLoop(self): | |
323 """Call the loop and return false (for not being called again by gobject mainloop). | |
324 Usefull for calling loop when there is no input in stdin""" | |
325 self.loopCB() | |
326 return False | |
327 | |
328 def __key_handling(self, k): | |
329 """Handle key and transmit to active window.""" | |
330 | |
331 ### General keys, handled _everytime_ ### | |
332 if k == C('x'): | |
333 if os.getenv('TERM')=='screen': | |
334 os.system('screen -X remove') | |
335 else: | |
336 self.loop.quit() | |
337 | |
338 ## windows navigation | |
339 elif k == C('l') and not self.contactList.isHidden(): | |
340 """We go to the contact list""" | |
341 self.contactList.activate(not self.contactList.isActive()) | |
342 if self.currentChat: | |
343 self.editBar.activate(not self.contactList.isActive()) | |
344 | |
345 elif k == curses.KEY_F2: | |
346 self.contactList.hide(not self.contactList.isHidden()) | |
347 if self.contactList.isHidden(): | |
348 self.contactList.activate(False) #TODO: auto deactivation when hiding ? | |
349 if self.currentChat: | |
350 self.editBar.activate(True) | |
351 self.sizer.update() | |
352 | |
353 ## Chat Params ## | |
354 elif k == C('c'): | |
355 self.chatParams["color"] = not self.chatParams["color"] | |
356 self.color(self.chatParams["color"]) | |
357 elif k == C('t'): | |
358 self.chatParams["timestamp"] = not self.chatParams["timestamp"] | |
359 self.chat_wins[self.currentChat].update() | |
360 elif k == C('s'): | |
361 self.chatParams["short_nick"] = not self.chatParams["short_nick"] | |
362 self.chat_wins[self.currentChat].update() | |
363 | |
364 ## misc ## | |
365 elif k == curses.KEY_RESIZE: | |
366 stdscr.erase() | |
367 height, width = stdscr.getmaxyx() | |
368 if height<5 and width<35: | |
369 stdscr.addstr("Pleeeeasse, I can't even breathe !") | |
370 else: | |
371 for win in self.listWins: | |
372 win.resizeAdapt() | |
373 for win in self.chat_wins.keys(): | |
374 self.chat_wins[win].resizeAdapt() | |
375 self.sizer.update() # FIXME: everything need to be managed by the sizer | |
376 | |
377 ## we now throw the key to win handlers ## | |
378 else: | |
379 for win in self.listWins: | |
380 if win.isActive(): | |
381 win.handleKey(k) | |
382 if self.currentChat: | |
383 self.chat_wins[self.currentChat].handleKey(k) | |
384 | |
385 def loopCB(self, source="", cb_condition=""): | |
386 """This callback is called by the main loop""" | |
387 #pressed = self.contactList.window.getch() | |
388 pressed = stdscr.getch() | |
389 if pressed != curses.ERR: | |
390 self.__key_handling(pressed) | |
391 self.editBar.replace_cur() | |
392 curses.doupdate() | |
393 | |
394 | |
395 return True | |
396 | |
397 | |
398 sat = SortilegeApp() | |
399 sat.start() |