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