~ubuntu-branches/debian/sid/groundcontrol/sid

« back to all changes in this revision

Viewing changes to nautilus-groundcontrol.py

  • Committer: Bazaar Package Importer
  • Author(s): Luke Faraone
  • Date: 2010-02-07 18:26:54 UTC
  • Revision ID: james.westby@ubuntu.com-20100207182654-u0n26lkazgfog4et
Tags: upstream-1.5
ImportĀ upstreamĀ versionĀ 1.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright 2009 Martin Owens
 
3
#
 
4
# This program is free software: you can redistribute it and/or modify
 
5
#  it under the terms of the GNU General Public License as published by
 
6
#  the Free Software Foundation, either version 3 of the License, or
 
7
#  (at your option) any later version.
 
8
#
 
9
#  This program is distributed in the hope that it will be useful,
 
10
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
#  GNU General Public License for more details.
 
13
#
 
14
#  You should have received a copy of the GNU General Public License
 
15
#  along with this program.  If not, see <http://www.gnu.org/licenses/>
 
16
#
 
17
"""
 
18
This is a simple extension for nautilus to allow project integration.
 
19
"""
 
20
 
 
21
import os
 
22
import gtk
 
23
import gio
 
24
import gobject
 
25
import nautilus
 
26
import logging
 
27
import webbrowser
 
28
 
 
29
from urlparse import urlparse
 
30
from urllib import url2pathname
 
31
from bzrlib import errors
 
32
 
 
33
from GroundControl.projects import ProjectSelection, Project, ProjectCreateApp
 
34
from GroundControl.bugs     import BugSelection
 
35
from GroundControl.branches import BranchSelection
 
36
from GroundControl.commiter import CommitChanges, RevertChanges
 
37
from GroundControl.butties  import get_functions
 
38
from GroundControl.gtkviews import IconManager
 
39
from GroundControl.helper   import HelpApp
 
40
from GroundControl.merger   import RequestMergeApp, merge_url
 
41
from GroundControl.bazaar   import (
 
42
    BzrBranch, CommitStatusApp, PushStatusApp, BranchStatusApp,
 
43
    CheckoutStatusApp, PullStatusApp, RevertStatusApp, MergeStatusApp,
 
44
    UnlockStatusApp
 
45
)
 
46
from GroundControl.configuration import ConfigGui
 
47
from GroundControl.base import (
 
48
    PROJECT_XDG, PROJECT_NAME,
 
49
    is_online, set_online_changed
 
50
)
 
51
from GroundControl import __appname__, __version__
 
52
from GroundControl.launchpad import DEFAULT_CRED
 
53
 
 
54
# Do not use launchpadlib within this main thread
 
55
# It's not thread safe and causes lots of errors.
 
56
 
 
57
print _("Initializing %s-%s extension" % (__appname__, __version__))
 
58
BIN_DIR = './bin'
 
59
HOME_DIR = os.path.expanduser('~/')
 
60
STATUS_ICONS = IconManager('')
 
61
 
 
62
class GroundControlBar(nautilus.LocationWidgetProvider, nautilus.InfoProvider):
 
63
    """Project management extension class"""
 
64
    def __init__(self):
 
65
        self._lpa    = None
 
66
        self._status = None
 
67
        self._giomon = None
 
68
        self._change_watch = False
 
69
        self._file_changed = False
 
70
        set_online_changed(self.update_widget)
 
71
        self._reset_vars()
 
72
 
 
73
    def _reset_vars(self):
 
74
        """Set some basic variables"""
 
75
        self._path    = None
 
76
        self._project = None
 
77
        self._widget  = None
 
78
        self._url     = None
 
79
        self._window  = None
 
80
 
 
81
    @property
 
82
    def status(self):
 
83
        """Get the status of the Launchpad configuration"""
 
84
        return os.path.exists(DEFAULT_CRED % PROJECT_NAME)
 
85
 
 
86
    def get_widget(self, url, window):
 
87
        """
 
88
        Returns either None or a Gtk widget to decorate the Nautilus
 
89
        window with, based on whether the current directory is a storage
 
90
        directory.
 
91
        """
 
92
        self._reset_vars()
 
93
        parsed_url = urlparse(url)
 
94
        self._url = url
 
95
        self._online = is_online()
 
96
        self._window = window
 
97
 
 
98
        if parsed_url.scheme == "file" and parsed_url.path:
 
99
            path = url2pathname(parsed_url.path)
 
100
            self._path = path
 
101
            self._widget = BarWidget(self, online=self._online)
 
102
            self._project = self.get_project(path)
 
103
 
 
104
            # Cancel any monitoring
 
105
            self.moniter_directory(None)
 
106
            # Convience variable for widget
 
107
            bar = self._widget
 
108
            config = os.path.join(path, '.groundcontrol')
 
109
 
 
110
            # Create the default file on contact, this isn't ideal
 
111
            # But it prepares the ground for more interesting features
 
112
            if path == os.path.join(HOME_DIR, _('Projects')):
 
113
                if not os.path.exists(config):
 
114
                    fhl = open(config, 'w')
 
115
                    fhl.write('\n')
 
116
                    fhl.close()
 
117
            
 
118
            # First part, the projects directory
 
119
            if os.path.exists(config):
 
120
                # Pre-functionality, check to see if our stuff works
 
121
                if not self.status:
 
122
                    msg = _("Please Enter Account Details")
 
123
                    if not self._online:
 
124
                        msg = _("Account Details Not Available")
 
125
                    bar.new_mode('configure', msg)
 
126
                    bar.button(_("Configure Launchpad"), self.configure_gui)
 
127
                    bar.icon('launchpad')
 
128
                    return bar
 
129
 
 
130
                bar.new_mode('projects', _("Your Projects"))
 
131
                bar.button(_("Fetch Project"), self.project_gui)
 
132
                bar.button(_("Fix Bug"), self.bugfix_gui)
 
133
                bar.icon('groundcontrol')
 
134
                return bar
 
135
 
 
136
            # Second part, an actual project
 
137
            if self._project:
 
138
                if self._project.broken:
 
139
                    # This means the file borked and needs to be regenerated
 
140
                    bar.new_mode('project', "Project Configuration Corrupted")
 
141
                    if self._online:
 
142
                        bar.button(_("Get New Data"), self._project.regenerate,
 
143
                            self._window, self.update_widget)
 
144
                    bar.icon('groundcontrol')
 
145
                else:
 
146
                    bar.new_mode('project', "%s" % self._project.name)
 
147
                    if self._online:
 
148
                        bar.button(_("Fetch Branch"), self.branch_gui)
 
149
                        bar.button(_("Fix Bug"), self.bugfix_gui,
 
150
                            self._project.pid)
 
151
                    bar.icon(self._project.logo())
 
152
                return bar
 
153
 
 
154
            # Third part, inside a code branch
 
155
            if os.path.exists(os.path.join(path, '.bzr')):
 
156
                try:
 
157
                    return self.bazaar_choices(path, bar)
 
158
                except errors.BzrError, error:
 
159
                    message = str(error)
 
160
                    logging.warn("Found a broken bazaar branch! %s" % message)
 
161
                    bar.new_mode('broken', _('Broken Bzr Branch %s' % message))
 
162
                    bar.icon('error')
 
163
                    return bar
 
164
        return None
 
165
 
 
166
    def hide_widget(self):
 
167
        """We always need a bar widget, but we sometimes need to hide it"""
 
168
        #logging.debug("TRYING TO HIDE %s" % self._widget.get_parent())
 
169
        self._widget.get_parent().hide()
 
170
 
 
171
    def update_widget(self, widget=None):
 
172
        """Refresh the bar widget"""
 
173
        if not self._widget:
 
174
            return
 
175
        self._status = None
 
176
        oldparent = self._widget.get_parent()
 
177
        self._widget.destroy()
 
178
        logging.debug("Updated: Getting a new Nautilus widget")
 
179
        new_widget = self.get_widget(self._url, self._window)
 
180
        if new_widget:
 
181
            oldparent.pack_start(new_widget)
 
182
            oldparent.show()
 
183
        else:
 
184
            oldparent.hide()
 
185
 
 
186
    def get_project(self, path):
 
187
        """Get project information for a dir"""
 
188
        try:
 
189
            return Project(path)
 
190
        except IOError:
 
191
            return None
 
192
 
 
193
    def configure_gui(self, widget=None):
 
194
        """Reconfigure the launchpad oauth login"""
 
195
        try:
 
196
            ConfigGui(
 
197
                parent=self._window,
 
198
                callback=self.update_widget,
 
199
                start_loop=True)
 
200
        except Exception, message:
 
201
            logging.error(_("Error in config GUI: %s") % message)
 
202
 
 
203
    def project_gui(self, widget=None):
 
204
        """Calls up the project selection gui"""
 
205
        ProjectSelection(self._path,
 
206
            callback=self.do_project_create,
 
207
            parent=self._window,
 
208
            start_loop=True)
 
209
 
 
210
    def do_project_create(self, project):
 
211
        """Create a new project with our project object"""
 
212
        ProjectCreateApp(path=self._path,
 
213
            project=project,
 
214
            parent=self._window,
 
215
            start_loop=True)
 
216
 
 
217
    def bugfix_gui(self, widget=None, project=None):
 
218
        """Calls up the bug selection gui"""
 
219
        BugSelection(project, ensure_project=self._path,
 
220
            callback=self.create_bugfix,
 
221
            parent=self._window,
 
222
            start_loop=True)
 
223
 
 
224
    def create_bugfix(self, bug, project=None):
 
225
        """Prepare to fix a bug we've selected"""
 
226
        self._working_bug = bug
 
227
        path = self._path
 
228
        if not project:
 
229
            path = os.path.join(self._path, bug.project_id)
 
230
        if os.path.exists(path):
 
231
            # Load project without touching bug.project because that
 
232
            # would take more time to load the object and it's attr.
 
233
            project = Project(path)
 
234
        else:
 
235
            # Error because this shouldn't happen
 
236
            raise Exception("Couldn't create projects directory!!")
 
237
        # We need a generated workname and branch name
 
238
        lpname = 'lp:%s' % project.pid
 
239
        workname = 'bugfix-lp-%s' % bug.id
 
240
        # At first we try a default development focus
 
241
        try:
 
242
            branch = BzrBranch(lpname)
 
243
            logging.debug("Found development target %s" % branch.url)
 
244
        except errors.InvalidURL:
 
245
            # Now that we know we don't have a development focus
 
246
            # Try and ask the user to choose a branch.
 
247
            BranchSelection(project.pid, path,
 
248
                workname=workname,
 
249
                parent=self._window,
 
250
                callback=self.create_fix_branch,
 
251
                start_loop=True)
 
252
        else:
 
253
            # Branch a new branch for our code using the development focus
 
254
            self.create_fix_branch(lpname=lpname, path=path, workname=workname)
 
255
 
 
256
    def create_fix_branch(self, **kwargs):
 
257
        """A special fix branch which is labeled as such"""
 
258
        path = os.path.join(kwargs['path'], kwargs['workname'])
 
259
        self.do_branch(**kwargs)
 
260
        fixes = self._working_bug.id
 
261
        title = self._working_bug.name
 
262
        bra = BzrBranch(path)
 
263
        bra.config.set_user_option('fixes', fixes)
 
264
        bra.config.set_user_option('bug_title', title)
 
265
 
 
266
    def branch_gui(self, widget=None):
 
267
        """Load the Branch Selecter GUI"""
 
268
        if self._project.pid:
 
269
            BranchSelection(self._project.pid, self._path,
 
270
                parent=self._window,
 
271
                callback=self.do_branch,
 
272
                start_loop=True)
 
273
 
 
274
    def do_branch(self, **kwargs):
 
275
        """Load a new window to deal with branching."""
 
276
        path = kwargs.pop('path', self._path)
 
277
        lpname = kwargs.pop('lpname')
 
278
        workname = kwargs.pop('workname')
 
279
        # No need to callback to refresh, we're in the wrong dir
 
280
        if not lpname:
 
281
            raise Exception("Can't fetch code, no branch specified")
 
282
 
 
283
        if workname:
 
284
            logging.debug("Branching Code...")
 
285
            BranchStatusApp(branch=lpname, path=path,
 
286
                workname=workname,
 
287
                parent=self._window,
 
288
                start_loop=True)
 
289
        else:
 
290
            logging.debug("Checking-out Code...")
 
291
            CheckoutStatusApp(branch=lpname, path=path,
 
292
                parent=self._window,
 
293
                start_loop=True)
 
294
 
 
295
    def bazaar_choices(self, path, widget):
 
296
        """Decide what choices to give to users inside a bazaar project"""
 
297
        brch = BzrBranch(path)
 
298
        project = self.get_project(os.path.dirname(path))
 
299
        self.moniter_directory(path)
 
300
        # Decide if we have a branch checkout (it has a push setup)
 
301
        if brch.has_branching() and brch.branch.get_push_location():
 
302
            fixes    = brch.config.get_user_option('fixes')
 
303
            bugtitle = brch.config.get_user_option('bug_title')
 
304
            changes  = brch.get_changes()
 
305
            logging.debug("Branch locked: %s" % brch.is_locked())
 
306
            if brch.is_locked():
 
307
                widget.new_mode('code-locked', _("Branch Locked"))
 
308
                widget.button(_("Unlock"), self.unlock_gui,
 
309
                    brch, offline=True)
 
310
            # Elsif there are non-commited changes
 
311
            elif changes.has_changed():
 
312
                widget.new_mode('code-modified', _("Files Modified"))
 
313
                widget.button(_("Commit Changes"), self.commit_gui,
 
314
                    brch, project, offline=True)
 
315
                widget.button(_("Revert Changes"), self.revert_gui,
 
316
                    brch, offline=True)
 
317
            # Elsif there are new files, we all want to commit them.
 
318
            elif brch.has_newfiles():
 
319
                widget.new_mode('code-modified', _("New Files"))
 
320
                widget.button(_("Commit New Files"), self.commit_gui,
 
321
                    brch, project, offline=True)
 
322
                widget.button(_("Revert Changes"), self.revert_gui,
 
323
                    brch, offline=True)
 
324
            # Elseif there is a difference between remote and local versions
 
325
            elif brch.has_commits():
 
326
                widget.new_mode('code-commited', _("Local Changes"))
 
327
                widget.button(_("Upload Changes"), self.do_push, brch)
 
328
                widget.button(_("Update"), self.do_resync, brch)
 
329
            # Elsif there has already been a merge request posted.
 
330
            elif brch.merge_revno():
 
331
                widget.new_mode('code-mergereq', _("Merge Requested"))
 
332
                widget.button(_("View Request"), self.view_merge, brch)
 
333
                #widget.button("Update Status", self.update_merge, brch)
 
334
            # Elsif there is no difference, but it's bigger
 
335
            # than the original version
 
336
            elif brch.has_pushes() and brch.is_child():
 
337
                widget.new_mode('code-uploaded',
 
338
                    _("Uploaded - Merge Required"))
 
339
                widget.button(_("Request Merge"), self.do_merge_request, brch)
 
340
                widget.button(_("Update"), self.do_resync, brch)
 
341
            else:
 
342
                # If there is nothing to do, then don't display anything
 
343
                #gobject.timeout_add( 200, self.hide_widget )
 
344
                # But there is something to do, do an update to trunk
 
345
                widget.new_mode('code-none', _("Code Branch"))
 
346
                widget.button(_("Update"), self.do_resync, brch)
 
347
                if project and not brch.is_child():
 
348
                    widget.button(_("Merge In"), self.merge_gui, brch, project)
 
349
            # We only want to display a single response for bug fixes
 
350
            # Except for when we have done our merge request.
 
351
            if fixes and not brch.merge_revno():
 
352
                mode = widget._mode_id
 
353
                title = "[lp:%s] %s" % (fixes, bugtitle)
 
354
                if mode == 'code-none':
 
355
                    widget.new_mode('code-none', title)
 
356
                elif mode != 'code-mergereq':
 
357
                    widget.new_mode('code-modified', title)
 
358
                    widget.button(_("Upload Fix"), self.submitfix_gui,
 
359
                        brch, project, fixes)
 
360
                widget.icon('fix-bug')
 
361
                return widget
 
362
        else:
 
363
            widget.new_mode('code-checkout', _("<b>Read-Only Branch</b>"))
 
364
            widget.button(_("Update to Latest"), self.do_resync, brch)
 
365
        self.add_custom_functions(widget, path)
 
366
        widget.icon('bazaar')
 
367
        return widget
 
368
 
 
369
    def unlock_gui(self, widget, branch):
 
370
        """Show the confirm commit dialog"""
 
371
        UnlockStatusApp(callback=self.update_widget,
 
372
                parent=self._window,
 
373
                start_loop=True,
 
374
                branch=branch)
 
375
 
 
376
    def submitfix_gui(self, widget, branch, project, fixing):
 
377
        """Show the confirm commit dialog"""
 
378
        if branch.get_changes().has_changed():
 
379
            # Only commit if we have changes to commit
 
380
            CommitChanges(branch=branch, project=project, fixing=fixing,
 
381
                callback=self.do_submitfix,
 
382
                parent=self._window,
 
383
                start_loop=True)
 
384
        else:
 
385
            # Move on to what's needed to do
 
386
            self.do_mergefix(branch)
 
387
 
 
388
    def do_submitfix(self, branch, **kwargs):
 
389
        """Submit a fix by commiting then merge the fix"""
 
390
        self.do_commit(branch=branch, **kwargs)
 
391
        self.do_mergefix(branch)
 
392
 
 
393
    def do_mergefix(self, branch):
 
394
        """Make a merge request if we don't already have one"""
 
395
        if not branch.merge_revno():
 
396
            self.do_push(None, branch)
 
397
            self.do_merge_request(None, branch)
 
398
        else:
 
399
            self.do_push(None, branch)
 
400
 
 
401
    def commit_gui(self, widget, branch, project):
 
402
        """Show the confirm commit dialog"""
 
403
        CommitChanges(branch=branch, project=project,
 
404
                callback=self.do_commit,
 
405
                parent=self._window,
 
406
                start_loop=True)
 
407
 
 
408
    def do_commit(self, **kwargs):
 
409
        """What to do after we've commited"""
 
410
        CommitStatusApp(callback=self.update_widget,
 
411
                parent=self._window,
 
412
                start_loop=True,
 
413
                **kwargs)
 
414
 
 
415
    def revert_gui(self, widget, branch):
 
416
        """Ask if we should revert the changes."""
 
417
        RevertChanges(branch=branch,
 
418
                parent=self._window,
 
419
                callback=self.do_revert,
 
420
                start_loop=True)
 
421
 
 
422
    def do_revert(self, **kwargs):
 
423
        """Now actually revert the branch"""
 
424
        RevertStatusApp(callback=self.update_widget,
 
425
                parent=self._window,
 
426
                start_loop=True,
 
427
                **kwargs)
 
428
 
 
429
    def do_resync(self, widget, branch):
 
430
        """Do a pull and then update out widget"""
 
431
        PullStatusApp(branch=branch,
 
432
                callback=self.update_widget,
 
433
                parent=self._window,
 
434
                start_loop=True)
 
435
 
 
436
    def do_push(self, widget, branch):
 
437
        """Do a push and then update our widget"""
 
438
        PushStatusApp(branch=branch,
 
439
                callback=self.update_widget,
 
440
                parent=self._window,
 
441
                start_loop=True)
 
442
 
 
443
    def do_merge_request(self, widget, branch):
 
444
        """Open the gui for creating a merge request"""
 
445
        RequestMergeApp(branch,
 
446
                parent=self._window,
 
447
                callback=self.update_widget,
 
448
                start_loop=True)
 
449
 
 
450
    def view_merge(self, widget, branch):
 
451
        """Somehow view the merge request, maybe open up the web browser"""
 
452
        webbrowser.open(merge_url(branch), autoraise=1)
 
453
 
 
454
    def update_merge(self, widget, branch):
 
455
        """Updates the merge request to check if it's done or not."""
 
456
        logging.warn(_("Functionality not written yet"))
 
457
 
 
458
    def merge_gui(self, widget, branch, project):
 
459
        """Show a list of branches for which to merge into this one"""
 
460
        if project.pid:
 
461
            BranchSelection(project.pid, self._path,
 
462
                workname='#merge',
 
463
                branch=branch,
 
464
                parent=self._window,
 
465
                callback=self.do_merge,
 
466
                start_loop=True)
 
467
 
 
468
    def do_merge(self, **kwargs):
 
469
        """Merge the given branch with the branch at path"""
 
470
        path = kwargs.pop('path', self._path)
 
471
        lpname = kwargs.pop('lpname')
 
472
        MergeStatusApp(branch=path, source=lpname,
 
473
                callback=self.update_widget,
 
474
                parent=self._window,
 
475
                start_loop=True)
 
476
 
 
477
    def update_file_info(self, file):
 
478
        """will hopefully let us update icons and such"""
 
479
        if self._widget and self._widget._mode_id == 'projects':
 
480
            if file.get_uri_scheme() == 'file':
 
481
                path = url2pathname(urlparse(file.get_uri()).path)
 
482
                project = self.get_project(path)
 
483
                if project and not project.broken:
 
484
                    file.add_emblem('package')
 
485
                    #file.add_string_attribute('custom_icon', '.logo.png')
 
486
                    logging.debug("Set emblem %s" % str(project.name))
 
487
 
 
488
    def moniter_directory(self, path):
 
489
        """Moniter one directory for changes, cancel any previous"""
 
490
        if self._giomon:
 
491
            self._giomon.cancel()
 
492
        if path:
 
493
            gfile = gio.file_parse_name(path)
 
494
            self._giomon = gfile.monitor_directory()
 
495
            self._giomon.connect("changed", self.file_changed)
 
496
            if not self._change_watch:
 
497
                gobject.timeout_add( 1000, self.check_file_changes )
 
498
                self._change_watch = True
 
499
 
 
500
    def file_changed(self, filemonitor, file, other_file, event_type):
 
501
        """Event for when files have changed"""
 
502
        self._file_changed = True
 
503
 
 
504
    def check_file_changes(self):
 
505
        """This is make changes more course and not so many of them"""
 
506
        if self._file_changed:
 
507
            self._file_changed = False
 
508
            logging.debug("Bazaar contents have changed - updating...")
 
509
            self.update_widget()
 
510
        if self._window and self._window.get_window():
 
511
            gobject.timeout_add( 1000, self.check_file_changes )
 
512
 
 
513
    def add_custom_functions(self, bar, path):
 
514
        """Add some buttons to the bar widget"""
 
515
        cmode = bar._mode_id
 
516
        for cmd in get_functions(path):
 
517
            if cmd.is_mode(cmode):
 
518
                ofl = cmd.opt.get('offline', 'True') == 'True'
 
519
                button = bar.button(None, cmd.run, offline=ofl)
 
520
                label = gtk.Label()
 
521
                label.set_markup("<i>%s</i>" % cmd.label)
 
522
                label.show()
 
523
                button.add(label)
 
524
                button.show()
 
525
 
 
526
 
 
527
class BarWidget(gtk.HBox):
 
528
    """Basic bar widget for location widgets"""
 
529
    def __init__(self, parent, *args, **kwargs):
 
530
        """Return a valid wiget for this box"""
 
531
        self._online = kwargs.pop('online', True)
 
532
        super(BarWidget, self).__init__(*args, **kwargs)
 
533
        self._bar = parent
 
534
        self._icon = None
 
535
        self._mode_id = None
 
536
        self._attached = []
 
537
        self._label = gtk.Label()
 
538
        self._label.set_alignment(0.0, 0.5)
 
539
        self._label.set_line_wrap(False)
 
540
        self.pack_start(self._label, expand=True, fill=True, padding=4)
 
541
        self.show()
 
542
 
 
543
    def new_mode(self, mode_id, label):
 
544
        """Clears all the existing settings and replaces the label"""
 
545
        self._mode_id = mode_id
 
546
        self._label.set_markup("<b>%s</b>" % label)
 
547
        self._label.show()
 
548
        for att in self._attached:
 
549
            self.remove(att)
 
550
        self._attached = []
 
551
        if self._icon:
 
552
            self.remove(self._icon)
 
553
            self._icon = None
 
554
        self.show_help_button()
 
555
        gobject.timeout_add( 200, self.show_parent )
 
556
 
 
557
    def show_parent(self):
 
558
        """show the widgets parent object"""
 
559
        parent = self.get_parent()
 
560
        if parent:
 
561
            self.get_parent().show()
 
562
        else:
 
563
            logging.debug("Couldn't show bar parent widget - not packed yet")
 
564
 
 
565
    def icon(self, iconname):
 
566
        """Sets a single location for an image icon on the line."""
 
567
        if not self._icon:
 
568
            self._icon = gtk.Image()
 
569
            self.pack_start(self._icon, expand=False, fill=False, padding=4)
 
570
            self.reorder_child(self._icon, 0)
 
571
        pixbuf = STATUS_ICONS.get_icon(iconname)
 
572
        if pixbuf:
 
573
            endresult = pixbuf.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
 
574
            self._icon.set_from_pixbuf(endresult)
 
575
            self._icon.show()
 
576
 
 
577
    def button(self, label, signal, *attrs, **kwargs):
 
578
        """Adds a new button to the bar widget and attaches a signal."""
 
579
        offline = kwargs.get('offline', False)
 
580
        if not (offline or self._online):
 
581
            return None
 
582
        button = gtk.Button()
 
583
        button.connect("clicked", signal, *attrs)
 
584
        if label:
 
585
            button.set_label(label)
 
586
            button.show()
 
587
        self._attached.append(button)
 
588
        self.pack_end(button, expand=False, fill=False, padding=4)
 
589
        return button
 
590
 
 
591
    def show_help_button(self):
 
592
        """Add in a helpful widget that runs show_help"""
 
593
        if HelpApp.is_help(self._mode_id):
 
594
            image = gtk.Image()
 
595
            pixbuf = image.render_icon(gtk.STOCK_HELP, gtk.ICON_SIZE_BUTTON)
 
596
            image.set_from_pixbuf(pixbuf)
 
597
            image.show()
 
598
            button = self.button(None, self.show_help, offline=True)
 
599
            button.set_relief(gtk.RELIEF_NONE)
 
600
            button.add(image)
 
601
            button.show()
 
602
 
 
603
    def show_help(self, widget=None):
 
604
        """Show a simple icon button that shows help when clicked"""
 
605
        if HelpApp.is_help(self._mode_id):
 
606
            HelpApp(self._mode_id, parent=self._bar._window, start_loop=True)
 
607