1# -*- coding: UTF-8 -*- 2from io import StringIO 3 4from gi.repository import Gtk, GObject 5 6from pychess.compat import create_task 7from pychess.Utils.const import DRAW, LOCAL, WHITE, BLACK, WAITING_TO_START, reprResult, \ 8 UNDOABLE_STATES, FIRST_PAGE, PREV_PAGE, NEXT_PAGE 9from pychess.Players.Human import Human 10from pychess.Utils.GameModel import GameModel 11from pychess.perspectives import perspective_manager 12from pychess.Variants import variants 13from pychess.Database.model import game, event, site, pl1, pl2 14from pychess.widgets import newGameDialog 15from pychess.Savers import pgn 16 17 18cols = (game.c.id, pl1.c.name, game.c.white_elo, pl2.c.name, game.c.black_elo, 19 game.c.result, game.c.date, event.c.name, site.c.name, game.c.round, 20 game.c.ply_count, game.c.eco, game.c.time_control, game.c.variant, game.c.fen) 21 22 23class GameList(Gtk.TreeView): 24 def __init__(self, persp): 25 GObject.GObject.__init__(self) 26 self.persp = persp 27 28 self.records = [] 29 self.preview_cid = None 30 31 # GTK_SELECTION_BROWSE - exactly one item is always selected 32 self.get_selection().set_mode(Gtk.SelectionMode.BROWSE) 33 34 self.liststore = Gtk.ListStore(int, str, str, str, str, str, str, str, 35 str, str, str, str, str, str, str) 36 self.modelsort = Gtk.TreeModelSort(self.liststore) 37 38 self.modelsort.set_sort_column_id(0, Gtk.SortType.ASCENDING) 39 self.modelsort.connect("sort-column-changed", self.sort_column_changed) 40 self.set_model(self.modelsort) 41 self.get_selection().set_mode(Gtk.SelectionMode.BROWSE) 42 self.set_headers_visible(True) 43 self.set_rules_hint(True) 44 self.set_search_column(1) 45 46 titles = (_("Id"), _("White"), _("W Elo"), _("Black"), _("B Elo"), 47 _("Result"), _("Date"), _("Event"), _("Site"), _("Round"), 48 _("Length"), "ECO", _("Time control"), _("Variant"), "FEN") 49 50 for i, title in enumerate(titles): 51 r = Gtk.CellRendererText() 52 column = Gtk.TreeViewColumn(title, r, text=i) 53 column.set_resizable(True) 54 column.set_reorderable(True) 55 column.set_sort_column_id(i) 56 self.append_column(column) 57 58 self.connect("row-activated", self.row_activated) 59 60 self.set_cursor(0) 61 self.columns_autosize() 62 self.gamemodel = GameModel() 63 self.ply = 0 64 65 # buttons 66 toolbar = Gtk.Toolbar() 67 68 firstButton = Gtk.ToolButton(stock_id=Gtk.STOCK_MEDIA_PREVIOUS) 69 firstButton.set_tooltip_text(_("First games")) 70 toolbar.insert(firstButton, -1) 71 72 prevButton = Gtk.ToolButton(stock_id=Gtk.STOCK_MEDIA_REWIND) 73 prevButton.set_tooltip_text(_("Previous games")) 74 toolbar.insert(prevButton, -1) 75 76 nextButton = Gtk.ToolButton(stock_id=Gtk.STOCK_MEDIA_FORWARD) 77 nextButton.set_tooltip_text(_("Next games")) 78 toolbar.insert(nextButton, -1) 79 80 firstButton.connect("clicked", self.on_first_clicked) 81 prevButton.connect("clicked", self.on_prev_clicked) 82 nextButton.connect("clicked", self.on_next_clicked) 83 84 limit_combo = Gtk.ComboBoxText() 85 for limit in ("100", "500", "1000", "5000"): 86 limit_combo.append_text(limit) 87 limit_combo.set_active(0) 88 89 toolitem = Gtk.ToolItem.new() 90 toolitem.add(limit_combo) 91 toolbar.insert(toolitem, -1) 92 limit_combo.connect("changed", self.on_limit_combo_changed) 93 94 sw = Gtk.ScrolledWindow() 95 sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) 96 sw.add(self) 97 98 self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 99 100 self.box.pack_start(sw, True, True, 0) 101 self.box.pack_start(toolbar, False, False, 0) 102 self.box.show_all() 103 104 def on_first_clicked(self, button): 105 self.load_games(direction=FIRST_PAGE) 106 107 def on_prev_clicked(self, button): 108 self.load_games(direction=PREV_PAGE) 109 110 def on_next_clicked(self, button): 111 self.load_games(direction=NEXT_PAGE) 112 113 def on_limit_combo_changed(self, combo): 114 text = combo.get_active_text() 115 if text is not None: 116 self.persp.chessfile.limit = int(text) 117 self.load_games(direction=FIRST_PAGE) 118 119 def sort_column_changed(self, treesortable): 120 sort_column_id, order = treesortable.get_sort_column_id() 121 if sort_column_id is None: 122 self.modelsort.set_sort_column_id(0, Gtk.SortType.ASCENDING) 123 sort_column_id, order = 0, Gtk.SortType.ASCENDING 124 125 self.set_search_column(sort_column_id) 126 is_desc = order == Gtk.SortType.DESCENDING 127 self.persp.chessfile.set_tag_order(cols[sort_column_id], is_desc) 128 self.load_games(direction=FIRST_PAGE) 129 130 def load_games(self, direction=FIRST_PAGE): 131 selection = self.get_selection() 132 if selection is not None and self.preview_cid is not None and \ 133 selection.handler_is_connected(self.preview_cid): 134 with GObject.signal_handler_block(selection, self.preview_cid): 135 self.liststore.clear() 136 else: 137 self.liststore.clear() 138 139 add = self.liststore.append 140 141 self.records = [] 142 records, plys = self.persp.chessfile.get_records(direction) 143 for i, rec in enumerate(records): 144 game_id = rec["Id"] 145 offs = rec["Offset"] 146 wname = rec["White"] 147 bname = rec["Black"] 148 welo = rec["WhiteElo"] 149 belo = rec["BlackElo"] 150 result = rec["Result"] 151 result = "½-½" if result == DRAW else reprResult[result] if result else "*" 152 event = "" if rec["Event"] is None else rec["Event"].replace("?", "") 153 site = "" if rec["Site"] is None else rec["Site"].replace("?", "") 154 round_ = "" if rec["Round"] is None else rec["Round"].replace("?", "") 155 date = "" if rec["Date"] is None else rec["Date"].replace(".??", "").replace("????.", "") 156 157 try: 158 ply = rec["PlyCount"] 159 length = str(int(ply) // 2) if ply else "" 160 except ValueError: 161 length = "" 162 eco = rec["ECO"] 163 tc = rec["TimeControl"] 164 variant = rec["Variant"] 165 variant = variants[variant].cecp_name.capitalize() if variant else "" 166 fen = rec["FEN"] 167 168 add([game_id, wname, welo, bname, belo, result, date, event, site, 169 round_, length, eco, tc, variant, fen]) 170 171 ply = plys.get(offs) if offs in plys else 0 172 self.records.append((rec, ply)) 173 174 self.set_cursor(0) 175 176 def get_record(self, path): 177 if path is None: 178 return None, None 179 else: 180 return self.records[self.modelsort.convert_path_to_child_path(path)[0]] 181 182 def row_activated(self, widget, path, col): 183 rec, ply = self.get_record(path) 184 if rec is None: 185 return 186 187 # Enable unfinished games to continue from newgamedialog 188 if rec["Result"] not in UNDOABLE_STATES: 189 newGameDialog.EnterNotationExtension.run() 190 model = self.persp.chessfile.loadToModel(rec) 191 text = pgn.save(StringIO(), model) 192 newGameDialog.EnterNotationExtension.sourcebuffer.set_text(text) 193 return 194 195 self.gamemodel = GameModel() 196 197 variant = rec["Variant"] 198 if variant: 199 self.gamemodel.tags["Variant"] = variant 200 201 # Lichess exports study .pgn without White and Black tags 202 wp = "" if rec["White"] is None else rec["White"] 203 bp = "" if rec["Black"] is None else rec["Black"] 204 p0 = (LOCAL, Human, (WHITE, wp), wp) 205 p1 = (LOCAL, Human, (BLACK, bp), bp) 206 self.persp.chessfile.loadToModel(rec, -1, self.gamemodel) 207 208 self.gamemodel.endstatus = self.gamemodel.status if self.gamemodel.status in UNDOABLE_STATES else None 209 self.gamemodel.status = WAITING_TO_START 210 211 perspective_manager.activate_perspective("games") 212 perspective = perspective_manager.get_perspective("games") 213 create_task(perspective.generalStart(self.gamemodel, p0, p1)) 214