Mercurial > libervia-backend
annotate frontends/sortilege_old/sortilege @ 272:1d2e0dfe7114
bridge: core & frontend sides of bridge are now generated
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 24 Jan 2011 22:05:04 +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() |