1import math 2import random 3from os.path import basename 4from urllib.request import urlopen 5from urllib.parse import unquote 6 7from gi.repository import Gtk, GObject, Pango 8 9from pychess.compat import create_task 10from pychess.Players.Human import Human 11from pychess.Players.engineNest import discoverer 12from pychess.System import uistuff, conf 13from pychess.Utils.GameModel import GameModel 14from pychess.Utils.IconLoader import load_icon, get_pixbuf 15from pychess.Utils.TimeModel import TimeModel 16from pychess.Utils.const import LOCAL, ARTIFICIAL, WHITE, BLACK, NORMALCHESS, LECTURE, LESSON, PUZZLE, ENDGAME 17from pychess.Variants import variants 18from pychess.ic import ICLogon 19from pychess.widgets import newGameDialog 20from pychess.widgets.Background import giveBackground 21from pychess.widgets.RecentChooser import recent_manager, recent_menu 22from pychess.perspectives import perspective_manager 23from pychess.perspectives.games import get_open_dialog 24from pychess.perspectives.learn.LecturesPanel import LECTURES, start_lecture_from 25from pychess.perspectives.learn.EndgamesPanel import ENDGAMES, start_endgame_from 26from pychess.perspectives.learn.LessonsPanel import LESSONS, start_lesson_from 27from pychess.perspectives.learn.PuzzlesPanel import PUZZLES, start_puzzle_from 28 29 30class TaskerManager(Gtk.Table): 31 def __init__(self): 32 GObject.GObject.__init__(self) 33 self.border = 20 34 giveBackground(self) 35 self.connect("draw", self.expose) 36 # self.set_homogeneous(True) 37 38 def expose(self, widget, ctx): 39 cairo_win = widget.get_window().cairo_create() 40 41 for widget in self.widgets: 42 x_loc = widget.get_allocation().x 43 y_loc = widget.get_allocation().y 44 width = widget.get_allocation().width 45 height = widget.get_allocation().height 46 47 cairo_win.move_to(x_loc - self.border, y_loc) 48 cairo_win.curve_to(x_loc - self.border, y_loc - self.border / 2., 49 x_loc - self.border / 2., y_loc - self.border, x_loc, 50 y_loc - self.border) 51 cairo_win.line_to(x_loc + width, y_loc - self.border) 52 cairo_win.curve_to(x_loc + width + self.border / 2., y_loc - self.border, 53 x_loc + width + self.border, y_loc - self.border / 2., 54 x_loc + width + self.border, y_loc) 55 cairo_win.line_to(x_loc + width + self.border, y_loc + height) 56 cairo_win.curve_to(x_loc + width + self.border, y_loc + height + self.border / 2., 57 x_loc + width + self.border / 2., y_loc + height + self.border, 58 x_loc + width, y_loc + height + self.border) 59 cairo_win.line_to(x_loc, y_loc + height + self.border) 60 cairo_win.curve_to(x_loc - self.border / 2., y_loc + height + self.border, 61 x_loc - self.border, y_loc + height + self.border / 2., 62 x_loc - self.border, y_loc + height) 63 64 style_ctxt = self.get_style_context() 65 bgcolor = style_ctxt.lookup_color("p_bg_color")[1] 66 darkcolor = style_ctxt.lookup_color("p_dark_color")[1] 67 68 cairo_win.set_source_rgba(bgcolor.red, bgcolor.green, bgcolor.blue, 69 bgcolor.alpha) 70 cairo_win.fill() 71 72 cairo_win.rectangle(x_loc - self.border, y_loc + height - 30, 73 width + self.border * 2, 30) 74 cairo_win.set_source_rgba(darkcolor.red, darkcolor.green, darkcolor.blue, 75 darkcolor.alpha) 76 cairo_win.fill() 77 78 def calcSpacings(self, n): 79 """ Will yield ranges like 80 ((.50,.50),) 81 ((.66,.33), (.33,.66)) 82 ((.75,.25), (.50,.50), (.25,.75)) 83 ((.80,.20), (.60,.40), (.40,.60), (.20,.80)) 84 Used to create the centering in the table """ 85 86 first = next = (n) / float(n + 1) 87 for i in range(n): 88 yield (next, 1 - next) 89 next = first - (1 - next) 90 91 def on_size_allocate(self, widget, allocation): 92 window = self.get_window() 93 if window is not None: 94 window.invalidate_rect(self.get_allocation(), False) 95 96 def packTaskers(self, *widgets): 97 self.widgets = widgets 98 99 for widget in widgets: 100 widget.connect("size-allocate", self.on_size_allocate) 101 root = math.sqrt(len(widgets)) 102 # Calculate number of rows 103 rows = int(math.ceil(root)) 104 # Calculate number of filled out rows 105 rrows = int(math.floor(root)) 106 # Calculate number of cols in filled out rows 107 cols = int(math.ceil(len(widgets) / float(rows))) 108 109 # Calculate spacings 110 111 vspac = [s[0] for s in self.calcSpacings(rows)] 112 hspac = [s[0] for s in self.calcSpacings(cols)] 113 114 # Clear and set up new size 115 116 for child in self.get_children(): 117 self.remove(child) 118 119 self.props.n_columns = cols 120 self.props.n_rows = rows 121 122 # Add filled out rows 123 124 for row in range(rows): 125 for col in range(cols): 126 widget = widgets[row * cols + col] 127 alignment = Gtk.Alignment.new(hspac[col], vspac[row], 0, 0) 128 alignment.add(widget) 129 self.attach(alignment, col, col + 1, row, row + 1) 130 return 131 132 # Add last row 133 134 if rows > rrows: 135 lastrow = Gtk.HBox() 136 # Calculate number of widgets in last row 137 numw = len(widgets) - cols * rrows 138 hspac = [s[0] for s in self.calcSpacings(numw)] 139 for col, widget in enumerate(widgets[-numw:]): 140 alignment = Gtk.Alignment.new(hspac[col], vspac[-1], 0, 0) 141 alignment.add(widget) 142 alignment.set_padding(self.border, self.border, self.border, 143 self.border) 144 lastrow.pack_start(alignment, True, True, 0) 145 146 self.attach(lastrow, 0, cols, rrows, rrows + 1) 147 148 149tasker = TaskerManager() 150tasker_widgets = uistuff.GladeWidgets("taskers.glade") 151 152 153class NewGameTasker(Gtk.Alignment): 154 def __init__(self): 155 GObject.GObject.__init__(self) 156 self.widgets = widgets = tasker_widgets 157 tasker = widgets["newGameTasker"] 158 tasker.unparent() 159 self.add(tasker) 160 161 startButton = self.widgets["startButton"] 162 startButton.set_name("startButton") 163 combo = Gtk.ComboBox() 164 uistuff.createCombo(combo, [ 165 (get_pixbuf("glade/white.png"), _("White")), 166 (get_pixbuf("glade/black.png"), _("Black")), 167 (get_pixbuf("glade/random.png"), _("Random"))]) 168 widgets["colorDock"].add(combo) 169 if combo.get_active() < 0: 170 combo.set_active(0) 171 widgets['yourColorLabel'].set_mnemonic_widget(combo) 172 173 # We need to wait until after engines have been discovered, to init the 174 # playerCombos. We use connect_after to make sure, that newGameDialog 175 # has also had time to init the constants we share with them. 176 self.playerCombo = Gtk.ComboBox() 177 widgets["opponentDock"].add(self.playerCombo) 178 discoverer.connect_after("all_engines_discovered", 179 self.__initPlayerCombo, widgets) 180 widgets['opponentLabel'].set_mnemonic_widget(self.playerCombo) 181 182 def on_skill_changed(scale): 183 # Just to make sphinx happy... 184 try: 185 pix = newGameDialog.skillToIconLarge[int(scale.get_value())] 186 widgets["skillImage"].set_from_pixbuf(pix) 187 except TypeError: 188 pass 189 190 widgets["skillSlider"].connect("value-changed", on_skill_changed) 191 on_skill_changed(widgets["skillSlider"]) 192 193 widgets["startButton"].connect("clicked", self.startClicked) 194 self.widgets["opendialog1"].connect("clicked", self.openDialogClicked) 195 196 def __initPlayerCombo(self, discoverer, widgets): 197 combo = self.playerCombo 198 uistuff.createCombo(combo, newGameDialog.playerItems[0]) 199 if combo.get_active() < 0: 200 combo.set_active(1) 201 uistuff.keep(self.playerCombo, "newgametasker_playercombo") 202 203 def on_playerCombobox_changed(widget): 204 widgets["skillSlider"].props.visible = widget.get_active() > 0 205 206 combo.connect("changed", on_playerCombobox_changed) 207 208 uistuff.keep(widgets["skillSlider"], "taskerSkillSlider") 209 widgets["skillSlider"].set_no_show_all(True) 210 on_playerCombobox_changed(self.playerCombo) 211 212 def openDialogClicked(self, button): 213 newGameDialog.NewGameMode.run() 214 215 def startClicked(self, button): 216 color = self.widgets["colorDock"].get_child().get_active() 217 if color == 2: 218 color = random.choice([WHITE, BLACK]) 219 220 opp = self.widgets["opponentDock"].get_child() 221 tree_iter = opp.get_active_iter() 222 if tree_iter is not None: 223 model = opp.get_model() 224 engine = model[tree_iter][1] 225 226 opponent = self.widgets["opponentDock"].get_child().get_active() 227 difficulty = int(self.widgets["skillSlider"].get_value()) 228 229 gamemodel = GameModel(TimeModel(5 * 60, 0)) 230 231 name = conf.get("firstName") 232 player0tup = (LOCAL, Human, (color, name), name) 233 if opponent == 0: 234 name = conf.get("secondName") 235 player1tup = (LOCAL, Human, (1 - color, name), name) 236 else: 237 engine = discoverer.getEngineByName(engine) 238 name = discoverer.getName(engine) 239 player1tup = (ARTIFICIAL, discoverer.initPlayerEngine, 240 (engine, 1 - color, difficulty, 241 variants[NORMALCHESS], 5 * 60, 0), name) 242 243 perspective = perspective_manager.get_perspective("games") 244 if color == WHITE: 245 create_task(perspective.generalStart(gamemodel, player0tup, player1tup)) 246 else: 247 create_task(perspective.generalStart(gamemodel, player1tup, player0tup)) 248 249 250big_start = load_icon(48, "stock_init", "gnome-globe", "applications-internet") 251 252 253class InternetGameTasker(Gtk.Alignment): 254 def __init__(self): 255 GObject.GObject.__init__(self) 256 self.widgets = tasker_widgets 257 tasker = self.widgets["internetGameTasker"] 258 tasker.unparent() 259 self.add(tasker) 260 261 if ICLogon.dialog is None: 262 ICLogon.dialog = ICLogon.ICLogon() 263 264 liststore = Gtk.ListStore(str) 265 liststore.append(["FICS"]) 266 liststore.append(["ICC"]) 267 self.ics_combo = self.widgets["ics_combo"] 268 self.ics_combo.set_model(liststore) 269 renderer_text = Gtk.CellRendererText() 270 self.ics_combo.pack_start(renderer_text, True) 271 self.ics_combo.add_attribute(renderer_text, "text", 0) 272 self.ics_combo.connect("changed", ICLogon.dialog.on_ics_combo_changed) 273 self.ics_combo.set_active(conf.get("ics_combo")) 274 275 self.widgets["connectButton"].connect("clicked", self.connectClicked) 276 self.widgets["opendialog2"].connect("clicked", self.openDialogClicked) 277 278 self.widgets["startIcon"].set_from_pixbuf(big_start) 279 280 uistuff.keep(self.widgets["ics_combo"], "ics_combo") 281 uistuff.keep(self.widgets["autoLogin"], "autoLogin") 282 283 def openDialogClicked(self, button): 284 ICLogon.run() 285 286 def connectClicked(self, button): 287 ICLogon.run() 288 if not ICLogon.dialog.connection: 289 ICLogon.dialog.widgets["connectButton"].clicked() 290 291 292class LearnTasker(Gtk.Alignment): 293 def __init__(self): 294 GObject.GObject.__init__(self) 295 self.widgets = tasker_widgets 296 tasker = self.widgets["learnTasker"] 297 tasker.unparent() 298 self.add(tasker) 299 300 startButton = self.widgets["learnButton"] 301 startButton.set_name("learnButton") 302 303 categorystore = Gtk.ListStore(int, str) 304 305 learn_mapping = { 306 LECTURE: (_("Lectures"), LECTURES), 307 LESSON: (_("Lessons"), LESSONS), 308 PUZZLE: (_("Puzzles"), PUZZLES), 309 ENDGAME: (_("Endgames"), ENDGAMES), 310 } 311 for key, value in learn_mapping.items(): 312 categorystore.append([key, value[0]]) 313 314 self.category_combo = self.widgets["category_combo"] 315 self.category_combo.set_model(categorystore) 316 renderer = Gtk.CellRendererText() 317 self.category_combo.pack_start(renderer, True) 318 self.category_combo.add_attribute(renderer, "text", 1) 319 320 self.learnstore = Gtk.ListStore(str, str) 321 self.learn_combo = self.widgets["learn_combo"] 322 self.learn_combo.set_model(self.learnstore) 323 renderer_text = Gtk.CellRendererText() 324 renderer_text.set_property("width-chars", 30) 325 renderer_text.set_property("ellipsize", Pango.EllipsizeMode.END) 326 self.learn_combo.pack_start(renderer_text, True) 327 self.learn_combo.add_attribute(renderer_text, "text", 1) 328 self.learn_combo.set_active(0) 329 330 def on_category_changed(combo): 331 tree_iter = combo.get_active_iter() 332 if tree_iter is None: 333 return 334 else: 335 model = combo.get_model() 336 self.category = model[tree_iter][0] 337 338 self.learnstore.clear() 339 if self.category == LECTURE: 340 for file_name, title, author in LECTURES: 341 self.learnstore.append([file_name, title]) 342 elif self.category == LESSON: 343 for file_name, title, author in LESSONS: 344 self.learnstore.append([file_name, title]) 345 elif self.category == PUZZLE: 346 for file_name, title, author in PUZZLES: 347 self.learnstore.append([file_name, title]) 348 elif self.category == ENDGAME: 349 for pieces, title in ENDGAMES: 350 self.learnstore.append([pieces, title]) 351 352 learn = conf.get("learncombo%s" % self.category) 353 self.learn_combo.set_active(learn) 354 355 def on_learn_changed(combo): 356 tree_iter = combo.get_active_iter() 357 if tree_iter is None: 358 return 359 else: 360 model = combo.get_model() 361 newlearn = model.get_path(tree_iter)[0] 362 conf.set("learncombo%s" % self.category, newlearn) 363 self.learn_combo.connect("changed", on_learn_changed) 364 365 self.category_combo.connect("changed", on_category_changed) 366 self.category = conf.get("categorycombo") 367 self.category_combo.set_active(self.category) 368 369 uistuff.keep(self.widgets["category_combo"], "categorycombo") 370 371 self.widgets["opendialog4"].connect("clicked", self.openDialogClicked) 372 self.widgets["learnButton"].connect("clicked", self.learnClicked) 373 374 def openDialogClicked(self, button): 375 perspective = perspective_manager.get_perspective("learn") 376 perspective.activate() 377 378 def learnClicked(self, button): 379 perspective = perspective_manager.get_perspective("learn") 380 perspective.activate() 381 382 tree_iter = self.learn_combo.get_active_iter() 383 if tree_iter is None: 384 return 385 else: 386 model = self.learn_combo.get_model() 387 source = model[tree_iter][0] 388 389 if self.category == LECTURE: 390 start_lecture_from(source) 391 elif self.category == LESSON: 392 start_lesson_from(source) 393 elif self.category == PUZZLE: 394 start_puzzle_from(source) 395 elif self.category == ENDGAME: 396 start_endgame_from(source) 397 398 399class DatabaseTasker(Gtk.Alignment): 400 def __init__(self): 401 GObject.GObject.__init__(self) 402 self.widgets = tasker_widgets 403 tasker = self.widgets["databaseTasker"] 404 tasker.unparent() 405 self.add(tasker) 406 407 startButton = self.widgets["openButton"] 408 startButton.set_name("openButton") 409 410 liststore = Gtk.ListStore(str, str) 411 412 self.recent_combo = self.widgets["recent_combo"] 413 self.recent_combo.set_model(liststore) 414 renderer_text = Gtk.CellRendererText() 415 renderer_text.set_property("width-chars", 30) 416 renderer_text.set_property("ellipsize", Pango.EllipsizeMode.END) 417 self.recent_combo.pack_start(renderer_text, True) 418 self.recent_combo.add_attribute(renderer_text, "text", 1) 419 420 self.on_recent_menu_changed(recent_manager, liststore) 421 recent_manager.connect("changed", self.on_recent_menu_changed, liststore) 422 423 self.widgets["opendialog3"].connect("clicked", self.openDialogClicked) 424 self.widgets["openButton"].connect("clicked", self.openClicked) 425 426 def on_recent_menu_changed(self, manager, liststore): 427 liststore.clear() 428 # Just to make sphinx happy... 429 try: 430 for uri in recent_menu.get_uris(): 431 liststore.append((uri, basename(unquote(uri)), )) 432 except TypeError: 433 pass 434 self.recent_combo.set_active(0) 435 436 def openDialogClicked(self, button): 437 dialog = get_open_dialog() 438 439 response = dialog.run() 440 if response == Gtk.ResponseType.OK: 441 filenames = dialog.get_filenames() 442 else: 443 filenames = None 444 445 dialog.destroy() 446 447 if filenames is not None: 448 for filename in filenames: 449 if filename.lower().endswith(".fen"): 450 newGameDialog.loadFileAndRun(filename) 451 else: 452 perspective = perspective_manager.get_perspective("database") 453 perspective.open_chessfile(filename) 454 455 def openClicked(self, button): 456 if self.widgets["createNew"].get_active(): 457 perspective = perspective_manager.get_perspective("database") 458 perspective.create_database() 459 460 else: 461 tree_iter = self.recent_combo.get_active_iter() 462 if tree_iter is None: 463 return 464 else: 465 model = self.recent_combo.get_model() 466 uri = model[tree_iter][0] 467 468 try: 469 urlopen(unquote(uri)).close() 470 perspective = perspective_manager.get_perspective("database") 471 perspective.open_chessfile(unquote(uri)) 472 recent_manager.add_item(uri) 473 except (IOError, OSError): 474 # shomething wrong whit the uri 475 recent_manager.remove_item(uri) 476 477 478new_game_tasker, internet_game_tasker, database_tasker, learn_tasker = \ 479 NewGameTasker(), InternetGameTasker(), DatabaseTasker(), LearnTasker() 480tasker.packTaskers(new_game_tasker, database_tasker, internet_game_tasker, learn_tasker) 481