~ubuntu-branches/ubuntu/trusty/groundcontrol/trusty-proposed

« back to all changes in this revision

Viewing changes to GroundControl/projects.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
Small app for selecting launchpad projects
 
19
"""
 
20
 
 
21
# Import standard python libs
 
22
import os
 
23
import gtk
 
24
import gtk.gdk
 
25
import pango
 
26
import logging
 
27
import yaml
 
28
 
 
29
# Various required variables and locations
 
30
from GroundControl.gtkviews import (
 
31
    GtkApp, ThreadedWindow,
 
32
    IconManager, TreeView
 
33
)
 
34
from GroundControl.gtkcommon import StatusApp
 
35
from GroundControl.launchpad import (
 
36
    LaunchpadProject, LaunchpadProjects, get_launchpad
 
37
)
 
38
 
 
39
project_icons = IconManager('project')
 
40
PROJECT_FILE = '.gcproject'
 
41
ITEM_MARKUP = """<b><big>%s</big></b>
 
42
%s
 
43
  <small><i>lp:%s</i></small>"""
 
44
 
 
45
from shutil import copyfile
 
46
from bzrlib import transport, bzrdir
 
47
 
 
48
 
 
49
class Project(object):
 
50
    """Simple object for managing a project directory"""
 
51
    def __init__(self, path):
 
52
        self._path   = path
 
53
        self._config = None
 
54
        self.broken  = False
 
55
        if self.config and not self.broken:
 
56
            logging.debug("Loaded project %s" % self.pid)
 
57
 
 
58
    @property
 
59
    def config(self):
 
60
        """Load a configuration file"""
 
61
        if not self._config:
 
62
            cfile = os.path.join(self._path, PROJECT_FILE)
 
63
            self.load_project(cfile)
 
64
            if not self._config:
 
65
                raise IOError("Can't find project configuration file")
 
66
            if type(self._config) != dict:
 
67
                self._config = None
 
68
        return self._config
 
69
 
 
70
    def load_project(self, filename, regenerate=True):
 
71
        """Try and load a project's configuration file"""
 
72
        if os.path.isdir(self._path) and os.path.exists(filename):
 
73
            fhl = open(filename, 'r')
 
74
            self._config = yaml.load(fhl)
 
75
            if type(self._config) != dict and regenerate:
 
76
                self.broken = True
 
77
            fhl.close()
 
78
 
 
79
    def regenerate(self, widget, window, callback=None):
 
80
        """Try and regenerate a project's configuration"""
 
81
        if not callback:
 
82
            callback = self.regen_done
 
83
        ProjectCreateApp(project=os.path.basename(self._path),
 
84
            path=os.path.dirname(self._path),
 
85
            parent=window,
 
86
            callback=callback,
 
87
            start_loop=True)
 
88
 
 
89
    def regen_done(self):
 
90
        """What happens when we've regnerated the configuration"""
 
91
        self.load_project(self._filename, False)
 
92
 
 
93
    @property
 
94
    def pid(self):
 
95
        """Return a project id (nickname)"""
 
96
        return str(self.config['id'])
 
97
 
 
98
    @property
 
99
    def name(self):
 
100
        """Return a project name (with spaces)"""
 
101
        return str(self.config['name'])
 
102
 
 
103
    def logo(self):
 
104
        """Return the prospective logo filename"""
 
105
        return os.path.join(self._path, '.logo.png')
 
106
 
 
107
 
 
108
class ProjectCreateApp(StatusApp):
 
109
    """Show a status window when we're making a project"""
 
110
    task_name = _("Creating Project")
 
111
 
 
112
    def do_work(self):
 
113
        """Make our project directory and populate it"""
 
114
        project = self.project
 
115
 
 
116
        # When regenerating a launchpad project's data we won't have
 
117
        # A complete project object, just it's id. So Load the object.
 
118
        if type(project) in (str, unicode):
 
119
            self.update(0, _("Getting Launchpad Details"))
 
120
            lpa = get_launchpad().launchpad
 
121
            try:
 
122
                project = LaunchpadProject(lpa, oid=project)
 
123
            except KeyError:
 
124
                logging.error("Couldn't find existing project, removing.")
 
125
                return os.unlink(os.path.join(self.path, project, PROJECT_FILE))
 
126
                
 
127
        self.path = os.path.join(self.path, project.id)
 
128
        if not os.path.exists(self.path):
 
129
            self.update(0.1, _("Creating Bazaar Cache"))
 
130
            to_transport = transport.get_transport(self.path)
 
131
            to_transport.ensure_base()
 
132
            repo_format = bzrdir.format_registry.make_bzrdir('default')
 
133
            newdir = repo_format.initialize_on_transport(to_transport)
 
134
            repo = newdir.create_repository(shared=True)
 
135
            repo.set_make_working_trees(True)
 
136
        filename = os.path.join(self.path, PROJECT_FILE)
 
137
        if filename:
 
138
            self.update(0.3, _("Creating Project Configuration"))
 
139
            filename = os.path.join(self.path, PROJECT_FILE)
 
140
            fhl = open(filename, 'w')
 
141
            fhl.write(yaml.dump({
 
142
                'id' : project.id,
 
143
                'name' : project.name,
 
144
                'desc' : project.desc,
 
145
            }))
 
146
            fhl.close()
 
147
            self.update(0.5, _("Getting Branding"))
 
148
            self._save_image(project, "brand")
 
149
            self.update(0.7, _("Getting Logo"))
 
150
            self._save_image(project, "logo")
 
151
            self.update(0.9, _("Getting Icon"))
 
152
            self._save_image(project, "icon")
 
153
            self.update(1, _("All Done"))
 
154
 
 
155
    def _save_image(self, project, name):
 
156
        """Save images to the disk as required"""
 
157
        filename = os.path.join( self.path, ".%s.png" % name )
 
158
        result = project._image_file(name)
 
159
        if result:
 
160
            copyfile( result, filename )
 
161
        else:
 
162
            logging.debug("Not saving image - %s doesn't exist." % name)
 
163
 
 
164
    def load_vars(self, args, kwargs):
 
165
        """Load our project creation"""
 
166
        self.project = kwargs.pop('project')
 
167
        self.path    = kwargs.pop('path')
 
168
 
 
169
 
 
170
class SelectionWindow(ThreadedWindow):
 
171
    """Select a project to import."""
 
172
    name = 'project_search'
 
173
 
 
174
    def load(self, path, *args, **kwargs):
 
175
        """Load the project selection GUI"""
 
176
        self.slist      = None
 
177
        self.child      = None
 
178
        self._path      = path
 
179
        self._selected  = None
 
180
        self._launchpad = None
 
181
        # Setup the list of projects from a search
 
182
        self.unselected()
 
183
        self.slist = ProjectsView(self.widget('projectslist'),
 
184
            selected=self.selected,
 
185
            unselected=self.unselected)
 
186
        super(SelectionWindow, self).load(*args, **kwargs)
 
187
 
 
188
    @property
 
189
    def launchpad(self):
 
190
        """Return a launchpad object"""
 
191
        if not self._launchpad:
 
192
            self._launchpad = get_launchpad()
 
193
        return self._launchpad
 
194
 
 
195
    def inital_thread(self):
 
196
        """What to run when we execute the thread."""
 
197
        # Set up the launchpad projects object and connect search
 
198
        self.lpp = LaunchpadProjects(self.launchpad.launchpad)
 
199
        self.lpp.connect_signal("search_finish", self.call, 'finish_search')
 
200
        self.lpp.connect_signal("search_result", self.call, 'add_search_item')
 
201
        self.call('finish_search')
 
202
 
 
203
    def add_search_item(self, item):
 
204
        """Add an item to the slist"""
 
205
        return self.slist.add_item(item)
 
206
 
 
207
    def signals(self):
 
208
        """project window signals"""
 
209
        return {
 
210
            'search' : self.project_search,
 
211
        }
 
212
 
 
213
    def get_args(self):
 
214
        """Return the selected project"""
 
215
        return {
 
216
            'project' : self.selected(),
 
217
        }
 
218
 
 
219
    def selected(self, item=None):
 
220
        """An item has surely been selected."""
 
221
        if item:
 
222
            self._selected = item
 
223
            self.widget('buttonok').set_sensitive(True)
 
224
        return self._selected
 
225
 
 
226
    def unselected(self):
 
227
        """All items unselected"""
 
228
        self._selected = None
 
229
        self.widget('buttonok').set_sensitive(False)
 
230
 
 
231
    def project_search(self, button=None):
 
232
        """Search for projects in launchpad."""
 
233
        self.slist.clear()
 
234
        self.widget("findButton").hide()
 
235
        self.widget("term").hide()
 
236
        self.widget("searchLabel").set_text(
 
237
            _("Searching Launchpad - please wait..."))
 
238
        terms = self.widget("term").get_text()
 
239
        # This is a threaded search, so be careful about
 
240
        # What is loaded and what is updated in gtk.
 
241
        self.slist.search_term(term=terms)
 
242
        self.start_thread(self.lpp.search, terms)
 
243
 
 
244
    def finish_search(self, widget=None):
 
245
        """Event of finishing the search process."""
 
246
        self.widget("findButton").show()
 
247
        self.widget("term").show()
 
248
        self.widget("searchLabel").set_text("Name:")
 
249
 
 
250
    def is_valid(self):
 
251
        """Return true is a project is selected"""
 
252
        return self._selected != None
 
253
 
 
254
 
 
255
class ProjectSelection(GtkApp):
 
256
    """Application for loading a project"""
 
257
    gtkfile = 'project-select.glade'
 
258
    windows = [ SelectionWindow ]
 
259
 
 
260
 
 
261
class ProjectsView(TreeView):
 
262
    """Controls and operates a table as a projects view."""
 
263
    # We can set variables here because this is expected to be
 
264
    # A single object class and not multiples.
 
265
    _search_term = None
 
266
 
 
267
    def setup(self):
 
268
        """Setup a treeview for showing services"""
 
269
        def text_cell_func(cell_layout, renderer, model, item_iter):
 
270
            """Render the text of the services tree view"""
 
271
            item = model.get_value(item_iter, 0)
 
272
            name = str(item.name).replace('&', '&amp;')
 
273
            desc = str(item.desc).replace('&', '&amp;')
 
274
            markup = ITEM_MARKUP % (name, desc, item.id)
 
275
            renderer.set_property("markup", markup)
 
276
 
 
277
        def icon_cell_func(column, cell, model, item_iter):
 
278
            """Reender the icons of the services tree view"""
 
279
            item = model.get_value(item_iter, 0)
 
280
            img = project_icons.get_icon(item.logo())
 
281
            img2 = img.scale_simple(64, 64, gtk.gdk.INTERP_BILINEAR)
 
282
            cell.set_property("pixbuf", img2)
 
283
            cell.set_property("visible", True)
 
284
            
 
285
        def sort_projects_func(model, iter1, iter2):
 
286
            """Sorts projects by name to have them ordered by search param, 
 
287
               rather than the order returned by LP"""
 
288
            search_term = self.search_term().lower()
 
289
            
 
290
            proj1 = str(model.get_value(iter1, 0).name).lower()
 
291
            proj2 = str(model.get_value(iter2, 0).name).lower()
 
292
            proj1 = cmp(search_term, proj1)
 
293
            proj2 = cmp(search_term, proj2)
 
294
            
 
295
            if proj1 == 0:
 
296
                return -1
 
297
            elif proj2 == 0:
 
298
                return 1
 
299
            else:
 
300
                return 0
 
301
 
 
302
        svlist = super(ProjectsView, self).setup()
 
303
        model = svlist.get_model()
 
304
        model.set_sort_func(0, sort_projects_func)
 
305
        model.set_sort_column_id(0, gtk.SORT_ASCENDING)
 
306
        
 
307
        column = gtk.TreeViewColumn((_("Online Projects")))
 
308
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
309
        column.set_expand(True)
 
310
        # The icon
 
311
        renderer_icon = gtk.CellRendererPixbuf()
 
312
        renderer_icon.set_property("ypad", 8)
 
313
        renderer_icon.set_property("xpad", 8)
 
314
        column.pack_start(renderer_icon, False)
 
315
        column.set_cell_data_func(renderer_icon, icon_cell_func)
 
316
        # The name
 
317
        renderer = gtk.CellRendererText()
 
318
        renderer.props.wrap_width = 660
 
319
        renderer.props.wrap_mode = pango.WRAP_WORD
 
320
        column.pack_start(renderer, True)
 
321
        column.set_cell_data_func(renderer, text_cell_func)
 
322
        # Add the required coluns to the treeview
 
323
        svlist.append_column(column)
 
324
        
 
325
    def search_term(self, term=None):
 
326
        """ Returns the most recently searched term, and sets search term
 
327
            when passed as a named parameter"""
 
328
        if term is not None:
 
329
            self._search_term = term
 
330
        return self._search_term
 
331