~ubuntu-branches/ubuntu/quantal/software-center/quantal-updates

« back to all changes in this revision

Viewing changes to softwarecenter/ui/gtk3/widgets/unused__pathbar.py

  • Committer: Package Import Robot
  • Author(s): Michael Vogt, Michael Vogt, Anthony Lenton, Gary Lasker, Natalia Bidart
  • Date: 2012-03-09 08:55:46 UTC
  • Revision ID: package-import@ubuntu.com-20120309085546-p7p4ppu59ishighc
Tags: 5.1.12
[ Michael Vogt ]
* lp:~mvo/software-center/webcam-string-fix:
- small fix to support the debtagshw updated tags for hardware::camera
  (split into webcam,digicam now)
* lp:~mvo/software-center/trivial-nm-state-override:
  - support the SOFTWARE_CENTER_NET_CONNECTED environment value to
    force the connected state as a stopgap workaround for bug 911706
* lp:~mvo/software-center/lp941361:
  - adds a general make_string_from_list() helper to build (somewhat)
    i18n friendly human readable strings easily (LP: #941361)
* lp:~mvo/software-center/expunge-cache:
  - merge os.nice() addition
* lp:~mvo/software-center/lp789596:
  - better (less scary) string for updates from third-party
    venders (LP: #789596)
* lp:~mvo/software-center/fix-refresh-of-whats-new:
  - fix missing refresh of the what's new content on a database
    reopen so that new content from the agent appears as soon
    as it finishes the update
* lp:~mvo/software-center/review-fixes:
  - ensure ui is correctly updated after submitting/modifying review
* lp:~mvo/software-center/simulate-slow-network:
  - add a small helper to simulate a slow network connection to
    see if we have hidden latencies in the UI when the network
    is (really) slow
* lp:~mvo/software-center/top-rated-refresh:
  - ensure that the top-rated apps are refreshed when we have new data
* lp:~mvo/software-center/apptreeview-tweaks:
  - fix 'load_range" errors when navigating the installed view
    (LP: #913675), and do some nice needed refactoring/cleanup

[ Anthony Lenton ]
* lp:~elachuni/software-center/dont-store:
  - consolidate Ubunty SSO logins to use "Ubuntu Software Center", fixes
    inconsistent UI and storing of two keys in they keyring, as well as
    being prompted to log in twice the first time that you review an app
* lp:~elachuni/software-center/pep8-test,
  lp:~elachuni/software-center/pep8-test-part2,
  lp:~elachuni/software-center/pep8-test-part3,
  lp:~elachuni/software-center/pep8-test-part4:
  - add a unit test to check code statically for pep8 compliance and update
    more and more of the code to meet it

[ Gary Lasker ]
* lp:~gary-lasker/software-center/fix-crash-lp944875:
  - Fix an intermittent crash that can happen if the installed view pane
    has not been built yet when a call to show the spinner is made as a
    result of a refresh (LP: #944875)
* lp:~gary-lasker/software-center/list-view-icon-coordinates-lp947624:
  - This branch adds support for providing the correct icon size and 
    on-screen coordinates values in the Unity launcher integration dbus 
    call when an installation is initiated via the list view
    (LP: #947624)
* lp:~gary-lasker/software-center/enable-published-date-unit-test:
  - unit test change only, enable the published-date unit test that was
    disabled pending deployment of support on the staging server

[ Natalia Bidart ]
* lp:~nataliabidart/software-center/one-auth:
  - use consistently the same app name for every Ubuntu SSO call to
    ensure SSO tokens are handled properly

Show diffs side-by-side

added added

removed removed

Lines of Context:
13
13
# pi constants
14
14
from math import pi
15
15
 
16
 
PI          = pi
 
16
PI = pi
17
17
PI_OVER_180 = pi / 180
18
18
 
19
19
 
30
30
 
31
31
        If the Shape is direction dependent, the Shape MUST
32
32
        implement <_layout_ltr> and <_layout_rtl> methods.
33
 
        
 
33
 
34
34
        If the Shape is not direction dependent, then it simply can
35
35
        override the <layout> method.
36
36
 
51
51
        self.name = 'Shapeless'
52
52
        self.hadjustment = 0
53
53
        self._color = 1, 0, 0
54
 
        return
55
54
 
56
55
    def __eq__(self, other):
57
56
        return self.name == other.name
61
60
            self._layout_ltr(cr, x, y, w, h, r, aw)
62
61
        else:
63
62
            self._layout_rtl(cr, x, y, w, h, r, aw)
64
 
        return
65
63
 
66
64
 
67
65
class ShapeRoundedRect(Shape):
79
77
    def __init__(self, direction=Gtk.TextDirection.LTR):
80
78
        Shape.__init__(self, direction)
81
79
        self.name = 'RoundedRect'
82
 
        return
83
80
 
84
81
    def layout(self, cr, x, y, w, h, r, aw):
85
82
        cr.new_sub_path()
86
 
        cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180)
87
 
        cr.arc(x+w-r, r+y, r, 270*PI_OVER_180, 0)
88
 
        cr.arc(x+w-r, y+h-r, r, 0, 90*PI_OVER_180)
89
 
        cr.arc(r+x, y+h-r, r, 90*PI_OVER_180, PI)
 
83
        cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
 
84
        cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0)
 
85
        cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180)
 
86
        cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI)
90
87
        cr.close_path()
91
 
        return
92
88
 
93
89
 
94
90
class ShapeStartArrow(Shape):
96
92
    def __init__(self, direction=Gtk.TextDirection.LTR):
97
93
        Shape.__init__(self, direction)
98
94
        self.name = 'StartArrow'
99
 
        return
100
95
 
101
96
    def _layout_ltr(self, cr, x, y, w, h, r, aw):
102
 
        haw = aw/2
 
97
        haw = aw / 2
103
98
 
104
99
        cr.new_sub_path()
105
 
        cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180)
 
100
        cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
106
101
 
107
102
        # arrow head
108
 
        cr.line_to(x+w-haw, y)
109
 
        cr.line_to(x+w+haw, y+(h/2))
110
 
        cr.line_to(x+w-haw, y+h)
111
 
        
112
 
        cr.arc(r+x, y+h-r, r, 90*PI_OVER_180, PI)
 
103
        cr.line_to(x + w - haw, y)
 
104
        cr.line_to(x + w + haw, y + (h / 2))
 
105
        cr.line_to(x + w - haw, y + h)
 
106
 
 
107
        cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI)
113
108
        cr.close_path()
114
 
        return
115
109
 
116
110
    def _layout_rtl(self, cr, x, y, w, h, r, aw):
117
 
        haw = aw/2
 
111
        haw = aw / 2
118
112
 
119
113
        cr.new_sub_path()
120
 
        cr.move_to(x-haw, (y+h)/2)
121
 
        cr.line_to(x+aw-haw, y)
122
 
        cr.arc(x+w-r, r+y, r, 270*PI_OVER_180, 0)
123
 
        cr.arc(x+w-r, y+h-r, r, 0, 90*PI_OVER_180)
124
 
        cr.line_to(x+aw-haw, y+h)
 
114
        cr.move_to(x - haw, (y + h) / 2)
 
115
        cr.line_to(x + aw - haw, y)
 
116
        cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0)
 
117
        cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180)
 
118
        cr.line_to(x + aw - haw, y + h)
125
119
        cr.close_path()
126
 
        return
127
120
 
128
121
 
129
122
class ShapeMidArrow(Shape):
133
126
        #~ self.draw_xoffset = -2
134
127
        self._color = 0, 1, 0
135
128
        self.name = 'MidArrow'
136
 
        return
137
129
 
138
130
    def _layout_ltr(self, cr, x, y, w, h, r, aw):
139
 
        self.hadjustment = haw = aw/2
140
 
        cr.move_to(x-haw-1, y)
 
131
        self.hadjustment = haw = aw / 2
 
132
        cr.move_to(x - haw - 1, y)
141
133
        # arrow head
142
 
        cr.line_to(x+w-haw, y)
143
 
        cr.line_to(x+w+haw, y+(h/2))
144
 
        cr.line_to(x+w-haw, y+h)
145
 
        cr.line_to(x-haw-1, y+h)
 
134
        cr.line_to(x + w - haw, y)
 
135
        cr.line_to(x + w + haw, y + (h / 2))
 
136
        cr.line_to(x + w - haw, y + h)
 
137
        cr.line_to(x - haw - 1, y + h)
146
138
 
147
 
        cr.line_to(x+haw-1, y+(h/2))
 
139
        cr.line_to(x + haw - 1, y + (h / 2))
148
140
 
149
141
        cr.close_path()
150
 
        return
151
142
 
152
143
    def _layout_rtl(self, cr, x, y, w, h, r, aw):
153
 
        self.hadjustment = haw = -aw/2
 
144
        self.hadjustment = haw = -aw / 2
154
145
 
155
 
        cr.move_to(x+haw, (h+y)/2)
156
 
        cr.line_to(x+aw+haw, y)
157
 
        cr.line_to(x+w-haw+1, y)
158
 
        cr.line_to(x+w-aw-haw+1, (y+h)/2)
159
 
        cr.line_to(x+w-haw+1, y+h)
160
 
        cr.line_to(x+aw+haw, y+h)
 
146
        cr.move_to(x + haw, (h + y) / 2)
 
147
        cr.line_to(x + aw + haw, y)
 
148
        cr.line_to(x + w - haw + 1, y)
 
149
        cr.line_to(x + w - aw - haw + 1, (y + h) / 2)
 
150
        cr.line_to(x + w - haw + 1, y + h)
 
151
        cr.line_to(x + aw + haw, y + h)
161
152
        cr.close_path()
162
 
        return
163
153
 
164
154
 
165
155
class ShapeEndCap(Shape):
169
159
        #~ self.draw_xoffset = -2
170
160
        self._color = 0, 0, 1
171
161
        self.name = 'EndCap'
172
 
        return
173
162
 
174
163
    def _layout_ltr(self, cr, x, y, w, h, r, aw):
175
 
        self.hadjustment = haw = aw/2
 
164
        self.hadjustment = haw = aw / 2
176
165
 
177
 
        cr.move_to(x-haw-1, y)
 
166
        cr.move_to(x - haw - 1, y)
178
167
        # rounded end
179
 
        cr.arc(x+w-r, r+y, r, 270*PI_OVER_180, 0)
180
 
        cr.arc(x+w-r, y+h-r, r, 0, 90*PI_OVER_180)
 
168
        cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0)
 
169
        cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180)
181
170
        # arrow
182
 
        cr.line_to(x-haw-1, y+h)
183
 
        cr.line_to(x+haw-1, y+(h/2))
 
171
        cr.line_to(x - haw - 1, y + h)
 
172
        cr.line_to(x + haw - 1, y + (h / 2))
184
173
        cr.close_path()
185
 
        return
186
174
 
187
175
    def _layout_rtl(self, cr, x, y, w, h, r, aw):
188
 
        self.hadjustment = haw = -aw/2
 
176
        self.hadjustment = haw = -aw / 2
189
177
 
190
 
        cr.arc(r+x, r+y, r, PI, 270*PI_OVER_180)
191
 
        cr.line_to(x+w-haw+1, y)
192
 
        cr.line_to(x+w-haw-aw+1, (y+h)/2)
193
 
        cr.line_to(x+w-haw+1, y+h)
194
 
        cr.arc(r+x, y+h-r, r, 90*PI_OVER_180, PI)
 
178
        cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
 
179
        cr.line_to(x + w - haw + 1, y)
 
180
        cr.line_to(x + w - haw - aw + 1, (y + h) / 2)
 
181
        cr.line_to(x + w - haw + 1, y + h)
 
182
        cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI)
195
183
        cr.close_path()
196
 
        return
197
184
 
198
185
 
199
186
class AnimationClock(GObject.GObject):
200
 
 
201
187
    _1SECOND = 1000
202
 
 
203
 
 
204
188
    __gsignals__ = {
205
 
        "animation-frame"    : (GObject.SignalFlags.RUN_LAST,
 
189
        "animation-frame": (GObject.SignalFlags.RUN_LAST,
206
190
                                None,
207
191
                                (float,),),
208
192
 
209
 
        "animation-finished" : (GObject.SignalFlags.RUN_FIRST,
 
193
        "animation-finished": (GObject.SignalFlags.RUN_FIRST,
210
194
                                None,
211
195
                                (bool,),),
212
196
                    }
213
197
 
214
 
 
215
198
    def __init__(self, fps, duration):
216
199
        GObject.GObject.__init__(self)
217
200
 
221
204
 
222
205
        self._clock = None
223
206
        self._progress = 0  # progress as an msec offset
224
 
        return
225
207
 
226
208
    def _get_timstep(self):
227
209
        d = self.duration
245
227
    def set_duration(self, duration):
246
228
        self.duration = float(duration)
247
229
        self._timestep = self._get_timstep()
248
 
        return
249
230
 
250
231
    def stop(self, who_called='?'):
251
232
 
257
238
        self._clock = None
258
239
        self._progress = 0
259
240
        self.in_progress = False
260
 
        return
261
241
 
262
242
    def start(self):
263
243
        self.stop(who_called='start')
264
 
        if not self.sequence: return
 
244
        if not self.sequence:
 
245
            return
265
246
 
266
247
        self._clock = GObject.timeout_add(self._timestep,
267
248
                                          self._schedule_animation_frame,
268
249
                                          priority=100)
269
250
        self.in_progress = True
270
 
        return
271
251
 
272
252
 
273
253
class PathBarAnimator(AnimationClock):
274
 
 
275
254
    # animation display constants
276
 
    FPS      = 50
 
255
    FPS = 50
277
256
    DURATION = 150  # spec says 150ms
278
257
 
279
258
    # animation modes
280
 
    NONE         = 'animation-none'
281
 
    OUT          = 'animation-out'
282
 
    IN           = 'animation-in'
 
259
    NONE = 'animation-none'
 
260
    OUT = 'animation-out'
 
261
    IN = 'animation-in'
283
262
    WIDTH_CHANGE = 'animation-width-change'
284
263
 
285
264
    def __init__(self, pathbar):
290
269
 
291
270
        self.connect('animation-frame', self._on_animation_frame)
292
271
        self.connect('animation-finished', self._on_animation_finished)
293
 
        return
294
272
 
295
273
    def _animate_out(self, part, progress, kwargs):
296
274
        real_alloc = part.get_allocation()
300
278
            xo *= -1
301
279
 
302
280
        anim_alloc = Gdk.Rectangle()
303
 
        anim_alloc.x = real_alloc.x-xo
 
281
        anim_alloc.x = real_alloc.x - xo
304
282
        anim_alloc.y = real_alloc.y
305
283
        anim_alloc.width = real_alloc.width
306
284
        anim_alloc.height = real_alloc.height
307
285
 
308
286
        part.new_frame(anim_alloc)
309
 
        return
310
287
 
311
288
    def _animate_in(self, part, progress, kwargs):
312
289
        real_alloc = part.get_allocation()
316
293
            xo *= -1
317
294
 
318
295
        anim_alloc = Gdk.Rectangle()
319
 
        anim_alloc.x = real_alloc.x-xo
 
296
        anim_alloc.x = real_alloc.x - xo
320
297
        anim_alloc.y = real_alloc.y
321
298
        anim_alloc.width = real_alloc.width
322
299
        anim_alloc.height = real_alloc.height
323
300
 
324
301
        part.new_frame(anim_alloc)
325
 
        return
326
302
 
327
303
    def _animate_width_change(self, part, progress, kwargs):
328
304
        start_w = kwargs['start_width']
330
306
 
331
307
        width = int(round(start_w + (end_w - start_w) * progress))
332
308
        part.set_size_request(width, part.get_height_request())
333
 
        return
334
309
 
335
310
    def _on_animation_frame(self, clock, progress):
336
 
        if not self.sequence: return
 
311
        if not self.sequence:
 
312
            return
337
313
 
338
314
        for actor, animation, kwargs in self.sequence:
339
 
 
340
 
            if animation == PathBarAnimator.NONE: continue
 
315
            if animation == PathBarAnimator.NONE:
 
316
                continue
341
317
 
342
318
            if animation == PathBarAnimator.OUT:
343
319
                self._animate_out(actor, progress, kwargs)
348
324
            elif animation == PathBarAnimator.WIDTH_CHANGE:
349
325
                self._animate_width_change(actor, progress, kwargs)
350
326
 
351
 
        return
352
 
 
353
327
    def _on_animation_finished(self, clock, interrupted):
354
328
        for actor, animation, kwargs in self.sequence:
355
329
            actor.animation_finished()
357
331
        self.sequence = []
358
332
        self.pathbar.psuedo_parts = []
359
333
        self.pathbar.queue_draw()
360
 
        return
361
334
 
362
335
    def append_animation(self, actor, animation, **kwargs):
363
336
        self.sequence.append((actor, animation, kwargs))
364
 
        return
365
337
 
366
338
    def reset(self, who_called='?'):
367
 
        AnimationClock.stop(self, who_called=who_called+'.reset')
 
339
        AnimationClock.stop(self, who_called=who_called + '.reset')
368
340
        self.sequence = []
369
 
        return
370
341
 
371
342
 
372
343
class PathBar(Gtk.HBox):
373
344
 
374
 
    MIN_PART_WIDTH = 25 # pixels
 
345
    MIN_PART_WIDTH = 25  # pixels
375
346
 
376
347
    def __init__(self):
377
348
        GObject.GObject.__init__(self)
405
376
        # les signales!
406
377
        self.connect('size-allocate', self._on_allocate)
407
378
        self.connect('draw', self._on_draw)
408
 
        return
409
379
 
410
380
    # sugar
411
381
    def __len__(self):
454
424
            self.animator.start()
455
425
        else:
456
426
            self.queue_draw()
457
 
        return
458
427
 
459
428
    def _on_draw(self, widget, cr):
460
429
        # always paint psuedo parts first
467
436
 
468
437
        # paint a frame around the entire pathbar
469
438
        width = self.get_parts_width()
470
 
        Gtk.render_background(context, cr, 1, 1, width-2, a.height-2)
 
439
        Gtk.render_background(context, cr, 1, 1, width - 2, a.height - 2)
471
440
 
472
441
        self._paint_widget_parts(cr, context, a.x, a.y)
473
442
 
486
455
                       part.animation_allocation or part.get_allocation(),
487
456
                       context,
488
457
                       xo, yo)
489
 
        return
490
458
 
491
459
    def _paint_psuedo_parts(self, cr, context, xo, yo):
492
460
        # a special case: paint psuedo parts paint first,
496
464
                       part.animation_allocation or part.get_allocation(),
497
465
                       context,
498
466
                       xo, yo)
499
 
        return
500
467
 
501
468
    def _shrink_parts(self, overhang):
502
469
        self.out_of_width = True
505
472
            old_width = part.get_width_request()
506
473
            new_width = max(self.MIN_PART_WIDTH, old_width - overhang)
507
474
 
508
 
            if False:#self.use_animations:
 
475
            if False:  # self.use_animations:
509
476
                self.animator.append_animation(part,
510
477
                                               PathBarAnimator.WIDTH_CHANGE,
511
478
                                               start_width=old_width,
515
482
                                      part.get_height_request())
516
483
 
517
484
            overhang -= old_width - new_width
518
 
            if overhang <= 0: break
519
 
        return
 
485
            if overhang <= 0:
 
486
                break
520
487
 
521
488
    def _grow_parts(self, claim):
522
489
        children = self.get_children()
528
495
                continue
529
496
 
530
497
            growth = min(claim, (part.get_natural_width() - part.width))
531
 
            if growth <= 0: break
 
498
            if growth <= 0:
 
499
                break
532
500
 
533
501
            claim -= growth
534
502
 
540
508
            else:
541
509
                part.set_size_request(part.width + growth,
542
510
                                      part.get_height_request())
543
 
        return
544
511
 
545
512
    def _make_space(self, part):
546
513
        children = self.get_children()
547
 
        if not children: return
 
514
        if not children:
 
515
            return
548
516
 
549
517
        cur_width = self.get_parts_width()
550
518
        incomming_width = cur_width + part.get_width_request()
553
521
        if overhang > 0:
554
522
            print 'shrink parts by:', overhang
555
523
            self._shrink_parts(overhang)
556
 
        return
557
524
 
558
525
    def _reclaim_space(self, part):
559
 
        if not self.out_of_width: return
 
526
        if not self.out_of_width:
 
527
            return
560
528
 
561
529
        claim = part.get_width_request()
562
530
        self._grow_parts(claim)
563
 
        return
564
531
 
565
532
    def _append_compose_parts(self, new_part):
566
533
        d = self.get_direction()
574
541
        else:
575
542
            new_part.set_shape(ShapeRoundedRect(d))
576
543
 
577
 
        if not n_parts > 1: return
 
544
        if not n_parts > 1:
 
545
            return
578
546
 
579
547
        new_mid = children[-1]
580
548
        new_mid.set_shape(ShapeMidArrow(d))
581
 
        return
582
549
 
583
550
    def _remove_compose_parts(self):
584
551
        d = self.get_direction()
595
562
        last = children[-1]
596
563
        last.set_shape(ShapeEndCap(d))
597
564
        self.queue_draw()
598
 
        return
599
565
 
600
566
    def _cleanup_revealer(self):
601
 
        if not self._revealer: return
 
567
        if not self._revealer:
 
568
            return
602
569
        GObject.source_remove(self._revealer)
603
570
        self._revealer = None
604
 
        return
605
571
 
606
572
    def _theme(self, part):
607
573
        #~ part.set_padding(self.theme['xpad'], self.theme['ypad'])
608
574
        part.set_padding(12, 4)
609
 
        return
610
575
 
611
576
    # public methods
612
577
    @property
613
578
    def first_part(self):
614
579
        children = self.get_children()
615
 
        if not children: return None
616
 
        return children[0]
 
580
        if children:
 
581
            return children[0]
617
582
 
618
583
    @property
619
584
    def last_part(self):
620
585
        children = self.get_children()
621
 
        if not children: return None
622
 
        return children[-1]
 
586
        if children:
 
587
            return children[-1]
623
588
 
624
589
    def reveal_part(self, part, animate=True):
625
590
        # do not do here:
629
594
        part_old_width = part.get_width_request()
630
595
        part_new_width = part.get_natural_width()
631
596
 
632
 
        if part_new_width == part_old_width: return
 
597
        if part_new_width == part_old_width:
 
598
            return
633
599
 
634
600
        change_amount = part_new_width - part_old_width
635
601
 
639
605
                old_width = part_old_width
640
606
                new_width = part_new_width
641
607
            else:
642
 
                if change_amount <= 0: continue
 
608
                if change_amount <= 0:
 
609
                    continue
643
610
 
644
611
                old_width = p.get_width_request()
645
612
                new_width = max(self.MIN_PART_WIDTH, old_width - change_amount)
655
622
                                   p.get_height_request())
656
623
 
657
624
        self.animator.start()
658
 
        return
659
625
 
660
626
    def queue_reveal_part(self, part):
661
627
 
667
633
        self._revealer = GObject.timeout_add(self._timeout_reveal,
668
634
                                             reveal_part_cb,
669
635
                                             part)
670
 
        return
671
636
 
672
637
    def get_parts_width(self):
673
638
        last = self.last_part
674
 
        if not last: return 0
 
639
        if not last:
 
640
            return 0
675
641
 
676
642
        if self.get_direction() != Gtk.TextDirection.RTL:
677
643
            return last.x + last.width - self.first_part.x
682
648
    def get_visual_width(self):
683
649
        last = self.last_part
684
650
        first = self.first_part
685
 
        if not last: return 0
 
651
        if not last:
 
652
            return 0
686
653
 
687
654
        la = last.animation_allocation or last.get_allocation()
688
655
        fa = first.animation_allocation or first.get_allocation()
691
658
            return la.x + la.width - fa.x
692
659
 
693
660
        return fa.x + fa.width - la.x
694
 
        
695
661
 
696
662
    def set_use_animations(self, use_animations):
697
663
        self.use_animations = use_animations
698
664
        if not use_animations and self.animator.in_progress:
699
665
            self.animator.reset()
700
 
        return
701
666
 
702
667
    def append(self, part):
703
668
        print 'append', part
721
686
        else:
722
687
            part.set_nopaint(False)
723
688
            part.queue_draw()
724
 
        return
725
689
 
726
690
    def pop(self):
727
691
        children = self.get_children()
728
 
        if not children: return
 
692
        if not children:
 
693
            return
729
694
 
730
695
        self.animator.reset('pop')
731
696
 
744
709
 
745
710
        last.destroy()
746
711
 
747
 
        if not self.use_animations: return
 
712
        if not self.use_animations:
 
713
            return
748
714
 
749
715
        self.animator.append_animation(part, PathBarAnimator.IN)
750
716
        self.animator.start()
751
 
        return
752
717
 
753
718
    def navigate_up(self):
754
719
        """ just another name for pop() """
760
725
    def __init__(self):
761
726
        self.animation_in_progress = False
762
727
        self.animation_allocation = None
763
 
        return
764
728
 
765
729
    @property
766
730
    def x(self):
786
750
 
787
751
        self.animation_allocation = allocation
788
752
        self.queue_draw()
789
 
        return
790
753
 
791
754
    def animation_finished(self):
792
755
        self.animation_in_progress = False
793
756
        self.animation_allocation = None
794
 
        if self.get_parent(): self.get_parent().queue_draw()
795
 
        return
 
757
        if self.get_parent():
 
758
            self.get_parent().queue_draw()
796
759
 
797
760
    def paint(self, cr, a, context, xo, yo):
798
 
        if self.is_nopaint: return
 
761
        if self.is_nopaint:
 
762
            return
799
763
 
800
764
        cr.save()
801
765
 
802
766
        x, y = 0, 0
803
767
        w, h = a.width, a.height
804
 
        arrow_width = 12#theme['arrow-width']
 
768
        arrow_width = 12  # theme['arrow-width']
805
769
 
806
770
        if isinstance(self, PathPart):
807
771
            _a = self.get_allocation()
808
772
            self.shape.layout(cr,
809
 
                              _a.x-xo+1, _a.y-yo,
 
773
                              _a.x - xo + 1, _a.y - yo,
810
774
                              w, h, 3, arrow_width)
811
775
            cr.clip()
812
776
        else:
813
777
            Gtk.render_background(context, cr,
814
 
                                  a.x-xo-10, a.y-yo,
815
 
                                  a.width+10, a.height)
 
778
                                  a.x - xo - 10, a.y - yo,
 
779
                                  a.width + 10, a.height)
816
780
 
817
 
        cr.translate(a.x-xo, a.y-yo)
 
781
        cr.translate(a.x - xo, a.y - yo)
818
782
 
819
783
        if self.shape.name.find('Arrow') != -1:
820
784
            # draw arrow head
821
 
            cr.move_to(w-arrow_width/2, 2)
822
 
            cr.line_to(w+5, h/2)
823
 
            cr.line_to(w-arrow_width/2, h-2)
 
785
            cr.move_to(w - arrow_width / 2, 2)
 
786
            cr.line_to(w + 5, h / 2)
 
787
            cr.line_to(w - arrow_width / 2, h - 2)
824
788
            # fetch the line color and stroke
825
789
            rgba = context.get_border_color(Gtk.StateFlags.NORMAL)
826
790
            cr.set_source_rgb(rgba.red, rgba.green, rgba.blue)
831
795
        e = self.layout.get_pixel_extents()[1]
832
796
        lw, lh = e.width, e.height
833
797
        pw, ph = a.width, a.height
834
 
 
835
 
        x = min(self.xpadding, (pw-lw)/2)
836
 
        y = (ph-lh)/2
 
798
 
 
799
        x = min(self.xpadding, (pw - lw) / 2)
 
800
        y = (ph - lh) / 2
837
801
 
838
802
        # layout area
839
803
        Gtk.render_layout(context,
840
 
                          cr, 
 
804
                          cr,
841
805
                          int(x),
842
806
                          int(y),
843
807
                          self.layout)
845
809
        # paint the focus frame if need be
846
810
        if isinstance(self, PathPart) and self.has_focus():
847
811
            # layout area
848
 
            x, w, h = x-2, lw+4, lh+1
 
812
            x, w, h = x - 2, lw + 4, lh + 1
849
813
            Gtk.render_focus(context, cr, x, y, w, h)
850
814
 
851
815
        cr.restore()
852
 
        return
853
816
 
854
817
 
855
818
class PsuedoPathPart(PathPartCommon):
872
835
        self.layout = real_part.create_pango_layout(self.label)
873
836
 
874
837
        self.is_nopaint = False
875
 
        return
876
838
 
877
839
    def get_allocation(self):
878
840
        return self.allocation
887
849
        return self.size_request[1]
888
850
 
889
851
    def animation_finished(self):
890
 
        return
 
852
        pass
891
853
 
892
854
    def queue_draw(self):
893
855
        a = self.allocation
894
856
        aw = 12
895
 
        self.parent.queue_draw_area(a.x-aw/2, a.y,
896
 
                                    a.width+aw, a.height)
897
 
        return
 
857
        self.parent.queue_draw_area(a.x - aw / 2, a.y,
 
858
                                    a.width + aw, a.height)
898
859
 
899
860
 
900
861
class PathPart(Gtk.EventBox, PathPartCommon):
901
862
 
902
863
    __gsignals__ = {
903
 
        "clicked" : (GObject.SignalFlags.RUN_LAST,
 
864
        "clicked": (GObject.SignalFlags.RUN_LAST,
904
865
                     None,
905
866
                     (),),
906
867
        }
924
885
 
925
886
        self.set_label(label)
926
887
        self._init_event_handling()
927
 
        return
928
888
 
929
889
    def __repr__(self):
930
890
        return "PathPart: '%s'" % self.label
940
900
        else:
941
901
            part.set_state(Gtk.StateFlags.PRELIGHT)
942
902
        self.queue_draw()
943
 
        return
944
903
 
945
904
    def _on_leave_notify(self, part, event):
946
905
        self.pathbar.queue_reveal_part(self.pathbar.last_part)
947
906
        part.set_state(Gtk.StateFlags.NORMAL)
948
907
        self.queue_draw()
949
 
        return
950
908
 
951
909
    def _on_button_press(self, part, event):
952
 
        if event.button != 1: return
 
910
        if event.button != 1:
 
911
            return
953
912
        self.pathbar._press_origin = part
954
913
        part.set_state(Gtk.StateFlags.ACTIVE)
955
914
        self.queue_draw()
956
 
        return
957
915
 
958
916
    def _on_button_release(self, part, event):
959
 
        if event.button != 1: return
 
917
        if event.button != 1:
 
918
            return
960
919
 
961
920
        if self.pathbar._press_origin != part:
962
921
            self.pathbar._press_origin = None
971
930
                                self.emit, 'clicked')
972
931
 
973
932
        self.queue_draw()
974
 
        return
975
933
 
976
934
    def _on_key_press(self, part, event):
977
935
        if event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter):
978
936
            part.set_state(Gtk.StateFlags.ACTIVE)
979
937
        self.queue_draw()
980
 
        return
981
938
 
982
939
    def _on_key_release(self, part, event):
983
940
        if event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter):
985
942
            GObject.timeout_add(self.pathbar._timeout_initial,
986
943
                                self.emit, 'clicked')
987
944
        self.queue_draw()
988
 
        return
989
945
 
990
946
    def _on_focus_in(self, part, event):
991
947
        self.pathbar.reveal_part(self)
992
 
        return
993
948
 
994
949
    def _on_focus_out(self, part, event):
995
950
        self.queue_draw()
996
 
        return
997
951
 
998
952
    # private methods
999
953
    def _init_event_handling(self):
1000
954
        self.set_property("can-focus", True)
1001
 
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK|
1002
 
                        Gdk.EventMask.BUTTON_RELEASE_MASK|
1003
 
                        Gdk.EventMask.KEY_RELEASE_MASK|
1004
 
                        Gdk.EventMask.KEY_PRESS_MASK|
1005
 
                        Gdk.EventMask.ENTER_NOTIFY_MASK|
 
955
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK |
 
956
                        Gdk.EventMask.BUTTON_RELEASE_MASK |
 
957
                        Gdk.EventMask.KEY_RELEASE_MASK |
 
958
                        Gdk.EventMask.KEY_PRESS_MASK |
 
959
                        Gdk.EventMask.ENTER_NOTIFY_MASK |
1006
960
                        Gdk.EventMask.LEAVE_NOTIFY_MASK)
1007
961
 
1008
962
        self.connect("enter-notify-event", self._on_enter_notify)
1013
967
        self.connect("key-release-event", self._on_key_release)
1014
968
        self.connect("focus-in-event", self._on_focus_in)
1015
969
        self.connect("focus-out-event", self._on_focus_out)
1016
 
        return
1017
970
 
1018
971
    def _calc_natural_size(self, who_called='?'):
1019
972
        ne = self.natural_extents
1020
973
        nw, nh = ne.width, ne.height
1021
974
 
1022
 
        nw += self.shape.hadjustment + 2*self.xpadding
1023
 
        nh += 2*self.ypadding
 
975
        nw += self.shape.hadjustment + 2 * self.xpadding
 
976
        nh += 2 * self.ypadding
1024
977
 
1025
978
        self.natural_size = nw, nh
1026
979
        self.set_size_request(nw, nh)
1027
 
        return
1028
980
 
1029
981
    # public methods
1030
982
    @property
1035
987
        self.xpadding = xpadding
1036
988
        self.ypadding = ypadding
1037
989
        self._calc_natural_size()
1038
 
        return
1039
990
 
1040
991
    def set_size_request(self, width, height):
1041
 
        width = max(2*self.xpadding+1, width)
1042
 
        height = max(2*self.ypadding+1, height)
1043
 
        self.layout.set_width(Pango.SCALE * (width - 2*self.xpadding))
 
992
        width = max(2 * self.xpadding + 1, width)
 
993
        height = max(2 * self.ypadding + 1, height)
 
994
        self.layout.set_width(Pango.SCALE * (width - 2 * self.xpadding))
1044
995
        Gtk.Widget.set_size_request(self, width, height)
1045
 
        return
1046
996
 
1047
997
    def set_nopaint(self, is_nopaint):
1048
998
        self.is_nopaint = is_nopaint
1049
999
        self.queue_draw()
1050
 
        return
1051
1000
 
1052
1001
    def set_shape(self, shape):
1053
 
        if shape == self.shape: return
 
1002
        if shape == self.shape:
 
1003
            return
1054
1004
        self.shape = shape
1055
1005
        self._calc_natural_size()
1056
1006
        self.queue_draw()
1057
 
        return
1058
1007
 
1059
1008
    def set_label(self, label):
1060
1009
        self.label = label
1068
1017
 
1069
1018
        self._calc_natural_size()
1070
1019
        self.queue_draw()
1071
 
        return
1072
1020
 
1073
1021
    def get_natural_size(self):
1074
1022
        return self.natural_size
1092
1040
            aw = 12
1093
1041
        else:
1094
1042
            aw = 0
1095
 
        self.queue_draw_area(a.x-aw/2, a.y,
1096
 
                             a.width+aw, a.height)
1097
 
        return
 
1043
        self.queue_draw_area(a.x - aw / 2, a.y,
 
1044
                             a.width + aw, a.height)
1098
1045
 
1099
1046
 
1100
1047
class NavigationBar(PathBar):
1103
1050
        PathBar.__init__(self)
1104
1051
        self.id_to_part = {}
1105
1052
        self._callback_id = None
1106
 
        return
1107
1053
 
1108
1054
    def _on_part_clicked(self, part):
1109
1055
        part.callback(self, part)
1110
 
        return
1111
1056
 
1112
1057
    def add_with_id(self, label, callback, id, do_callback=True, animate=True):
1113
1058
        """
1149
1094
                    self._callback_id = None
1150
1095
 
1151
1096
                # if i do not have call the callback in an idle,
1152
 
                # all hell breaks loose    
 
1097
                # all hell breaks loose
1153
1098
                self._callback_id = GObject.idle_add(callback,
1154
1099
                                                     self,      # pathbar
1155
1100
                                                     part)
1156
1101
 
1157
1102
            self.append(part)
1158
 
        return
1159
1103
 
1160
1104
    def remove_ids(self, *ids, **kwargs):
1161
1105
        parts = self.get_parts()
1169
1113
        index = len(parts)
1170
1114
 
1171
1115
        for id, part in self.id_to_part.iteritems():
1172
 
            if id not in ids: continue
 
1116
            if id not in ids:
 
1117
                continue
1173
1118
            if part not in parts:
1174
1119
                cleanup_ids.append(id)
1175
1120
                part.destroy()
1176
1121
            else:
1177
1122
                index = min(index, parts.index(part))
1178
1123
 
1179
 
        if index == len(parts): return
 
1124
        if index == len(parts):
 
1125
            return
1180
1126
 
1181
1127
        # cleanup any stale id:part pairs in the id_to_part dict
1182
1128
        for id in cleanup_ids:
1192
1138
 
1193
1139
        # the index is used to remove all parts after the index but we
1194
1140
        # keep one part around to animate its removal
1195
 
        for part in parts[index+1:]:
 
1141
        for part in parts[index + 1:]:
1196
1142
            part.destroy()
1197
1143
 
1198
1144
        animate = True
1207
1153
        if 'do_callback' in kwargs and kwargs['do_callback']:
1208
1154
            part = self[-1]
1209
1155
            part.callback(self, part)
1210
 
        return
1211
1156
 
1212
1157
    def remove_all(self, **kwargs):
1213
 
        if len(self) <= 1: return
 
1158
        if len(self) <= 1:
 
1159
            return
1214
1160
        ids = filter(lambda k: k != 'category',
1215
1161
                     self.id_to_part.keys())
1216
1162
        self.remove_ids(*ids, **kwargs)
1217
 
        return
1218
1163
 
1219
1164
    def has_id(self, id):
1220
 
        return self.id_to_part.has_key(id)
 
1165
        return id in self.id_to_part
1221
1166
 
1222
1167
    def get_parts(self):
1223
1168
        return self.get_children()
1224
1169
 
1225
1170
    def get_active(self):
1226
1171
        parts = self.get_parts()
1227
 
        if not parts: return None
1228
 
        return parts[-1]
 
1172
        if parts:
 
1173
            return parts[-1]
1229
1174
 
1230
1175
    def get_button_from_id(self, id):
1231
1176
        """
1232
1177
        return the button for the given id (or None)
1233
1178
        """
1234
 
        if not id in self.id_to_part:
1235
 
            return None
1236
 
        return self.id_to_part[id]
 
1179
        return self.id_to_part.get(id)
1237
1180
 
1238
1181
    def set_active_no_callback(self, part):
1239
 
        return
 
1182
        pass
1240
1183
 
1241
1184
 
1242
1185
class TestIt:
1247
1190
            t = entry.get_text() or 'no label %s' % len(pathbar)
1248
1191
            part = PathPart(t)
1249
1192
            pathbar.append(part)
1250
 
            return
1251
1193
 
1252
1194
        def remove(button, entry, pathbar):
1253
1195
            pathbar.pop()
1254
 
            return
1255
1196
 
1256
1197
        win = Gtk.Window()
1257
1198
        win.set_border_width(30)
1284
1225
        self.win = win
1285
1226
        self.win.pb = pb
1286
1227
 
 
1228
 
1287
1229
def get_test_pathbar_window():
1288
1230
    t = TestIt()
1289
1231
    return t.win
1291
1233
if __name__ == '__main__':
1292
1234
    win = get_test_pathbar_window()
1293
1235
    Gtk.main()
1294