window.py 27.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# -*- coding:utf-8 -*-
#
# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
#
# This file is part of Poezio.
#
# Poezio is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# Poezio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Poezio.  If not, see <http://www.gnu.org/licenses/>.

19 20
from gettext import (bindtextdomain, textdomain, bind_textdomain_codeset,
                     gettext as _)
21
from os.path import isfile
22 23 24 25

import locale
locale.setlocale(locale.LC_ALL, '')

26
import curses
27
from config import config
28

29 30
from threading import Lock

31
from message import Line
32
import theme
33

34 35
g_lock = Lock()

36 37 38 39 40 41
class Win(object):
    def __init__(self, height, width, y, x, parent_win):
        self._resize(height, width, y, x, parent_win)

    def _resize(self, height, width, y, x, parent_win):
        self.height, self.width, self.x, self.y = height, width, x, y
42 43 44
        try:
            self.win = parent_win.subwin(height, width, y, x)
        except:
45 46 47 48
            # When resizing in a too little height (less than 3 lines)
            # We don't need to resize the window, since this size
            # just makes no sense
            # Just don't crash when this happens.
49 50 51
            # (°>       also, a penguin
            # //\
            # V_/_
52
            pass
53
        self.win.idlok(1)
54
        self.win.leaveok(1)
55

56 57
    def refresh(self):
        self.win.noutrefresh()
58 59

class UserList(Win):
60
    def __init__(self, height, width, y, x, parent_win, visible):
61
        Win.__init__(self, height, width, y, x, parent_win)
62
        self.visible = visible
63 64 65 66
        self.color_role = {'moderator': theme.COLOR_USER_MODERATOR,
                           'participant':theme.COLOR_USER_PARTICIPANT,
                           'visitor':theme.COLOR_USER_VISITOR,
                           'none':theme.COLOR_USER_NONE
67
                           }
68 69 70 71 72
        self.color_show = {'xa':theme.COLOR_STATUS_XA,
                           'None':theme.COLOR_STATUS_NONE,
                           'dnd':theme.COLOR_STATUS_DND,
                           'away':theme.COLOR_STATUS_AWAY,
                           'chat':theme.COLOR_STATUS_CHAT
73
                           }
74 75

    def refresh(self, users):
76
        def compare_user(a, b):
77 78 79
            try:
                arole = self.color_role[a.role]
            except KeyError:
80
                arole = 5
81 82 83
            try:
                brole = self.color_role[b.role]
            except KeyError:
84
                brole = 5
85
            if arole == brole:
86 87 88
                if a.nick.lower() < b.nick.lower():
                    return -1
                return 1
89
            return arole - brole
90 91
        if not self.visible:
            return
92
        g_lock.acquire()
93
        self.win.erase()
94
        y = 0
95
        for user in sorted(users, compare_user):
96
            try:
97
                role_col = self.color_role[user.role]
98
            except KeyError:
99
                role_col = theme.COLOR_USER_NONE
100 101
            try:
                show_col = self.color_show[user.show]
102
            except KeyError:
103
                show_col = theme.COLOR_STATUS_NONE
104
            self.win.attron(curses.color_pair(show_col))
105
            self.win.addnstr(y, 0, theme.STATUS_CHAR, 1)
106 107
            self.win.attroff(curses.color_pair(show_col))
            self.win.attron(curses.color_pair(role_col))
108 109 110 111
            try:
                self.win.addnstr(y, 1, user.nick, self.width-1)
            except:
                pass
112
            self.win.attroff(curses.color_pair(role_col))
113
            y += 1
114 115
            if y == self.height:
                break
116
        self.win.refresh()
117
        g_lock.release()
118

119 120 121 122
    def resize(self, height, width, y, x, stdscr, visible):
        self.visible = visible
        if not visible:
            return
123 124
        self._resize(height, width, y, x, stdscr)

125
class Topic(Win):
126 127
    def __init__(self, height, width, y, x, parent_win, visible):
        self.visible = visible
128 129
        Win.__init__(self, height, width, y, x, parent_win)

130
    def resize(self, height, width, y, x, stdscr, visible):
131 132
        self._resize(height, width, y, x, stdscr)

133
    def refresh(self, topic, jid=None):
134 135
        if not self.visible:
            return
136
        g_lock.acquire()
137
        self.win.erase()
138 139
        if not jid:
            try:
140
                self.win.addstr(0, 0, topic, curses.color_pair(theme.COLOR_TOPIC_BAR))
141 142
                while True:
                    try:
143
                        self.win.addch(' ', curses.color_pair(theme.COLOR_TOPIC_BAR))
144 145
                    except:
                        break
146 147 148 149 150 151 152
            except:
                pass
        elif jid:
            room = jid.split('/')[0]
            nick = '/'.join(jid.split('/')[1:])
            topic = _('%(nick)s from room %(room)s' % {'nick': nick, 'room':room})
            self.win.addnstr(0, 0, topic.encode('utf-8') + " "*(self.width-len(topic)), self.width-1
153
                             , curses.color_pair(theme.COLOR_PRIVATE_ROOM_BAR))
154

155
        self.win.refresh()
156
        g_lock.release()
157

158 159 160 161 162 163 164 165
class RoomInfo(Win):
    def __init__(self, height, width, y, x, parent_win, visible):
        self.visible = visible
        Win.__init__(self, height, width, y, x, parent_win)

    def resize(self, height, width, y, x, stdscr, visible):
        self._resize(height, width, y, x, stdscr)

166 167 168 169 170 171 172
    def print_scroll_position(self, current_room):
        """
        Print, link in Weechat, a -PLUS(n)- where n
        is the number of available lines to scroll
        down
        """
        if current_room.pos > 0:
173
            self.win.addstr(' -PLUS(%s)-' % current_room.pos, curses.color_pair(theme.COLOR_SCROLLABLE_NUMBER) | curses.A_BOLD)
174

175 176 177 178 179
    def refresh(self, rooms, current):
        if not self.visible:
            return
        def compare_room(a, b):
            return a.nb - b.nb
180
        g_lock.acquire()
181
        self.win.erase()
182
        self.win.addnstr(0, 0, "[", self.width
183
                         ,curses.color_pair(theme.COLOR_INFORMATION_BAR))
184 185
        sorted_rooms = sorted(rooms, compare_room)
        for room in sorted_rooms:
186
            color = room.color_state
187
            try:
188
                self.win.addstr("%s" % str(room.nb), curses.color_pair(color))
189
                self.win.addstr(u"|".encode('utf-8'), curses.color_pair(theme.COLOR_INFORMATION_BAR))
190 191 192 193
            except:             # end of line
                break
        (y, x) = self.win.getyx()
        try:
194
            self.win.addstr(y, x-1, '] '+ current.name, curses.color_pair(theme.COLOR_INFORMATION_BAR))
195
        except:
196
            try:
197
                self.win.addstr(y, x-1, '] '+ current.name.encode('utf-8'), curses.color_pair(theme.COLOR_INFORMATION_BAR))
198 199
            except:
                pass
200
            pass
201
        self.print_scroll_position(current)
202 203
        while True:
            try:
204
                self.win.addstr(' ', curses.color_pair(theme.COLOR_INFORMATION_BAR))
205 206
            except:
                break
207
        self.win.refresh()
208
        g_lock.release()
209

210
class TextWin(Win):
211
    """
212
    Just keep ONE single window for the text area and rewrite EVERYTHING
213
    on each change. (thanks weechat :o)
214
    """
215 216
    def __init__(self, height, width, y, x, parent_win, visible):
        self.visible = visible
217 218 219 220 221
        self.height = height
        self.width = width
        self.y = y
        self.x = x
        self.parent_win = parent_win
222
        Win.__init__(self, height, width, y, x, parent_win)
223 224
        self.win.scrollok(1)

225
    def build_lines_from_messages(self, messages):
226
        """
227 228
        From all the existing messages in the window, create the that will
        be displayed on the screen
229
        """
230 231
        lines = []
        for message in messages:
232 233 234
            if message == None:  # line separator
                lines.append(None)
                continue
235
            txt = message.txt
236 237
            if not txt:
                continue
238
            offset = 11         # length of the time
239 240 241 242 243 244
            if message.nickname and len(message.nickname) >= 30:
                nick = message.nickname[:30]+u'…'
            else:
                nick = message.nickname
            if nick:
                offset += len(nick) + 2 # + nick + spaces length
245
            first = True
246
            this_line_was_broken_by_space = False
247 248 249 250
            while txt != '':
                if txt[:self.width-offset].find('\n') != -1:
                    limit = txt[:self.width-offset].find('\n')
                else:
251
                    # break between words if possible
252 253
                    if len(txt) >= self.width-offset:
                        limit = txt[:self.width-offset].rfind(' ')
254 255 256 257 258 259 260
                        this_line_was_broken_by_space = True
                        if limit <= 0:
                            limit = self.width-offset-1
                            this_line_was_broken_by_space = False
                    else:
                        limit = self.width-offset-1
                        this_line_was_broken_by_space = False
261
                color = message.user.color if message.user else None
262 263
                if not first:
                    nick = None
264 265 266
                    time = None
                else:
                    time = message.time
267
                l = Line(nick, color,
268
                         time,
269 270 271
                         txt[:limit], message.color,
                         offset)
                lines.append(l)
272 273 274 275
                if this_line_was_broken_by_space:
                    txt = txt[limit+1:] # jump the space at the start of the line
                else:
                    txt = txt[limit:]
276 277
                if txt.startswith('\n'):
                    txt = txt[1:]
278
                first = False
279
        return lines
280
        return lines[-len(messages):] # return only the needed number of lines
281

282
    def refresh(self, room):
283
        """
284 285
        Build the Line objects from the messages, and then write
        them in the text area
286
        """
287 288
        if not self.visible:
            return
289
        g_lock.acquire()
290
        self.win.erase()
291
        lines = self.build_lines_from_messages(room.messages)
292 293 294 295
        if room.pos + self.height > len(lines):
            room.pos = len(lines) - self.height
            if room.pos < 0:
                room.pos = 0
296
        if room.pos != 0:
297
            lines = lines[-self.height-room.pos:-room.pos]
298
        else:
299
            lines = lines[-self.height:]
300 301 302
        y = 0
        for line in lines:
            self.win.move(y, 0)
303 304 305 306
            if line == None:
                self.write_line_separator()
                y += 1
                continue
307 308 309 310 311 312
            if line.time is not None:
                self.write_time(line.time)
            if line.nickname is not None:
                self.write_nickname(line.nickname.encode('utf-8'), line.nickname_color)
            self.write_text(y, line.text_offset, line.text, line.text_color)
            y += 1
313
        self.win.refresh()
314
        g_lock.release()
315

316 317 318
    def write_line_separator(self):
        """
        """
319
        self.win.attron(curses.color_pair(theme.COLOR_NEW_TEXT_SEPARATOR))
320
        self.win.addstr(' -'*(self.width/2))
321
        self.win.attroff(curses.color_pair(theme.COLOR_NEW_TEXT_SEPARATOR))
322

323
    def write_text(self, y, x, txt, color):
324
        """
325
        write the text of a line.
326 327
        """
        txt = txt.encode('utf-8')
328 329
        if color:
            self.win.attron(curses.color_pair(color))
330 331 332 333
        try:
            self.win.addstr(y, x, txt)
        except:  # bug 1665
            pass
334 335
        if color:
            self.win.attroff(curses.color_pair(color))
336

337
    def write_nickname(self, nickname, color):
338 339 340 341
        """
        Write the nickname, using the user's color
        and return the number of written characters
        """
342 343
        if color:
            self.win.attron(curses.color_pair(color))
344
        self.win.addstr(nickname)
345 346
        if color:
            self.win.attroff(curses.color_pair(color))
347
        self.win.addnstr("> ", 2)
348

349
    def write_time(self, time):
350 351 352
        """
        Write the date on the yth line of the window
        """
353 354 355 356 357 358 359 360 361
        self.win.attron(curses.color_pair(theme.COLOR_TIME_BRACKETS))
        self.win.addnstr('[', 1)
        self.win.attroff(curses.color_pair(theme.COLOR_TIME_BRACKETS))

        self.win.attron(curses.color_pair(theme.COLOR_TIME_NUMBERS))
        self.win.addnstr(time.strftime("%H"), 2)
        self.win.attroff(curses.color_pair(theme.COLOR_TIME_NUMBERS))

        self.win.attron(curses.color_pair(theme.COLOR_TIME_SEPARATOR))
362
        self.win.addnstr(':', 1)
363 364 365 366 367 368 369
        self.win.attroff(curses.color_pair(theme.COLOR_TIME_SEPARATOR))

        self.win.attron(curses.color_pair(theme.COLOR_TIME_NUMBERS))
        self.win.addnstr(time.strftime("%M"), 2)
        self.win.attroff(curses.color_pair(theme.COLOR_TIME_NUMBERS))

        self.win.attron(curses.color_pair(theme.COLOR_TIME_SEPARATOR))
370
        self.win.addnstr(':', 1)
371 372 373 374 375 376 377 378 379 380 381
        self.win.attroff(curses.color_pair(theme.COLOR_TIME_SEPARATOR))

        self.win.attron(curses.color_pair(theme.COLOR_TIME_NUMBERS))
        self.win.addnstr(time.strftime('%S'), 2)
        self.win.attroff(curses.color_pair(theme.COLOR_TIME_NUMBERS))

        self.win.attron(curses.color_pair(theme.COLOR_TIME_BRACKETS))
        self.win.addstr(']')
        self.win.attroff(curses.color_pair(theme.COLOR_TIME_BRACKETS))

        self.win.addstr(' ')
382

383
    def resize(self, height, width, y, x, stdscr, visible):
384
        self.visible = visible
385 386
        self._resize(height, width, y, x, stdscr)

387 388
class Input(Win):
    """
389
    The line where text is entered
390
    """
391
    def __init__(self, height, width, y, x, stdscr, visible):
392
        Win.__init__(self, height, width, y, x, stdscr)
393
        curses.curs_set(1)
394
        self.win.leaveok(0)
395
        self.visible = visible
396 397
        self.history = []
        self.text = u''
398
        self.clipboard = None
399 400 401
        self.pos = 0            # cursor position
        self.line_pos = 0 # position (in self.text) of
        # the first char to display on the screen
402
        self.histo_pos = 0
403
        self.hit_list = [] # current possible completion (normal)
404 405
        self.last_completion = None # Contains the last nickname completed,
                                    # if last key was a tab
406

407 408 409 410
    def resize(self, height, width, y, x, stdscr, visible):
        self.visible = visible
        if not visible:
            return
411
        self._resize(height, width, y, x, stdscr)
412
        self.win.leaveok(0)
413
        self.win.clear()
414
        self.win.addnstr(0, 0, self.text.encode('utf-8'), self.width-1)
415

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
    def jump_word_left(self):
        """
        Move the cursor one word to the left
        """
        if not len(self.text) or self.pos == 0:
            return
        previous_space = self.text[:self.pos+self.line_pos].rfind(' ')
        if previous_space == -1:
            previous_space = 0
        diff = self.pos+self.line_pos-previous_space
        for i in xrange(diff):
            self.key_left()

    def jump_word_right(self):
        """
        Move the cursor one word to the right
        """
        if len(self.text) == self.pos+self.line_pos or not len(self.text):
            return
        next_space = self.text.find(' ', self.pos+self.line_pos+1)
        if next_space == -1:
            next_space = len(self.text)
        diff = next_space - (self.pos+self.line_pos)
        for i in xrange(diff):
            self.key_right()

    def delete_word(self):
        """
        Delete the word just before the cursor
        """
        if not len(self.text) or self.pos == 0:
            return
        previous_space = self.text[:self.pos+self.line_pos].rfind(' ')
        if previous_space == -1:
            previous_space = 0
        diff = self.pos+self.line_pos-previous_space
        for i in xrange(diff):
            self.key_backspace(False)
        self.rewrite_text()

    def delete_end_of_line(self):
        """
        Cut the text from cursor to the end of line
        """
        if len(self.text) == self.pos+self.line_pos:
            return              # nothing to cut
        self.clipboard = self.text[self.pos+self.line_pos:]
        self.text = self.text[:self.pos+self.line_pos]
        self.key_end()

    def delete_begining_of_line(self):
        """
        Cut the text from cursor to the begining of line
        """
        if self.pos+self.line_pos == 0:
            return
        self.clipboard = self.text[:self.pos+self.line_pos]
        self.text = self.text[self.pos+self.line_pos:]
        self.key_home()

    def paste_clipboard(self):
        """
        Insert what is in the clipboard at the cursor position
        """
        if not self.clipboard or len(self.clipboard) == 0:
            return
        for letter in self.clipboard:
483
            self.do_command(letter.encode('utf-8'))
484

485
    def key_dc(self):
486 487 488
        """
        delete char just after the cursor
        """
489
        self.reset_completion()
490 491 492 493
        if self.pos + self.line_pos == len(self.text):
            return              # end of line, nothing to delete
        self.text = self.text[:self.pos+self.line_pos]+self.text[self.pos+self.line_pos+1:]
        self.rewrite_text()
494

495
    def key_up(self):
496 497 498
        """
        Get the previous line in the history
        """
499 500
        if not len(self.history):
            return
501
        self.win.erase()
502 503 504
        if self.histo_pos >= 0:
            self.histo_pos -= 1
        self.text = self.history[self.histo_pos+1]
505
        self.key_end()
506 507

    def key_down(self):
508 509 510
        """
        Get the next line in the history
        """
511 512
        if not len(self.history):
            return
513
        self.reset_completion()
514
        self.win.erase()
515 516 517
        if self.histo_pos < len(self.history)-1:
            self.histo_pos += 1
            self.text = self.history[self.histo_pos]
518
            self.key_end()
519 520 521 522
        else:
            self.histo_pos = len(self.history)-1
            self.text = u''
            self.pos = 0
523 524
            self.line_pos = 0
            self.rewrite_text()
525 526

    def key_home(self):
527 528 529
        """
        Go to the begining of line
        """
530
        self.reset_completion()
531
        self.pos = 0
532 533
        self.line_pos = 0
        self.rewrite_text()
534

535 536 537 538 539 540
    def key_end(self, reset=False):
        """
        Go to the end of line
        """
        if reset:
            self.reset_completion()
541
        if len(self.text) >= self.width-1:
542 543
            self.pos = self.width-1
            self.line_pos = len(self.text)-self.pos
544
        else:
545 546 547
            self.pos = len(self.text)
            self.line_pos = 0
        self.rewrite_text()
548 549

    def key_left(self):
550 551 552
        """
        Move the cursor one char to the left
        """
553
        self.reset_completion()
554
        (y, x) = self.win.getyx()
555 556 557
        if self.pos == self.width-1 and self.line_pos > 0:
            self.line_pos -= 1
        elif self.pos >= 1:
558
            self.pos -= 1
559
        self.rewrite_text()
560 561

    def key_right(self):
562 563 564
        """
        Move the cursor one char to the right
        """
565
        self.reset_completion()
566
        (y, x) = self.win.getyx()
567 568 569 570
        if self.pos == self.width-1:
            if self.line_pos + self.width-1 < len(self.text):
                self.line_pos += 1
        elif self.pos < len(self.text):
571
            self.pos += 1
572
        self.rewrite_text()
573

574
    def key_backspace(self, reset=True):
575 576 577
        """
        Delete the char just before the cursor
        """
578
        self.reset_completion()
579
        (y, x) = self.win.getyx()
580 581 582 583
        if self.pos == 0:
            return
        self.text = self.text[:self.pos+self.line_pos-1]+self.text[self.pos+self.line_pos:]
        self.key_left()
584 585
        if reset:
            self.rewrite_text()
586

587
    def auto_completion(self, user_list):
588 589 590
        """
        Complete the nickname
        """
591
        if self.pos+self.line_pos != len(self.text): # or len(self.text) == 0
592
            return # we don't complete if cursor is not at the end of line
593
        completion_type = config.get('completion', 'normal')
594
        if completion_type == 'shell' and self.text != '':
595 596 597 598 599
            self.shell_completion(user_list)
        else:
            self.normal_completion(user_list)

    def reset_completion(self):
600 601 602
        """
        Reset the completion list (called on ALL keys except tab)
        """
603
        self.hit_list = []
604
        self.last_completion = None
605 606

    def normal_completion(self, user_list):
607 608 609
        """
        Normal completion
        """
610 611
        if " " not in self.text.strip() or\
                self.last_completion and self.text == self.last_completion+config.get('after_completion', ',')+" ":
612
            after = config.get('after_completion', ',')+" "
613 614 615
            #if " " in self.text.strip() and (not self.last_completion or ' ' in self.last_completion):
        else:
            after = " " # don't put the "," if it's not the begining of the sentence
616
        (y, x) = self.win.getyx()
617
        if not self.last_completion:
618
            # begin is the begining of the nick we want to complete
619
            if self.text.strip() != '':
620 621 622
                begin = self.text.split()[-1].encode('utf-8').lower()
            else:
                begin = ''
623 624
            hit_list = []       # list of matching nicks
            for user in user_list:
625 626
                if user.lower().startswith(begin):
                    hit_list.append(user)
627 628 629
            if len(hit_list) == 0:
                return
            self.hit_list = hit_list
630
            end = len(begin.decode('utf-8'))
631
        else:
632
            begin = self.text[-len(after)-len(self.last_completion):-len(after)]
633
            self.hit_list.append(self.hit_list.pop(0)) # rotate list
634
            end = len(begin.decode('utf-8')) + len(after)
635 636
        self.text = self.text[:-end]
        nick = self.hit_list[0] # take the first hit
637
        self.last_completion = nick.decode('utf-8')
638
        self.text += nick.decode('utf-8') +after
639
        self.key_end(False)
640 641

    def shell_completion(self, user_list):
642 643 644
        """
        Shell-like completion
        """
645
        if " " in self.text.strip():
646 647 648
            after = " " # don't put the "," if it's not the begining of the sentence
        else:
            after = config.get('after_completion', ',')+" "
649
        (y, x) = self.win.getyx()
650 651 652 653
        if self.text != '':
            begin = self.text.split()[-1].encode('utf-8').lower()
        else:
            begin = ''
654 655
        hit_list = []       # list of matching nicks
        for user in user_list:
656 657
            if user.lower().startswith(begin):
                hit_list.append(user)
658 659 660 661
        if len(hit_list) == 0:
            return
        end = False
        nick = ''
662 663
        last_completion = self.last_completion
        self.last_completion = True
664 665
        if len(hit_list) == 1:
            nick = hit_list[0] + after
666 667
            self.last_completion = False
        elif last_completion:
668 669 670
            for n in hit_list:
                if begin.lower() == n.lower():
                    nick = n+after # user DO want this completion (tabbed twice on it)
671
                    self.last_completion = False
672 673 674 675 676 677 678 679 680
        if nick == '':
            while not end and len(nick) < len(hit_list[0]):
                nick = hit_list[0][:len(nick)+1]
                for hit in hit_list:
                    if not hit.lower().startswith(nick.lower()):
                        end = True
                        break
            if end:
                nick = nick[:-1]
681 682
        x -= len(begin.decode('utf-8'))
        self.text = self.text[:-len(begin.decode('utf-8'))]
683
        self.text += nick.decode('utf-8')
684
        self.key_end(False)
685

686
    def do_command(self, key, reset=True):
687
        self.reset_completion()
688
        self.text = self.text[:self.pos+self.line_pos]+key.decode('utf-8')+self.text[self.pos+self.line_pos:]
689
        (y, x) = self.win.getyx()
690
        if x == self.width-1:
691 692 693
            self.line_pos += 1
        else:
            self.pos += 1
694 695
        if reset:
            self.rewrite_text()
696 697

    def get_text(self):
698 699 700
        """
        Clear the input and return the text entered so far
        """
701
        txt = self.text
702 703
        self.text = u''
        self.pos = 0
704
        self.line_pos = 0
705 706 707
        if len(txt) != 0:
            self.history.append(txt)
            self.histo_pos = len(self.history)-1
708
        return txt.encode('utf-8')
709

710 711 712 713 714
    def rewrite_text(self):
        """
        Refresh the line onscreen, from the pos and pos_line
        """
        self.clear_text()
715 716 717 718
        try:                    # FIXME: this try should NOT be needed
            self.win.addstr(self.text[self.line_pos:self.line_pos+self.width-1].encode('utf-8'))
        except:
            pass
719 720 721
        self.win.move(0, self.pos)
        self.refresh()

722
    def refresh(self):
723 724
        if not self.visible:
            return
725
        self.win.refresh()
726 727

    def clear_text(self):
728
        self.win.erase()
729 730 731 732

class Window(object):
    """
    The whole "screen" that can be seen at once in the terminal.
733
    It contains an userlist, an input zone, a topic zone and a chat zone
734 735 736 737 738 739 740 741 742 743 744
    """
    def __init__(self, stdscr):
        """
        name is the name of the Tab, and it's also
        the JID of the chatroom.
        A particular tab is the "Info" tab which has no
        name (None). This info tab should be unique.
        The stdscr should be passed to know the size of the
        terminal
        """
        self.size = (self.height, self.width) = stdscr.getmaxyx()
745 746 747 748 749
        if self.height < 10 or self.width < 60:
            visible = False
        else:
            visible = True
        if visible:
750
            stdscr.attron(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
751
            stdscr.vline(1, 9*(self.width/10), curses.ACS_VLINE, self.height-2)
752
            stdscr.attroff(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
753
        self.user_win = UserList(self.height-3, (self.width/10)-1, 1, 9*(self.width/10)+1, stdscr, visible)
754 755
        self.topic_win = Topic(1, self.width, 0, 0, stdscr, visible)
        self.info_win = RoomInfo(1, self.width, self.height-2, 0, stdscr, visible)
756 757
        self.text_win = TextWin(self.height-3, (self.width/10)*9, 1, 0, stdscr, visible)
        self.input = Input(1, self.width, self.height-1, 0, stdscr, visible)
758 759 760

    def resize(self, stdscr):
        """
761
        Resize the whole window. i.e. all its sub-windows
762 763
        """
        self.size = (self.height, self.width) = stdscr.getmaxyx()
764
        if self.height < 10 or self.width < 50:
765 766 767 768
            visible = False
        else:
            visible = True
        if visible:
769
            stdscr.attron(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
770
            stdscr.vline(1, 9*(self.width/10), curses.ACS_VLINE, self.height-2)
771
            stdscr.attroff(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
772
        text_width = (self.width/10)*9;
773 774
        self.topic_win.resize(1, self.width, 0, 0, stdscr, visible)
        self.info_win.resize(1, self.width, self.height-2, 0, stdscr, visible)
775 776
        self.text_win.resize(self.height-3, text_width, 1, 0, stdscr, visible)
        self.user_win.resize(self.height-3, self.width-text_width-1, 1, text_width+1, stdscr, visible)
777
        self.input.resize(1, self.width, self.height-1, 0, stdscr, visible)
778

779 780 781 782 783
    def refresh(self, rooms):
        """
        'room' is the current one
        """
        room = rooms[0]         # get current room
784
        self.text_win.refresh(room)
785
        self.user_win.refresh(room.users)
786
        self.topic_win.refresh(room.topic, room.jid)
787
        self.info_win.refresh(rooms, room)
788 789 790 791 792
        self.input.refresh()

    def do_command(self, key):
        self.input.do_command(key)
        self.input.refresh()