gui.py 41 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
from time import sleep

25
import sys
26
import os
27
import re
28
import curses
29 30
import webbrowser

31
from datetime import datetime
32 33

import common
34

35
from handler import Handler
36
from config import config
37
from window import Window
38 39
from user import User
from room import Room
40
from message import Message
41
from keyboard import read_char
42
from common import is_jid_the_same, jid_get_domain, is_jid
43

44 45
def doupdate():
    curses.doupdate()
46

47 48
class Gui(object):
    """
49
    User interface using ncurses
50
    """
51 52 53 54
    def __init__(self, stdscr=None, muc=None):
        self.init_curses(stdscr)
        self.stdscr = stdscr
        self.window = Window(stdscr)
55
        self.rooms = [Room('Info', '', self.window)]
56
        self.ignores = {}
57

58
        self.muc = muc
59

60
        self.commands = {
61
            'help': (self.command_help, u'\_o< KOIN KOIN KOIN'),
62
            'join': (self.command_join, _("Usage: /join [room_name][@server][/nick] [password]\nJoin: Join the specified room. You can specify a nickname after a slash (/). If no nickname is specified, you will use the default_nick in the configuration file. You can omit the room name: you will then join the room you\'re looking at (useful if you were kicked). You can also provide a room_name without specifying a server, the server of the room you're currently in will be used. You can also provide a password to join the room.\nExamples:\n/join room@server.tld\n/join room@server.tld/John\n/join room2\n/join /me_again\n/join\n/join room@server.tld/my_nick password\n/join / password")),
63 64 65 66 67 68 69 70
            'quit': (self.command_quit, _("Usage: /quit\nQuit: Just disconnect from the server and exit poezio.")),
            'exit': (self.command_quit, _("Usage: /exit\nExit: Just disconnect from the server and exit poezio.")),
            'next': (self.rotate_rooms_right, _("Usage: /next\nNext: Go to the next room.")),
            'n': (self.rotate_rooms_right, _("Usage: /n\nN: Go to the next room.")),
            'prev': (self.rotate_rooms_left, _("Usage: /prev\nPrev: Go to the previous room.")),
            'p': (self.rotate_rooms_left, _("Usage: /p\nP: Go to the previous room.")),
            'win': (self.command_win, _("Usage: /win <number>\nWin: Go to the specified room.")),
            'w': (self.command_win, _("Usage: /w <number>\nW: Go to the specified room.")),
71 72
            'ignore': (self.command_ignore, _("Usage: /ignore <nickname> \nIgnore: Ignore a specified nickname.")),
            'unignore': (self.command_unignore, _("Usage: /unignore <nickname>\nUnignore: Remove the specified nickname from the ignore list.")),
73 74 75 76 77 78 79
            'part': (self.command_part, _("Usage: /part [message]\n Part: disconnect from a room. You can specify an optional message.")),
            'show': (self.command_show, _("Usage: /show <availability> [status]\nShow: Change your availability and (optionaly) your status. The <availability> argument is one of \"avail, available, ok, here, chat, away, afk, dnd, busy, xa\" and the optional [message] argument will be your status message")),
            'away': (self.command_away, _("Usage: /away [message]\nAway: Sets your availability to away and (optional) sets your status message. This is equivalent to '/show away [message]'")),
            'busy': (self.command_busy, _("Usage: /busy [message]\nBusy: Sets your availability to busy and (optional) sets your status message. This is equivalent to '/show busy [message]'")),
            'avail': (self.command_avail, _("Usage: /avail [message]\nAvail: Sets your availability to available and (optional) sets your status message. This is equivalent to '/show available [message]'")),
            'available': (self.command_avail, _("Usage: /available [message]\nAvailable: Sets your availability to available and (optional) sets your status message. This is equivalent to '/show available [message]'")),
           'bookmark': (self.command_bookmark, _("Usage: /bookmark [roomname][/nick]\nBookmark: Bookmark the specified room (you will then auto-join it on each poezio start). This commands uses the same syntaxe as /join. Type /help join for syntaxe examples. Note that when typing \"/bookmark\" on its own, the room will be bookmarked with the nickname you\'re currently using in this room (instead of default_nick)")),
80
            'unquery': (self.command_unquery, _("Usage: /unquery\nClose the private conversation window")),
81 82 83
            'set': (self.command_set, _("Usage: /set <option> [value]\nSet: Sets the value to the option in your configuration file. You can, for example, change your default nickname by doing `/set default_nick toto` or your resource with `/set resource blabla`. You can also set an empty value (nothing) by providing no [value] after <option>.")),
            'kick': (self.command_kick, _("Usage: /kick <nick> [reason]\nKick: Kick the user with the specified nickname. You also can give an optional reason.")),
            'topic': (self.command_topic, _("Usage: /topic <subject> \nTopic: Change the subject of the room")),
84
            'link': (self.command_link, _("Usage: /link [option] [number]\nLink: Interact with a link in the conversation. Available options are 'open', 'copy'. Open just opens the link in the browser if it's http://, Copy just copy the link in the clipboard. An optional number can be provided, it indicates which link to interact with.")),
85
            'query': (self.command_query, _('Usage: /query <nick> [message]\nQuery: Open a private conversation with <nick>. This nick has to be present in the room you\'re currently in. If you specified a message after the nickname, it will immediately be sent to this user')),
86

87
            'nick': (self.command_nick, _("Usage: /nick <nickname> \nNick: Change your nickname in the current room"))
88 89
            }

90 91 92 93 94 95 96
        self.key_func = {
            "KEY_LEFT": self.window.input.key_left,
            "KEY_RIGHT": self.window.input.key_right,
            "KEY_UP": self.window.input.key_up,
            "KEY_END": self.window.input.key_end,
            "KEY_HOME": self.window.input.key_home,
            "KEY_DOWN": self.window.input.key_down,
97 98
            "KEY_PPAGE": self.scroll_page_up,
            "KEY_NPAGE": self.scroll_page_down,
99
            "KEY_DC": self.window.input.key_dc,
100
            "KEY_F(5)": self.rotate_rooms_left,
101
            "^P": self.rotate_rooms_left,
102
            "KEY_F(6)": self.rotate_rooms_right,
103
            "^N": self.rotate_rooms_right,
104 105
            "\t": self.completion,
            "^I": self.completion,
106
            "KEY_BTAB": self.last_words_completion,
107
            "KEY_RESIZE": self.resize_window,
108
            "KEY_BACKSPACE": self.window.input.key_backspace,
109
            '^?': self.window.input.key_backspace,
110 111 112 113 114 115 116 117 118 119 120 121
            '^J': self.execute,
            '\n': self.execute,
            '^D': self.window.input.key_dc,
            '^W': self.window.input.delete_word,
            '^K': self.window.input.delete_end_of_line,
            '^U': self.window.input.delete_begining_of_line,
            '^Y': self.window.input.paste_clipboard,
            '^A': self.window.input.key_home,
            '^E': self.window.input.key_end,
            'M-f': self.window.input.jump_word_right,
            '^X': self.go_to_important_room,
            'M-b': self.window.input.jump_word_left
122 123
            }

124 125 126 127 128
        self.handler = Handler()
        self.handler.connect('on-connected', self.on_connected)
        self.handler.connect('join-room', self.join_room)
        self.handler.connect('room-presence', self.room_presence)
        self.handler.connect('room-message', self.room_message)
129
        self.handler.connect('private-message', self.private_message)
130
        self.handler.connect('error-message', self.room_error)
131
        self.handler.connect('error', self.information)
132

133 134 135 136 137 138 139
    def resize_window(self):
        """
        Resize the whole screen
        """
        self.window.resize(self.stdscr)
        self.window.refresh(self.rooms)

140
    def main_loop(self, stdscr):
141 142 143
        """
        main loop waiting for the user to press a key
        """
144
        self.refresh_window()
145
        while True:
146
            doupdate()
147 148 149 150
            char=read_char(stdscr)
            # search for keyboard shortcut
            if char in self.key_func.keys():
                self.key_func[char]()
151
            else:
152 153
                if len(char.decode('utf-8')) > 1:
                    continue    # ignore non-handled keyboard shortcuts
154
                self.window.do_command(char)
155

156
    def current_room(self):
157 158 159
        """
        returns the current room, the one we are viewing
        """
160
        return self.rooms[0]
161 162

    def get_room_by_name(self, name):
163 164 165 166
        """
        returns the room that has this name
        """
        for room in self.rooms:
167
            if room.name.decode('utf-8') == name:
168 169
                return room
        return None
170

171
    def init_curses(self, stdscr):
172 173 174
        """
        ncurses initialization
        """
175 176
        curses.start_color()
        curses.noecho()
177
        curses.curs_set(0)
178
        curses.use_default_colors()
179
        stdscr.keypad(True)
180 181
        curses.init_pair(1, curses.COLOR_WHITE,
                         curses.COLOR_BLUE)
182 183 184 185
        curses.init_pair(2, curses.COLOR_WHITE, -1) # Visitor
        curses.init_pair(3, curses.COLOR_CYAN, -1)
        curses.init_pair(4, curses.COLOR_RED, -1) # Admin
        curses.init_pair(5, curses.COLOR_BLUE, -1) # Participant
186 187 188 189
        curses.init_pair(6, curses.COLOR_CYAN, -1)
        curses.init_pair(7, curses.COLOR_GREEN, -1)
        curses.init_pair(8, curses.COLOR_MAGENTA, -1)
        curses.init_pair(9, curses.COLOR_YELLOW, -1)
190 191 192 193 194 195 196 197 198 199
        curses.init_pair(10, curses.COLOR_WHITE,
                         curses.COLOR_CYAN) # current room
        curses.init_pair(11, curses.COLOR_WHITE,
                         curses.COLOR_BLUE) # normal room
        curses.init_pair(12, curses.COLOR_WHITE,
                         curses.COLOR_MAGENTA) # new message room
        curses.init_pair(13, curses.COLOR_WHITE,
                         curses.COLOR_RED) # highlight room
        curses.init_pair(14, curses.COLOR_WHITE,
                         curses.COLOR_YELLOW)
200
        curses.init_pair(15, curses.COLOR_WHITE, # new message in private room
201
                         curses.COLOR_GREEN)
202 203
        curses.init_pair(16, curses.COLOR_YELLOW,
                         curses.COLOR_BLUE)
204

205
    def reset_curses(self):
206 207 208 209 210
        """
        Reset terminal capabilities to what they were before ncurses
        init
        """
        curses.echo()
211
        curses.nocbreak()
212
        curses.endwin()
213

214
    def on_connected(self, jid):
215
        """
216
        We are connected when authentification confirmation is received
217
        """
218 219
        self.information(_("Welcome on Poezio \o/!"))
        self.information(_("Your JID is %s") % jid)
220

221 222 223 224 225 226 227
    def refresh_window(self):
        """
        Refresh everything
        """
        self.current_room().set_color_state(common.ROOM_STATE_CURRENT)
        self.window.refresh(self.rooms)

228
    def join_room(self, room, nick):
229 230 231
        """
        join the specified room (muc), using the specified nick
        """
232
        r = Room(room, nick, self.window)
233 234 235 236 237 238 239 240
        self.current_room().set_color_state(11)
        if self.current_room().nb == 0:
            self.rooms.append(r)
        else:
            for ro in self.rooms:
                if ro.nb == 0:
                    self.rooms.insert(self.rooms.index(ro), r)
                    break
241 242
        self.command_win("%s" % r.nb)
        self.refresh_window()
243

244
    def completion(self):
245 246 247
        """
        Called when Tab is pressed, complete the nickname in the input
        """
248 249 250 251 252 253 254 255 256
        def compare_users(a, b):
            """
            Used to sort users by their availability
            """
            if a.show == b.show:
                return 0
            if a.show is None:
                return -1
            return 1
257 258
        if len(self.window.input.text) == 0:
            self.last_talked_completion()
259 260
        else:
            self.window.input.auto_completion([user.nick for user in sorted(self.current_room().users, compare_users)])
261

262 263 264 265 266 267 268 269 270
    def last_talked_completion(self):
        """
        If tab is used while the input is empty, insert the nickname
        of the last person who spoke
        """
        for msg in self.current_room().messages[::-1]:
            if msg.nickname is not None and msg.nickname != self.current_room().own_nick:
                self.window.input.text = msg.nickname+config.get('after_completion', ',')+" "
                self.window.input.key_end()
271
                return
272

273 274 275 276 277 278 279
    def last_words_completion(self):
        """
        Complete the input with words recently said
        """
        # build the list of the recent words
        char_we_dont_want = [',', '(', ')', '.']
        words = list()
280
        for msg in self.current_room().messages[:-9:-1]:
281 282 283 284
            for word in msg.txt.split():
                for char in char_we_dont_want: # remove the chars we don't want
                    word = word.replace(char, '')
                if len(word) > 5:
285
                    words.append(word.encode('utf-8'))
286 287
        self.window.input.auto_completion(words)

288 289 290 291 292 293 294 295 296
    def go_to_important_room(self):
        """
        Go to the next room with activity, in this order:
        - A personal conversation with a new message
        - A Muc with an highlight
        - A Muc with any new message
        """
        for room in self.rooms:
            if room.color_state == 15:
297
                self.command_win('%s' % room.nb)
298 299 300
                return
        for room in self.rooms:
            if room.color_state == 13:
301
                self.command_win('%s' % room.nb)
302 303 304
                return
        for room in self.rooms:
            if room.color_state == 12:
305 306
                self.command_win('%s' % room.nb)
                return
307

308
    def rotate_rooms_right(self, args=None):
309 310 311
        """
        rotate the rooms list to the right
        """
312
        self.current_room().set_color_state(common.ROOM_STATE_NONE)
313
        self.rooms.append(self.rooms.pop(0))
314 315
        self.current_room().set_color_state(common.ROOM_STATE_CURRENT)
        self.refresh_window()
316

317
    def rotate_rooms_left(self, args=None):
318 319 320
        """
        rotate the rooms list to the right
        """
321
        self.current_room().set_color_state(common.ROOM_STATE_NONE)
322
        self.rooms.insert(0, self.rooms.pop())
323 324
        self.current_room().set_color_state(common.ROOM_STATE_CURRENT)
        self.refresh_window()
325

326
    def scroll_page_down(self, args=None):
327
        self.current_room().scroll_down(self.window.text_win.height-1)
328
        self.refresh_window()
329 330

    def scroll_page_up(self, args=None):
331
        self.current_room().scroll_up(self.window.text_win.height-1)
332
        self.refresh_window()
333

334
    def room_error(self, room, error, msg):
335 336 337
        """
        Display the error on the room window
        """
338 339
        if not error:
            return
340
        room = self.get_room_by_name(room)
341 342
        code = error.getAttr('code')
        typ = error.getAttr('type')
343 344 345 346
        if error.getTag('text'):
            body = error.getTag('text').getData()
        else:
            body = _('Unknown error')
347 348
        self.add_message_to_room(room, _('Error: %(code)s-%(msg)s: %(body)s' %
                                   {'msg':msg, 'code':code, 'body':body}))
349
        if code == '401':
350
            room.add(_('To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)'))
351
        self.refresh_window()
352

353 354 355 356 357 358 359 360 361 362
    def private_message(self, stanza):
        """
        When a private message is received
        """
        jid = stanza.getFrom()
        nick_from = stanza.getFrom().getResource()
        room_from = stanza.getFrom().getStripped()
        room = self.get_room_by_name(jid) # get the tab with the private conversation
        if not room: # It's the first message we receive: create the tab
            room = self.open_private_window(room_from, nick_from, False)
363 364
            if not room:
                return
365 366 367 368 369 370 371 372 373 374
        body = stanza.getBody()
        self.add_message_to_room(room, body, None, nick_from)
        self.window.input.refresh()
        doupdate()

    def open_private_window(self, room_name, user_nick, focus=True):
        complete_jid = room_name+'/'+user_nick
        for room in self.rooms: # if the room exists, focus it and return
            if room.jid:
                if room.jid == complete_jid:
375
                    self.command_win('%s' % room.nb)
376 377
                    return
        # create the new tab
378 379 380 381
        room = self.get_room_by_name(room_name)
        if not room:
            return None
        own_nick = room.own_nick
382 383 384 385 386 387 388 389 390 391 392 393 394
        r = Room(complete_jid, own_nick, self.window, complete_jid)
        # insert it in the rooms
        if self.current_room().nb == 0:
            self.rooms.append(r)
        else:
            for ro in self.rooms:
                if ro.nb == 0:
                    self.rooms.insert(self.rooms.index(ro), r)
                    break
        if focus:               # focus the room if needed
            while self.current_room().nb != r.nb:
                self.rooms.insert(0, self.rooms.pop())
        # self.window.new_room(r)
395
        self.refresh_window()
396 397
        return r

398
    def room_message(self, stanza, date=None):
399 400 401
        """
        Display the message on the room window
        """
402
        delay_tag = stanza.getTag('delay', namespace='urn:xmpp:delay')
403
        if delay_tag:
404 405 406
            delayed = True
            date = common.datetime_tuple(delay_tag.getAttr('stamp'))
        else:
407 408 409 410 411 412 413 414
            # We support the OLD and deprecated XEP: http://xmpp.org/extensions/xep-0091.html
            # But it sucks, please, Jabber servers, don't do this :(
            delay_tag = stanza.getTag('x', namespace='jabber:x:delay')
            if delay_tag:
                delayed = True
                date = common.datetime_tuple(delay_tag.getAttr('stamp'))
            else:
                delayed = False
415 416 417
        if stanza.getType() != 'groupchat':
            return  # ignore all messages not comming from a MUC
        nick_from = stanza.getFrom().getResource()
418
        room_from = stanza.getFrom().getStripped()
419 420
        if (self.ignores.has_key(room_from)) and (nick_from in self.ignores[room_from]):
            return
421
        room = self.get_room_by_name(room_from)
422
	if not room:
423
	    self.information(_("message received for a non-existing room: %s") % (room_from))
424
            return
425
        body = stanza.getBody()
426 427
        subject = stanza.getSubject()
        if subject:
428
            if nick_from:
429
                self.add_message_to_room(room, _("%(nick)s changed the subject to: %(subject)s") % {'nick':nick_from, 'subject':subject}, date)
430
            else:
431
                self.add_message_to_room(room, _("The subject is: %(subject)s") % {'subject':subject}, date)
432
            room.topic = subject.encode('utf-8').replace('\n', '|')
433 434
            if room == self.current_room():
                self.window.topic_win.refresh(room.topic)
435
        elif body:
436
            if body.startswith('/me '):
437
                self.add_message_to_room(room, "* "+nick_from + ' ' + body[4:], date)
438
            else:
439 440
                date = date if delayed == True else None
                self.add_message_to_room(room, body, date, nick_from)
441
        self.refresh_window()
442
        doupdate()
443 444

    def room_presence(self, stanza):
445 446 447 448
        """
        Display the presence on the room window and update the
        presence information of the concerned user
        """
449 450
        from_nick = stanza.getFrom().getResource()
        from_room = stanza.getFrom().getStripped()
451
	room = self.get_room_by_name(from_room)
452
	if not room:
453
            return
454
        else:
455 456 457 458 459 460
            msg = None
            affiliation = stanza.getAffiliation()
            show = stanza.getShow()
            status = stanza.getStatus()
            role = stanza.getRole()
            if not room.joined:     # user in the room BEFORE us.
461 462
                # ignore redondant presence message, see bug #1509
                if from_nick not in [user.nick for user in room.users]:
463 464
                    new_user = User(from_nick, affiliation, show, status, role)
                    room.users.append(new_user)
465 466 467
                    if from_nick.encode('utf-8') == room.own_nick:
                        room.joined = True
                        self.add_message_to_room(room, _("Your nickname is %s") % (from_nick))
468
                        new_user.color = 2
469 470 471 472 473 474
            else:
                change_nick = stanza.getStatusCode() == '303'
                kick = stanza.getStatusCode() == '307'
                user = room.get_user_by_name(from_nick)
                # New user
                if not user:
475 476
                    room.users.append(User(from_nick, affiliation,
                                           show, status, role))
477 478
                    hide_exit_join = config.get('hide_exit_join', -1)
                    if hide_exit_join != 0:
479
                        self.add_message_to_room(room, _("%(nick)s joined the room %(roomname)s") % {'nick':from_nick, 'roomname': room.name})
480 481 482 483
                # nick change
                elif change_nick:
                    if user.nick == room.own_nick:
                        room.own_nick = stanza.getNick().encode('utf-8')
484 485 486 487
                        # also change our nick in all private discussion of this room
                        for _room in self.rooms:
                            if _room.jid is not None and is_jid_the_same(_room.jid, room.name):
                                _room.own_nick = stanza.getNick()
488
                    user.change_nick(stanza.getNick())
489
                    self.add_message_to_room(room, _('%(old)s is now known as %(new)s') % {'old':from_nick, 'new':stanza.getNick()})
490 491 492 493 494 495 496 497
                    # rename the private tabs if needed
                    private_room = self.get_room_by_name(stanza.getFrom())
                    if private_room:
                        self.add_message_to_room(private_room, _('%(old_nick)s is now known as %(new_nick)s') % {'old_nick':from_nick, 'new_nick':stanza.getNick()})
                        new_jid = private_room.name.split('/')[0]+'/'+stanza.getNick()
                        private_room.jid = new_jid
                        private_room.name = new_jid

498 499 500
                # kick
                elif kick:
                    room.users.remove(user)
501
                    try:
502
                        reason = stanza.getReason()
503 504
                    except:
                        reason = ''
505
                    try:
506
                        by = stanza.getActor()
507 508 509 510 511
                    except:
                        by = None
                    if from_nick == room.own_nick: # we are kicked
                        room.disconnect()
                        if by:
512
                            self.add_message_to_room(room,  _("You have been kicked by %(by)s. Reason: %(reason)s") % {'by':by, 'reason':reason})
513
                        else:
514
                            self.add_message_to_room(room, _("You have been kicked. Reason: %s") % (reason))
515 516 517
                        # try to auto-rejoin
                        if config.get('autorejoin', 'false') == 'true':
                            self.muc.join_room(room.name, room.own_nick)
518 519
                    else:
                        if by:
520
                            self.add_message_to_room(room, _("%(nick)s has been kicked by %(by)s. Reason: %(reason)s") % {'nick':from_nick, 'by':by, 'reason':reason})
521
                        else:
522
                            self.add_message_to_room(room, _("%(nick)s has been kicked. Reason: %(reason)s") % {'nick':from_nick, 'reason':reason})
523 524 525
                # user quit
                elif status == 'offline' or role == 'none':
                    room.users.remove(user)
526 527 528
                    hide_exit_join = config.get('hide_exit_join', -1) if config.get('hide_exit_join', -1) >= -1 else -1
                    if hide_exit_join == -1 or user.has_talked_since(hide_exit_join):
                        self.add_message_to_room(room, _('%s has left the room') % (from_nick))
529 530 531
                    private_room = self.get_room_by_name(stanza.getFrom())
                    if private_room:
                        self.add_message_to_room(private_room, _('%s has left the room') % (from_nick))
532 533
                # status change
                else:
534 535 536 537 538 539 540 541 542 543 544
                    # build the message
                    msg = _('%s changed his/her status: ')% from_nick
                    if affiliation != user.affiliation:
                        msg += _('affiliation: %s,') % affiliation
                    if role != user.role:
                        msg += _('role: %s,') % role
                    if show != user.show:
                        msg += _('show: %s,') % show
                    if status != user.status:
                        msg += _('status: %s,') % status
                    msg = msg[:-1] # remove the last ","
545
                    hide_status_change = config.get('hide_status_change', -1) if config.get('hide_status_change', -1) >= -1 else -1
546
                    if (hide_status_change == -1 or \
547
                            user.has_talked_since(hide_status_change) or\
548 549 550 551 552 553
                            user.nick == room.own_nick)\
                            and\
                            (affiliation != user.affiliation or\
                                role != user.role or\
                                show != user.show or\
                                status != user.status):
554
                        # display the message in the room
555
                        self.add_message_to_room(room, msg)
556 557 558 559 560
                    private_room = self.get_room_by_name(stanza.getFrom())
                    if private_room: # display the message in private
                        self.add_message_to_room(private_room, msg)
                    # finally, effectively change the user status
                    user.update(affiliation, show, status, role)
561 562
            if room == self.current_room():
                self.window.user_win.refresh(room.users)
563
        self.window.input.refresh()
564
        doupdate()
565

566
    def add_message_to_room(self, room, txt, time=None, nickname=None):
567
        """
568 569
        Add the message to the room and refresh the associated component
        of the interface
570
        """
571
        room.add_message(txt, time, nickname)
572
        if room == self.current_room():
573 574
            self.window.text_win.refresh(room)
        else:
575
            self.window.info_win.refresh(self.rooms, self.current_room())
576
        self.window.input.refresh()
577 578

    def execute(self):
579 580 581
        """
        Execute the /command or just send the line on the current room
        """
582 583
        line = self.window.input.get_text()
        self.window.input.clear_text()
584
        self.window.input.refresh()
585 586
        if line == "":
            return
587
        if line.startswith('/') and not line.startswith('/me '):
588
            command = line.strip()[:].split()[0][1:]
589 590
            arg = line[1+len(command):]
            # example. on "/link 0 open", command = "link" and arg = "0 open"
591
            if command in self.commands.keys():
592
                func = self.commands[command][0]
593
                func(arg)
594
                return
595
            else:
596
                self.add_message_to_room(self.current_room(), _("Error: unknown command (%s)") % (command))
597
        elif self.current_room().name != 'Info':
598 599 600 601 602
            if self.current_room().jid is not None:
                self.muc.send_private_message(self.current_room().name, line)
                self.add_message_to_room(self.current_room(), line.decode('utf-8'), None, self.current_room().own_nick)
            else:
                self.muc.send_message(self.current_room().name, line)
603
        self.window.input.refresh()
604
        doupdate()
605

606
    def command_help(self, arg):
607 608 609
        """
        /help <command_name>
        """
610
        args = arg.split()
611 612
        room = self.current_room()
        if len(args) == 0:
613
            msg = _('Available commands are: ')
614 615
            for command in self.commands.keys():
                msg += "%s " % command
616
            msg += _("\nType /help <command_name> to know what each command does")
617 618 619 620
        if len(args) == 1:
            if args[0] in self.commands.keys():
                msg = self.commands[args[0]][1]
            else:
621
                msg = _('Unknown command: %s') % args[0]
622
        self.add_message_to_room(room, msg)
623

624
    def command_win(self, arg):
625 626 627
        """
        /win <number>
        """
628
        args = arg.split()
629 630 631 632 633 634 635 636 637 638
        if len(args) != 1:
            self.command_help(['win'])
            return
        try:
            nb = int(args[0])
        except ValueError:
            self.command_help(['win'])
            return
        if self.current_room().nb == nb:
            return
639
        self.current_room().set_color_state(common.ROOM_STATE_NONE)
640 641 642 643 644
        start = self.current_room()
        self.rooms.append(self.rooms.pop(0))
        while self.current_room().nb != nb:
            self.rooms.append(self.rooms.pop(0))
            if self.current_room() == start:
645 646
                self.current_room().set_color_state(common.ROOM_STATE_CURRENT)
                self.refresh_window()
647
                return
648 649
        self.current_room().set_color_state(common.ROOM_STATE_CURRENT)
        self.refresh_window()
650

651
    def command_kick(self, arg):
652 653 654
        """
        /kick <nick> [reason]
        """
655
        args = arg.split()
656 657 658 659 660 661 662 663 664 665 666 667 668
        if len(args) < 1:
            self.command_help(['kick'])
            return
        nick = args[0]
        if len(args) >= 2:
            reason = ' '.join(args[1:])
        else:
            reason = ''
        if self.current_room().name == 'Info' or not self.current_room().joined:
            return
        roomname = self.current_room().name
        self.muc.eject_user(roomname, 'kick', nick, reason)

669
    def command_join(self, arg):
670 671 672
        """
        /join [room][/nick] [password]
        """
673
        args = arg.split()
674
        password = None
675 676 677 678 679 680
        if len(args) == 0:
            r = self.current_room()
            if r.name == 'Info':
                return
            room = r.name
            nick = r.own_nick
681
        else:
682 683
            info = args[0].split('/')
            if len(info) == 1:
684 685 686 687
                default = os.environ.get('USER') if os.environ.get('USER') else 'poezio'
                nick = config.get('default_nick', '')
                if nick == '':
                    nick = default
688 689
            else:
                nick = info[1]
690
            if info[0] == '':   # happens with /join /nickname, which is OK
691 692 693 694
                r = self.current_room()
                if r.name == 'Info':
                    return
                room = r.name
695 696
                if nick == '':
                    nick = r.own_nick
697 698
            else:
                room = info[0]
699 700 701 702 703 704
            if not is_jid(room): # no server is provided, like "/join hello"
                # use the server of the current room if available
                # check if the current room's name has a server
                if is_jid(self.current_room().name):
                    room += '@%s' % jid_get_domain(self.current_room().name)
                else:           # no server could be found, print a message and return
705
                    self.add_message_to_room(self.current_room(), _("You didn't specify a server for the room you want to join"))
706
                    return
707
            r = self.get_room_by_name(room)
708 709
        if len(args) == 2:       # a password is provided
            password = args[1]
710
        if r and r.joined:       # if we are already in the room
711
            self.add_message_to_room(self.current_room(), _("already in room [%s]") % room)
712
            return
713
        self.muc.join_room(room, nick, password)
714
        if not r:   # if the room window exists, we don't recreate it.
715
            self.join_room(room, nick)
716
        else:
717
            r.own_nick = nick
718
            # r.own_nick = nick
719
            r.users = []
720

721
    def command_bookmark(self, arg):
722 723 724
        """
        /bookmark [room][/nick]
        """
725
        args = arg.split()
726 727 728 729 730 731 732 733 734 735 736 737 738
        nick = None
        if len(args) == 0:
            room = self.current_room()
            if room.name == 'Info':
                return
            roomname = room.name
            if room.joined:
                nick = room.own_nick
        else:
            info = args[0].split('/')
            if len(info) == 2:
                nick = info[1]
            roomname = info[0]
739 740
            if roomname == '':
                roomname = self.current_room().name
741 742 743 744
        if nick:
            res = roomname+'/'+nick
        else:
            res = roomname
745 746 747 748 749 750 751 752 753
        bookmarked = config.get('rooms', '')
        # check if the room is already bookmarked.
        # if yes, replace it (i.e., update the associated nick)
        bookmarked = bookmarked.split(':')
        for room in bookmarked:
            if room.split('/')[0] == roomname:
                bookmarked.remove(room)
                break
        bookmarked = ':'.join(bookmarked)
754 755 756
        bookmarks = bookmarked+':'+res
        config.set_and_save('rooms', bookmarks)
        self.add_message_to_room(self.current_room(), _('Your bookmarks are now: %s') % bookmarks)
757

758
    def command_set(self, arg):
759 760 761
        """
        /set <option> [value]
        """
762
        args = arg.split()
763
        if len(args) != 2 and len(args) != 1:
764 765 766
            self.command_help(['set'])
            return
        option = args[0]
767 768 769 770
        if len(args) == 2:
            value = args[1]
        else:
            value = ''
771
        config.set_and_save(option, value)
772 773
        msg = "%s=%s" % (option, value)
        room = self.current_room()
774
        self.add_message_to_room(room, msg)
775

776
    def command_show(self, arg):
777 778 779
        """
        /show <status> [msg]
        """
780
        args = arg.split()
781 782 783 784
        possible_show = {'avail':None,
                         'available':None,
                         'ok':None,
                         'here':None,
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
                         'chat':'chat',
                         'away':'away',
                         'afk':'away',
                         'dnd':'dnd',
                         'busy':'dnd',
                         'xa':'xa'
                         }
        if len(args) < 1:
            return
        if not args[0] in possible_show.keys():
            self.command_help(['show'])
            return
        show = possible_show[args[0]]
        if len(args) > 1:
            msg = ' '.join(args[1:])
        else:
            msg = None
        for room in self.rooms:
            if room.joined:
                self.muc.change_show(room.name, room.own_nick, show, msg)

806
    def command_ignore(self, arg):
807 808 809
        """
        /ignore <nick>
        """
810
        args = arg.split()
811
        if len(args) != 1:
812 813 814
            self.command_help(['ignore'])
            return
        if self.current_room().name == 'Info' or not self.current_room().joined:
815
            return
816 817 818 819 820 821 822 823 824
        roomname = self.current_room().name
        nick = args[0]
        if not self.ignores.has_key(roomname):
            self.ignores[roomname] = set() # no need for any order
        if nick not in self.ignores[roomname]:
            self.ignores[roomname].add(nick)
            self.add_message_to_room(self.current_room(), _("%s is now ignored") % nick)
        else:
            self.add_message_to_room(self.current_room(), _("%s is already ignored") % nick)
825

826
    def command_unignore(self, arg):
827 828 829
        """
        /unignore <nick>
        """
830
        args = arg.split()
831
        if len(args) != 1:
832 833 834 835 836 837 838 839
            self.command_help(['unignore'])
            return
        if self.current_room().name == 'Info' or not self.current_room().joined:
            return
        roomname = self.current_room().name
        nick = args[0]
        if not self.ignores.has_key(roomname) or (nick not in self.ignores[roomname]):
            self.add_message_to_room(self.current_room(), _("%s was not ignored") % nick)
840
            return
841 842 843 844
        self.ignores[roomname].remove(nick)
        if self.ignores[roomname] == set():
            del self.ignores[roomname]
        self.add_message_to_room(self.current_room(), _("%s is now unignored") % nick)
845

846
    def command_away(self, arg):
louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13's avatar
<