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