2
# Copyright 2009 Martin Owens
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.
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.
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/>
18
Small app for selecting launchpad projects
21
# Import standard python libs
29
# Various required variables and locations
30
from GroundControl.gtkviews import (
31
GtkApp, ThreadedWindow,
34
from GroundControl.gtkcommon import StatusApp
35
from GroundControl.launchpad import (
36
LaunchpadProject, LaunchpadProjects, get_launchpad
39
project_icons = IconManager('project')
40
PROJECT_FILE = '.gcproject'
41
ITEM_MARKUP = """<b><big>%s</big></b>
43
<small><i>lp:%s</i></small>"""
45
from shutil import copyfile
46
from bzrlib import transport, bzrdir
49
class Project(object):
50
"""Simple object for managing a project directory"""
51
def __init__(self, path):
55
if self.config and not self.broken:
56
logging.debug("Loaded project %s" % self.pid)
60
"""Load a configuration file"""
62
cfile = os.path.join(self._path, PROJECT_FILE)
63
self.load_project(cfile)
65
raise IOError("Can't find project configuration file")
66
if type(self._config) != dict:
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:
79
def regenerate(self, widget, window, callback=None):
80
"""Try and regenerate a project's configuration"""
82
callback = self.regen_done
83
ProjectCreateApp(project=os.path.basename(self._path),
84
path=os.path.dirname(self._path),
90
"""What happens when we've regnerated the configuration"""
91
self.load_project(self._filename, False)
95
"""Return a project id (nickname)"""
96
return str(self.config['id'])
100
"""Return a project name (with spaces)"""
101
return str(self.config['name'])
104
"""Return the prospective logo filename"""
105
return os.path.join(self._path, '.logo.png')
108
class ProjectCreateApp(StatusApp):
109
"""Show a status window when we're making a project"""
110
task_name = _("Creating Project")
113
"""Make our project directory and populate it"""
114
project = self.project
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
122
project = LaunchpadProject(lpa, oid=project)
124
logging.error("Couldn't find existing project, removing.")
125
return os.unlink(os.path.join(self.path, project, PROJECT_FILE))
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)
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({
143
'name' : project.name,
144
'desc' : project.desc,
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"))
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)
160
copyfile( result, filename )
162
logging.debug("Not saving image - %s doesn't exist." % name)
164
def load_vars(self, args, kwargs):
165
"""Load our project creation"""
166
self.project = kwargs.pop('project')
167
self.path = kwargs.pop('path')
170
class SelectionWindow(ThreadedWindow):
171
"""Select a project to import."""
172
name = 'project_search'
174
def load(self, path, *args, **kwargs):
175
"""Load the project selection GUI"""
179
self._selected = None
180
self._launchpad = None
181
# Setup the list of projects from a search
183
self.slist = ProjectsView(self.widget('projectslist'),
184
selected=self.selected,
185
unselected=self.unselected)
186
super(SelectionWindow, self).load(*args, **kwargs)
190
"""Return a launchpad object"""
191
if not self._launchpad:
192
self._launchpad = get_launchpad()
193
return self._launchpad
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')
203
def add_search_item(self, item):
204
"""Add an item to the slist"""
205
return self.slist.add_item(item)
208
"""project window signals"""
210
'search' : self.project_search,
214
"""Return the selected project"""
216
'project' : self.selected(),
219
def selected(self, item=None):
220
"""An item has surely been selected."""
222
self._selected = item
223
self.widget('buttonok').set_sensitive(True)
224
return self._selected
226
def unselected(self):
227
"""All items unselected"""
228
self._selected = None
229
self.widget('buttonok').set_sensitive(False)
231
def project_search(self, button=None):
232
"""Search for projects in launchpad."""
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)
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:")
251
"""Return true is a project is selected"""
252
return self._selected != None
255
class ProjectSelection(GtkApp):
256
"""Application for loading a project"""
257
gtkfile = 'project-select.glade'
258
windows = [ SelectionWindow ]
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.
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('&', '&')
273
desc = str(item.desc).replace('&', '&')
274
markup = ITEM_MARKUP % (name, desc, item.id)
275
renderer.set_property("markup", markup)
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)
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()
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)
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)
307
column = gtk.TreeViewColumn((_("Online Projects")))
308
column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
309
column.set_expand(True)
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)
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)
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"""
329
self._search_term = term
330
return self._search_term