~ubuntu-branches/ubuntu/raring/pybik/raring

« back to all changes in this revision

Viewing changes to pybiklib/drawingarea.py

  • Committer: Package Import Robot
  • Author(s): B. Clausius
  • Date: 2013-02-03 17:35:32 UTC
  • mfrom: (1.1.2)
  • Revision ID: package-import@ubuntu.com-20130203173532-a71ulf5b07fcul37
Tags: 1.0.1-1
* New upstream release
  + Improved user interface.
  + Added Towers and Bricks (non cubic puzzles).
  + Added an option to show the back faces.
  + The cube can be manipulated with the keyboard.
  + Animation is faster and rendering more beautiful.
  + Added more pretty patterns.
  + Added a new solver.
  + Added new translations.
* More generic watch file based on the proposal by Bart Martens
* Updated debhelper dependency and compat to 9
* Updated Standards-Version to 3.9.4, no changes needed
* debian/copyright:
  + Updated Format URL for final copyright format 1.0
  + Added paragraphs for image files
* Updated Build-Depends: new: python-numpy, python-qt4, help2man
* Updated Depends for transitions:
  + GTK2/GConf -> Qt4 (PySide or PyQt4)
  + GtkGlExt -> QtOpenGL (PySide or PyQt4)
* Suggests python-opengl (unusual usage) and gconf2 (config transition)
* Splittet into an arch dependent and an arch independent package
  (increased size and build time)
* Enabled parallel build for the architecture independent part
* Install autogenerated README file without install paragraph
* Replace the license file (displayed in the about box) by a link
  to usr/share/common-licenses/GPL-3 and add lintian override

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#-*- coding:utf-8 -*-
 
2
 
 
3
#  Pybik -- A 3 dimensional magic cube game.
 
4
#  Copyright © 2009-2012  B. Clausius <barcc@gmx.de>
 
5
#
 
6
#  This program is free software: you can redistribute it and/or modify
 
7
#  it under the terms of the GNU General Public License as published by
 
8
#  the Free Software Foundation, either version 3 of the License, or
 
9
#  (at your option) any later version.
 
10
#
 
11
#  This program is distributed in the hope that it will be useful,
 
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
#  GNU General Public License for more details.
 
15
#
 
16
#  You should have received a copy of the GNU General Public License
 
17
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
# Ported from GNUbik
 
20
# Original filename: glarea.c
 
21
# Original copyright and license: 2003, 2004  John Darrington,  GPL3+
 
22
 
 
23
from __future__ import print_function, division, unicode_literals
 
24
 
 
25
import os
 
26
from collections import namedtuple
 
27
import math
 
28
 
 
29
# pylint: disable=W0614,W0401
 
30
from .debug import *
 
31
 
 
32
from PySide.QtOpenGL import *
 
33
from PySide.QtCore import *
 
34
from PySide.QtGui import *
 
35
# pylint: enable=W0614,W0401
 
36
 
 
37
# no need for OpenGL module for just two constants
 
38
GL_RGBA, GL_TEXTURE_2D = 6408, 3553
 
39
if DEBUG:
 
40
    from OpenGL import GL
 
41
    assert GL.GL_RGBA == GL_RGBA and GL.GL_TEXTURE_2D == GL_TEXTURE_2D
 
42
 
 
43
from . import config
 
44
import gldraw
 
45
import glarea
 
46
from .textures import textures
 
47
from .settings import settings
 
48
from . import model
 
49
 
 
50
 
 
51
class CubeArea (QGLWidget):
 
52
    end_animation = Signal(bool)
 
53
    request_rotation = Signal((int, int, bool))
 
54
    request_swap_blocks = Signal((int, int, int, bool))
 
55
    request_rotate_block = Signal((int, bool))
 
56
    drop_color = Signal((int, str, str))
 
57
    drop_file = Signal((int, str, str))
 
58
    
 
59
    def __init__(self):
 
60
        glformat = QGLFormat()
 
61
        if settings.draw.samples > 0:
 
62
            glformat.setSampleBuffers(True)
 
63
            glformat.setSamples(2**settings.draw.samples)
 
64
        if settings.draw.accum > 0:
 
65
            glformat.setAccum(True)
 
66
        else:
 
67
            accum_buffers = 0
 
68
        QGLWidget.__init__(self, glformat)
 
69
        
 
70
        self.model = model.empty_model
 
71
        
 
72
        self.selection_debug_info = None
 
73
        
 
74
        self.last_mouse_x = -1
 
75
        self.last_mouse_y = -1
 
76
        self.button_down_background = False
 
77
        self.timer_animate = QTimer() # the animate timer
 
78
        self.timer_animate.timeout.connect(self._on_animate)
 
79
        self.mouse_xy = -1, -1
 
80
        # Structure to hold copy of the last selection taken or None
 
81
        self.pickdata = None
 
82
        self.selection_mode = None
 
83
        self.editing_model = False
 
84
        self.stop_requested = False
 
85
        settings.keystore.changed.connect(self.on_settings_changed, Qt.QueuedConnection)
 
86
        
 
87
        if DEBUG_FPS:
 
88
            self.monotonic_time = QElapsedTimer()
 
89
            self.monotonic_time.start()
 
90
            self.render_count = 0
 
91
            self.fps = 0.
 
92
            
 
93
        self.speed = settings.draw.speed
 
94
        glarea.init_module()
 
95
        gldraw.init_module()
 
96
        self.rotation_x, self.rotation_y = glarea.set_rotation_xy(*settings.draw.default_rotation)
 
97
        accum_buffers = settings.draw.accum if self.format().accum() else 0
 
98
        sample_buffers = self.format().sampleBuffers()
 
99
        glarea.set_antialiasing(accum_buffers, sample_buffers)
 
100
        
 
101
        self.setAcceptDrops(True)
 
102
        self.setFocusPolicy(Qt.StrongFocus)
 
103
        self.setMinimumSize(200, 200)
 
104
        
 
105
        self.cursors = None
 
106
        self.load_cursors()
 
107
        self.update_selection_pending = False
 
108
        self.set_cursor()
 
109
        self.setMouseTracking(True)
 
110
        
 
111
    def load_cursors(self):
 
112
        cursors = []
 
113
        # Load 3 cursors from file (n - ne)
 
114
        for i, (x, y) in enumerate([(8, 0), (15, 0), (15, 0)]):
 
115
            filename = os.path.join(config.UI_DIR, 'mouse_{}.png'.format(i))
 
116
            image = QImage(filename)
 
117
            cursors.append((image, x, y))
 
118
        # 1 cursor (nnw)
 
119
        image, x, y = cursors[1]
 
120
        cursors.insert(0, (image.mirrored(True, False), 15-x, y))
 
121
        # 12 cursors (ene - nw)
 
122
        transform = QTransform()
 
123
        transform.rotate(90)
 
124
        for i in range(4, 16):
 
125
            image, x, y = cursors[-4]
 
126
            cursors.append((image.transformed(transform), 15-y, x))
 
127
        cursors.append(cursors[0])
 
128
        self.cursors = [QCursor(QPixmap.fromImage(image), x, y) for image, x, y in cursors[1:]]
 
129
        # cursor for center faces
 
130
        filename = os.path.join(config.UI_DIR, 'mouse_ccw.png')
 
131
        cursor = QCursor(QPixmap(filename), 7, 7)
 
132
        self.cursors.append(cursor)
 
133
        # background cursor
 
134
        cursor = QCursor()
 
135
        cursor.setShape(Qt.CursorShape.CrossCursor)
 
136
        self.cursors.append(cursor)
 
137
        
 
138
    def apply_design(self):
 
139
        for i in xrange(6):
 
140
            color = settings.theme.face[i].color
 
141
            imagefile = settings.theme.face[i].image
 
142
            imagemode = settings.theme.face[i].mode
 
143
            self.set_face_design(i, color, imagefile, imagemode)
 
144
        self.set_lighting(settings.draw.lighting)
 
145
        self.set_background_color(settings.theme.bgcolor)
 
146
        
 
147
    stock_texnames = [-1, 0]
 
148
    def set_face_texture(self, faceidx, imagefile):
 
149
        if imagefile.startswith('/'):
 
150
            pixbuf = textures.create_pixbuf_from_file(imagefile)
 
151
        else:
 
152
            pixbuf = textures.get_stock_pixbuf(imagefile)
 
153
        if pixbuf is None:
 
154
            texname = -1
 
155
        else:
 
156
            texname = self.bindTexture(pixbuf, GL_TEXTURE_2D, GL_RGBA,
 
157
                                   QGLContext.BindOption.LinearFilteringBindOption|
 
158
                                   QGLContext.BindOption.MipmapBindOption)
 
159
        if not imagefile.startswith('/') and texname not in self.stock_texnames:
 
160
            self.stock_texnames.append(texname)
 
161
        texname = gldraw.set_face_texture(faceidx, texname)
 
162
        if texname not in self.stock_texnames:
 
163
            self.deleteTexture(texname)
 
164
        
 
165
    def set_face_design(self, i, color, imagefile, imagemode):
 
166
        rgba = QColor()
 
167
        rgba.setNamedColor(color)
 
168
        gldraw.set_face_rendering(i, red=rgba.redF(), green=rgba.greenF(), blue=rgba.blueF(),
 
169
                                     imagemode=imagemode)
 
170
        self.set_face_texture(i, imagefile)
 
171
        
 
172
    def set_model(self, model_):
 
173
        self.model = model_
 
174
        glarea.set_frustrum(self.model.bounding_sphere_radius, settings.draw.zoom)
 
175
        gldraw.set_model(self.model)
 
176
        self.set_selection_mode(settings.draw.selection)
 
177
        
 
178
    @staticmethod
 
179
    def apply_to_glmodel(cubestate):
 
180
        gldraw.set_transformations(cubestate.blocks)
 
181
        
 
182
    def initializeGL(self):
 
183
        if DEBUG_MSG:
 
184
            glcontext = self.context()
 
185
            glformat = glcontext.format()
 
186
            glrformat = glcontext.requestedFormat()
 
187
            def printglattr(name, *attrs):
 
188
                print('  {}: '.format(name), end='')
 
189
                def get_value(glformat, attr):
 
190
                    if isinstance(attr, basestring):
 
191
                        return getattr(glformat, attr)()
 
192
                    else:
 
193
                        return attr(glformat)
 
194
                values = [get_value(glformat, a) for a in attrs]
 
195
                rvalues = [get_value(glrformat, a) for a in attrs]
 
196
                if values == rvalues:
 
197
                    print(*values)
 
198
                else:
 
199
                    print(*values, end=' (')
 
200
                    print(*rvalues, end=')\n')
 
201
            print('OpenGL format:')
 
202
            printglattr('accum', 'accum', 'accumBufferSize')
 
203
            printglattr('alpha', 'alpha', 'alphaBufferSize')
 
204
            printglattr('rgb', 'redBufferSize', 'greenBufferSize', 'blueBufferSize')
 
205
            printglattr('depth', 'depth', 'depthBufferSize')
 
206
            printglattr('directRendering', 'directRendering')
 
207
            printglattr('doubleBuffer', 'doubleBuffer')
 
208
            printglattr('hasOverlay', 'hasOverlay')
 
209
            printglattr('version', lambda glformat: '{}.{} 0x{:x}'.format(
 
210
                                                            glformat.majorVersion(),
 
211
                                                            glformat.minorVersion(),
 
212
                                                            int(glformat.openGLVersionFlags())))
 
213
            printglattr('plane', 'plane')
 
214
            printglattr('profile', 'profile')
 
215
            printglattr('rgba', 'rgba')
 
216
            printglattr('samples', 'sampleBuffers', 'samples')
 
217
            printglattr('stencil', 'stencil', 'stencilBufferSize')
 
218
            printglattr('stereo', 'stereo')
 
219
            printglattr('swapInterval', 'swapInterval')
 
220
        glarea.init_glarea()
 
221
        self.apply_design()
 
222
        
 
223
    def draw_face_debug(self):
 
224
        maxis, mslice, mdir, block, symbol, face, center, angle = self.pickdata
 
225
        self.qglColor(QColor(255, 255, 255))
 
226
        self.renderText(2, 18, "block %s, face %s (%s), center %s" % (block.index, symbol, face, center))
 
227
        self.renderText(2, 34, "axis %s, slice %s, dir %s" % (maxis, mslice, mdir))
 
228
        self.renderText(2, 50, "angle %s" % (angle))
 
229
        
 
230
    def paintGL(self):
 
231
        glarea.display()
 
232
        if DEBUG:
 
233
            if DEBUG_DRAW:
 
234
                if self.pickdata is not None:
 
235
                    self.draw_face_debug()
 
236
                if self.selection_debug_info is not None:
 
237
                    gldraw.draw_select_debug(*self.selection_debug_info)
 
238
            if DEBUG_FPS:
 
239
                self.render_count += 1
 
240
                if self.monotonic_time.elapsed() > 1000:
 
241
                    elapsed = self.monotonic_time.restart()
 
242
                    self.fps = 1000. / elapsed * self.render_count
 
243
                    self.render_count = 0
 
244
                self.qglColor(QColor(255, 255, 255))
 
245
                self.renderText(2, 18, "FPS %.1f" % self.fps)
 
246
        
 
247
    def resizeGL(self, width, height):
 
248
        glarea.resize(width, height)
 
249
        
 
250
    MODIFIER_MASK = int(Qt.ShiftModifier | Qt.ControlModifier)
 
251
    def keyPressEvent(self, event):
 
252
        modifiers = int(event.modifiers()) & self.MODIFIER_MASK
 
253
        if modifiers:
 
254
            return QGLWidget.keyPressEvent(self, event)
 
255
        elif event.key() == Qt.Key_Right:
 
256
            self.rotation_x += 2
 
257
        elif event.key() == Qt.Key_Left:
 
258
            self.rotation_x -= 2
 
259
        elif event.key() == Qt.Key_Up:
 
260
            self.rotation_y -= 2
 
261
        elif event.key() == Qt.Key_Down:
 
262
            self.rotation_y += 2
 
263
        else:
 
264
            return QGLWidget.keyPressEvent(self, event)
 
265
            
 
266
        self.rotation_x, self.rotation_y = glarea.set_rotation_xy(self.rotation_x, self.rotation_y)
 
267
        self.update()
 
268
        self.update_selection()
 
269
        
 
270
    PickData = namedtuple('PickData', 'maxis mslice mdir block symbol face center angle')
 
271
    def pick_polygons(self, x, y):
 
272
        '''Identify the block at screen co-ordinates x,y.'''
 
273
        
 
274
        height = self.height()
 
275
        index = glarea.pick_polygons(x, height-y, 1)
 
276
        if not index:
 
277
            self.selection_debug_info = None
 
278
            self.pickdata = None
 
279
            return
 
280
        maxis, mslice, mdir, face, center, block, symbol, face_center, edge_center = self.model.pick_data[index]
 
281
        
 
282
        if center:
 
283
            angle, self.selection_debug_info = None, None
 
284
        else:
 
285
            angle, self.selection_debug_info = glarea.get_cursor_angle(face_center, edge_center)
 
286
        self.pickdata = self.PickData(maxis, mslice, mdir, block, symbol, face, center, angle)
 
287
        
 
288
    def update_selection(self):
 
289
        '''This func determines which block the mouse is pointing at'''
 
290
        if self.timer_animate.isActive():
 
291
            if self.pickdata is not None:
 
292
                self.pickdata = None
 
293
                self.set_cursor()
 
294
            return
 
295
        if self.update_selection_pending:
 
296
            return
 
297
        QTimer.singleShot(0, self.on_idle_update_selection)
 
298
        self.update_selection_pending = True
 
299
        
 
300
    def on_idle_update_selection(self):
 
301
        if self.timer_animate.isActive():
 
302
            if self.pickdata is not None:
 
303
                self.pickdata = None
 
304
                self.set_cursor()
 
305
        else:
 
306
            self.pick_polygons(*self.mouse_xy)
 
307
            self.set_cursor()
 
308
            if DEBUG_DRAW:
 
309
                if not self.timer_animate.isActive():
 
310
                    self.updateGL()
 
311
        self.update_selection_pending = False
 
312
        
 
313
    def mouseMoveEvent(self, event):
 
314
        self.mouse_xy = event.x(), event.y()
 
315
        
 
316
        if not self.button_down_background:
 
317
            self.update_selection()
 
318
            return
 
319
            
 
320
        # perform rotation
 
321
        offset_x = event.x() - self.last_mouse_x
 
322
        offset_y = event.y() - self.last_mouse_y
 
323
        self.rotation_x, self.rotation_y = glarea.set_rotation_xy(
 
324
                                                        self.rotation_x + offset_x,
 
325
                                                        self.rotation_y + offset_y)
 
326
        self.rotation_x -= offset_x
 
327
        self.rotation_y -= offset_y
 
328
        self.update()
 
329
        
 
330
    def mousePressEvent(self, event):
 
331
        if self.pickdata is not None:
 
332
            if self.timer_animate.isActive():
 
333
                return
 
334
            # make a move
 
335
            if self.editing_model:
 
336
                if event.modifiers() & Qt.ControlModifier:
 
337
                    if event.button() == Qt.LeftButton:
 
338
                        self.request_rotate_block.emit(self.pickdata.block.index, False)
 
339
                    elif event.button() == Qt.RightButton:
 
340
                        self.request_rotate_block.emit(self.pickdata.block.index, True)
 
341
                else:
 
342
                    if event.button() == Qt.LeftButton:
 
343
                        self.request_swap_blocks.emit(self.pickdata.block.index,
 
344
                                    self.pickdata.maxis, self.pickdata.mslice, self.pickdata.mdir)
 
345
            else:
 
346
                mslice = -1 if event.modifiers() & Qt.ControlModifier else self.pickdata.mslice
 
347
                if event.button() == Qt.LeftButton:
 
348
                    self.request_rotation.emit(self.pickdata.maxis, mslice, self.pickdata.mdir)
 
349
                elif event.button() == Qt.RightButton and settings.draw.selection_nick == 'simple':
 
350
                    self.request_rotation.emit(self.pickdata.maxis, mslice, not self.pickdata.mdir)
 
351
        elif event.button() == Qt.LeftButton:
 
352
            # start rotation
 
353
            self.button_down_background = True
 
354
            self.last_mouse_x = event.x()
 
355
            self.last_mouse_y = event.y()
 
356
        self.update()
 
357
        
 
358
    def mouseReleaseEvent(self, event):
 
359
        if event.button() != Qt.LeftButton:
 
360
            return
 
361
            
 
362
        if self.button_down_background:
 
363
            # end rotation
 
364
            self.rotation_x += event.x() - self.last_mouse_x
 
365
            self.rotation_y += event.y() - self.last_mouse_y
 
366
            self.button_down_background = False
 
367
            self.update_selection()
 
368
            
 
369
    def wheelEvent(self, event):
 
370
        if event.orientation() == Qt.Vertical:
 
371
            zoom = settings.draw.zoom * math.pow(1.1, -event.delta() / 120)
 
372
            zoom_min, zoom_max = settings.draw.zoom_range
 
373
            if zoom < zoom_min:
 
374
                zoom = zoom_min
 
375
            if zoom > zoom_max:
 
376
                zoom = zoom_max
 
377
            settings.draw.zoom = zoom
 
378
            
 
379
    def dragEnterEvent(self, event):
 
380
        debug('drag enter:', event.mimeData().formats())
 
381
        if (event.mimeData().hasFormat("text/uri-list") or
 
382
                event.mimeData().hasFormat("application/x-color")):
 
383
            event.acceptProposedAction()
 
384
            
 
385
    def dropEvent(self, event):
 
386
        # when a drag is in progress the pickdata is not updated, so do it now
 
387
        self.pick_polygons(event.pos().x(), event.pos().y())
 
388
        
 
389
        mime_data = event.mimeData()
 
390
        if mime_data.hasFormat("application/x-color"):
 
391
            color = mime_data.colorData()
 
392
            if color is None:
 
393
                debug("Received invalid color data")
 
394
                return
 
395
                
 
396
            if self.pickdata is not None:
 
397
                self.drop_color.emit(self.pickdata.block.index, self.pickdata.symbol, color.name())
 
398
            else:
 
399
                self.drop_color.emit(-1, '', color.name())
 
400
        elif mime_data.hasFormat("text/uri-list"):
 
401
            if self.pickdata is None:
 
402
                debug('Background image is not supported.')
 
403
                return
 
404
            uris = mime_data.urls()
 
405
            for uri in uris:
 
406
                if not uri.isLocalFile():
 
407
                    debug('filename "%s" not found or not a local file.' % uri.toString())
 
408
                    continue
 
409
                filename = uri.toLocalFile()
 
410
                if not filename or not os.path.exists(filename):
 
411
                    debug('filename "%s" not found.' % filename)
 
412
                    continue
 
413
                self.drop_file.emit(self.pickdata.block.index, self.pickdata.symbol, filename)
 
414
                break # For now,  just use the first one.
 
415
        # Ignore all others
 
416
        
 
417
    def set_cursor(self):
 
418
        if self.pickdata is None or self.button_down_background:
 
419
            index = -1
 
420
        elif self.pickdata.angle is None:
 
421
            index = -2
 
422
        else:
 
423
            index = int((self.pickdata.angle+180) / 22.5 + 0.5) % 16
 
424
        self.setCursor(self.cursors[index])
 
425
        
 
426
    def set_std_cursor(self):
 
427
        QTimer.singleShot(0, lambda: self.unsetCursor())
 
428
        
 
429
    @staticmethod
 
430
    def set_lighting(enable):
 
431
        glarea.set_lighting(enable)
 
432
        
 
433
    def set_selection_mode(self, mode):
 
434
        self.selection_mode = mode
 
435
        gldraw.set_pick_model(*self.model.gl_pick_data(mode))
 
436
        self.update_selection()
 
437
        
 
438
    def set_editing_mode(self, enable):
 
439
        self.editing_model = enable
 
440
        if enable:
 
441
            gldraw.set_pick_model(*self.model.gl_pick_data(0))
 
442
        else:
 
443
            gldraw.set_pick_model(*self.model.gl_pick_data(self.selection_mode))
 
444
        self.update_selection()
 
445
        
 
446
    @staticmethod
 
447
    def set_background_color(color):
 
448
        rgba = QColor()
 
449
        rgba.setNamedColor(color)
 
450
        glarea.set_background_color(rgba.redF(), rgba.greenF(), rgba.blueF())
 
451
        
 
452
    def reset_rotation(self):
 
453
        '''Reset cube rotation'''
 
454
        self.rotation_x, self.rotation_y = glarea.set_rotation_xy(*settings.draw.default_rotation)
 
455
        self.update()
 
456
        
 
457
    ### Animation
 
458
    
 
459
    def animate_rotation(self, move_data, blocks, stop_after):
 
460
        self.stop_requested = stop_after
 
461
        axis = (self.model.axesI if move_data.dir else self.model.axes)[move_data.axis]
 
462
        angle = self.model.symmetry[move_data.axis]
 
463
        gldraw.start_animate(angle, *axis)
 
464
        for block_id, selected in blocks:
 
465
            gldraw.set_animation_block(block_id, selected)
 
466
        self.timer_animate.start(0 if DEBUG_VFPS else 20)
 
467
        self.update_selection()
 
468
        
 
469
    def _on_animate(self):
 
470
        increment = self.speed * 1e-02 * 20
 
471
        increment = min(increment, 45)
 
472
        unfinished = gldraw.step_animate(increment)
 
473
        if unfinished:
 
474
            self.updateGL()
 
475
            return
 
476
            
 
477
        # we have finished the animation sequence now
 
478
        gldraw.end_animate()
 
479
        self.timer_animate.stop()
 
480
        self.end_animation.emit(self.stop_requested)
 
481
        self.updateGL()
 
482
        self.update_selection()
 
483
        self.stop_requested = False
 
484
        
 
485
    def animate_abort(self, update=True):
 
486
        self.timer_animate.stop()
 
487
        gldraw.end_animate()
 
488
        if update:
 
489
            self.end_animation.emit(self.stop_requested)
 
490
            self.update()
 
491
            self.update_selection()
 
492
        self.stop_requested = False
 
493
        
 
494
    def on_settings_changed(self, key):
 
495
        if key == 'draw.speed':
 
496
            self.speed = settings.draw.speed
 
497
        elif key == 'draw.lighting':
 
498
            self.set_lighting(settings.draw.lighting)
 
499
        elif key == 'draw.accum':
 
500
            if self.format().accum():
 
501
                glarea.set_antialiasing(settings.draw.accum)
 
502
        elif key == 'draw.samples':
 
503
            if self.format().samples():
 
504
                samples = 2**settings.draw.samples
 
505
                if samples == 1:
 
506
                    glarea.set_antialiasing(None, False)
 
507
                elif samples == self.format().samples():
 
508
                    glarea.set_antialiasing(None, True)
 
509
        elif key == 'draw.selection':
 
510
            self.set_selection_mode(settings.draw.selection)
 
511
        elif key.startswith('theme.face.'):
 
512
            i = int(key.split('.')[2])
 
513
            if key == 'theme.face.{}.color'.format(i):
 
514
                color = QColor()
 
515
                color.setNamedColor(settings.theme.face[i].color)
 
516
                gldraw.set_face_rendering(i, red=color.redF(), green=color.greenF(), blue=color.blueF())
 
517
            elif key == 'theme.face.{}.image'.format(i):
 
518
                self.set_face_texture(i, settings.theme.face[i].image)
 
519
            elif key == 'theme.face.{}.mode'.format(i):
 
520
                imagemode = settings.theme.face[i].mode
 
521
                gldraw.set_face_rendering(i, imagemode=imagemode)
 
522
        elif key == 'theme.bgcolor':
 
523
            self.set_background_color(settings.theme.bgcolor)
 
524
        elif key == 'draw.zoom':
 
525
            glarea.set_frustrum(self.model.bounding_sphere_radius, settings.draw.zoom)
 
526
        else:
 
527
            debug('Unknown settings key changed:', key)
 
528
        self.update()
 
529
        
 
530