1from time import strftime, localtime
2import random
3
4from gi.repository import Gtk, Gdk, Pango, GObject
5
6from pychess.System import uistuff
7from pychess.widgets import insert_formatted
8from pychess.Utils.IconLoader import load_icon
9from pychess.ic.ICGameModel import ICGameModel
10
11
12class ChatView(Gtk.Box):
13    __gsignals__ = {
14        'messageAdded': (GObject.SignalFlags.RUN_FIRST, None,
15                         (str, str, object)),
16        'messageTyped': (GObject.SignalFlags.RUN_FIRST, None, (str, )),
17    }
18
19    def __init__(self, gamemodel=None):
20        Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
21        self.gamemodel = gamemodel
22
23        # States for the color generator
24        self.colors = {}
25        self.startpoint = random.random()
26
27        # Inits the read view
28        self.readView = Gtk.TextView()
29
30        self.sw = Gtk.ScrolledWindow()
31        self.sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
32        self.sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
33
34        self.sw.add(self.readView)
35        self.readView.set_editable(False)
36        self.readView.set_cursor_visible(False)
37        self.readView.props.wrap_mode = Gtk.WrapMode.WORD
38        self.readView.props.pixels_below_lines = 1
39        self.readView.props.pixels_above_lines = 2
40        self.readView.props.left_margin = 2
41
42        if isinstance(self.gamemodel, ICGameModel):
43            self.refresh = Gtk.Image()
44            self.refresh.set_from_pixbuf(load_icon(16, "view-refresh",
45                                                   "stock-refresh"))
46            label = _("Observers")
47            self.obs_btn = Gtk.Button()
48            self.obs_btn.set_image(self.refresh)
49            self.obs_btn.set_label(label)
50            self.obs_btn_cid = self.obs_btn.connect("clicked", self.on_obs_btn_clicked)
51
52            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
53
54            # Inits the observers view
55            self.obsView = Gtk.TextView()
56            self.obsView.set_cursor_visible(False)
57
58            self.obsView.set_editable(False)
59            self.obsView.props.wrap_mode = Gtk.WrapMode.WORD
60            self.obsView.props.pixels_below_lines = 1
61            self.obsView.props.pixels_above_lines = 2
62            self.obsView.props.left_margin = 2
63
64            text_buffer = self.obsView.get_buffer()
65            iter = text_buffer.get_end_iter()
66            anchor1 = text_buffer.create_child_anchor(iter)
67            self.obsView.add_child_at_anchor(self.obs_btn, anchor1)
68            self.button_tag = text_buffer.create_tag("observers")
69            text_buffer.insert_with_tags_by_name(iter, " ", "observers")
70            text_buffer.insert(iter, "\n")
71
72            if not self.gamemodel.offline_lecture:
73                vbox.pack_start(self.obsView, False, True, 0)
74            vbox.pack_start(self.sw, True, True, 0)
75
76            self.pack_start(vbox, True, True, 0)
77        else:
78            self.pack_start(self.sw, True, True, 0)
79
80        # Create a 'log mark' in the beginning of the text buffer. Because we
81        # query the log asynchronously and in chunks, we can use this to insert
82        # it correctly after previous log messages, but before the new messages.
83        start = self.readView.get_buffer().get_start_iter()
84        self.readView.get_buffer().create_mark("logMark", start)
85
86        # Inits the write view
87        self.writeView = Gtk.Entry()
88
89        box = Gtk.Box()
90        self.pack_start(self.writeView, False, False, 0)
91        box.add(self.writeView)
92
93        if self.gamemodel is not None and self.gamemodel.offline_lecture:
94            label = _("Go on")
95            self.go_on_btn = Gtk.Button()
96            self.go_on_btn.set_label(label)
97            self.go_on_btn_cid = self.go_on_btn.connect(
98                "clicked", lambda btn: self.gamemodel.lecture_skip_event.set())
99            box.add(self.go_on_btn)
100
101            label = _("Pause")
102            self.pause_btn = Gtk.Button()
103            self.pause_btn.set_label(label)
104            self.pause_btn_cid = self.pause_btn.connect(
105                "clicked", lambda btn: self.gamemodel.lecture_pause_event.set())
106            box.add(self.pause_btn)
107
108        self.pack_start(box, False, False, 0)
109
110        self.writeview_cid = self.writeView.connect("key-press-event", self.onKeyPress)
111        self.cid = None
112        if self.gamemodel is not None:
113            self.cid = self.gamemodel.connect_after("game_terminated", self.on_game_terminated)
114
115    def on_game_terminated(self, model):
116        if isinstance(self.gamemodel, ICGameModel):
117            self.obs_btn.disconnect(self.obs_btn_cid)
118        self.writeView.disconnect(self.writeview_cid)
119        if self.cid is not None:
120            self.gamemodel.disconnect(self.cid)
121
122    def on_obs_btn_clicked(self, other):
123        if not self.gamemodel.connection.ICC:
124            allob = 'allob ' + str(self.gamemodel.ficsgame.gameno)
125            self.gamemodel.connection.client.run_command(allob)
126
127    def update_observers(self, other, observers):
128        """ Rebuilds observers list text """
129        text_buf = self.obsView.get_buffer()
130        start_iter = text_buf.get_end_iter()
131        start_iter.backward_to_tag_toggle(self.button_tag)
132        start_iter.forward_char()
133        end_iter = text_buf.get_end_iter()
134        text_buf.delete(start_iter, end_iter)
135        iter = text_buf.get_end_iter()
136
137        obs_list = observers.split()
138        for player in obs_list:
139            # Colourize only players able to interact with chat View
140            if player.endswith("(U)"):
141                text_buf.insert(iter, "%s " % player[:-3])
142            elif "(" in player:
143                pref = player.split('(', 1)[0]
144                self._ensureColor(pref)
145                text_buf.insert_with_tags_by_name(iter, "%s " % player,
146                                                  pref + "_bold")
147            else:
148                text_buf.insert(iter, "%s " % player)
149        self.obsView.show_all()
150
151    def _ensureColor(self, pref):
152        """ Ensures that the tags for pref_normal and pref_bold are set in the text buffer """
153        text_buf = self.readView.get_buffer()
154        if pref not in self.colors:
155            color = uistuff.genColor(len(self.colors) + 1, self.startpoint)
156            self.colors[pref] = color
157            color = [int(c * 255) for c in color]
158            color = "#" + "".join([hex(v)[2:].zfill(2) for v in color])
159            text_buf.create_tag(pref + "_normal", foreground=color)
160            text_buf.create_tag(pref + "_bold", foreground=color,
161                                weight=Pango.Weight.BOLD)
162            if isinstance(self.gamemodel, ICGameModel):
163                otb = self.obsView.get_buffer()
164                otb.create_tag(pref + "_normal", foreground=color)
165                otb.create_tag(pref + "_bold",
166                               foreground=color,
167                               weight=Pango.Weight.BOLD)
168
169    def clear(self):
170        self.writeView.get_buffer().props.text = ""
171        self.readView.get_buffer().props.text = ""
172        tagtable = self.readView.get_buffer().get_tag_table()
173        for i in range(len(self.colors)):
174            tagtable.remove("%d_normal" % i)
175            tagtable.remove("%d_bold" % i)
176        self.colors.clear()
177
178    def __addMessage(self, iter, time, sender, text):
179        pref = sender.lower()
180        text_buffer = self.readView.get_buffer()
181        iter = text_buffer.get_end_iter()
182        text_buffer.create_mark("end", iter, False)
183        if text_buffer.props.text:
184            text_buffer.insert(iter, "\n")
185
186        # Calculate a color for the sender
187        self._ensureColor(pref)
188        # Insert time, name and text with different stylesd
189        text_buffer.insert_with_tags_by_name(iter, "(%s) " % time, pref + "_normal")
190        text_buffer.insert_with_tags_by_name(iter, sender + ": ", pref + "_bold")
191        insert_formatted(self.readView, iter, text)
192
193        # Scroll the mark onscreen.
194        mark = text_buffer.get_mark("end")
195        text_buffer.move_mark(mark, iter)
196        self.readView.scroll_mark_onscreen(mark)
197
198        # This is used to buzz the user and add senders to a list of active participants
199        self.emit("messageAdded", sender, text, self.colors[pref])
200
201    def insertLogMessage(self, timestamp, sender, text):
202        """ Takes a list of (timestamp, sender, text) pairs, and inserts them in
203            the beginning of the document.
204            All text will be in a gray color
205        """
206        text_buffer = self.readView.get_buffer()
207        iter = text_buffer.get_iter_at_mark(text_buffer.get_mark("logMark"))
208        time = strftime("%H:%M:%S", localtime(timestamp))
209        self.__addMessage(iter, time, sender, text)
210
211    def addMessage(self, sender, text):
212        text_buffer = self.readView.get_buffer()
213        iter = text_buffer.get_end_iter()
214        self.__addMessage(iter, strftime("%H:%M:%S"), sender, text)
215
216    def disable(self, message):
217        """ Sets the write field insensitive, in cases where the channel is
218            read only. Use the message to give the user a propriate
219            exlpanation """
220        self.writeView.set_sensitive(False)
221        self.writeView.set_text(message)
222
223    def enable(self):
224        self.writeView.set_text("")
225        self.writeView.set_sensitive(True)
226
227    def onKeyPress(self, widget, event):
228        if event.keyval in list(map(Gdk.keyval_from_name,
229                                    ("Return", "KP_Enter"))):
230            if not event.get_state() & Gdk.ModifierType.CONTROL_MASK:
231                buffer = self.writeView.get_buffer()
232                if buffer.props.text:
233                    self.emit("messageTyped", buffer.props.text)
234                    buffer.props.text = ""
235                return True
236