~ubuntu-branches/ubuntu/precise/gnome-games/precise-proposed

« back to all changes in this revision

Viewing changes to glchess/src/lib/gtkui/gtkui.py

  • Committer: Package Import Robot
  • Author(s): Rodrigo Moya
  • Date: 2011-05-30 13:32:04 UTC
  • mfrom: (1.3.4)
  • mto: (163.1.3 precise)
  • mto: This revision was merged to the branch mainline in revision 143.
  • Revision ID: package-import@ubuntu.com-20110530133204-celaq1v1dsxc48q1
Tags: upstream-3.0.2
ImportĀ upstreamĀ versionĀ 3.0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
# -*- coding: utf-8 -*-
3
 
 
4
 
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
5
 
__license__ = 'GNU General Public License Version 2'
6
 
__copyright__ = 'Copyright 2005-2006  Robert Ancell'
7
 
 
8
 
__all__ = ['GtkUI']
9
 
 
10
 
# TODO: Extend base UI classes?
11
 
 
12
 
import os
13
 
import sys
14
 
import time
15
 
from gettext import gettext as _
16
 
 
17
 
import gobject
18
 
import gtk
19
 
import gtk.gdk
20
 
import cairo
21
 
import pango
22
 
 
23
 
from glchess.defaults import *
24
 
 
25
 
# Stop PyGTK from catching exceptions
26
 
os.environ['PYGTK_FATAL_EXCEPTIONS'] = '1'
27
 
 
28
 
import glchess.config
29
 
import glchess.ui
30
 
import glchess.chess.board
31
 
import dialogs
32
 
import log
33
 
import chessview
34
 
import network
35
 
 
36
 
# Mark all windows with our icon
37
 
gtk.window_set_default_icon_name(ICON_NAME)
38
 
 
39
 
def loadUIFile(name, root = None):
40
 
    ui = gtk.Builder()
41
 
    ui.set_translation_domain(DOMAIN)
42
 
    ui.add_from_file(os.path.join(UI_DIR, name))
43
 
    return ui
44
 
 
45
 
class GLibTimer(glchess.ui.Timer):
46
 
    """
47
 
    """
48
 
    
49
 
    # FIXME: time.time() is _not_ monotonic so this is not really safe at all...
50
 
 
51
 
    def __init__(self, ui, feedback, duration):
52
 
        self.ui        = ui
53
 
        self.feedback  = feedback
54
 
        self.tickTime  = 0
55
 
        self.reportedTickTime = 0
56
 
        
57
 
        self.timer     = None
58
 
        self.tickTimer = None
59
 
        self.startTime = None
60
 
        self.set(duration * 1000)
61
 
        
62
 
    def set(self, duration):
63
 
        """
64
 
        """
65
 
        if self.timer is not None:
66
 
            gobject.source_remove(self.timer)
67
 
        if self.tickTimer is not None:
68
 
            gobject.source_remove(self.tickTimer)
69
 
        self.duration = duration
70
 
        self.consumed = 0
71
 
        
72
 
        # Notified if the second has changed      
73
 
        
74
 
    def __consumed(self, now):
75
 
        # Total time - time at last start - time since start
76
 
        if self.startTime is None:
77
 
            return self.consumed
78
 
        else:
79
 
            return self.consumed + (now - self.startTime)
80
 
        
81
 
    def getRemaining(self):
82
 
        """Extends ui.Timer"""
83
 
        return self.duration - self.__consumed(int(1000 * time.time()))
84
 
 
85
 
    def pause(self):
86
 
        """Extends ui.Timer"""
87
 
        if self.timer is None:
88
 
            return
89
 
        
90
 
        # Calculate the amount of time to use when restarted
91
 
        self.consumed = self.__consumed(int(1000 * time.time()))
92
 
        
93
 
        # Remove timers
94
 
        gobject.source_remove(self.timer)
95
 
        if self.tickTimer is not None:
96
 
            gobject.source_remove(self.tickTimer)
97
 
        self.timer = None
98
 
        self.tickTimer = None
99
 
    
100
 
    def run(self):
101
 
        """Extends ui.Timer"""
102
 
        if self.timer is not None:
103
 
            return
104
 
        
105
 
        # Notify when all time runs out
106
 
        self.startTime = int(1000 * time.time())
107
 
        self.timer = gobject.timeout_add(self.duration - self.consumed, self.__expired)
108
 
        
109
 
        # Notify on the next second boundary
110
 
        self.__setSecondTimer(self.startTime)
111
 
 
112
 
    def __setSecondTimer(self, now):
113
 
        """Set a timer to expire on the next second boundary"""
114
 
        assert(self.tickTimer is None)
115
 
        
116
 
        # Round the remaining time up to the nearest second
117
 
        consumed = self.__consumed(now)
118
 
        t = 1000 * (consumed / 1000 + 1)
119
 
        if t <= self.reportedTickTime:
120
 
            self.tickTime = self.reportedTickTime + 1000
121
 
        else:
122
 
            self.tickTime = t
123
 
        
124
 
        # Notify on this time
125
 
        if self.tickTime > self.duration:
126
 
            self.tickTimer = None
127
 
        else:
128
 
            self.tickTimer = gobject.timeout_add(self.tickTime - consumed, self.__tick)
129
 
 
130
 
    def __expired(self):
131
 
        """Called by GLib main loop"""
132
 
        self.feedback.onTick(0)
133
 
        self.feedback.onExpired()
134
 
        if self.tickTimer is not None:
135
 
            gobject.source_remove(self.tickTimer)
136
 
        self.timer = None
137
 
        self.tickTimer = None
138
 
        return False
139
 
 
140
 
    def __tick(self):
141
 
        """Called by GLib main loop"""
142
 
        self.reportedTickTime = self.tickTime
143
 
        self.feedback.onTick((self.duration - self.tickTime) / 1000)
144
 
        self.tickTimer = None
145
 
        self.__setSecondTimer(int(1000 * time.time()))
146
 
        return False
147
 
 
148
 
    def delete(self):
149
 
        """Extends ui.Timer"""
150
 
        gobject.source_remove(self.timer)
151
 
 
152
 
class GtkUI(glchess.ui.UI):
153
 
    """
154
 
    """
155
 
 
156
 
    def __init__(self, feedback):
157
 
        """Constructor for a GTK+ glChess GUI"""
158
 
        self.feedback = feedback
159
 
        self._watches = {}
160
 
        self.__networkGames = {}
161
 
        self.newGameDialog = None
162
 
        self.loadGameDialog = None
163
 
        self.__aboutDialog = None
164
 
        self.__saveGameDialogs = {}
165
 
        self.__joinGameDialogs = []
166
 
 
167
 
        # The time stored for animation
168
 
        self.__lastTime         = None
169
 
        self.__animationTimer   = None
170
 
    
171
 
        self.__renderGL         = False
172
 
        self.openGLInfoPrinted  = False
173
 
 
174
 
        self.__attentionCounter = 0
175
 
 
176
 
        self.whiteTimeString    = 'āˆž'
177
 
        self.blackTimeString    = 'āˆž'
178
 
    
179
 
        # Theo window width and height when unmaximised and not fullscreen
180
 
        self.width              = None
181
 
        self.height             = None
182
 
        self.isFullscreen       = False
183
 
        self.isMaximised        = False
184
 
    
185
 
        self.view               = None
186
 
 
187
 
        self._gui = loadUIFile('glchess.ui')
188
 
        self._gui.connect_signals(self)
189
 
        
190
 
        self.mainWindow = self._gui.get_object('glchess_app')
191
 
        
192
 
        # Create the model for the player types
193
 
        self.__playerModel = gtk.ListStore(str, str, str)
194
 
        iter = self.__playerModel.append()
195
 
        # Translators: Player Type Combo: Player is human controlled
196
 
        self.__playerModel.set(iter, 0, '', 1, 'stock_person', 2, _('Human'))
197
 
        
198
 
        self.__logWindow = log.LogWindow(self._gui.get_object('log_notebook'))
199
 
        
200
 
        # Make preferences dialog
201
 
        self.preferences = dialogs.GtkPreferencesDialog(self)
202
 
 
203
 
        # Balance space on each side of the history combo
204
 
        group = gtk.SizeGroup(gtk.SIZE_GROUP_BOTH)
205
 
        group.add_widget(self.__getWidget('left_nav_box'))
206
 
        group.add_widget(self.__getWidget('right_nav_box'))
207
 
 
208
 
        # History combo displays text data
209
 
        combo = self.__getWidget('history_combo')
210
 
        cell = gtk.CellRendererText()
211
 
        combo.pack_start(cell, False)
212
 
        combo.add_attribute(cell, 'text', 2)
213
 
        
214
 
        self._updateViewButtons()
215
 
        
216
 
        # Watch for config changes
217
 
        for key in ['show_toolbar', 'show_history', 'fullscreen',
218
 
                    'show_3d', 'show_3d_smooth', 'piece_style', 'show_comments',
219
 
                    'show_numbering', 'show_move_hints', 'width', 'height',
220
 
                    'move_format', 'promotion_type', 'board_view',
221
 
                    'enable_networking']:
222
 
            glchess.config.watch(key, self.__applyConfig)
223
 
 
224
 
    # Public methods
225
 
    
226
 
    def watchFileDescriptor(self, fd):
227
 
        """Extends ui.UI"""
228
 
        self._watches[fd] = gobject.io_add_watch(fd, gobject.IO_IN | gobject.IO_PRI | gobject.IO_HUP | gobject.IO_ERR, self.__readData)
229
 
        
230
 
    def unwatchFileDescriptor(self, fd):
231
 
        """Extends ui.UI"""
232
 
        gobject.source_remove(self._watches.pop(fd))
233
 
        
234
 
    def writeFileDescriptor(self, fd):
235
 
        """Extends ui.UI"""
236
 
        gobject.io_add_watch(fd, gobject.IO_OUT, self.__writeData)
237
 
        
238
 
    def addTimer(self, feedback, duration):
239
 
        """Extends ui.UI"""
240
 
        return GLibTimer(self, feedback, duration)
241
 
 
242
 
    def __timerExpired(self, method):
243
 
        method()
244
 
        return True
245
 
 
246
 
    def __readData(self, fd, condition):
247
 
        #print (fd, condition)
248
 
        return self.feedback.onReadFileDescriptor(fd)
249
 
 
250
 
    def __writeData(self, fd, condition):
251
 
        #print (fd, condition)
252
 
        return self.feedback.onWriteFileDescriptor(fd)
253
 
 
254
 
    def addAIEngine(self, name):
255
 
        """Register an AI engine.
256
 
        
257
 
        'name' is the name of the engine.
258
 
        TODO: difficulty etc etc
259
 
        """
260
 
        iter = self.__playerModel.append()
261
 
        self.__playerModel.set(iter, 0, name, 1, 'stock_notebook', 2, name)
262
 
 
263
 
    def setView(self, title, feedback, isPlayable = True):
264
 
        """Extends ui.UI"""
265
 
        moveFormat = glchess.config.get('move_format')
266
 
        showComments = glchess.config.get('show_comments')
267
 
        self.view = chessview.GtkView(self, feedback, moveFormat = moveFormat, showComments = showComments)
268
 
        self.view.setTitle(title)
269
 
        self.view.isPlayable = isPlayable
270
 
        self.view.viewWidget.setRenderGL(self.__renderGL)
271
 
        viewport = self.__getWidget('game_viewport')
272
 
        child = viewport.get_child()
273
 
        if child is not None:
274
 
            viewport.remove(child)
275
 
        viewport.add(self.view.widget)
276
 
 
277
 
        # Set toolbar/menu buttons to state for this game
278
 
        self._updateViewButtons()
279
 
        
280
 
        # Update timers
281
 
        if self.view is not None:
282
 
            self.setTimers(self.view.whiteTime, self.view.blackTime)
283
 
 
284
 
        return self.view
285
 
 
286
 
    def updateTitle(self):
287
 
        """
288
 
        """
289
 
        # Set the window title to the name of the game
290
 
        if self.view is not None and len(self.view.title) > 0:
291
 
            if self.view.needsSaving:
292
 
                # Translators: Window title when playing a game that needs saving
293
 
                title = _('Chess - *%(game_name)s') % {'game_name': self.view.title}
294
 
            else:
295
 
                # Translators: Window title when playing a game that is saved
296
 
                title = _('Chess - %(game_name)s') % {'game_name': self.view.title}
297
 
        else:
298
 
            # Translators: Window title when not playing a game
299
 
            title = _('Chess')            
300
 
        self.mainWindow.set_title(title)
301
 
 
302
 
    def addLogWindow(self, title, executable, description):
303
 
        """
304
 
        """
305
 
        return self.__logWindow.addView(title, executable, description)
306
 
    
307
 
    def setTimers(self, whiteTime, blackTime):
308
 
        """
309
 
        """
310
 
        # Translators: Game Timer Label: Indicates that game has no time limit
311
 
        unlimitedTimeText = _('āˆž')
312
 
        
313
 
        if whiteTime is None:
314
 
            whiteString = unlimitedTimeText
315
 
        else:
316
 
            t = whiteTime[1]
317
 
            whiteString = '%i:%02i' % (t / 60, t % 60)
318
 
        if blackTime is None:
319
 
            blackString = unlimitedTimeText
320
 
        else:
321
 
            t = blackTime[1]
322
 
            blackString = '%i:%02i' % (t / 60, t % 60)
323
 
            
324
 
        if whiteString != self.whiteTimeString:
325
 
            self.whiteTimeString = whiteString
326
 
            self._gui.get_object('white_time_label').queue_draw()
327
 
        if blackString != self.blackTimeString:
328
 
            self.blackTimeString = blackString
329
 
            self._gui.get_object('black_time_label').queue_draw()
330
 
 
331
 
    def run(self):
332
 
        """Run the UI.
333
 
        
334
 
        This method will not return.
335
 
        """        
336
 
        # Load configuration
337
 
        for name in ['show_toolbar', 'show_history', 'show_3d', 'show_3d_smooth',
338
 
                     'piece_style', 'show_comments', 'show_numbering', 'show_move_hints',
339
 
                     'move_format', 'promotion_type', 'board_view', 'maximised',
340
 
                     'enable_networking']:
341
 
            try:
342
 
                value = glchess.config.get(name)
343
 
            except glchess.config.Error:
344
 
                pass
345
 
            else:
346
 
                self.__applyConfig(name, value)
347
 
        self.__resize()
348
 
        
349
 
        self.mainWindow.show()
350
 
 
351
 
        # Apply the fullscreen flag after the window has been shown otherwise
352
 
        # gtk.Window.unfullscreen() stops working if the window is set to fullscreen
353
 
        # before being shown. I haven't been able to reproduce this in the simple
354
 
        # case (GTK+ 2.10.6-0ubuntu3).
355
 
        self.__applyConfig('fullscreen', glchess.config.get('fullscreen'))
356
 
        
357
 
        gtk.main()
358
 
        
359
 
    # Extended methods
360
 
 
361
 
    def reportGameLoaded(self, game):
362
 
        """Extends glchess.ui.UI"""
363
 
        dialogs.GtkNewGameDialog(self, self.__playerModel, game)
364
 
 
365
 
    def addNetworkDialog(self, feedback):
366
 
        """Extends glchess.ui.UI"""
367
 
        self.__networkDialog = network.GtkNetworkGameDialog(self, feedback)
368
 
        return self.__networkDialog
369
 
                                 
370
 
    def addNetworkGame(self, name, game):
371
 
        """Extends glchess.ui.UI"""
372
 
        self.__networkDialog.addNetworkGame(name, game)
373
 
 
374
 
    def removeNetworkGame(self, game):
375
 
        """Extends glchess.ui.UI"""
376
 
        self.__networkDialog.removeNetworkGame(game)
377
 
            
378
 
    def requestSave(self, title):
379
 
        """Extends glchess.ui.UI"""
380
 
        dialog = gtk.MessageDialog(flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
381
 
                                   type = gtk.MESSAGE_WARNING,
382
 
                                   message_format = title)
383
 
        # Translators: Save Game Dialog: Notice that game needs saving
384
 
        dialog.format_secondary_text(_("If you don't save the changes to this game will be permanently lost"))
385
 
        # Translators: Save Game Dialog: Discard game button
386
 
        dialog.add_button(_('Close _without saving'), gtk.RESPONSE_OK)
387
 
        dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)
388
 
        dialog.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)
389
 
 
390
 
        response = dialog.run()
391
 
        dialog.destroy()
392
 
        if response == gtk.RESPONSE_ACCEPT:
393
 
            return glchess.ui.SAVE_YES
394
 
        elif response == gtk.RESPONSE_OK:
395
 
            return glchess.ui.SAVE_NO
396
 
        else:
397
 
            return glchess.ui.SAVE_ABORT
398
 
 
399
 
    def close(self):
400
 
        """Extends glchess.ui.UI"""
401
 
        # Save the window size
402
 
        if self.width is not None:
403
 
            glchess.config.set('width', self.width)
404
 
        if self.height is not None:
405
 
            glchess.config.set('height', self.height)
406
 
 
407
 
    # Protected methods
408
 
    
409
 
    def _incAttentionCounter(self, offset):
410
 
        """
411
 
        """
412
 
        self.__attentionCounter += offset
413
 
        self.__updateAttention()
414
 
        
415
 
    def __updateAttention(self):
416
 
        """
417
 
        """
418
 
        widget = self.mainWindow
419
 
        widget.set_urgency_hint(self.__attentionCounter != 0 and not widget.is_active())
420
 
        
421
 
    def _on_focus_changed(self, widget, event):
422
 
        """Gtk+ callback"""
423
 
        self.__updateAttention()
424
 
 
425
 
    def _saveView(self, view, path):
426
 
        """
427
 
        """
428
 
        if path is None:
429
 
            error = None
430
 
        else:
431
 
            error = view.feedback.save(path)
432
 
 
433
 
        if error is not None:
434
 
            return error        
435
 
        self.__saveGameDialogs.pop(view)
436
 
 
437
 
    # Private methods
438
 
 
439
 
    def __resize(self):
440
 
        try:
441
 
            width = glchess.config.get('width')
442
 
            height = glchess.config.get('height')
443
 
        except glchess.config.Error:
444
 
            return
445
 
        
446
 
        self.mainWindow.resize(width, height)
447
 
 
448
 
    def __applyConfig(self, name, value):
449
 
        """
450
 
        """
451
 
        if name == 'width' or name == 'height':
452
 
            self.__resize()
453
 
            return
454
 
 
455
 
        # Show/hide the toolbar
456
 
        if name == 'show_toolbar':
457
 
            toolbar = self.__getWidget('toolbar')
458
 
            if value is True:
459
 
                toolbar.show()
460
 
            else:
461
 
                toolbar.hide()
462
 
                
463
 
        elif name == 'enable_networking':
464
 
            menuItem = self.__getWidget('menu_play_online_item')
465
 
            toolbarButton = self.__getWidget('play_online_button')
466
 
            if value is True:
467
 
                menuItem.show()
468
 
                toolbarButton.show()
469
 
            else:
470
 
                menuItem.hide()
471
 
                toolbarButton.hide()
472
 
            
473
 
        # Show/hide the history
474
 
        elif name == 'show_history':
475
 
            box = self.__getWidget('navigation_box')
476
 
            if value is True:
477
 
                box.show()
478
 
            else:
479
 
                box.hide()
480
 
                
481
 
        # Maximised mode
482
 
        elif name == 'maximised':
483
 
            window = self.mainWindow
484
 
            if value is True:
485
 
                window.maximize()
486
 
            else:
487
 
                window.unmaximize()
488
 
 
489
 
        # Fullscreen mode
490
 
        elif name == 'fullscreen':
491
 
            window = self.mainWindow
492
 
            if value is True:
493
 
                window.fullscreen()
494
 
            else:
495
 
                window.unfullscreen()
496
 
 
497
 
        # Enable/disable OpenGL rendering
498
 
        elif name == 'show_3d':            
499
 
            if value and not chessview.haveGLSupport:
500
 
                # Translators: No 3D Dialog: Title
501
 
                title = _('Unable to enable 3D mode')
502
 
                errors = '\n'.join(chessview.openGLErrors)
503
 
                # Translators: No 3D Dialog: Notification to user that they do not have libraries required to enable 3D.
504
 
                # %(error)s will be replaced with a list of reasons why 3D is not available.
505
 
                description = _("""You are unable to play in 3D mode due to the following problems:
506
 
%(errors)s
507
 
 
508
 
Please contact your system administrator to resolve these problems, until then you will be able to play chess in 2D mode.""") % {'errors': errors}
509
 
                dialog = gtk.MessageDialog(type = gtk.MESSAGE_WARNING, message_format = title)
510
 
                dialog.format_secondary_text(description)
511
 
                dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
512
 
                dialog.run()
513
 
                dialog.destroy()
514
 
                glchess.config.set('show_3d', False)
515
 
                value = False
516
 
            self.__renderGL = value
517
 
            self.__getWidget('menu_view_3d').set_active(value)
518
 
            self.view.viewWidget.setRenderGL(value)
519
 
 
520
 
        elif name == 'show_3d_smooth':
521
 
            if not chessview.haveGLAccumSupport:
522
 
                value = False
523
 
            self.view.feedback.showSmooth(value)
524
 
                
525
 
        elif name == 'piece_style':
526
 
            pass
527
 
 
528
 
        elif name == 'show_comments':
529
 
            self.view.setShowComments(value)
530
 
 
531
 
        elif name == 'show_move_hints':
532
 
            self.view.feedback.showMoveHints(value)
533
 
 
534
 
        elif name == 'show_numbering':
535
 
            self.view.feedback.showBoardNumbering(value)
536
 
 
537
 
        elif name == 'move_format':
538
 
            self.view.setMoveFormat(value)
539
 
                
540
 
        elif name == 'promotion_type':
541
 
            pass
542
 
    
543
 
        elif name == 'board_view':
544
 
            pass
545
 
 
546
 
        else:
547
 
            assert(False), 'Unknown config item: %s' % name
548
 
 
549
 
    def startAnimation(self):
550
 
        """Start the animation callback"""
551
 
        if self.__animationTimer is None:
552
 
            self.__lastTime = time.time()
553
 
            self.__animationTimer = gobject.timeout_add(10, self.__animate)
554
 
 
555
 
    def __animate(self):
556
 
        # Get the timestep, if it is less than zero or more than a second
557
 
        # then the system clock was probably changed.
558
 
        now = time.time()
559
 
        step = now - self.__lastTime
560
 
        if step < 0.0:
561
 
            step = 0.0
562
 
        elif step > 1.0:
563
 
            step = 1.0
564
 
        self.__lastTime = now
565
 
        
566
 
        # Animate!
567
 
        animating = self.feedback.onAnimate(step)
568
 
        if not animating:
569
 
            self.__animationTimer = None
570
 
        
571
 
        # Keep/delete timer
572
 
        return animating
573
 
 
574
 
    def __getWidget(self, name):
575
 
        widget = self._gui.get_object(name)
576
 
        assert(widget is not None), 'Unable to find widget: %s' % name
577
 
        return widget
578
 
 
579
 
    def _on_white_time_paint(self, widget, event):
580
 
        """Gtk+ callback"""
581
 
        self.__drawTime(self.whiteTimeString, widget, (0.0, 0.0, 0.0), (1.0, 1.0, 1.0))
582
 
 
583
 
    def _on_black_time_paint(self, widget, event):
584
 
        """Gtk+ callback"""
585
 
        self.__drawTime(self.blackTimeString, widget, (1.0, 1.0, 1.0), (0.0, 0.0, 0.0))
586
 
 
587
 
    def __drawTime(self, text, widget, fg, bg):
588
 
        """
589
 
        """
590
 
        if widget.state == gtk.STATE_INSENSITIVE:
591
 
            alpha = 0.5
592
 
        else:
593
 
            alpha = 1.0
594
 
        context = widget.window.cairo_create()
595
 
        context.set_source_rgba(bg[0], bg[1], bg[2], alpha)
596
 
        context.paint()
597
 
        
598
 
        (_, _, w, h) = widget.get_allocation()
599
 
        
600
 
        context.set_source_rgba(fg[0], fg[1], fg[2], alpha)
601
 
        context.select_font_face('fixed', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
602
 
        context.set_font_size(0.6 * h)
603
 
        (x_bearing, y_bearing, width, height, _, _) = context.text_extents(text)
604
 
        context.move_to((w - width) / 2 - x_bearing, (h - height) / 2 - y_bearing)
605
 
        context.show_text(text)
606
 
        
607
 
        # Resize to fit text
608
 
        widget.set_size_request(int(width) + 6, -1)
609
 
 
610
 
    def _on_toggle_3d_clicked(self, widget):
611
 
        """Gtk+ callback"""
612
 
        if widget.get_active():
613
 
            value = True
614
 
        else:
615
 
            value = False
616
 
        glchess.config.set('show_3d', value)
617
 
 
618
 
    def _on_show_logs_clicked(self, widget):
619
 
        """Gtk+ callback"""
620
 
        window = self._gui.get_object('log_window')
621
 
        if widget.get_active():
622
 
            window.present()
623
 
        else:
624
 
            window.hide()
625
 
 
626
 
    def _on_history_combo_changed(self, widget):
627
 
        """Gtk+ callback"""
628
 
        model = widget.get_model()
629
 
        iter = widget.get_active_iter()
630
 
        if iter is None:
631
 
            return
632
 
        
633
 
        # Get the move number
634
 
        moveNumber = model.get_value(iter, 1)
635
 
        
636
 
        if moveNumber == len(model) - 1:
637
 
            moveNumber = -1
638
 
 
639
 
        # Disable buttons when at the end
640
 
        haveMoves = len(model) > 1
641
 
        for widget in ('first_move_button', 'prev_move_button'):
642
 
            self.__getWidget(widget).set_sensitive(haveMoves and moveNumber != 0)
643
 
        for widget in ('last_move_button', 'next_move_button'):
644
 
            self.__getWidget(widget).set_sensitive(haveMoves and moveNumber != -1)
645
 
 
646
 
        self.view._setMoveNumber(moveNumber)
647
 
 
648
 
    def __selectMoveNumber(self, moveNumber):
649
 
        """FIXME
650
 
        """
651
 
        combo = self.__getWidget('history_combo')
652
 
        
653
 
        # Limit moves to the maximum value
654
 
        maxNumber = len(combo.get_model())
655
 
 
656
 
        # Allow negative indexing
657
 
        if moveNumber < 0:
658
 
            moveNumber = maxNumber + moveNumber
659
 
        if moveNumber < 0:
660
 
            moveNumber = 0
661
 
        if moveNumber >= maxNumber:
662
 
            moveNumber = maxNumber - 1
663
 
        
664
 
        combo.set_active(moveNumber)
665
 
 
666
 
    def __selectMoveNumberRelative(self, offset):
667
 
        """FIXME
668
 
        """
669
 
        combo = self.__getWidget('history_combo')
670
 
        selected = combo.get_active()
671
 
        maxNumber = len(combo.get_model())
672
 
        new = selected + offset
673
 
        if new < 0:
674
 
            new = 0
675
 
        elif new >= maxNumber:
676
 
            new = maxNumber - 1
677
 
        self.__selectMoveNumber(new)
678
 
 
679
 
    def _on_history_start_clicked(self, widget):
680
 
        """Gtk+ callback"""
681
 
        self.__selectMoveNumber(0)
682
 
 
683
 
    def _on_history_previous_clicked(self, widget):
684
 
        """Gtk+ callback"""
685
 
        self.__selectMoveNumberRelative(-1)
686
 
 
687
 
    def _on_history_next_clicked(self, widget):
688
 
        """Gtk+ callback"""
689
 
        self.__selectMoveNumberRelative(1)
690
 
 
691
 
    def _on_history_latest_clicked(self, widget):
692
 
        """Gtk+ callback"""
693
 
        self.__selectMoveNumber(-1)
694
 
 
695
 
    def _updateViewButtons(self):
696
 
        """
697
 
        """
698
 
        enable = self.view is not None and self.view.isPlayable
699
 
        for widget in ('menu_save_item', 'menu_save_as_item'):
700
 
            self.__getWidget(widget).set_sensitive(enable)
701
 
 
702
 
        combo = self.__getWidget('history_combo')
703
 
        if self.view is None:
704
 
            if combo.get_model() != None:
705
 
                combo.set_model(None)
706
 
        else:
707
 
            (model, selected) = self.view._getModel()
708
 
            combo.set_model(model)
709
 
            if selected < 0:
710
 
                selected = len(model) + selected
711
 
            combo.set_active(selected)
712
 
        self.__getWidget('navigation_box').set_sensitive(enable)
713
 
        
714
 
        enable = enable and self.view.gameResult is None
715
 
        '''FIXME! for widget in ('menu_resign', 'resign_button', 'menu_claim_draw'):
716
 
            self.__getWidget(widget).set_sensitive(enable)'''
717
 
 
718
 
    def _on_new_game_button_clicked(self, widget):
719
 
        """Gtk+ callback"""
720
 
        if self.newGameDialog:
721
 
            self.newGameDialog.window.present()
722
 
        else:
723
 
            self.newGameDialog = dialogs.GtkNewGameDialog(self, self.__playerModel)
724
 
 
725
 
    def _on_join_game_button_clicked(self, widget):
726
 
        """Gtk+ callback"""
727
 
        self.feedback.onNewNetworkGame()
728
 
 
729
 
    def _on_open_game_button_clicked(self, widget):
730
 
        """Gtk+ callback"""
731
 
        if self.loadGameDialog:
732
 
            self.loadGameDialog.window.present()
733
 
        else:
734
 
            self.loadGameDialog = dialogs.GtkLoadGameDialog(self)
735
 
        
736
 
    def _on_save_game_button_clicked(self, widget):
737
 
        """Gtk+ callback"""
738
 
        if self.view.feedback.getFileName() is not None:
739
 
            self.view.feedback.save()
740
 
            return
741
 
        
742
 
        try:
743
 
            dialog = self.__saveGameDialogs[self.view]
744
 
        except KeyError:
745
 
            dialog = self.__saveGameDialogs[self.view] = dialogs.GtkSaveGameDialog(self, self.view)
746
 
        dialog.window.present()
747
 
 
748
 
    def _on_save_as_game_button_clicked(self, widget):
749
 
        """Gtk+ callback"""
750
 
        try:
751
 
            dialog = self.__saveGameDialogs[self.view]
752
 
        except KeyError:
753
 
            dialog = self.__saveGameDialogs[self.view] = dialogs.GtkSaveGameDialog(self, self.view, self.view.feedback.getFileName())
754
 
        dialog.window.present()
755
 
        
756
 
    def _on_undo_move_clicked(self, widget):
757
 
        """Gtk+ callback"""
758
 
        self.view.feedback.undo()
759
 
 
760
 
    def _on_resign_clicked(self, widget):
761
 
        """Gtk+ callback"""
762
 
        self.view.feedback.resign()
763
 
 
764
 
    def _on_claim_draw_clicked(self, widget):
765
 
        """Gtk+ callback"""
766
 
        if self.view.feedback.claimDraw():
767
 
            return
768
 
        
769
 
        # Translators: Draw Dialog: Title
770
 
        title = _("Unable to claim draw")
771
 
        # Translators: Draw Dialog: Notify user why they cannot claim draw
772
 
        message = _("""You may claim a draw when:
773
 
a) The board has been in the same state three times (Three fold repetition)
774
 
b) Fifty moves have occurred where no pawn has moved and no piece has been captured (50 move rule)""")
775
 
        
776
 
        dialog = gtk.MessageDialog(flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
777
 
                                   type = gtk.MESSAGE_WARNING,
778
 
                                   message_format = title)
779
 
        dialog.format_secondary_text(message)
780
 
        dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)
781
 
        dialog.run()
782
 
        dialog.destroy()
783
 
 
784
 
    def _on_preferences_clicked(self, widget):
785
 
        """Gtk+ callback"""
786
 
        self.preferences.setVisible(True)
787
 
 
788
 
    def _on_help_clicked(self, widget):
789
 
        """Gtk+ callback"""
790
 
        try:
791
 
            gtk.show_uri(self.mainWindow.get_screen(), "ghelp:glchess", gtk.get_current_event_time())
792
 
        except gobject.GError, e:
793
 
            # TODO: This should be a pop-up dialog
794
 
            print _('Unable to display help: %s') % str(e)
795
 
 
796
 
    def _on_view_fullscreen_clicked(self, widget):
797
 
        """Gtk+ callback"""
798
 
        glchess.config.set('fullscreen', True)
799
 
        
800
 
    def _on_view_unfullscreen_clicked(self, widget):
801
 
        """Gtk+ callback"""
802
 
        glchess.config.set('fullscreen', False)
803
 
        
804
 
    def _on_3d_support_dialog_delete_event(self, widget, event):
805
 
        """Gtk+ callback"""
806
 
        # Stop the dialog from deleting itself
807
 
        return True
808
 
 
809
 
    def _on_3d_support_dialog_response(self, widget, responseId):
810
 
        """Gtk+ callback"""
811
 
        if self.__aboutDialog is not None:
812
 
            return
813
 
        widget.hide()
814
 
        return False
815
 
 
816
 
    def _on_about_clicked(self, widget):
817
 
        """Gtk+ callback"""
818
 
        if self.__aboutDialog is not None:
819
 
            return
820
 
        
821
 
        dialog = self.__aboutDialog = gtk.AboutDialog()
822
 
        dialog.set_transient_for(self.mainWindow)
823
 
        dialog.set_name(APPNAME)
824
 
        dialog.set_version(VERSION)
825
 
        dialog.set_copyright(COPYRIGHT)
826
 
        dialog.set_license(LICENSE[0] + '\n\n' + LICENSE[1] + '\n\n' +LICENSE[2])
827
 
        dialog.set_wrap_license(True)
828
 
        dialog.set_comments(DESCRIPTION)
829
 
        dialog.set_authors(AUTHORS)
830
 
        dialog.set_artists(ARTISTS)
831
 
        dialog.set_translator_credits(_("translator-credits"))
832
 
        dialog.set_website(WEBSITE)
833
 
        dialog.set_website_label(WEBSITE_LABEL)
834
 
        dialog.set_logo_icon_name(ICON_NAME)
835
 
        dialog.connect('response', self._on_glchess_about_dialog_close)
836
 
        dialog.show()
837
 
        
838
 
    def _on_glchess_about_dialog_close(self, widget, event):
839
 
        """Gtk+ callback"""
840
 
        self.__aboutDialog.destroy()
841
 
        self.__aboutDialog = None
842
 
        return False
843
 
        
844
 
    def _on_log_window_delete_event(self, widget, event):
845
 
        """Gtk+ callback"""
846
 
        self._gui.get_object('menu_view_logs').set_active(False)
847
 
        
848
 
        # Stop the event - the window will be closed by the menu event
849
 
        return True
850
 
    
851
 
    def _on_resize(self, widget, event):
852
 
        """Gtk+ callback"""
853
 
        if self.isMaximised or self.isFullscreen:
854
 
            return
855
 
        self.width = event.width
856
 
        self.height = event.height
857
 
 
858
 
    def _on_window_state_changed(self, widget, event):
859
 
        """Gtk+ callback"""
860
 
        if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED:
861
 
            self.isMaximised = event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED != 0
862
 
            glchess.config.set('maximised', self.isMaximised)
863
 
            
864
 
        if event.changed_mask & gtk.gdk.WINDOW_STATE_FULLSCREEN:
865
 
            self.isFullscreen = event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN != 0
866
 
            if self.isFullscreen:
867
 
                self._gui.get_object('menu_fullscreen').hide()
868
 
                self._gui.get_object('menu_leave_fullscreen').show()
869
 
            else:
870
 
                self._gui.get_object('menu_leave_fullscreen').hide()
871
 
                self._gui.get_object('menu_fullscreen').show()
872
 
 
873
 
    def _on_close_window(self, widget, event):
874
 
        """Gtk+ callback"""
875
 
        self.feedback.onQuit()
876
 
        return True
877
 
        
878
 
    def _on_menu_quit(self, widget):
879
 
        """Gtk+ callback"""
880
 
        self.feedback.onQuit()
881
 
 
882
 
if __name__ == '__main__':
883
 
    ui = GtkUI()
884
 
    ui.run()