1# -*- coding: UTF-8 -*- 2 3from math import pi, cos, sin 4 5import cairo 6from gi.repository import GLib, Gtk, Gdk, Pango, PangoCairo, GObject 7 8from pychess.System import conf 9from pychess.Utils import formatTime 10from pychess.Utils.const import BLACK, WHITE, LOCAL, UNFINISHED_STATES, DRAW, WHITEWON, BLACKWON, UNKNOWN_STATE 11from . import preferencesDialog 12 13 14class ChessClock(Gtk.DrawingArea): 15 def __init__(self): 16 GObject.GObject.__init__(self) 17 self.connect("draw", self.expose) 18 self.names = [_("White"), _("Black")] 19 20 self.model = None 21 self.short_on_time = [False, False] 22 self.alarm_spin = conf.get("alarm_spin") 23 24 conf.notify_add("alarm_spin", self.on_alarm_spin) 25 26 def on_alarm_spin(self, *args): 27 self.alarm_spin = conf.get("alarm_spin") 28 29 def expose(self, widget, ctx): 30 context = widget.get_window().cairo_create() 31 clip_ext = context.clip_extents() 32 33 if clip_ext[0] > 0 or clip_ext[2] < self.get_allocated_width(): 34 self.redraw_canvas() 35 return False 36 37 rec = Gdk.Rectangle() 38 rec.x, rec.y, rec.width, rec.height = clip_ext[0], clip_ext[1], \ 39 clip_ext[2] - clip_ext[0], clip_ext[3] - clip_ext[1] 40 context.rectangle(rec.x, rec.y, rec.width, rec.height) 41 context.clip() 42 self.draw(context) 43 return False 44 45 def draw(self, context): 46 47 style_ctxt = self.get_style_context() 48 self.light = style_ctxt.lookup_color("p_light_color")[1] 49 self.dark = style_ctxt.lookup_color("p_dark_color")[1] 50 if not self.model: 51 return 52 53 # Draw graphical Clock. Should this be moved to preferences? 54 drawClock = True 55 56 rect = Gdk.Rectangle() 57 clip_ext = context.clip_extents() 58 rect.x, rect.y, rect.width, rect.height = clip_ext[0], clip_ext[1], \ 59 clip_ext[2] - clip_ext[0], clip_ext[3] - clip_ext[1] 60 context.rectangle(rect.width / 2. * self.model.movingColor, 0, 61 rect.width / 2., rect.height) 62 63 context.set_source_rgba(self.dark.red, self.dark.green, self.dark.blue, 64 self.dark.alpha) 65 context.fill_preserve() 66 context.new_path() 67 68 time0 = self.names[0], self.formatedCache[WHITE] 69 layout0 = self.create_pango_layout(" %s: %s " % (time0)) 70 layout0.set_font_description(Pango.FontDescription("Sans Serif 17")) 71 72 time1 = self.names[1], self.formatedCache[BLACK] 73 layout1 = self.create_pango_layout(" %s: %s " % (time1)) 74 layout1.set_font_description(Pango.FontDescription("Sans Serif 17")) 75 76 dbl_max = max(layout1.get_pixel_size()[0], layout0.get_pixel_size()[0]) * 2 77 self.set_size_request(dbl_max + rect.height + 7, -1) 78 79 pangoScale = float(Pango.SCALE) 80 81 # Analog clock code. 82 def paintClock(player): 83 clock_y = rect.height / 2. 84 clock_x = clock_y + rect.width / 2. * player + 1 85 rec = rect.height / 2. - 3.5 86 87 context.arc(clock_x, clock_y, rec - 1, 0, 2 * pi) 88 linear = cairo.LinearGradient(clock_x - rec * 2, clock_y - rec * 2, 89 clock_x + rec * 2, clock_y + rec * 2) 90 linear.add_color_stop_rgba(0, 1, 1, 1, 0.3) 91 linear.add_color_stop_rgba(1, 0, 0, 0, 0.3) 92 # context.set_source_rgba( 0, 0, 0, .3) 93 context.set_source(linear) 94 context.fill() 95 96 linear = cairo.LinearGradient(clock_x - rec, clock_y - rec, 97 clock_x + rec, clock_y + rec) 98 linear.add_color_stop_rgba(0, 0, 0, 0, 0.5) 99 linear.add_color_stop_rgba(1, 1, 1, 1, 0.5) 100 context.arc(clock_x, clock_y, rec, 0, 2 * pi) 101 context.set_source(linear) 102 context.set_line_width(2.5) 103 context.stroke() 104 105 starttime = float(self.model.getInitialTime()) or 1 106 used = self.model.getPlayerTime(player) / starttime 107 if used > 0: 108 if used > 0: 109 context.arc(clock_x, clock_y, rec - .8, -(used + 0.25) * 2 * pi, -0.5 * 110 pi) 111 context.line_to(clock_x, clock_y) 112 context.close_path() 113 elif used == 0: 114 context.arc(clock_x, clock_y, rec - .8, -0.5 * pi, 1.5 * pi) 115 context.line_to(clock_x, clock_y) 116 117 radial = cairo.RadialGradient(clock_x, clock_y, 3, clock_x, clock_y, rec) 118 if player == 0: 119 # radial.add_color_stop_rgb(0, .73, .74, .71) 120 radial.add_color_stop_rgb(0, .93, .93, .92) 121 radial.add_color_stop_rgb(1, 1, 1, 1) 122 else: 123 # radial.add_color_stop_rgb(0, .53, .54, .52) 124 radial.add_color_stop_rgb(0, .18, .20, .21) 125 radial.add_color_stop_rgb(1, 0, 0, 0) 126 context.set_source(radial) 127 context.fill() 128 129 x_loc = clock_x - cos((used - 0.25) * 2 * pi) * (rec - 1) 130 y_loc = clock_y + sin((used - 0.25) * 2 * pi) * (rec - 1) 131 context.move_to(clock_x, clock_y - rec + 1) 132 context.line_to(clock_x, clock_y) 133 context.line_to(x_loc, y_loc) 134 context.set_line_width(0.2) 135 if player == 0: 136 context.set_source_rgb(0, 0, 0) 137 else: 138 context.set_source_rgb(1, 1, 1) 139 context.stroke() 140 141 if drawClock: 142 paintClock(WHITE) 143 if (self.model.movingColor or WHITE) == WHITE: 144 context.set_source_rgba(self.light.red, self.light.green, 145 self.light.blue, self.light.alpha) 146 else: 147 context.set_source_rgba(self.dark.red, self.dark.green, 148 self.dark.blue, self.dark.alpha) 149 y_loc = rect.height / 2. - layout0.get_extents()[0].height / pangoScale / 2 \ 150 - layout0.get_extents()[0].y / pangoScale 151 context.move_to(rect.height - 7, y_loc) 152 PangoCairo.show_layout(context, layout0) 153 154 if drawClock: 155 paintClock(BLACK) 156 if self.model.movingColor == BLACK: 157 context.set_source_rgba(self.light.red, self.light.green, 158 self.light.blue, self.light.alpha) 159 else: 160 context.set_source_rgba(self.dark.red, self.dark.green, 161 self.dark.blue, self.dark.alpha) 162 y_loc = rect.height / 2. - layout0.get_extents()[0].height / pangoScale / 2 \ 163 - layout0.get_extents()[0].y / pangoScale 164 context.move_to(rect.width / 2. + rect.height - 7, y_loc) 165 PangoCairo.show_layout(context, layout1) 166 167 def redraw_canvas(self): 168 def do_redraw_canvas(): 169 if self.get_window(): 170 allocation = self.get_allocation() 171 rect = Gdk.Rectangle() 172 rect.x, rect.y, rect.width, rect.height = (0, 0, allocation.width, 173 allocation.height) 174 self.get_window().invalidate_rect(rect, True) 175 self.get_window().process_updates(True) 176 177 GLib.idle_add(do_redraw_canvas) 178 179 def setModel(self, model): 180 self.model = model 181 self.model.connect("time_changed", self.time_changed) 182 self.model.connect("player_changed", self.player_changed) 183 self.formatedCache = [formatTime(self.model.getPlayerTime( 184 self.model.movingColor or WHITE))] * 2 185 if model.secs != 0 or model.gain != 0: 186 GLib.timeout_add(100, self.update) 187 188 def time_changed(self, model): 189 self.update() 190 191 def player_changed(self, model): 192 self.redraw_canvas() 193 194 def update(self, wmovecount=-1, bmovecount=-1): 195 if self.model.ended: 196 return False 197 if len(self.model.gamemodel.players) < 2: 198 return not self.model.ended 199 alarm_time = int(self.alarm_spin) 200 if self.model.getPlayerTime(self.model.movingColor) <= alarm_time and \ 201 self.model.gamemodel.players[self.model.movingColor].__type__ == LOCAL and \ 202 self.model.gamemodel.status in UNFINISHED_STATES and \ 203 self.model.secs != 0 and \ 204 self.model.gamemodel.endstatus not in (DRAW, WHITEWON, BLACKWON, UNKNOWN_STATE) and \ 205 not self.short_on_time[self.model.movingColor]: 206 self.short_on_time[self.model.movingColor] = True 207 preferencesDialog.SoundTab.playAction("shortOnTime") 208 209 if self.model.paused and wmovecount == -1 and bmovecount == -1: 210 return not self.model.ended 211 white_time = formatTime(self.model.getPlayerTime(WHITE, wmovecount)) 212 black_time = formatTime(self.model.getPlayerTime(BLACK, bmovecount)) 213 if self.formatedCache != [white_time, black_time]: 214 self.formatedCache = [white_time, black_time] 215 self.redraw_canvas() 216 return not self.model.ended 217