##// END OF EJS Templates
fixed problem with tooltips flickering.
marcink -
r1187:08d77411 beta
parent child Browse files
Show More
@@ -1,700 +1,642 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10
11 11 from datetime import datetime
12 12 from pygments.formatters import HtmlFormatter
13 13 from pygments import highlight as code_highlight
14 14 from pylons import url, request, config
15 15 from pylons.i18n.translation import _, ungettext
16 16
17 17 from webhelpers.html import literal, HTML, escape
18 18 from webhelpers.html.tools import *
19 19 from webhelpers.html.builder import make_tag
20 20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 23 password, textarea, title, ul, xml_declaration, radio
24 24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 25 mail_to, strip_links, strip_tags, tag_re
26 26 from webhelpers.number import format_byte_size, format_bit_size
27 27 from webhelpers.pylonslib import Flash as _Flash
28 28 from webhelpers.pylonslib.secure_form import secure_form
29 29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 32 from webhelpers.date import time_ago_in_words
33 33 from webhelpers.paginate import Page
34 34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 35 convert_boolean_attrs, NotGiven
36 36
37 37 from vcs.utils.annotate import annotate_highlight
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib import str2bool, safe_unicode
40 40
41 41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 42 """
43 43 Reset button
44 44 """
45 45 _set_input_attrs(attrs, type, name, value)
46 46 _set_id_attr(attrs, id, name)
47 47 convert_boolean_attrs(attrs, ["disabled"])
48 48 return HTML.input(**attrs)
49 49
50 50 reset = _reset
51 51
52 52
53 53 def get_token():
54 54 """Return the current authentication token, creating one if one doesn't
55 55 already exist.
56 56 """
57 57 token_key = "_authentication_token"
58 58 from pylons import session
59 59 if not token_key in session:
60 60 try:
61 61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 62 except AttributeError: # Python < 2.4
63 63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 64 session[token_key] = token
65 65 if hasattr(session, 'save'):
66 66 session.save()
67 67 return session[token_key]
68 68
69 69 class _GetError(object):
70 70 """Get error from form_errors, and represent it as span wrapped error
71 71 message
72 72
73 73 :param field_name: field to fetch errors for
74 74 :param form_errors: form errors dict
75 75 """
76 76
77 77 def __call__(self, field_name, form_errors):
78 78 tmpl = """<span class="error_msg">%s</span>"""
79 79 if form_errors and form_errors.has_key(field_name):
80 80 return literal(tmpl % form_errors.get(field_name))
81 81
82 82 get_error = _GetError()
83 83
84 84 class _ToolTip(object):
85 85
86 86 def __call__(self, tooltip_title, trim_at=50):
87 87 """Special function just to wrap our text into nice formatted
88 88 autowrapped text
89 89
90 90 :param tooltip_title:
91 91 """
92 92
93 93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
94 94 .replace('\n', '<br/>')
95 95
96 96 def activate(self):
97 97 """Adds tooltip mechanism to the given Html all tooltips have to have
98 98 set class `tooltip` and set attribute `tooltip_title`.
99 99 Then a tooltip will be generated based on that. All with yui js tooltip
100 100 """
101 101
102 102 js = '''
103 103 YAHOO.util.Event.onDOMReady(function(){
104 104 function toolTipsId(){
105 105 var ids = [];
106 106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
107 107
108 108 for (var i = 0; i < tts.length; i++) {
109 109 //if element doesn't not have and id autogenerate one for tooltip
110 110
111 111 if (!tts[i].id){
112 112 tts[i].id='tt'+i*100;
113 113 }
114 114 ids.push(tts[i].id);
115 115 }
116 116 return ids
117 117 };
118 118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
119 context: toolTipsId(),
119 context: [[toolTipsId()],"tl","bl",null,[0,5]],
120 120 monitorresize:false,
121 121 xyoffset :[0,0],
122 122 autodismissdelay:300000,
123 123 hidedelay:5,
124 124 showdelay:20,
125 125 });
126 126
127 // Set the text for the tooltip just before we display it. Lazy method
128 myToolTips.contextTriggerEvent.subscribe(
129 function(type, args) {
130
131 var context = args[0];
132
133 //positioning of tooltip
134 var tt_w = this.element.clientWidth;//tooltip width
135 var tt_h = this.element.clientHeight;//tooltip height
136
137 var context_w = context.offsetWidth;
138 var context_h = context.offsetHeight;
139
140 var pos_x = YAHOO.util.Dom.getX(context);
141 var pos_y = YAHOO.util.Dom.getY(context);
142
143 var display_strategy = 'right';
144 var xy_pos = [0,0];
145 switch (display_strategy){
146
147 case 'top':
148 var cur_x = (pos_x+context_w/2)-(tt_w/2);
149 var cur_y = (pos_y-tt_h-4);
150 xy_pos = [cur_x,cur_y];
151 break;
152 case 'bottom':
153 var cur_x = (pos_x+context_w/2)-(tt_w/2);
154 var cur_y = pos_y+context_h+4;
155 xy_pos = [cur_x,cur_y];
156 break;
157 case 'left':
158 var cur_x = (pos_x-tt_w-4);
159 var cur_y = pos_y-((tt_h/2)-context_h/2);
160 xy_pos = [cur_x,cur_y];
161 break;
162 case 'right':
163 var cur_x = (pos_x+context_w+4);
164 var cur_y = pos_y-((tt_h/2)-context_h/2);
165 xy_pos = [cur_x,cur_y];
166 break;
167 default:
168 var cur_x = (pos_x+context_w/2)-(tt_w/2);
169 var cur_y = pos_y-tt_h-4;
170 xy_pos = [cur_x,cur_y];
171 break;
172
173 }
174
175 this.cfg.setProperty("xy",xy_pos);
176
177 });
178
179 //Mouse out
180 myToolTips.contextMouseOutEvent.subscribe(
181 function(type, args) {
182 var context = args[0];
183
184 });
185 127 });
186 128 '''
187 129 return literal(js)
188 130
189 131 tooltip = _ToolTip()
190 132
191 133 class _FilesBreadCrumbs(object):
192 134
193 135 def __call__(self, repo_name, rev, paths):
194 136 if isinstance(paths, str):
195 137 paths = safe_unicode(paths)
196 138 url_l = [link_to(repo_name, url('files_home',
197 139 repo_name=repo_name,
198 140 revision=rev, f_path=''))]
199 141 paths_l = paths.split('/')
200 142 for cnt, p in enumerate(paths_l):
201 143 if p != '':
202 144 url_l.append(link_to(p, url('files_home',
203 145 repo_name=repo_name,
204 146 revision=rev,
205 147 f_path='/'.join(paths_l[:cnt + 1]))))
206 148
207 149 return literal('/'.join(url_l))
208 150
209 151 files_breadcrumbs = _FilesBreadCrumbs()
210 152
211 153 class CodeHtmlFormatter(HtmlFormatter):
212 154 """My code Html Formatter for source codes
213 155 """
214 156
215 157 def wrap(self, source, outfile):
216 158 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
217 159
218 160 def _wrap_code(self, source):
219 161 for cnt, it in enumerate(source):
220 162 i, t = it
221 163 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
222 164 yield i, t
223 165
224 166 def _wrap_tablelinenos(self, inner):
225 167 dummyoutfile = StringIO.StringIO()
226 168 lncount = 0
227 169 for t, line in inner:
228 170 if t:
229 171 lncount += 1
230 172 dummyoutfile.write(line)
231 173
232 174 fl = self.linenostart
233 175 mw = len(str(lncount + fl - 1))
234 176 sp = self.linenospecial
235 177 st = self.linenostep
236 178 la = self.lineanchors
237 179 aln = self.anchorlinenos
238 180 nocls = self.noclasses
239 181 if sp:
240 182 lines = []
241 183
242 184 for i in range(fl, fl + lncount):
243 185 if i % st == 0:
244 186 if i % sp == 0:
245 187 if aln:
246 188 lines.append('<a href="#%s%d" class="special">%*d</a>' %
247 189 (la, i, mw, i))
248 190 else:
249 191 lines.append('<span class="special">%*d</span>' % (mw, i))
250 192 else:
251 193 if aln:
252 194 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
253 195 else:
254 196 lines.append('%*d' % (mw, i))
255 197 else:
256 198 lines.append('')
257 199 ls = '\n'.join(lines)
258 200 else:
259 201 lines = []
260 202 for i in range(fl, fl + lncount):
261 203 if i % st == 0:
262 204 if aln:
263 205 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
264 206 else:
265 207 lines.append('%*d' % (mw, i))
266 208 else:
267 209 lines.append('')
268 210 ls = '\n'.join(lines)
269 211
270 212 # in case you wonder about the seemingly redundant <div> here: since the
271 213 # content in the other cell also is wrapped in a div, some browsers in
272 214 # some configurations seem to mess up the formatting...
273 215 if nocls:
274 216 yield 0, ('<table class="%stable">' % self.cssclass +
275 217 '<tr><td><div class="linenodiv" '
276 218 'style="background-color: #f0f0f0; padding-right: 10px">'
277 219 '<pre style="line-height: 125%">' +
278 220 ls + '</pre></div></td><td class="code">')
279 221 else:
280 222 yield 0, ('<table class="%stable">' % self.cssclass +
281 223 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
282 224 ls + '</pre></div></td><td class="code">')
283 225 yield 0, dummyoutfile.getvalue()
284 226 yield 0, '</td></tr></table>'
285 227
286 228
287 229 def pygmentize(filenode, **kwargs):
288 230 """pygmentize function using pygments
289 231
290 232 :param filenode:
291 233 """
292 234
293 235 return literal(code_highlight(filenode.content,
294 236 filenode.lexer, CodeHtmlFormatter(**kwargs)))
295 237
296 238 def pygmentize_annotation(repo_name, filenode, **kwargs):
297 239 """pygmentize function for annotation
298 240
299 241 :param filenode:
300 242 """
301 243
302 244 color_dict = {}
303 245 def gen_color(n=10000):
304 246 """generator for getting n of evenly distributed colors using
305 247 hsv color and golden ratio. It always return same order of colors
306 248
307 249 :returns: RGB tuple
308 250 """
309 251 import colorsys
310 252 golden_ratio = 0.618033988749895
311 253 h = 0.22717784590367374
312 254
313 255 for c in xrange(n):
314 256 h += golden_ratio
315 257 h %= 1
316 258 HSV_tuple = [h, 0.95, 0.95]
317 259 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
318 260 yield map(lambda x:str(int(x * 256)), RGB_tuple)
319 261
320 262 cgenerator = gen_color()
321 263
322 264 def get_color_string(cs):
323 265 if color_dict.has_key(cs):
324 266 col = color_dict[cs]
325 267 else:
326 268 col = color_dict[cs] = cgenerator.next()
327 269 return "color: rgb(%s)! important;" % (', '.join(col))
328 270
329 271 def url_func(repo_name):
330 272 def _url_func(changeset):
331 273 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
332 274 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
333 275
334 276 tooltip_html = tooltip_html % (changeset.author,
335 277 changeset.date,
336 278 tooltip(changeset.message))
337 279 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
338 280 short_id(changeset.raw_id))
339 281 uri = link_to(
340 282 lnk_format,
341 283 url('changeset_home', repo_name=repo_name,
342 284 revision=changeset.raw_id),
343 285 style=get_color_string(changeset.raw_id),
344 286 class_='tooltip',
345 287 title=tooltip_html
346 288 )
347 289
348 290 uri += '\n'
349 291 return uri
350 292 return _url_func
351 293
352 294 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
353 295
354 296 def get_changeset_safe(repo, rev):
355 297 from vcs.backends.base import BaseRepository
356 298 from vcs.exceptions import RepositoryError
357 299 if not isinstance(repo, BaseRepository):
358 300 raise Exception('You must pass an Repository '
359 301 'object as first argument got %s', type(repo))
360 302
361 303 try:
362 304 cs = repo.get_changeset(rev)
363 305 except RepositoryError:
364 306 from rhodecode.lib.utils import EmptyChangeset
365 307 cs = EmptyChangeset()
366 308 return cs
367 309
368 310
369 311 def is_following_repo(repo_name, user_id):
370 312 from rhodecode.model.scm import ScmModel
371 313 return ScmModel().is_following_repo(repo_name, user_id)
372 314
373 315 flash = _Flash()
374 316
375 317
376 318 #==============================================================================
377 319 # MERCURIAL FILTERS available via h.
378 320 #==============================================================================
379 321 from mercurial import util
380 322 from mercurial.templatefilters import person as _person
381 323
382 324 def _age(curdate):
383 325 """turns a datetime into an age string."""
384 326
385 327 if not curdate:
386 328 return ''
387 329
388 330 agescales = [("year", 3600 * 24 * 365),
389 331 ("month", 3600 * 24 * 30),
390 332 ("day", 3600 * 24),
391 333 ("hour", 3600),
392 334 ("minute", 60),
393 335 ("second", 1), ]
394 336
395 337 age = datetime.now() - curdate
396 338 age_seconds = (age.days * agescales[2][1]) + age.seconds
397 339 pos = 1
398 340 for scale in agescales:
399 341 if scale[1] <= age_seconds:
400 342 if pos == 6:pos = 5
401 343 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
402 344 pos += 1
403 345
404 346 return _('just now')
405 347
406 348 age = lambda x:_age(x)
407 349 capitalize = lambda x: x.capitalize()
408 350 email = util.email
409 351 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
410 352 person = lambda x: _person(x)
411 353 short_id = lambda x: x[:12]
412 354
413 355
414 356 def bool2icon(value):
415 357 """Returns True/False values represented as small html image of true/false
416 358 icons
417 359
418 360 :param value: bool value
419 361 """
420 362
421 363 if value is True:
422 364 return HTML.tag('img', src=url("/images/icons/accept.png"),
423 365 alt=_('True'))
424 366
425 367 if value is False:
426 368 return HTML.tag('img', src=url("/images/icons/cancel.png"),
427 369 alt=_('False'))
428 370
429 371 return value
430 372
431 373
432 374 def action_parser(user_log, feed=False):
433 375 """This helper will action_map the specified string action into translated
434 376 fancy names with icons and links
435 377
436 378 :param user_log: user log instance
437 379 :param feed: use output for feeds (no html and fancy icons)
438 380 """
439 381
440 382 action = user_log.action
441 383 action_params = ' '
442 384
443 385 x = action.split(':')
444 386
445 387 if len(x) > 1:
446 388 action, action_params = x
447 389
448 390 def get_cs_links():
449 391 revs_limit = 5 #display this amount always
450 392 revs_top_limit = 50 #show upto this amount of changesets hidden
451 393 revs = action_params.split(',')
452 394 repo_name = user_log.repository.repo_name
453 395
454 396 from rhodecode.model.scm import ScmModel
455 397 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
456 398 invalidation_list=[])
457 399
458 400 message = lambda rev: get_changeset_safe(repo, rev).message
459 401
460 402 cs_links = " " + ', '.join ([link_to(rev,
461 403 url('changeset_home',
462 404 repo_name=repo_name,
463 405 revision=rev), title=tooltip(message(rev)),
464 406 class_='tooltip') for rev in revs[:revs_limit] ])
465 407
466 408 compare_view = (' <div class="compare_view tooltip" title="%s">'
467 409 '<a href="%s">%s</a> '
468 410 '</div>' % (_('Show all combined changesets %s->%s' \
469 411 % (revs[0], revs[-1])),
470 412 url('changeset_home', repo_name=repo_name,
471 413 revision='%s...%s' % (revs[0], revs[-1])
472 414 ),
473 415 _('compare view'))
474 416 )
475 417
476 418 if len(revs) > revs_limit:
477 419 uniq_id = revs[0]
478 420 html_tmpl = ('<span> %s '
479 421 '<a class="show_more" id="_%s" href="#more">%s</a> '
480 422 '%s</span>')
481 423 if not feed:
482 424 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
483 425 % (len(revs) - revs_limit),
484 426 _('revisions'))
485 427
486 428 if not feed:
487 429 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
488 430 else:
489 431 html_tmpl = '<span id="%s"> %s </span>'
490 432
491 433 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
492 434 url('changeset_home',
493 435 repo_name=repo_name, revision=rev),
494 436 title=message(rev), class_='tooltip')
495 437 for rev in revs[revs_limit:revs_top_limit]]))
496 438 if len(revs) > 1:
497 439 cs_links += compare_view
498 440 return cs_links
499 441
500 442 def get_fork_name():
501 443 repo_name = action_params
502 444 return _('fork name ') + str(link_to(action_params, url('summary_home',
503 445 repo_name=repo_name,)))
504 446
505 447 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
506 448 'user_created_repo':(_('[created] repository'), None),
507 449 'user_forked_repo':(_('[forked] repository'), get_fork_name),
508 450 'user_updated_repo':(_('[updated] repository'), None),
509 451 'admin_deleted_repo':(_('[delete] repository'), None),
510 452 'admin_created_repo':(_('[created] repository'), None),
511 453 'admin_forked_repo':(_('[forked] repository'), None),
512 454 'admin_updated_repo':(_('[updated] repository'), None),
513 455 'push':(_('[pushed] into'), get_cs_links),
514 456 'push_remote':(_('[pulled from remote] into'), get_cs_links),
515 457 'pull':(_('[pulled] from'), None),
516 458 'started_following_repo':(_('[started following] repository'), None),
517 459 'stopped_following_repo':(_('[stopped following] repository'), None),
518 460 }
519 461
520 462 action_str = action_map.get(action, action)
521 463 if feed:
522 464 action = action_str[0].replace('[', '').replace(']', '')
523 465 else:
524 466 action = action_str[0].replace('[', '<span class="journal_highlight">')\
525 467 .replace(']', '</span>')
526 468
527 469 action_params_func = lambda :""
528 470
529 471 if callable(action_str[1]):
530 472 action_params_func = action_str[1]
531 473
532 474 return [literal(action), action_params_func]
533 475
534 476 def action_parser_icon(user_log):
535 477 action = user_log.action
536 478 action_params = None
537 479 x = action.split(':')
538 480
539 481 if len(x) > 1:
540 482 action, action_params = x
541 483
542 484 tmpl = """<img src="%s%s" alt="%s"/>"""
543 485 map = {'user_deleted_repo':'database_delete.png',
544 486 'user_created_repo':'database_add.png',
545 487 'user_forked_repo':'arrow_divide.png',
546 488 'user_updated_repo':'database_edit.png',
547 489 'admin_deleted_repo':'database_delete.png',
548 490 'admin_created_repo':'database_add.png',
549 491 'admin_forked_repo':'arrow_divide.png',
550 492 'admin_updated_repo':'database_edit.png',
551 493 'push':'script_add.png',
552 494 'push_remote':'connect.png',
553 495 'pull':'down_16.png',
554 496 'started_following_repo':'heart_add.png',
555 497 'stopped_following_repo':'heart_delete.png',
556 498 }
557 499 return literal(tmpl % ((url('/images/icons/')),
558 500 map.get(action, action), action))
559 501
560 502
561 503 #==============================================================================
562 504 # PERMS
563 505 #==============================================================================
564 506 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
565 507 HasRepoPermissionAny, HasRepoPermissionAll
566 508
567 509 #==============================================================================
568 510 # GRAVATAR URL
569 511 #==============================================================================
570 512
571 513 def gravatar_url(email_address, size=30):
572 514 if not str2bool(config['app_conf'].get('use_gravatar')):
573 515 return "/images/user%s.png" % size
574 516
575 517 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
576 518 default = 'identicon'
577 519 baseurl_nossl = "http://www.gravatar.com/avatar/"
578 520 baseurl_ssl = "https://secure.gravatar.com/avatar/"
579 521 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
580 522
581 523 if isinstance(email_address, unicode):
582 524 #hashlib crashes on unicode items
583 525 email_address = email_address.encode('utf8', 'replace')
584 526 # construct the url
585 527 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
586 528 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
587 529
588 530 return gravatar_url
589 531
590 532
591 533 #==============================================================================
592 534 # REPO PAGER
593 535 #==============================================================================
594 536 class RepoPage(Page):
595 537
596 538 def __init__(self, collection, page=1, items_per_page=20,
597 539 item_count=None, url=None, branch_name=None, **kwargs):
598 540
599 541 """Create a "RepoPage" instance. special pager for paging
600 542 repository
601 543 """
602 544 self._url_generator = url
603 545
604 546 # Safe the kwargs class-wide so they can be used in the pager() method
605 547 self.kwargs = kwargs
606 548
607 549 # Save a reference to the collection
608 550 self.original_collection = collection
609 551
610 552 self.collection = collection
611 553
612 554 # The self.page is the number of the current page.
613 555 # The first page has the number 1!
614 556 try:
615 557 self.page = int(page) # make it int() if we get it as a string
616 558 except (ValueError, TypeError):
617 559 self.page = 1
618 560
619 561 self.items_per_page = items_per_page
620 562
621 563 # Unless the user tells us how many items the collections has
622 564 # we calculate that ourselves.
623 565 if item_count is not None:
624 566 self.item_count = item_count
625 567 else:
626 568 self.item_count = len(self.collection)
627 569
628 570 # Compute the number of the first and last available page
629 571 if self.item_count > 0:
630 572 self.first_page = 1
631 573 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
632 574 self.last_page = self.first_page + self.page_count - 1
633 575
634 576 # Make sure that the requested page number is the range of valid pages
635 577 if self.page > self.last_page:
636 578 self.page = self.last_page
637 579 elif self.page < self.first_page:
638 580 self.page = self.first_page
639 581
640 582 # Note: the number of items on this page can be less than
641 583 # items_per_page if the last page is not full
642 584 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
643 585 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
644 586
645 587 iterator = self.collection.get_changesets(start=self.first_item,
646 588 end=self.last_item,
647 589 reverse=True,
648 590 branch_name=branch_name)
649 591 self.items = list(iterator)
650 592
651 593 # Links to previous and next page
652 594 if self.page > self.first_page:
653 595 self.previous_page = self.page - 1
654 596 else:
655 597 self.previous_page = None
656 598
657 599 if self.page < self.last_page:
658 600 self.next_page = self.page + 1
659 601 else:
660 602 self.next_page = None
661 603
662 604 # No items available
663 605 else:
664 606 self.first_page = None
665 607 self.page_count = 0
666 608 self.last_page = None
667 609 self.first_item = None
668 610 self.last_item = None
669 611 self.previous_page = None
670 612 self.next_page = None
671 613 self.items = []
672 614
673 615 # This is a subclass of the 'list' type. Initialise the list now.
674 616 list.__init__(self, self.items)
675 617
676 618
677 619 def changed_tooltip(nodes):
678 620 if nodes:
679 621 pref = ': <br/> '
680 622 suf = ''
681 623 if len(nodes) > 30:
682 624 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
683 625 return literal(pref + '<br/> '.join([safe_unicode(x.path) for x in nodes[:30]]) + suf)
684 626 else:
685 627 return ': ' + _('No Files')
686 628
687 629
688 630
689 631 def repo_link(groups_and_repos):
690 632 groups, repo_name = groups_and_repos
691 633
692 634 if not groups:
693 635 return repo_name
694 636 else:
695 637 def make_link(group):
696 638 return link_to(group.group_name, url('repos_group', id=group.group_id))
697 639 return literal(' &raquo; '.join(map(make_link, groups)) + \
698 640 " &raquo; " + repo_name)
699 641
700 642
General Comments 0
You need to be logged in to leave comments. Login now