Module eagle
[hide private]
[frames] | no frames]

Source Code for Module eagle

   1  #!/usr/bin/env python2 
   2  # -*- coding: utf-8 -*- 
   3   
   4  # Copyright (C) 2005 by Gustavo Sverzut Barbieri 
   5  # 
   6  # This program is free software; you can redistribute it and/or 
   7  # modify it under the terms of the GNU Lesser General Public License 
   8  # as published by the Free Software Foundation; either version 2 
   9  # of the License, or (at your option) any later version. 
  10  # 
  11  # This program is distributed in the hope that it will be useful, 
  12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  # GNU General Public License for more details. 
  15  # 
  16  # You should have received a copy of the GNU Lesser General Public License 
  17  # along with this program; if not, write to the Free Software 
  18  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
  19   
  20  # Changed: $LastChangedBy: gustavo $ at $LastChangedDate: 2006-05-25 16:10:31 -0300 (Qui, 25 Mai 2006) $ 
  21   
  22  __author__ = "Gustavo Sverzut Barbieri" 
  23  __author_email__ = "barbieri@gmail.com" 
  24  __license__ = "LGPL" 
  25  __url__ = "http://www.gustavobarbieri.com.br/eagle/" 
  26  __version__ = "0.5" 
  27  __revision__ = "$Rev: 50 $" 
  28  __description__ = """\ 
  29  Eagle is an abstraction layer atop Graphical Toolkits focused on 
  30  making simple applications easy to build while powerful in features. 
  31  """ 
  32  __long_description__ = """\ 
  33  Eagle is an abstraction layer atop Graphical Toolkits focused on 
  34  making simple applications easy to build while powerful in features. 
  35   
  36  With Eagle you have many facilities to build application that needs 
  37  just some buttons, user input and a canvas to draw. 
  38   
  39  Canvas is really simple, what makes Eagle a great solution to 
  40  Computer Graphics and Image Processing software, the primary focus 
  41  of this library. 
  42   
  43  User input widgets are persistent, you just need to mark them 
  44  "persistent" or put them in the preferences area. 
  45   
  46  Eagle is not meant to be another Graphical Toolkit, you already 
  47  have a bunch of them, like Qt, Gtk, wxWidgets (to name just a few). 
  48  It's focused on applications that have few windows, with buttons, 
  49  simple user input and canvas. Widgets are laid out automatically 
  50  in 5 areas: left, right, top, bottom and center. 
  51   
  52  It provides useful widgets like: Color selector, Font selector, 
  53  Quit button, Preferences button and bialog, About dialog and Help 
  54  dialog. 
  55  """ 
  56  __doc__ = __long_description__ 
  57   
  58  __all__ = [ 
  59      "run", "quit", "get_value", "set_value", 
  60      "get_app_by_id", "get_widget_by_id", 
  61      "show", "hide", "set_active", "set_inactive", "close", 
  62      "App", 
  63      "Entry", "Password", 
  64      "Spin", "IntSpin", "UIntSpin", 
  65      "CheckBox", 
  66      "Progress", 
  67      "Color", "Font", 
  68      "Button", "AboutButton", "CloseButton", "QuitButton", "HelpButton", 
  69      "OpenFileButton", "SelectFolderButton", "SaveFileButton", 
  70      "PreferencesButton", 
  71      "Selection", 
  72      "Group", "Table", 
  73      "HSeparator", "VSeparator", 
  74      "Label", 
  75      "Canvas", "Image", 
  76      "information", "info", "error", "err", "warning", "warn", 
  77      "yesno", "confirm", 
  78      "AboutDialog", "HelpDialog", "FileChooser", 
  79      "RichText", 
  80      ] 
  81   
  82  import os 
  83  import sys 
  84  import gc 
  85  import cPickle as pickle 
  86  import htmllib 
  87  import formatter 
  88  import weakref 
  89   
  90  try: 
  91      import pygtk 
  92      pygtk.require( "2.0" ) 
  93      import gtk 
  94      import pango 
  95      import gobject 
  96  except ImportError, e: 
  97      sys.stderr.writelines( 
  98          ( "Missing module: ", str( e ), "\n", 
  99            "This module is part of pygtk (http://pygtk.org).\n", 
 100            ) ) 
 101      sys.exit( -1 ) 
 102   
 103  try: 
 104      import hildon 
 105  except ImportError, e: 
 106      sys.stderr.writelines( 
 107          ( "Missing module: ", str( e ), "\n", 
 108            "This module is part of pymaemo (http://pymaemo.sf.net).\n", 
 109            "HINT: Are you trying to use this on your desktop? If so use" 
 110            "eagle-gtk instead!\n" 
 111            ) ) 
 112      sys.exit( -1 ) 
 113   
 114  required_gtk = ( 2, 6, 0 ) 
 115  m = gtk.check_version( *required_gtk ) 
 116  if m: 
 117      sys.stderr.writelines( 
 118          ( "Error checking GTK version: %s\n" 
 119            "This system requires pygtk >= %s, you have %s installed.\n" ) 
 120          % ( m, 
 121              ".".join( [ str( v ) for v in required_gtk ] ), 
 122              ".".join( [ str( v ) for v in gtk.pygtk_version ] ) 
 123              ) ) 
 124      sys.exit( -1 ) 
 125   
 126  gtk.gdk.threads_init() # enable threads 
 127   
 128  _apps = {} 
 129   
 130   
131 -def _gen_ro_property( name, doc="" ):
132 """Generates a Read-Only property. 133 134 The generated property can be assigned only one value, if this value 135 is not None, it cannot be changed. 136 """ 137 naming = "__ro_%s__" % ( name, )
138 - def get( self ):
139 try: 140 return getattr( self, naming ) 141 except AttributeError: 142 return None
143 # get()
144 - def set( self, value ):
145 try: 146 v = getattr( self, naming ) 147 except AttributeError: 148 v = None 149 if v is None: 150 setattr( self, naming, value ) 151 else: 152 raise Exception( "Read Only property '%s'." % ( name, ) )
153 # set() 154 return property( get, set, None, doc )
155 # _gen_ro_property() 156 157
158 -def _callback_tuple( callback ):
159 if not isinstance( callback, ( tuple, list ) ): 160 if callback is None: 161 return tuple() 162 elif callable( callback ): 163 return ( callback, ) 164 else: 165 raise TypeError( "Callback '%s' is not callable!" % ( callback, ) ) 166 else: 167 for c in callback: 168 if not callable( c ): 169 raise TypeError( "Callback '%s' is not callable!" % ( c, ) ) 170 return callback
171 # _callback_tuple() 172 173
174 -def _str_tuple( string ):
175 if not isinstance( string, ( tuple, list ) ): 176 if string is None: 177 return tuple() 178 else: 179 return ( str( string ), ) 180 else: 181 return tuple( [ str( s ) for s in string ] )
182 # _str_tuple() 183 184
185 -def _obj_tuple( obj ):
186 if not isinstance( obj, ( tuple, list ) ): 187 if obj is None: 188 return tuple() 189 else: 190 return ( obj, ) 191 else: 192 return tuple( obj )
193 # _obj_tuple() 194 195
196 -def _set_icon_list( gtkwidget, stock_id ):
197 style = gtkwidget.get_style() 198 iconset = style.lookup_icon_set( stock_id ) 199 if iconset: 200 icons = [] 201 for s in iconset.get_sizes(): 202 i = iconset.render_icon( style, 203 gtk.TEXT_DIR_NONE, 204 gtk.STATE_NORMAL, 205 s, 206 gtkwidget, 207 None 208 ) 209 icons.append( i ) 210 gtkwidget.set_icon_list( *icons )
211 # _set_icon_list() 212 213
214 -class _Table( gtk.Table ):
215 """Internal widget to arrange components in tabular form. 216 217 @warning: never use it directly in Eagle applications! 218 """ 219 220 padding = 3 221 id = _gen_ro_property( "id" ) 222 children = _gen_ro_property( "children" ) 223 horizontal = _gen_ro_property( "horizontal" ) 224 225
226 - def __init__( self, id, children, horizontal=False ):
227 self.id = id 228 self.horizontal = horizontal 229 self.children = _obj_tuple( children ) 230 231 gtk.Table.__init__( self ) 232 233 self.set_name( id ) 234 self.set_homogeneous( False ) 235 self.__setup_gui__()
236 # __init__() 237 238
239 - def __setup_gui__( self ):
240 """Lay out components in a horizontal or vertical table.""" 241 if not self.children: 242 return 243 244 n = len( self.children ) 245 246 if self.horizontal: 247 self.resize( 2, n ) 248 else: 249 self.resize( n, 2 ) 250 251 for idx, c in enumerate( self.children ): 252 w = c.__get_widgets__() 253 xrm, yrm = c.__get_resize_mode__() 254 255 if len( w ) == 1: 256 # use last one, in case of LabelEntry without label 257 if isinstance( xrm, ( tuple, list ) ): 258 xrm = xrm[ -1 ] 259 if isinstance( yrm, ( tuple, list ) ): 260 yrm = yrm[ -1 ] 261 262 if self.horizontal: 263 row0 = 0 264 row1 = 2 265 col0 = idx 266 col1 = idx + 1 267 else: 268 row0 = idx 269 row1 = idx + 1 270 col0 = 0 271 col1 = 2 272 273 self.attach( w[ 0 ], col0, col1, row0, row1, 274 xoptions=xrm, 275 yoptions=yrm, 276 xpadding=self.padding, 277 ypadding=self.padding ) 278 279 elif len( w ) == 2: 280 if isinstance( xrm, int ): 281 xrm = ( xrm, xrm ) 282 if isinstance( yrm, int ): 283 yrm = ( yrm, yrm ) 284 285 if self.horizontal: 286 row0 = 0 287 row1 = 1 288 row2 = 1 289 row3 = 2 290 col0 = idx 291 col1 = idx + 1 292 col2 = idx 293 col3 = idx + 1 294 else: 295 row0 = idx 296 row1 = idx + 1 297 row2 = idx 298 row3 = idx + 1 299 col0 = 0 300 col1 = 1 301 col2 = 1 302 col3 = 2 303 self.attach( w[ 0 ], col0, col1, row0, row1, 304 xoptions=xrm[ 0 ], 305 yoptions=yrm[ 0 ], 306 xpadding=self.padding, 307 ypadding=self.padding ) 308 self.attach( w[ 1 ], col2, col3, row2, row3, 309 xoptions=xrm[ 1 ], 310 yoptions=yrm[ 1 ], 311 xpadding=self.padding, 312 ypadding=self.padding )
313 # __setup_gui__() 314 315
316 - def __get_widgets__( self ):
317 return self.children
318 # __get_widgets__() 319 # _Table 320 321
322 -class _Panel( gtk.ScrolledWindow ):
323 """Internal widget to arrange components. 324 325 @warning: never use it directly in Eagle applications! 326 """ 327 328 spacing = 5 329 app = _gen_ro_property( "app" ) 330 id = _gen_ro_property( "id" ) 331 children = _gen_ro_property( "children" ) 332 333 _horizontal = None 334 _hscrollbar_policy = None 335 _vscrollbar_policy = None 336
337 - def __init__( self, app, id, children ):
338 self.app = app 339 self.id = id 340 self.children = _obj_tuple( children ) 341 342 gtk.ScrolledWindow.__init__( self ) 343 self.set_name( id ) 344 self.__setup_gui__() 345 self.__add_widgets_to_app__()
346 # __init__() 347 348
349 - def __setup_gui__( self ):
350 self.set_shadow_type( gtk.SHADOW_NONE ) 351 self.set_policy( hscrollbar_policy=self._hscrollbar_policy, 352 vscrollbar_policy=self._vscrollbar_policy ) 353 self._tab = _Table( self.id, self.children, self._horizontal ) 354 self.add_with_viewport( self._tab ) 355 self.get_child().set_shadow_type( gtk.SHADOW_NONE )
356 # __setup_gui__() 357 358
359 - def __add_widgets_to_app__( self ):
360 for w in self.children: 361 self.app.__add_widget__( w )
362 # __add_widgets_to_app__() 363 364
365 - def __get_widgets__( self ):
366 return self._tab.__get_widgets__()
367 # __get_widgets__() 368 # _Panel 369 370
371 -class _VPanel( _Panel ):
372 """Internal widget to arrange components vertically. 373 374 @warning: never use it directly in Eagle applications! 375 """ 376 377 _horizontal = False 378 _hscrollbar_policy = gtk.POLICY_NEVER 379 _vscrollbar_policy = gtk.POLICY_AUTOMATIC
380 # _VPanel 381 382
383 -class _HPanel( _Panel ):
384 """Internal widget to arrange components horizontally. 385 386 @warning: never use it directly in Eagle applications! 387 """ 388 389 _horizontal = True 390 _hscrollbar_policy = gtk.POLICY_AUTOMATIC 391 _vscrollbar_policy = gtk.POLICY_NEVER
392 # _HPanel 393 394
395 -class _EGObject( object ):
396 """The basic Eagle Object. 397 398 All eagle objects provides an attribute "id". 399 400 @warning: never use it directly in Eagle applications! 401 """ 402 403 id = _gen_ro_property( "id" ) 404
405 - def __init__( self, id ):
406 self.id = id
407 # __init__() 408 409
410 - def __str__( self ):
411 return "%s( id=%r )" % ( self.__class__.__name__, self.id )
412 # __str__() 413 __repr__ = __str__
414 # _EGObject 415 416
417 -class AutoGenId( object ):
418 """Mix-In to auto-generate ids. 419 420 @warning: never use it directly in Eagle applications! 421 """ 422 last_id_num = 0 423
424 - def __get_id__( classobj ):
425 n = "%s-%d" % ( classobj.__name__, classobj.last_id_num ) 426 classobj.last_id_num += 1 427 return n
428 # __get_id__() 429 __get_id__ = classmethod( __get_id__ )
430 # AutoGenId 431 432
433 -class Image( _EGObject, AutoGenId ):
434 """ 435 An image that can be loaded from files or binary data and saved to files. 436 """ 437 _id2obj_ = weakref.WeakValueDictionary() 438
439 - def __init__( self, **kargs ):
440 """Image constructor. 441 442 Images can be constructed in 2 ways using keyword arguments: 443 - from files, in this case you give it B{filename} keyword: 444 445 >>> Image( filename='myfile.png' ) 446 447 - from raw data, in this case you need to provide at least 448 B{data}, B{width} and B{height} as arguments. Optional 449 arguments are I{depth}, I{has_alpha} and I{row_stride}. 450 See L{load_data()} for more information: 451 452 >>> Image( data=data, width=200, height=200, depth=32, has_alpha=False ) 453 454 @see: L{load_data()} 455 @see: L{load_file()} 456 """ 457 id = kargs.get( "id" ) or self.__get_id__() 458 _EGObject.__init__( self, id ) 459 460 self._img = None 461 462 if "filename" in kargs: 463 self.load_file( kargs[ "filename" ] ) 464 elif "data" in kargs and "width" in kargs and "height" in kargs: 465 k = { "data": kargs[ "data" ], 466 "width": kargs[ "width" ], 467 "height": kargs[ "height" ], 468 } 469 if "depth" in kargs: 470 k[ "depth" ] = kargs[ "depth" ] 471 if "has_alpha" in kargs: 472 k[ "has_alpha" ] = kargs[ "has_alpha" ] 473 if "rowstride" in kargs: 474 k[ "rowstride" ] = kargs[ "rowstride" ] 475 self.load_data( **k ) 476 elif "__int_image__" in kargs: 477 if isinstance( kargs[ "__int_image__" ], gtk.gdk.Pixbuf ): 478 self._img = kargs[ "__int_image__" ] 479 else: 480 raise ValueError( "Wrong internal image given!" ) 481 elif len( kargs ) > 0: 482 params = [ "%s=%r" % kv for kv in kargs.iteritems() ] 483 raise ValueError( "Unknow parameters: %s" % params ) 484 485 Image._id2obj_[ self.id ] = self
486 # __init__() 487 488
489 - def __get_gtk_pixbuf__( self ):
490 return self._img
491 # __get_gtk_pixbuf__() 492 493
494 - def __get_by_id__( klass, id ):
495 return klass._id2obj_[ id ]
496 # __get_by_id__() 497 __get_by_id__ = classmethod( __get_by_id__ ) 498 499
500 - def __del__( self ):
501 gc.collect()
502 # __del__() 503 504
505 - def save( self, filename, format=None, **options ):
506 """Save image to a file. 507 508 If format is not specified, it will be guessed from filename. 509 510 Format may be an extension or a mime type, see 511 L{get_writable_formats()}. 512 513 @see: L{get_writable_formats()}. 514 @raise Exception: if errors happened during write 515 @raise ValueError: if format is unsupported 516 """ 517 if isinstance( filename, ( tuple, list ) ): 518 filename = os.path.join( *filename ) 519 520 if format is None: 521 format = filename.split( os.path.extsep )[ -1 ] 522 523 format = format.lower() 524 t = None 525 for f in self.get_writable_formats(): 526 if format == f[ "name" ] or \ 527 format in f[ "extensions" ] or \ 528 format in f[ "mime_types" ]: 529 t = f[ "name" ] 530 break 531 if t: 532 try: 533 self._img.save( filename, t, options ) 534 except gobject.GError, e: 535 raise Exception( e ) 536 else: 537 raise ValueError( "Unsupported file format: \"%s\"" % format )
538 # save() 539 540
541 - def get_formats( self ):
542 """Get supported image format information. 543 544 @return: list of dicts with keys: 545 - B{name}: format name 546 - B{description}: format description 547 - B{extensions}: extensions that match format 548 - B{mime_types}: mime types that match format 549 - B{is_writable}: if it is possible to write in this format, otherwise 550 it's just readable 551 """ 552 return gtk.gdk.pixbuf_get_formats()
553 # get_formats() 554 555
556 - def get_writable_formats( self ):
557 """Get formats that support saving/writing. 558 559 @see: L{get_formats()} 560 """ 561 k = [] 562 for f in self.get_formats(): 563 if f[ "is_writable" ]: 564 k.append( f ) 565 return k
566 # get_writable_formats() 567 568
569 - def load_file( self, filename ):
570 """Load image from file given its filename. 571 572 filename may be a string or a tuple/list with path elements, 573 this helps your program to stay portable across different platforms. 574 575 >>> i = Image() 576 >>> i.load_file( 'img.png' ) 577 >>> i.load_file( ( 'test', 'img.png' ) ) 578 """ 579 if isinstance( filename, ( tuple, list ) ): 580 filename = os.path.join( *filename ) 581 582 try: 583 self._img = gtk.gdk.pixbuf_new_from_file( filename ) 584 except gobject.GError, e: 585 raise Exception( e )
586 # load_file() 587 588
589 - def load_data( self, data, width, height, 590 depth=24, has_alpha=None, rowstride=None ):
591 """Load image from raw data. 592 593 If no value is provided as B{has_alpha}, then it's set to C{False} 594 if B{depth} is less or equal 24 or set to C{True} if depth is 32. 595 596 If no value is provided as B{rowstride}, then it's set to 597 M{width * depth / bits_per_sample}. 598 599 >>> i = Image() 600 >>> i.load_data( my_data1, 800, 600, depth=32, has_alpha=False ) 601 >>> i.load_data( my_data2, 400, 300, depth=24 ) 602 """ 603 colorspace = gtk.gdk.COLORSPACE_RGB 604 bits_per_sample = 8 605 606 if has_alpha is None: 607 if depth <= 24: 608 has_alpha=False 609 else: 610 has_alpha=True 611 612 if rowstride is None: 613 rowstride = width * depth / bits_per_sample 614 615 if len( data ) < height * rowstride: 616 raise ValueError( ( "data must be at least " 617 "width * height * rowstride long." 618 "Values are: data size=%d, required=%d" ) % 619 ( len( data ), height * rowstride ) ) 620 621 if isinstance( data, list ): 622 # Convert to allowed types, from fastest to slower 623 try: 624 import Numeric 625 data = Numeric.array( data, typecode=Numeric.Character ) 626 except ImportError: 627 try: 628 import array 629 data = array.array( 'c', data ) 630 except: 631 data = tuple( data ) 632 633 self._img = gtk.gdk.pixbuf_new_from_data( data, colorspace, 634 has_alpha, bits_per_sample, 635 width, height, rowstride )
636 # load_data() 637 638
639 - def get_data( self ):
640 """Return raw data and information about this image. 641 642 @return: a tuple of: 643 - width 644 - height 645 - depth 646 - has alpha? 647 - rowstride 648 - raw pixel data 649 """ 650 return ( self.get_width(), self.get_height(), self.get_depth(), 651 self.has_alpha(), self.get_rowstride(), 652 self._img.get_pixels() )
653 # get_data() 654
655 - def get_width( self ):
656 return self._img.get_width()
657 # get_width() 658 659
660 - def get_height( self ):
661 return self._img.get_height()
662 # get_height() 663 664
665 - def get_size( self ):
666 """Return a tuple ( width, heigt )""" 667 return ( self.get_width(), self.get_height() )
668 # get_size() 669 670
671 - def get_rowstride( self ):
672 """Row stride is the allocated size of a row. 673 674 Generally, rowstride is the number of elements in a row multiplied 675 by the size of each element (bits per pixel). 676 677 But there are cases that there is more space left, a padding, to 678 align it to some boundary, so you may get different value for 679 row stride. 680 """ 681 return self._img.get_rowstride()
682 # get_rowstride() 683 684
685 - def get_n_channels( self ):
686 """Number of channels.""" 687 return self._img.get_n_channels()
688 # get_n_channels() 689 690
691 - def get_bits_per_pixel( self ):
692 """Bits per pixel""" 693 return self.get_n_channels() * self._img.get_bits_per_sample()
694 # get_bits_per_pixel() 695 get_depth = get_bits_per_pixel 696 697
698 - def has_alpha( self ):
699 """If it has an alpha channel""" 700 return self._img.get_has_alpha()
701 # has_alpha() 702 # Image 703 704 705
706 -class _EGWidget( _EGObject ):
707 """The base of every Graphical Component in Eagle. 708 709 @warning: never use it directly in Eagle applications! 710 """ 711 app = _gen_ro_property( "app" ) 712
713 - def __init__( self, id, app=None ):
714 _EGObject.__init__( self, id ) 715 if app is not None: 716 self.app = app 717 self._widgets = tuple()
718 # __init__() 719 720
721 - def __get_resize_mode__( self ):
722 "Return a tuple with ( horizontal, vertical ) resize mode" 723 return ( gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND )
724 # __get_resize_mode__() 725 726
727 - def __get_widgets__( self ):
728 """Return a list of B{internal} widgets this Eagle widget contains. 729 730 @warning: never use it directly in Eagle applications! 731 """ 732 return self._widgets
733 # __get_widgets__() 734 735
736 - def set_active( self, active=True ):
737 """Set the widget as active. 738 739 An active widget have their actions enabled, while an inactive 740 (active=False) will be grayed and actions disabled. 741 """ 742 for w in self.__get_widgets__(): 743 w.set_sensitive( active )
744 # set_active() 745 746
747 - def set_inactive( self ):
748 """Same as L{set_active}( False )""" 749 self.set_active( False )
750 # set_inactive() 751 752
753 - def show( self ):
754 """Make widget visible.""" 755 for w in self.__get_widgets__(): 756 w.show()
757 # show() 758 759
760 - def hide( self ):
761 """Make widget invisible.""" 762 for w in self.__get_widgets__(): 763 w.hide()
764 # hide() 765 # _EGWidget 766 767
768 -class _EGDataWidget( _EGWidget ):
769 """The base of every Eagle widget that holds data. 770 771 Widgets that holds data implement the interface with L{get_value}() and 772 L{set_value}(). 773 774 It can be made persistent with L{persistent}=True 775 """ 776 persistent = False 777
778 - def __init__( self, id, persistent, app=None ):
779 _EGObject.__init__( self, id ) 780 if app is not None: 781 self.app = app 782 self.persistent = persistent 783 self._widgets = tuple()
784 # __init__() 785 786
787 - def get_value( self ):
788 """Get data from this widget.""" 789 raise NotImplementedError( "%s doesn't implement get_value()" % 790 self.__class__.__name__ )
791 # get_value() 792
793 - def set_value( self, value ):
794 """Set data to this widget.""" 795 raise NotImplementedError( "%s doesn't implement set_value()" % 796 self.__class__.__name__ )
797 # set_value() 798 # _EGDataWidget 799 800
801 -class AboutDialog( _EGWidget, AutoGenId ):
802 """A window that displays information about the application. 803 804 @attention: avoid using this directly, use L{AboutButton} instead. 805 """ 806 border = 3 807 spacing = 6 808 width = 600 809 height = 400 810 margin = 3 811
812 - def __init__( self, app, 813 title, author=None, description=None, help=None, 814 version=None, license=None, copyright=None ):
815 _EGWidget.__init__( self, self.__get_id__(), app ) 816 self.title = str( title ) 817 self.author = _str_tuple( author ) 818 self.description = _str_tuple( description ) 819 self.help = _str_tuple( help ) 820 self.version = _str_tuple( version ) 821 self.license = _str_tuple( license ) 822 self.copyright = _str_tuple( copyright ) 823 824 self.__setup_gui__()
825 # __init__() 826 827
828 - def __del__( self ):
829 self._diag.destroy()
830 # __del__() 831 832
833 - def __setup_gui__( self ):
834 win = self.app.__get_window__() 835 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE ) 836 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT 837 self._diag = gtk.Dialog( title=( "About: %s" % self.title ), 838 parent=win, 839 flags=flags, buttons=btns ) 840 841 settings = self._diag.get_settings() 842 try: 843 settings.set_property( "gtk-button-images", False ) 844 except: 845 pass 846 847 self._diag.set_border_width( self.border ) 848 self._diag.set_default_size( self.width, self.height ) 849 self._diag.set_has_separator( False ) 850 self._diag.vbox.set_spacing( self.spacing ) 851 852 _set_icon_list( self._diag, gtk.STOCK_ABOUT ) 853 854 self._text = RichText( id="About-%s" % self.app.id ) 855 self._diag.vbox.pack_start( self._text._widgets[ 0 ], True, True ) 856 857 self.__setup_text__()
858 # __setup_gui__() 859 860
861 - def __setup_text__( self ):
862 self._text.append( "<h1>%s</h1>" % self.title ) 863 864 if self.version: 865 v = ".".join( self.version ) 866 self._text.append( "<i>%s</i>" % v ) 867 868 self._text.append( "<hr />" ) 869 870 if self.description: 871 self._text.append( "<h2>Description</h2>" ) 872 for l in self.description: 873 self._text.append( "<p>%s</p>" % l ) 874 875 if self.license: 876 self._text.append( "<h2>License</h2><p>" ) 877 self._text.append( ", ".join( self.license ) ) 878 self._text.append( "</p>" ) 879 880 if self.author: 881 if len( self.author ) == 1: 882 self._text.append( "<h2>Author</h2>" ) 883 else: 884 self._text.append( "<h2>Authors</h2>" ) 885 886 self._text.append( "<ul>" ) 887 for a in self.author: 888 self._text.append( "<li>%s</li>" % a ) 889 self._text.append( "</ul>" ) 890 891 if self.help: 892 self._text.append( "<h2>Help</h2>" ) 893 for l in self.help: 894 self._text.append( "<p>%s</p>" % l ) 895 896 if self.copyright: 897 self._text.append( "<h2>Copyright</h2>" ) 898 for l in self.copyright: 899 self._text.append( "<p>%s</p>" % l )
900 # __setup_text__() 901 902
903 - def run( self ):
904 self._diag.show_all() 905 self._diag.run() 906 self._diag.hide()
907 # run() 908 # AboutDialog 909 910
911 -class HelpDialog( _EGWidget, AutoGenId ):
912 """A window that displays application help. 913 914 @attention: avoid using this directly, use L{HelpButton} instead. 915 """ 916 border = 3 917 spacing = 6 918 width = 600 919 height = 400 920 margin = 3 921
922 - def __init__( self, app, title, help=None ):
923 _EGWidget.__init__( self, self.__get_id__(), app ) 924 self.title = title 925 self.help = _str_tuple( help ) 926 self.__setup_gui__()
927 # __init__() 928 929
930 - def __del__( self ):
931 self._diag.destroy()
932 # __del__() 933 934
935 - def __setup_gui__( self ):
936 win = self.app.__get_window__() 937 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE ) 938 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT 939 self._diag = gtk.Dialog( title=( "Help: %s" % self.title ), 940 parent=win, 941 flags=flags, buttons=btns ) 942 943 settings = self._diag.get_settings() 944 try: 945 settings.set_property( "gtk-button-images", False ) 946 except: 947 pass 948 949 self._diag.set_border_width( self.border ) 950 self._diag.set_default_size( self.width, self.height ) 951 self._diag.set_has_separator( False ) 952 self._diag.vbox.set_spacing( self.spacing ) 953 _set_icon_list( self._diag, gtk.STOCK_HELP ) 954 955 self._text = RichText( id="About-%s" % self.app.id ) 956 self._diag.vbox.pack_start( self._text._widgets[ 0 ], True, True ) 957 958 self.__setup_text__()
959 # __setup_gui__() 960 961
962 - def __setup_text__( self ):
963 self._text.append( "<h1>%s</h1>" % self.title ) 964 self._text.append( "<h2>Help</h2>" ) 965 for l in self.help: 966 self._text.append( "<p>%s</p>" % l )
967 # __setup_text__() 968 969
970 - def run( self ):
971 self._diag.show_all() 972 self._diag.run() 973 self._diag.hide()
974 # run() 975 # HelpDialog 976 977
978 -class FileChooser( _EGWidget, AutoGenId ):
979 """A dialog to choose a file. 980 981 @attention: avoid using this directly, use L{App.file_chooser}, 982 L{OpenFileButton}, L{SaveFileButton} or L{SelectFolderButton} instead. 983 """ 984 ACTION_OPEN = 0 985 ACTION_SAVE = 1 986 ACTION_SELECT_FOLDER = 2 987 ACTION_CREATE_FOLDER = 3 988
989 - def __init__( self, app, action, filename=None, 990 title=None, filter=None, multiple=False ):
991 """Dialog to choose files. 992 993 filter may be a single pattern (ie: '*.png'), mime type 994 (ie: 'text/html') or a list of patterns or mime types or 995 a list of lists, each sub list with a filter name and mime type/ 996 patterns accepted. Examples: 997 [ [ 'Images', '*.ppm', 'image/jpeg', 'image/png' ], 998 [ 'Text', '*.text', 'text/plain' ], 999 ] 1000 """ 1001 _EGWidget.__init__( self, self.__get_id__(), app ) 1002 self.action = action 1003 self.filter = filter 1004 self.multiple = multiple or False 1005 self.filename = filename 1006 self.title = title or self.__gen_title__() 1007 1008 if self.multiple: 1009 warn( "Maemo doesn't support multiple file selection!" ) 1010 1011 self.__setup_gui__()
1012 # __init__() 1013 1014
1015 - def __gen_title__( self ):
1016 t = { self.ACTION_OPEN: "Open: %s", 1017 self.ACTION_SAVE: "Save: %s", 1018 self.ACTION_SELECT_FOLDER: "Open Folder: %s", 1019 self.ACTION_CREATE_FOLDER: "Create Folder: %s", 1020 } 1021 title = t.get( self.action, t[ self.ACTION_OPEN ] ) 1022 return title % self.app.title
1023 # __gen_title__() 1024 1025
1026 - def __del__( self ):
1027 self._diag.destroy()
1028 # __del__() 1029 1030
1031 - def __setup_gui__( self ):
1032 win = self.app.__get_window__() 1033 a = { self.ACTION_OPEN: gtk.FILE_CHOOSER_ACTION_OPEN, 1034 self.ACTION_SAVE: gtk.FILE_CHOOSER_ACTION_SAVE, 1035 self.ACTION_SELECT_FOLDER: gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, 1036 self.ACTION_CREATE_FOLDER: gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER, 1037 }.get( self.action, gtk.FILE_CHOOSER_ACTION_OPEN ) 1038 1039 self._diag = hildon.FileChooserDialog( parent=win, 1040 action=a ) 1041 _set_icon_list( self._diag, gtk.STOCK_OPEN ) 1042 if self.filter: 1043 sys.stderr.write( "Maemo HildonFileChooserDialog doesn't support " 1044 "filters" ) 1045 if self.filename: 1046 self._diag.set_filename( self.filename )
1047 # __setup_gui__() 1048 1049
1050 - def run( self ):
1051 self._diag.show_all() 1052 r = self._diag.run() 1053 self._diag.hide() 1054 if r == gtk.RESPONSE_OK: 1055 return self._diag.get_filename() 1056 else: 1057 return None
1058 # run() 1059 # FileChooser 1060 1061
1062 -class PreferencesDialog( _EGWidget, AutoGenId ):
1063 """A dialog to present user with preferences. 1064 1065 Preferences is another L{App} area, just like C{left}, C{right}, C{center}, 1066 C{top} or C{bottom}, but will be displayed in a separate window. 1067 1068 @attention: avoid using this directly, use L{PreferencesButton} instead. 1069 """
1070 - def __init__( self, app, children ):
1071 _EGWidget.__init__( self, self.__get_id__(), app ) 1072 self.children = _obj_tuple( children ) 1073 self.__setup_gui__() 1074 self.__add_widgets_to_app__()
1075 # __init__() 1076 1077
1078 - def __del__( self ):
1079 self._diag.destroy()
1080 # __del__() 1081 1082
1083 - def __setup_gui__( self ):
1084 win = self.app.__get_window__() 1085 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE ) 1086 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT 1087 self._diag = gtk.Dialog( title=( "Preferences: %s" % self.app.title ), 1088 parent=win, 1089 flags=flags, buttons=btns ) 1090 1091 settings = self._diag.get_settings() 1092 try: 1093 settings.set_property( "gtk-button-images", False ) 1094 except: 1095 pass 1096 1097 self._diag.set_default_size( 400, 300 ) 1098 _set_icon_list( self._diag, gtk.STOCK_PREFERENCES ) 1099 1100 self._sw = gtk.ScrolledWindow() 1101 self._diag.get_child().pack_start( self._sw, expand=True, fill=True ) 1102 1103 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC, 1104 vscrollbar_policy=gtk.POLICY_AUTOMATIC ) 1105 1106 self._tab = _Table( self.id, self.children ) 1107 self._sw.add_with_viewport( self._tab ) 1108 self._sw.get_child().set_shadow_type( gtk.SHADOW_NONE ) 1109 self._sw.set_shadow_type( gtk.SHADOW_NONE )
1110 # __setup_gui__() 1111 1112
1113 - def __add_widgets_to_app__( self ):
1114 for w in self.children: 1115 if isinstance( w, _EGDataWidget ): 1116 w.persistent = True 1117 self.app.__add_widget__( w )
1118 # __add_widgets_to_app__() 1119 1120
1121 - def run( self ):
1122 self._diag.show_all() 1123 self._diag.run() 1124 self._diag.hide()
1125 # run() 1126 # PreferencesDialog 1127 1128
1129 -class DebugDialog( _EGObject, AutoGenId ):
1130 """Dialog to show uncaught exceptions. 1131 1132 This dialog shows information about uncaught exceptions and also save 1133 the traceback to a file. 1134 """ 1135 # Most of DebugDialog code came from Gazpacho code! Thanks! 1136 border = 3 1137 spacing = 6 1138 width = 600 1139 height = 400 1140 margin = 3 1141
1142 - def __init__( self ):
1143 _EGObject.__init__( self, self.__get_id__() ) 1144 self.__setup_gui__()
1145 # __init__() 1146 1147
1148 - def __setup_gui__( self ):
1149 b = ( gtk.STOCK_QUIT, gtk.RESPONSE_CLOSE ) 1150 self._diag = gtk.Dialog( "Application Crashed!", 1151 parent=None, 1152 flags=gtk.DIALOG_MODAL, 1153 buttons=b ) 1154 1155 settings = self._diag.get_settings() 1156 try: 1157 settings.set_property( "gtk-button-images", False ) 1158 except: 1159 pass 1160 1161 self._diag.set_border_width( self.border ) 1162 self._diag.set_default_size( self.width, self.height ) 1163 self._diag.set_has_separator( False ) 1164 self._diag.vbox.set_spacing( self.spacing ) 1165 1166 self._hbox1 = gtk.HBox() 1167 1168 self._label1 = gtk.Label( "<b>Exception type:</b>" ) 1169 self._label1.set_use_markup( True ) 1170 self._hbox1.pack_start( self._label1, False, False, self.spacing ) 1171 self._label1.show() 1172 1173 self._exctype = gtk.Label() 1174 self._hbox1.pack_start( self._exctype, False, True ) 1175 self._exctype.show() 1176 1177 self._diag.vbox.pack_start( self._hbox1, False, False ) 1178 self._hbox1.show() 1179 1180 self._hbox2 = gtk.HBox() 1181 1182 self._label2 = gtk.Label( "<b>This info was saved to:</b>" ) 1183 self._label2.set_use_markup( True ) 1184 self._hbox2.pack_start( self._label2, False, False, self.spacing ) 1185 self._label2.show() 1186 1187 self._save_name = gtk.Label() 1188 self._hbox2.pack_start( self._save_name, False, True ) 1189 self._save_name.show() 1190 1191 self._diag.vbox.pack_start( self._hbox2, False, False ) 1192 self._hbox2.show() 1193 1194 self._sw = gtk.ScrolledWindow() 1195 self._sw.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC ) 1196 self._sw.set_shadow_type( gtk.SHADOW_IN ) 1197 self._text = gtk.TextView() 1198 self._text.set_editable( False ) 1199 self._text.set_cursor_visible( False ) 1200 self._text.set_wrap_mode( gtk.WRAP_WORD ) 1201 self._text.set_left_margin( self.margin ) 1202 self._text.set_right_margin( self.margin ) 1203 1204 self._sw.add( self._text ) 1205 self._text.show() 1206 self._diag.vbox.pack_start( self._sw, expand=True, fill=True ) 1207 self._sw.show() 1208 self.__setup_text__()
1209 # __setup_gui__() 1210 1211
1212 - def __setup_text__( self ):
1213 self._buf = self._text.get_buffer() 1214 self._buf.create_tag( "label", weight=pango.WEIGHT_BOLD ) 1215 self._buf.create_tag( "code", foreground="gray25", 1216 family="monospace" ) 1217 self._buf.create_tag( "exc", foreground="#880000", 1218 weight=pango.WEIGHT_BOLD )
1219 # __setup_text__() 1220 1221
1222 - def show_exception( self, exctype, value, tb ):
1223 import traceback 1224 self._exctype.set_text( str( exctype ) ) 1225 self.print_tb( tb ) 1226 1227 lines = traceback.format_exception_only( exctype, value ) 1228 msg = lines[ 0 ] 1229 result = msg.split( ' ', 1 ) 1230 if len( result ) == 1: 1231 msg = result[ 0 ] 1232 arguments = "" 1233 else: 1234 msg, arguments = result 1235 1236 self._insert_text( "\n" ) 1237 self._insert_text( msg, "exc" ) 1238 self._insert_text( " " ) 1239 self._insert_text( arguments )
1240 # show_exception() 1241 1242
1243 - def save_exception( self, exctype, value, tb ):
1244 import traceback 1245 import time 1246 progname = os.path.split( sys.argv[ 0 ] )[ -1 ] 1247 filename = "%s-%s-%s.tb" % ( progname, 1248 os.getuid(), 1249 int( time.time() ) ) 1250 filename = os.path.join( os.path.sep, "tmp", filename ) 1251 f = open( filename, "wb" ) 1252 try: 1253 os.chmod( filename, 0600 ) 1254 except: 1255 pass 1256 1257 for e in traceback.format_exception( exctype, value, tb ): 1258 f.write( e ) 1259 f.close() 1260 self._save_name.set_text( filename ) 1261 sys.stderr.write( "Traceback saved to '%s'.\n" % filename )
1262 # save_exception() 1263 1264
1265 - def print_tb( self, tb, limit=None ):
1266 import linecache 1267 1268 if limit is None: 1269 if hasattr( sys, "tracebacklimit" ): 1270 limit = sys.tracebacklimit 1271 n = 0 1272 while tb is not None and ( limit is None or n < limit ): 1273 f = tb.tb_frame 1274 lineno = tb.tb_lineno 1275 co = f.f_code 1276 filename = co.co_filename 1277 name = co.co_name 1278 self._print_file( filename, lineno, name ) 1279 line = linecache.getline( filename, lineno ) 1280 if line: 1281 self._insert_text( " " + line.strip() + "\n\n", "code" ) 1282 tb = tb.tb_next 1283 n = n+1
1284 # print_tb() 1285 1286
1287 - def _insert_text( self, text, *tags ):
1288 end_iter = self._buf.get_end_iter() 1289 self._buf.insert_with_tags_by_name( end_iter, text, *tags )
1290 # _insert_text() 1291 1292
1293 - def _print_file( self, filename, lineno, name ):
1294 if filename.startswith( os.getcwd() ): 1295 filename = filename.replace( os.getcwd(), '' )[ 1 : ] 1296 1297 self._insert_text( "File: ", "label" ) 1298 self._insert_text( filename ) 1299 self._insert_text( "\n" ) 1300 self._insert_text( "Line: ", "label" ) 1301 self._insert_text( str( lineno ) ) 1302 self._insert_text( "\n" ) 1303 self._insert_text( "Function: ", "label" ) 1304 self._insert_text( name ) 1305 self._insert_text( "\n" )
1306 # _print_file() 1307
1308 - def _start_debugger( self ):
1309 import pdb 1310 pdb.pm()
1311 # _start_debugger() 1312 1313 1314
1315 - def run( self, error=None ):
1316 r = self._diag.run() 1317 if r == gtk.RESPONSE_CLOSE or gtk.RESPONSE_DELETE_EVENT: 1318 raise SystemExit( error )
1319 # run() 1320 1321
1322 - def except_hook( exctype, value, tb ):
1323 if exctype is not KeyboardInterrupt: 1324 d = DebugDialog() 1325 d.save_exception( exctype, value, tb ) 1326 d.show_exception( exctype, value, tb ) 1327 d.run() 1328 d.destroy() 1329 1330 raise SystemExit
1331 # except_hook() 1332 except_hook = staticmethod( except_hook )
1333 # DebugDialog 1334 sys.excepthook = DebugDialog.except_hook 1335 1336
1337 -class _EGWidLabelEntry( _EGDataWidget ):
1338 """Widget that holds a label and an associated Entry. 1339 1340 @note: _EGWidLabelEntry must B{NOT} be used directly! You should use 1341 a widget that specialize this instead. 1342 1343 @attention: B{Widget Developers:} You must setup an instance attribute 1344 C{_entry} before using it, since this will be set as mnemonic for this 1345 label and also returned in L{__get_widgets__}(). 1346 """
1347 - def __init__( self, id, persistent, label="" ):
1348 _EGDataWidget.__init__( self, id, persistent ) 1349 self.__label = label 1350 self.__setup_gui__()
1351 # __init__() 1352 1353
1354 - def __setup_gui__( self ):
1355 if self.__label is not None: 1356 self._label = gtk.Label( self.__label ) 1357 self._label.set_justify( gtk.JUSTIFY_RIGHT ) 1358 self._label.set_alignment( xalign=1.0, yalign=0.5 ) 1359 self._label.set_mnemonic_widget( self._entry ) 1360 self._widgets = ( self._label, self._entry ) 1361 else: 1362 self._widgets = ( self._entry, )
1363 # __setup_gui__() 1364 1365
1366 - def get_value( self ):
1367 return self._entry.get_value()
1368 # get_value() 1369 1370
1371 - def set_value( self, value ):
1372 self._entry.set_value( value )
1373 # set_value() 1374 1375
1376 - def set_label( self, label ):
1377 if self.__label is None: 1378 raise ValueError( "You cannot change label of widget created " 1379 "without one. Create it with placeholder! " 1380 "(label='')" ) 1381 self.__label = label 1382 self._label.set_text( self.__label )
1383 # set_label() 1384 1385
1386 - def get_label( self ):
1387 return self.__label
1388 # get_label() 1389 1390 label = property( get_label, set_label ) 1391 1392
1393 - def __str__( self ):
1394 return "%s( id=%r, label=%r, value=%r )" % \ 1395 ( self.__class__.__name__, self.id, self.label, 1396 self.get_value() )
1397 # __str__() 1398 __repr__ = __str__ 1399 1400
1401 - def __get_resize_mode__( self ):
1402 """Resize mode. 1403 1404 First tuple of tuple is about horizontal mode for label and entry. 1405 Second tuple of tuple is about vertical mode for label and entry. 1406 """ 1407 return ( ( 0, gtk.FILL | gtk.EXPAND ), 1408 ( 0, 0 ) )
1409 # __get_resize_mode__() 1410 # _EGWidLabelEntry 1411 1412
1413 -class App( _EGObject, AutoGenId ):
1414 """An application window. 1415 1416 This is the base of Eagle programs, since it will hold every graphical 1417 component. 1418 1419 An App window is split in 5 areas: 1420 - left 1421 - right 1422 - center 1423 - top 1424 - bottom 1425 the first 3 have a vertical layout, the other have horizontal layout. 1426 Every area has its own scroll bars that are shown automatically when 1427 need. 1428 1429 Also provided is an extra area, that is shown in another window. This is 1430 the preferences area. It have a vertical layout and components that 1431 hold data are made persistent automatically. You should use 1432 L{PreferencesButton} to show this area. 1433 1434 Extra information like author, description, help, version, license and 1435 copyright are used in specialized dialogs. You may show these dialogs 1436 with L{AboutButton} and L{HelpButton}. 1437 1438 Widgets can be reach with L{get_widget_by_id}, example: 1439 >>> app = App( "My App", left=Entry( id="entry" ) ) 1440 >>> app.get_widget_by_id( "entry" ) 1441 Entry( id='entry', label='entry', value='' ) 1442 1443 You may also reach widgets using dict-like syntax, but with the 1444 special case for widgets that hold data, these will be provided 1445 using their L{set_data<_EGDataWidget.set_data>} and 1446 L{get_data<_EGDataWidget.get_data>}, it make things easier, but 1447 B{be careful to don't misuse it!}. Example: 1448 1449 >>> app= App( "My App", left=Entry( id="entry" ), 1450 ... right=Canvas( "canvas", 300, 300 ) ) 1451 >>> app[ "entry" ] 1452 '' 1453 >>> app[ "entry" ] = "text" 1454 >>> app[ "entry" ] 1455 'text' 1456 >>> app[ "canvas" ] 1457 Canvas( id='canvas', width=300, height=300, label='' ) 1458 >>> app[ "canvas" ].draw_text( "hello" ) 1459 >>> app[ "entry" ].get_value() # will fail, since it's a data widget 1460 1461 """ 1462 border_width = 0 1463 spacing = 3 1464 1465 title = _gen_ro_property( "title" ) 1466 left = _gen_ro_property( "left" ) 1467 right = _gen_ro_property( "right" ) 1468 top = _gen_ro_property( "top" ) 1469 bottom = _gen_ro_property( "bottom" ) 1470 center = _gen_ro_property( "center" ) 1471 preferences = _gen_ro_property( "preferences" ) 1472 statusbar = _gen_ro_property( "statusbar" ) 1473 _widgets = _gen_ro_property( "_widgets" ) 1474
1475 - def __init__( self, title, id=None, 1476 center=None, left=None, right=None, top=None, bottom=None, 1477 preferences=None, 1478 quit_callback=None, data_changed_callback=None, 1479 author=None, description=None, help=None, version=None, 1480 license=None, copyright=None, 1481 statusbar=False ):
1482 """App Constructor. 1483 1484 @param title: application name, to be displayed in the title bar. 1485 @param id: unique id to this application, or None to generate one 1486 automatically. 1487 @param center: list of widgets to be laid out vertically in the 1488 window's center. 1489 @param left: list of widgets to be laid out vertically in the 1490 window's left side. 1491 @param right: list of widgets to be laid out vertically in the 1492 window's right side. 1493 @param top: list of widgets to be laid out horizontally in the 1494 window's top. 1495 @param bottom: list of widgets to be laid out horizontally in the 1496 window's bottom. 1497 @param preferences: list of widgets to be laid out vertically in 1498 another window, this can be shown with L{PreferencesButton}. 1499 @param statusbar: if C{True}, an statusbar will be available and 1500 usable with L{status_message} method. 1501 @param author: the application author or list of author, used in 1502 L{AboutDialog}, this can be shown with L{AboutButton}. 1503 @param description: application description, used in L{AboutDialog}. 1504 @param help: help text, used in L{AboutDialog} and L{HelpDialog}, this 1505 can be shown with L{HelpButton}. 1506 @param version: application version, used in L{AboutDialog}. 1507 @param license: application license, used in L{AboutDialog}. 1508 @param copyright: application copyright, used in L{AboutDialog}. 1509 @param quit_callback: function (or list of functions) that will be 1510 called when application is closed. Function will receive as 1511 parameter the reference to App. If return value is False, 1512 it will abort closing the window. 1513 @param data_changed_callback: function (or list of functions) that will 1514 be called when some widget that holds data have its data 1515 changed. Function will receive as parameters: 1516 - App reference 1517 - Widget reference 1518 - new value 1519 """ 1520 _EGObject.__init__( self, id ) 1521 self.title = title 1522 self.left = left 1523 self.right = right 1524 self.top = top 1525 self.bottom = bottom 1526 self.center = center 1527 self.preferences = preferences 1528 self.author = _str_tuple( author ) 1529 self.description = _str_tuple( description ) 1530 self.help = _str_tuple( help ) 1531 self.version = _str_tuple( version ) 1532 self.license = _str_tuple( license ) 1533 self.copyright = _str_tuple( copyright ) 1534 self.statusbar = statusbar 1535 self._widgets = {} 1536 1537 self.quit_callback = _callback_tuple( quit_callback ) 1538 self.data_changed_callback = _callback_tuple( data_changed_callback ) 1539 1540 self.__add_to_app_list__() 1541 self.__setup_gui__() 1542 self.__setup_connections__() 1543 self.load()
1544 # __init__() 1545 1546
1547 - def __getitem__( self, name ):
1548 w = self.get_widget_by_id( name ) 1549 if isinstance( w, _EGDataWidget ): 1550 return w.get_value() 1551 else: 1552 return w
1553 # __getitem__() 1554 1555
1556 - def __setitem__( self, name, value ):
1557 w = self.get_widget_by_id( name ) 1558 if w is None: 1559 raise ValueError( "Could not find any widget with id=%r" % name ) 1560 elif isinstance( w, _EGDataWidget ): 1561 return w.set_value( value ) 1562 else: 1563 raise TypeError( 1564 "Could not set value of widget '%s' of type '%s'." % \ 1565 ( name, type( w ).__name__ ) )
1566 # __setitem__() 1567 1568
1569 - def get_widget_by_id( self, widget_id ):
1570 """Return referece to widget with provided id or None if not found.""" 1571 if isinstance( widget_id, _EGWidget ) and \ 1572 widget_id in self._widgets.itervalues(): 1573 return widget_id 1574 else: 1575 return self._widgets.get( widget_id, None )
1576 # get_widget_by_id() 1577 1578
1579 - def show_about_dialog( self ):
1580 """Show L{AboutDialog} of this App.""" 1581 diag = AboutDialog( app=self, 1582 title=self.title, 1583 author=self.author, 1584 description=self.description, 1585 help=self.help, 1586 version=self.version, 1587 license=self.license, 1588 copyright=self.copyright, 1589 ) 1590 diag.run()
1591 # show_about_dialog() 1592 1593
1594 - def show_help_dialog( self ):
1595 """Show L{HelpDialog} of this App.""" 1596 diag = HelpDialog( app=self, 1597 title=self.title, 1598 help=self.help, 1599 ) 1600 diag.run()
1601 # show_help_dialog() 1602 1603
1604 - def file_chooser( self, action, filename=None, 1605 filter=None, multiple=False ):
1606 """Show L{FileChooser} and return selected file(s). 1607 1608 @param action: must be one of ACTION_* as defined in L{FileChooser}. 1609 @param filter: a pattern (ie: '*.png'), mime type or a list. 1610 1611 @see: L{FileChooser} 1612 """ 1613 diag = FileChooser( app=self, action=action, 1614 filename=filename, filter=filter, 1615 multiple=multiple ) 1616 return diag.run()
1617 # file_chooser() 1618 1619
1620 - def show_preferences_dialog( self ):
1621 """Show L{PreferencesDialog} associated with this App.""" 1622 return self._preferences.run()
1623 # show_preferences_dialog() 1624 1625
1626 - def __get_window__( self ):
1627 return self._win
1628 # __get_window__() 1629 1630
1631 - def __add_to_app_list__( self ):
1632 if not self.id: 1633 self.id = self.__get_id__() 1634 1635 if self.id in _apps: 1636 raise ValueError( "App id '%s' already existent!" % self.id ) 1637 1638 _apps[ self.id ] = self
1639 # __add_to_app_list__() 1640 1641
1642 - def __add_widget__( self, widget ):
1643 if widget.id in self._widgets: 1644 w = self._widgets[ widget.id ] 1645 raise ValueError( ( "Object id \"%s\" (type: %s) already in " 1646 "application \"%s\" as type: %s!" ) % \ 1647 ( widget.id, 1648 widget.__class__.__name__, 1649 self.id, 1650 w.__class__.__name__ ) ) 1651 else: 1652 if widget.app is None: 1653 self._widgets[ widget.id ] = widget 1654 widget.app = self 1655 elif widget.app != self: 1656 try: 1657 id = widget.app.id 1658 except: 1659 id = widget.app 1660 raise ValueError( ( "Object \"%s\" already is in another " 1661 "App: \"%s\"" ) % \ 1662 ( widget.id, id ) )
1663 # __add_widget__() 1664 1665
1666 - def __setup_gui__( self ):
1667 self._win = hildon.App() 1668 self._win.set_name( self.id ) 1669 self._win.set_title( self.title ) 1670 1671 self._mv = hildon.AppView( self.title ) 1672 self._win.set_appview( self._mv ) 1673 self._mv.set_fullscreen_key_allowed( True ) 1674 1675 self._top_layout = gtk.VBox( False ) 1676 self._mv.add( self._top_layout ) 1677 1678 self._hbox = gtk.HBox( False, self.spacing ) 1679 self._hbox.set_border_width( self.border_width ) 1680 self._top_layout.pack_start( self._hbox, expand=True, fill=True ) 1681 1682 self.__setup_menus__() 1683 self.__setup_gui_left__() 1684 self.__setup_gui_right__() 1685 self.__setup_gui_center__() 1686 self.__setup_gui_top__() 1687 self.__setup_gui_bottom__() 1688 self.__setup_gui_preferences__() 1689 1690 self._vbox = gtk.VBox( False, self.spacing ) 1691 1692 has_top = bool( self._top.__get_widgets__() ) 1693 has_bottom = bool( self._bottom.__get_widgets__() ) 1694 has_left = bool( self._left.__get_widgets__() ) 1695 has_right = bool( self._right.__get_widgets__() ) 1696 has_center = bool( self._center.__get_widgets__() ) 1697 1698 expand_top = False 1699 expand_bottom = False 1700 expand_left = False 1701 expand_right = False 1702 expand_center = has_center 1703 1704 # Left and Right just expand if there is no center 1705 if has_left and not has_center: 1706 expand_left = True 1707 if has_right and not has_center: 1708 expand_right = True 1709 1710 # Top and Bottom just expand if there is no center 1711 if has_top and not has_center: 1712 expand_top = True 1713 if has_bottom and not has_center: 1714 expand_bottom = True 1715 1716 # Create Vertical layout with ( Top | Center | Bottom ) 1717 has_vl = has_top or has_center or has_bottom 1718 if has_vl: 1719 if has_top: 1720 self._vbox.pack_start( self._top, expand_top, True ) 1721 if has_center or has_bottom: 1722 self._vbox.pack_start( gtk.HSeparator(), False, True ) 1723 1724 if has_center: 1725 self._vbox.pack_start( self._center, expand_center, True ) 1726 if has_bottom: 1727 self._vbox.pack_start( gtk.HSeparator(), False, True ) 1728 1729 if has_bottom: 1730 self._vbox.pack_start( self._bottom, expand_bottom, True ) 1731 1732 # Create Horizontal layout with ( Left | VL | Right ) 1733 # where VL is Vertical layout created before 1734 if has_left: 1735 self._hbox.pack_start( self._left, expand_left, True ) 1736 if has_vl or has_right: 1737 self._hbox.pack_start( gtk.VSeparator(), False, True ) 1738 1739 if has_vl: 1740 self._hbox.pack_start( self._vbox, True, True ) 1741 if has_right: 1742 self._hbox.pack_start( gtk.VSeparator(), False, True ) 1743 1744 if has_right: 1745 self._hbox.pack_start( self._right, expand_right, True ) 1746 1747 1748 if self.statusbar: 1749 self._statusbar = gtk.Statusbar() 1750 self._statusbar_ctx = self._statusbar.get_context_id( self.title ) 1751 self._statusbar.set_has_resize_grip( True ) 1752 self._top_layout.pack_end( self._statusbar, 1753 expand=False, fill=True ) 1754 1755 settings = self._win.get_settings() 1756 try: 1757 settings.set_property( "gtk-button-images", False ) 1758 except: 1759 pass 1760 self._win.show_all()
1761 # __setup_gui__() 1762 1763
1764 - def __setup_menus__( self ):
1765 self._menu = self._mv.get_menu() 1766 1767 mi = gtk.MenuItem( "Help" ) 1768 mi.connect( "activate", lambda *x: self.show_help_dialog() ) 1769 self._menu.append( mi ) 1770 1771 mi = gtk.MenuItem( "About" ) 1772 mi.connect( "activate", lambda *x: self.show_about_dialog() ) 1773 self._menu.append( mi ) 1774 1775 mi = gtk.MenuItem( "Preferences" ) 1776 mi.connect( "activate", lambda *x: self.show_preferences_dialog() ) 1777 self._menu.append( mi ) 1778 1779 mi = gtk.MenuItem( "Close" ) 1780 mi.connect( "activate", lambda *x: self.close() ) 1781 self._menu.append( mi ) 1782 1783 mi = gtk.MenuItem( "Quit" ) 1784 mi.connect( "activate", lambda *x: quit() ) 1785 self._menu.append( mi ) 1786 1787 self._menu.show_all()
1788 # __setup_menus__() 1789 1790
1791 - def __setup_gui_left__( self ):
1792 self._left = _VPanel( self, id="left", children=self.left )
1793 # __setup_gui_left__() 1794 1795
1796 - def __setup_gui_right__( self ):
1797 self._right =_VPanel( self, id="right", children=self.right )
1798 # __setup_gui_right__() 1799 1800
1801 - def __setup_gui_center__( self ):
1802 self._center = _VPanel( self, id="center", children=self.center )
1803 # __setup_gui_center__() 1804 1805
1806 - def __setup_gui_top__( self ):
1807 self._top = _HPanel( self, id="top", children=self.top )
1808 # __setup_gui_top__() 1809 1810
1811 - def __setup_gui_bottom__( self ):
1812 self._bottom = _HPanel( self, id="bottom", children=self.bottom )
1813 # __setup_gui_bottom__() 1814 1815
1816 - def __setup_gui_preferences__( self ):
1817 self._preferences = PreferencesDialog( self, 1818 children=self.preferences )
1819 # __setup_gui_preferences__() 1820 1821
1822 - def __setup_connections__( self ):
1823 self._win.connect( "delete_event", self.__delete_event__ )
1824 # __setup_connections__() 1825 1826
1827 - def data_changed( self, widget, value ):
1828 """Notify that widget changed it's value. 1829 1830 Probably you will not need to call this directly. 1831 """ 1832 self.save() 1833 for c in self.data_changed_callback: 1834 c( self, widget, value )
1835 # data_changed() 1836 1837
1838 - def __do_close__( self ):
1839 self.save() 1840 1841 for c in self.quit_callback: 1842 if not c( self ): 1843 return False 1844 1845 del _apps[ self.id ] 1846 if not _apps: 1847 gtk.main_quit() 1848 1849 return True
1850 # __do_close__() 1851 1852
1853 - def __delete_event__( self, *args ):
1854 if self.__do_close__(): 1855 return False 1856 else: 1857 return True
1858 # __delete_event__() 1859 1860
1861 - def __persistence_filename__( self ):
1862 - def mkdir( d ):
1863 if not os.path.exists( d ): 1864 mkdir( os.path.dirname( d ) ) 1865 os.mkdir( d )
1866 # mkdir() 1867 1868 fname = "%s.save_data" % self.id 1869 home = os.environ.get( "HOME", "." ) 1870 binname = os.path.realpath( sys.argv[ 0 ] )[ 1 : ] 1871 d = os.path.join( home, ".eagle", binname ) 1872 1873 mkdir( d ) 1874 1875 return os.path.join( d, fname )
1876 # __persistence_filename__() 1877 1878
1879 - def save( self ):
1880 """Save data from widgets to file. 1881 1882 Probably you will not need to call this directly. 1883 """ 1884 d = {} 1885 for id, w in self._widgets.iteritems(): 1886 if isinstance( w, _EGDataWidget ) and w.persistent: 1887 d[ id ] = w.get_value() 1888 1889 if d: 1890 f = open( self.__persistence_filename__(), "wb" ) 1891 pickle.dump( d, f, pickle.HIGHEST_PROTOCOL ) 1892 f.close()
1893 # save() 1894 1895
1896 - def load( self ):
1897 """Load data to widgets from file. 1898 1899 Probably you will not need to call this directly. 1900 """ 1901 try: 1902 f = open( self.__persistence_filename__(), "rb" ) 1903 except IOError: 1904 return 1905 1906 d = pickle.load( f ) 1907 f.close() 1908 1909 for id, v in d.iteritems(): 1910 try: 1911 w = self._widgets[ id ] 1912 except KeyError: 1913 w = None 1914 if isinstance( w, _EGDataWidget ) and w.persistent: 1915 w.set_value( v )
1916 # load() 1917 1918
1919 - def close( self ):
1920 """Close application window.""" 1921 if self.__do_close__(): 1922 self._win.destroy()
1923 # close() 1924 1925
1926 - def status_message( self, message ):
1927 """Display a message in status bar and retrieve its identifier for 1928 later removal. 1929 1930 @see: L{remove_status_message} 1931 @note: this only active if statusbar=True 1932 """ 1933 if self.statusbar: 1934 return self._statusbar.push( self._statusbar_ctx, message ) 1935 else: 1936 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
1937 # status_message() 1938 1939
1940 - def remove_status_message( self, message_id ):
1941 """Remove a previously displayed message. 1942 1943 @see: L{status_message} 1944 @note: this only active if statusbar=True 1945 """ 1946 if self.statusbar: 1947 self._statusbar.remove( self._statusbar_ctx, message_id ) 1948 else: 1949 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
1950 # remove_status_message() 1951 1952
1953 - def timeout_add( self, interval, callback ):
1954 """Register a function to be called after a given timeout/interval. 1955 1956 @param interval: milliseconds between calls. 1957 @param callback: function to call back. This function gets as 1958 argument the app reference and must return C{True} to 1959 keep running, if C{False} is returned, it will not be 1960 called anymore. 1961 @return: id number to be used in L{remove_event_source} 1962 """
1963 - def wrap( *args ):
1964 return callback( self )
1965 # wrap() 1966 return gobject.timeout_add( interval, wrap )
1967 # timeout_add() 1968 1969
1970 - def idle_add( self, callback ):
1971 """Register a function to be called when system is idle. 1972 1973 System is idle if there is no other event pending. 1974 1975 @param callback: function to call back. This function gets as 1976 argument the app reference and must return C{True} to 1977 keep running, if C{False} is returned, it will not be 1978 called anymore. 1979 @return: id number to be used in L{remove_event_source} 1980 """
1981 - def wrap( *args ):
1982 return callback( self )
1983 # wrap() 1984 return gobject.idle_add( wrap )
1985 # idle_add() 1986 1987 1988
1989 - def io_watch( self, file, callback, 1990 on_in=False, on_out=False, on_urgent=False, on_error=False, 1991 on_hungup=False ):
1992 """Register a function to be called after an Input/Output event. 1993 1994 @param file: any file object or file descriptor (integer). 1995 @param callback: function to be called back, parameters will be the 1996 application that generated the event, the file that triggered 1997 it and on_in, on_out, on_urgent, on_error or on_hungup, 1998 being True those that triggered the event. 1999 The function must return C{True} to be called back again, 2000 otherwise it is automatically removed. 2001 @param on_in: there is data to read. 2002 @param on_out: data can be written without blocking. 2003 @param on_urgent: there is urgent data to read. 2004 @param on_error: error condition. 2005 @param on_hungup: hung up (the connection has been broken, usually for 2006 pipes and sockets). 2007 @return: id number to be used in L{remove_event_source} 2008 """
2009 - def wrap( source, cb_condition ):
2010 on_in = bool( cb_condition & gobject.IO_IN ) 2011 on_out = bool( cb_condition & gobject.IO_OUT ) 2012 on_urgent = bool( cb_condition & gobject.IO_PRI ) 2013 on_error = bool( cb_condition & gobject.IO_ERR ) 2014 on_hungup = bool( cb_condition & gobject.IO_HUP ) 2015 return callback( self, source, on_in=on_in, 2016 on_out=on_out, on_urgent=on_urgent, 2017 on_error=on_error, on_hungup=on_hungup )
2018 # wrap() 2019 2020 condition = 0 2021 if on_in: 2022 condition |= gobject.IO_IN 2023 if on_out: 2024 condition |= gobject.IO_OUT 2025 if on_urgent: 2026 condition |= gobject.IO_PRI 2027 if on_error: 2028 condition |= gobject.IO_ERR 2029 if on_hungup: 2030 condition |= gobject.IO_HUP 2031 return gobject.io_add_watch( file, condition, wrap )
2032 # io_watch() 2033 2034
2035 - def remove_event_source( self, event_id ):
2036 """Remove an event generator like those created by L{timeout_add}, 2037 L{idle_add} or L{io_watch}. 2038 2039 @param event_id: value returned from L{timeout_add}, 2040 L{idle_add} or L{io_watch}. 2041 2042 @return: C{True} if it was removed. 2043 """ 2044 return gobject.source_remove( event_id )
2045 # remove_event_source() 2046 # App 2047 2048
2049 -class Canvas( _EGWidget ):
2050 """The drawing area. 2051 2052 Eagle's drawing area (Canvas) is provided with a frame and an optional 2053 label, together with scrollbars, to make it fit everywhere. 2054 2055 """ 2056 padding = 5 2057 bgcolor= "black" 2058 2059 LEFT = -1 2060 CENTER = 0 2061 RIGHT = 1 2062 2063 FONT_OPTION_BOLD = 1 2064 FONT_OPTION_OBLIQUE = 2 2065 FONT_OPTION_ITALIC = 4 2066 2067 FONT_NAME_NORMAL = "normal" 2068 FONT_NAME_SERIF = "serif" 2069 FONT_NAME_SANS = "sans" 2070 FONT_NAME_MONO = "monospace" 2071 2072 MOUSE_BUTTON_1 = 1 2073 MOUSE_BUTTON_2 = 2 2074 MOUSE_BUTTON_3 = 4 2075 MOUSE_BUTTON_4 = 8 2076 MOUSE_BUTTON_5 = 16 2077 2078 label = _gen_ro_property( "label" ) 2079
2080 - def __init__( self, id, width, height, label="", bgcolor=None, 2081 scrollbars=True, callback=None ):
2082 """Canvas Constructor. 2083 2084 @param id: unique identifier. 2085 @param width: width of the drawing area in pixels, widget can be 2086 larger or smaller because and will use scrollbars if need. 2087 @param height: height of the drawing area in pixels, widget can be 2088 larger or smaller because and will use scrollbars if need. 2089 @param label: label to display in the widget frame around the 2090 drawing area. If None, no label or frame will be shown. 2091 @param bgcolor: color to paint background. 2092 @param scrollbars: whenever to use scrollbars and make canvas 2093 fit small places. 2094 @param callback: function (or list of functions) to call when 2095 mouse state changed in the drawing area. Function will get 2096 as parameters: 2097 - App reference 2098 - Canvas reference 2099 - Button state (or'ed MOUSE_BUTTON_*) 2100 - horizontal positon (x) 2101 - vertical positon (y) 2102 2103 @todo: honor the alpha value while drawing colors. 2104 """ 2105 _EGWidget.__init__( self, id ) 2106 self.__label = label 2107 self.width = width 2108 self.height = height 2109 self.scrollbars = scrollbars 2110 2111 self._pixmap = None 2112 self._callback = _callback_tuple( callback ) 2113 2114 # style and color context must be set just after drawing area is 2115 # attached to a window, otherwise they'll be empty and useless. 2116 # This is done in configure_event. 2117 self._style = None 2118 self._fg_gc_normal = None 2119 self._bg_gc_normal = None 2120 2121 if bgcolor is not None: 2122 self.bgcolor = self.__color_from__( bgcolor ) 2123 2124 self.__setup_gui__( width, height ) 2125 self.__setup_connections__()
2126 # __init__() 2127 2128
2129 - def __setup_gui__( self, width, height ):
2130 self._sw = gtk.ScrolledWindow() 2131 self._area = gtk.DrawingArea() 2132 2133 self._sw.set_border_width( self.padding ) 2134 2135 if self.label is not None: 2136 self._frame = gtk.Frame( self.label ) 2137 self._frame.add( self._sw ) 2138 self._frame.set_shadow_type( gtk.SHADOW_OUT ) 2139 root = self._frame 2140 else: 2141 root = self._sw 2142 2143 self._area.set_size_request( width, height ) 2144 self._sw.add_with_viewport( self._area ) 2145 if self.scrollbars: 2146 policy = gtk.POLICY_AUTOMATIC 2147 border = gtk.SHADOW_IN 2148 else: 2149 policy = gtk.POLICY_NEVER 2150 border = gtk.SHADOW_NONE 2151 2152 self._sw.set_policy( hscrollbar_policy=policy, 2153 vscrollbar_policy=policy ) 2154 self._sw.child.set_shadow_type( border ) 2155 self._sw.show_all() 2156 2157 self._widgets = ( root, )
2158 # __setup_gui__() 2159 2160
2161 - def __set_useful_attributes__( self ):
2162 self._style = self._area.get_style() 2163 self._fg_gc_normal = self._style.fg_gc[ gtk.STATE_NORMAL ] 2164 self._bg_gc_normal = self._style.bg_gc[ gtk.STATE_NORMAL ]
2165 # __set_useful_attributes__() 2166 2167
2168 - def __setup_connections__( self ):
2169 - def configure_event( widget, event ):
2170 if self._pixmap is None: 2171 self.__set_useful_attributes__() 2172 w, h = self._area.size_request() 2173 self.resize( w, h ) 2174 return True
2175 # configure_event() 2176 self._area.connect( "configure_event", configure_event ) 2177 2178
2179 - def expose_event( widget, event ):
2180 x , y, width, height = event.area 2181 gc = widget.get_style().fg_gc[ gtk.STATE_NORMAL ] 2182 widget.window.draw_drawable( gc, self._pixmap, x, y, x, y, 2183 width, height ) 2184 return False
2185 # expose_event() 2186 self._area.connect( "expose_event", expose_event ) 2187 2188
2189 - def get_buttons( state ):
2190 buttons = 0 2191 if state & gtk.gdk.BUTTON1_MASK: 2192 buttons |= self.MOUSE_BUTTON_1 2193 if state & gtk.gdk.BUTTON2_MASK: 2194 buttons |= self.MOUSE_BUTTON_2 2195 if state & gtk.gdk.BUTTON3_MASK: 2196 buttons |= self.MOUSE_BUTTON_3 2197 if state & gtk.gdk.BUTTON4_MASK: 2198 buttons |= self.MOUSE_BUTTON_4 2199 if state & gtk.gdk.BUTTON5_MASK: 2200 buttons |= self.MOUSE_BUTTON_5 2201 return buttons
2202 # get_buttons() 2203 2204 buttons_map = { 2205 1: self.MOUSE_BUTTON_1, 2206 2: self.MOUSE_BUTTON_2, 2207 3: self.MOUSE_BUTTON_3, 2208 4: self.MOUSE_BUTTON_4, 2209 5: self.MOUSE_BUTTON_5, 2210 } 2211
2212 - def button_press_event( widget, event ):
2213 if self._pixmap != None: 2214 btns = get_buttons( event.state ) 2215 btns |= buttons_map[ event.button ] 2216 2217 x = int( event.x ) 2218 y = int( event.y ) 2219 2220 for c in self._callback: 2221 c( self.app, self, btns, x, y ) 2222 return True
2223 # button_press_event() 2224 if self._callback: 2225 self._area.connect( "button_press_event", button_press_event ) 2226 2227
2228 - def button_release_event( widget, event ):
2229 if self._pixmap != None: 2230 btns = get_buttons( event.state ) 2231 btns &= ~buttons_map[ event.button ] 2232 2233 x = int( event.x ) 2234 y = int( event.y ) 2235 2236 for c in self._callback: 2237 c( self.app, self, btns, x, y ) 2238 return True
2239 # button_press_event() 2240 if self._callback: 2241 self._area.connect( "button_release_event", button_release_event ) 2242 2243
2244 - def motion_notify_event( widget, event ):
2245 if self._pixmap is None: 2246 return True 2247 2248 if event.is_hint: 2249 x, y, state = event.window.get_pointer() 2250 else: 2251 x = event.x 2252 y = event.y 2253 state = event.state 2254 2255 2256 btns = get_buttons( state ) 2257 x = int( x ) 2258 y = int( y ) 2259 2260 if btns: 2261 for c in self._callback: 2262 c( self.app, self, btns, x, y ) 2263 2264 return True
2265 # motion_notify_event() 2266 if self._callback: 2267 self._area.connect( "motion_notify_event", motion_notify_event ) 2268 2269 2270 # Enable events 2271 self._area.set_events( gtk.gdk.EXPOSURE_MASK | 2272 gtk.gdk.LEAVE_NOTIFY_MASK | 2273 gtk.gdk.BUTTON_PRESS_MASK | 2274 gtk.gdk.BUTTON_RELEASE_MASK | 2275 gtk.gdk.POINTER_MOTION_MASK | 2276 gtk.gdk.POINTER_MOTION_HINT_MASK )
2277 # __setup_connections__() 2278 2279
2280 - def __get_resize_mode__( self ):
2281 return ( gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND )
2282 # __get_resize_mode__() 2283 2284 2285
2286 - def __color_from__( color ):
2287 """Convert from color to internal representation. 2288 2289 Gets a string, integer or tuple/list arguments and converts into 2290 internal color representation. 2291 """ 2292 a = 255 2293 2294 if isinstance( color, str ): 2295 try: 2296 c = gtk.gdk.color_parse( color ) 2297 r = int( c.red / 65535.0 * 255 ) 2298 g = int( c.green / 65535.0 * 255 ) 2299 b = int( c.blue / 65535.0 * 255 ) 2300 except ValueError, e: 2301 raise ValueError( "%s. color=%r" % ( e, color ) ) 2302 elif isinstance( color, gtk.gdk.Color ): 2303 r = int( color.red / 65535.0 * 255 ) 2304 g = int( color.green / 65535.0 * 255 ) 2305 b = int( color.blue / 65535.0 * 255 ) 2306 elif isinstance( color, int ): 2307 r = ( color >> 16 ) & 0xff 2308 g = ( color >> 8 ) & 0xff 2309 b = ( color & 0xff ) 2310 elif isinstance( color, ( tuple, list ) ): 2311 if len( color) == 3: 2312 r, g, b = color 2313 else: 2314 a, r, g, b = color 2315 2316 return a, r, g, b
2317 # __color_from__() 2318 __color_from__ = staticmethod( __color_from__ ) 2319 2320
2321 - def __to_gtk_color__( color ):
2322 r = int( color[ 1 ] / 255.0 * 65535 ) 2323 g = int( color[ 2 ] / 255.0 * 65535 ) 2324 b = int( color[ 3 ] / 255.0 * 65535 ) 2325 return gtk.gdk.Color( r, g, b )
2326 # __to_gtk_color__() 2327 __to_gtk_color__ = staticmethod( __to_gtk_color__ ) 2328 2329
2330 - def __configure_gc__( self, fgcolor=None, bgcolor=None, fill=None, 2331 line_width=None, line_style=None ):
2332 if fgcolor is not None: 2333 fgcolor = self.__color_from__( fgcolor ) 2334 if bgcolor is not None: 2335 bgcolor = self.__color_from__ ( bgcolor ) 2336 2337 k = {} 2338 if fill is not None: 2339 k[ "fill" ] = gtk.gdk.SOLID 2340 if line_width is not None: 2341 k[ "line_width" ] = line_width 2342 if line_style is not None: 2343 k[ "line_style" ] = line_style 2344 2345 gc = self._pixmap.new_gc( **k ) 2346 2347 if fgcolor is not None: 2348 gc.set_rgb_fg_color( self.__to_gtk_color__( fgcolor ) ) 2349 if bgcolor is not None: 2350 gc.set_rgb_bg_color( self.__to_gtk_color__( bgcolor ) ) 2351 return gc
2352 # __configure_gc__() 2353 2354
2355 - def resize( self, width, height ):
2356 """Resize the drawing area.""" 2357 old = self._pixmap 2358 self._pixmap = gtk.gdk.Pixmap( self._area.window, width, height ) 2359 if old is None: 2360 # Paint with bg color 2361 self.clear() 2362 else: 2363 # copy old contents over this 2364 w, h = old.get_size() 2365 self._pixmap.draw_drawable( self._fg_gc_normal, old, 2366 0, 0, 0, 0, w, h )
2367 # resize() 2368 2369
2370 - def draw_image( self, image, x=0, y=0, 2371 width=None, height=None, 2372 src_x=0, src_y=0 ):
2373 """Draw image on canvas. 2374 2375 By default it draws entire image at top canvas corner. 2376 2377 You may restrict which image areas to use with src_x, src_y, width 2378 and height. 2379 2380 You may choose position on canvas with x and y. 2381 """ 2382 if not isinstance( image, Image ): 2383 raise TypeError( ( "image must be instance of Image class, " 2384 "but %s found!" ) % ( type( image ).__name__ ) ) 2385 2386 p = image.__get_gtk_pixbuf__() 2387 2388 if src_x >= p.get_width(): 2389 raise ValueError( "src_x is greater or equal width!" ) 2390 2391 if src_y >= p.get_height(): 2392 raise ValueError( "src_y is greater or equal height!" ) 2393 2394 if width is None or width < 1: 2395 width = p.get_width() 2396 2397 if height is None or height < 1: 2398 height = p.get_height() 2399 2400 if src_x + width > p.get_width(): 2401 width = p.get_width() - src_x 2402 if src_y + height > p.get_height(): 2403 height = p.get_height() - src_y 2404 2405 self._pixmap.draw_pixbuf( self._fg_gc_normal, 2406 p, src_x, src_y, x, y, width, height ) 2407 self._area.queue_draw_area( x, y, width, width )
2408 # draw_image() 2409 2410
2411 - def draw_text( self, text, x=0, y=0, 2412 fgcolor=None, bgcolor=None, 2413 font_name=None, font_size=None, font_options=0, 2414 font_family=None, 2415 width=None, wrap_word=False, 2416 alignment=LEFT, justify=True ):
2417 """Draw text on canvas. 2418 2419 By default text is draw with current font and colors at top canvas 2420 corner. 2421 2422 You may limit width providing a value and choose if it should wrap 2423 at words (wrap_word=True) or characters (wrap_word=False). 2424 2425 2426 Colors can be specified with fgcolor an bgcolor. If not provided, the 2427 system foreground color is used and no background color is used. 2428 2429 Font name, family, size and options may be specified using 2430 font_name, font_family, font_size and font_options, respectively. 2431 Try to avoid using system specific font fames, use those provided 2432 by FONT_NAME_*. 2433 2434 Font options is OR'ed values from FONT_OPTIONS_*. 2435 2436 Font name is a string that have all the information, like 2437 "sans bold 12". This is returned by L{Font}. 2438 2439 Text alignment is one of LEFT, RIGHT or CENTER. 2440 """ 2441 if fgcolor is not None: 2442 fgcolor = self.__to_gtk_color__( self.__color_from__( fgcolor ) ) 2443 if bgcolor is not None: 2444 bgcolor = self.__to_gtk_color__( self.__color_from__( bgcolor ) ) 2445 2446 layout = self._area.create_pango_layout( text ) 2447 if width is not None: 2448 layout.set_width( width * pango.SCALE ) 2449 if wrap_word: 2450 layout.set_wrap( pango.WRAP_WORD ) 2451 2452 layout.set_justify( justify ) 2453 alignment = { self.LEFT: pango.ALIGN_LEFT, 2454 self.CENTER: pango.ALIGN_CENTER, 2455 self.RIGHT: pango.ALIGN_RIGHT }.get( alignment, 2456 pango.ALIGN_CENTER ) 2457 layout.set_alignment( alignment ) 2458 2459 if font_name or font_size or font_options or font_family: 2460 if font_name: 2461 fd = pango.FontDescription( font_name ) 2462 else: 2463 fd = layout.get_context().get_font_description() 2464 2465 if font_size: 2466 fd.set_size( font_size * pango.SCALE) 2467 if font_options: 2468 if font_options & self.FONT_OPTION_BOLD: 2469 fd.set_weight( pango.WEIGHT_BOLD ) 2470 if font_options & self.FONT_OPTION_ITALIC: 2471 fd.set_style( pango.STYLE_ITALIC ) 2472 if font_options & self.FONT_OPTION_OBLIQUE: 2473 fd.set_style( pango.STYLE_OBLIQUE ) 2474 layout.set_font_description( fd ) 2475 2476 self._pixmap.draw_layout( self._fg_gc_normal, x, y, layout, 2477 fgcolor, bgcolor ) 2478 w, h = layout.get_pixel_size() 2479 self._area.queue_draw_area( x, y, w, h )
2480 # draw_text() 2481 2482 2483
2484 - def draw_point( self, x, y, color=None ):
2485 """Draw point.""" 2486 gc = self.__configure_gc__( fgcolor=color ) 2487 self._pixmap.draw_point( gc, x, y ) 2488 self._area.queue_draw_area( x, y, 1, 1 )
2489 # draw_point() 2490 2491
2492 - def draw_points( self, points, color=None ):
2493 """Draw points. 2494 2495 Efficient way to draw more than one point with the same 2496 characteristics. 2497 """ 2498 gc = self.__configure_gc__( fgcolor=color ) 2499 self._pixmap.draw_points( gc, points ) 2500 w, h = self._pixmap.get_size() 2501 self._area.queue_draw_area( 0, 0, w, h )
2502 # draw_poinst() 2503 2504
2505 - def draw_line( self, x0, y0, x1, y1, color=None, size=1 ):
2506 """Draw line.""" 2507 gc = self.__configure_gc__( fgcolor=color, line_width=size ) 2508 self._pixmap.draw_line( gc, x0, y0, x1, y1 ) 2509 2510 size2 = size * 2 2511 2512 w, h = abs( x1 - x0 ) + size2, abs( y1 - y0 ) + size2 2513 x, y = max( min( x0, x1 ) - size, 0 ), max( min( y0, y1 ) - size, 0 ) 2514 self._area.queue_draw_area( x, y, w, h )
2515 # draw_line() 2516 2517
2518 - def draw_segments( self, segments, color=None, size=1 ):
2519 """Draw line segments. 2520 2521 Efficient way to draw more than one line with the same 2522 characteristics. 2523 2524 Lines are not connected, use L{draw_lines} instead. 2525 """ 2526 gc = self.__configure_gc__( fgcolor=color, line_width=size ) 2527 self._pixmap.draw_segments( gc, segments ) 2528 w, h = self._pixmap.get_size() 2529 self._area.queue_draw_area( 0, 0, w, h )
2530 # draw_segments() 2531 2532
2533 - def draw_lines( self, points, color=None, size=1 ):
2534 """Draw lines connecting points. 2535 2536 Points are connected using lines, but first and last points 2537 are not connected, use L{draw_polygon} instead. 2538 """ 2539 gc = self.__configure_gc__( fgcolor=color, line_width=size ) 2540 self._pixmap.draw_lines( gc, points ) 2541 w, h = self._pixmap.get_size() 2542 self._area.queue_draw_area( 0, 0, w, h )
2543 # draw_lines() 2544 2545
2546 - def draw_rectangle( self, x, y, width, height, color=None, size=1, 2547 fillcolor=None, filled=False ):
2548 """Draw rectagle. 2549 2550 If L{filled} is C{True}, it will be filled with L{fillcolor}. 2551 2552 If L{color} is provided, it will draw the rectangle's frame, even 2553 if L{filled} is C{True}. 2554 """ 2555 if filled: 2556 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled ) 2557 self._pixmap.draw_rectangle( gc, True, x, y, width, height ) 2558 2559 if size > 0: 2560 gc = self.__configure_gc__( fgcolor=color, line_width=size ) 2561 self._pixmap.draw_rectangle( gc, False, x, y, width, height ) 2562 else: 2563 size = 0 2564 2565 half = size / 2 2566 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2567 # draw_rectangle() 2568 2569
2570 - def draw_arc( self, x, y, width, height, start_angle, end_angle, 2571 color=None, size=1, fillcolor=None, filled=False ):
2572 """Draw arc on canvas. 2573 2574 Arc will be the part of an ellipse that starts at ( L{x}, L{y} ) 2575 and have size of L{width}xL{height}. 2576 2577 L{start_angle} and L{end_angle} are in radians, starts from the 2578 positive x-axis and are counter-clockwise. 2579 2580 If L{filled} is C{True}, it will be filled with L{fillcolor}. 2581 2582 If L{color} is provided, it will draw the arc's frame, even 2583 if L{filled} is C{True}. Frame here is just the curve, not the 2584 straight lines that are limited by L{start_angle} and L{end_angle}. 2585 """ 2586 # GTK: angles are in 1/64 of degree and must be integers 2587 mult = 180.0 / 3.1415926535897931 * 64.0 2588 start_angle = int( mult * start_angle ) 2589 end_angle = int( mult * end_angle ) 2590 2591 if filled: 2592 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled ) 2593 self._pixmap.draw_arc( gc, True, x, y, width, height, 2594 start_angle, end_angle ) 2595 if size > 0: 2596 gc = self.__configure_gc__( fgcolor=color, line_width=size ) 2597 self._pixmap.draw_arc( gc, False, x, y, width, height, 2598 start_angle, end_angle ) 2599 else: 2600 size = 0 2601 2602 half = size / 2 2603 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2604 # draw_arc() 2605 2606
2607 - def draw_polygon( self, points, color=None, size=1, 2608 fillcolor=None, filled=False ):
2609 """Draw polygon on canvas. 2610 2611 If L{filled} is C{True}, it will be filled with L{fillcolor}. 2612 2613 If L{color} is provided, it will draw the polygon's frame, even 2614 if L{filled} is C{True}. 2615 """ 2616 if filled: 2617 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled ) 2618 self._pixmap.draw_polygon( gc, True, points ) 2619 2620 if size > 0: 2621 gc = self.__configure_gc__( fgcolor=color, line_width=size ) 2622 self._pixmap.draw_polygon( gc, False, points ) 2623 else: 2624 size = 0 2625 2626 w, h = self._pixmap.get_size() 2627 self._area.queue_draw_area( 0, 0, w, h )
2628 # draw_polygon() 2629 2630
2631 - def clear( self ):
2632 """Clear using bgcolor.""" 2633 self.fill( self.bgcolor )
2634 # clear() 2635 2636
2637 - def fill( self, color ):
2638 w, h = self.get_size() 2639 self.draw_rectangle( 0, 0, w, h, color, size=0, fillcolor=color, 2640 filled=True )
2641 # fill() 2642 2643
2644 - def get_size( self ):
2645 return self._pixmap.get_size()
2646 # get_size() 2647 2648
2649 - def get_image( self ):
2650 """Get the L{Image} that represents this drawing area.""" 2651 w, h = self._pixmap.get_size() 2652 img = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, True, 8, w, h ) 2653 img.get_from_drawable( self._pixmap, self._area.get_colormap(), 2654 0, 0, 0, 0, w, h ) 2655 return Image( __int_image__=img )
2656 # get_image() 2657 2658
2659 - def set_label( self, label ):
2660 if self.__label is None: 2661 raise ValueError( "You cannot change label of widget created " 2662 "without one. Create it with placeholder! " 2663 "(label='')" ) 2664 self.__label = label 2665 self._frame.set_label( self.__label )
2666 # set_label() 2667 2668
2669 - def get_label( self ):
2670 return self.__label
2671 # get_label() 2672 2673 label = property( get_label, set_label ) 2674 2675
2676 - def __str__( self ):
2677 return "%s( id=%r, width=%r, height=%r, label=%r )" % \ 2678 ( self.__class__.__name__, self.id, self.width, self.height, 2679 self.label )
2680 # __str__() 2681 __repr__ = __str__
2682 # Canvas 2683 2684
2685 -class _MultiLineEntry( gtk.ScrolledWindow ):
2686 - def __init__( self ):
2687 gtk.ScrolledWindow.__init__( self ) 2688 self.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC, 2689 vscrollbar_policy=gtk.POLICY_AUTOMATIC ) 2690 self.set_shadow_type( gtk.SHADOW_IN ) 2691 2692 self.textview = gtk.TextView() 2693 self.add( self.textview ) 2694 2695 self.textview.set_editable( True ) 2696 self.textview.set_cursor_visible( True ) 2697 self.textview.set_left_margin( 2 ) 2698 self.textview.set_right_margin( 2 ) 2699 self.textview.get_buffer().connect( "changed", self.__emit_changed__ )
2700 # __init__() 2701 2702
2703 - def __emit_changed__( self, *args ):
2704 self.emit( "changed" )
2705 # __emit_changed__ 2706 2707
2708 - def set_text( self, value ):
2709 self.textview.get_buffer().set_text( value )
2710 # set_text() 2711 2712
2713 - def get_text( self ):
2714 b = self.textview.get_buffer() 2715 return b.get_text( b.get_start_iter(), b.get_end_iter() )
2716 # get_text() 2717 # _MultiLineEntry 2718 gobject.signal_new( "changed", 2719 _MultiLineEntry, 2720 gobject.SIGNAL_RUN_LAST, 2721 gobject.TYPE_NONE, 2722 tuple() ) 2723 2724
2725 -class Entry( _EGWidLabelEntry ):
2726 """Text entry. 2727 2728 The simplest user input component. Its contents are free-form and not 2729 filtered/masked. 2730 """ 2731 value = _gen_ro_property( "value" ) 2732 callback = _gen_ro_property( "callback" ) 2733 multiline = _gen_ro_property( "multiline" ) 2734
2735 - def __init__( self, id, label="", value="", callback=None, 2736 persistent=False, multiline=False ):
2737 """Entry constructor. 2738 2739 @param id: unique identifier. 2740 @param label: what to show on a label on the left side of the widget. 2741 @param value: initial content. 2742 @param callback: function (or list of functions) that will 2743 be called when this widget have its data changed. 2744 Function will receive as parameters: 2745 - App reference 2746 - Widget reference 2747 - new value 2748 @param persistent: if this widget should save its data between 2749 sessions. 2750 @param multiline: if this entry can be multi-line. 2751 """ 2752 self.value = value 2753 self.callback = _callback_tuple( callback ) 2754 self.multiline = bool( multiline ) 2755 2756 _EGWidLabelEntry.__init__( self, id, persistent, label ) 2757 2758 self.__setup_gui__() 2759 self.__setup_connections__()
2760 # __init__() 2761 2762
2763 - def __setup_gui__( self ):
2764 if self.multiline: 2765 self._entry = _MultiLineEntry() 2766 else: 2767 self._entry = gtk.Entry() 2768 2769 self._entry.set_name( self.id ) 2770 self._entry.set_text( self.value ) 2771 2772 _EGWidLabelEntry.__setup_gui__( self )
2773 # __setup_gui__() 2774 2775
2776 - def __setup_connections__( self ):
2777 - def callback( obj ):
2778 v = self.get_value() 2779 self.app.data_changed( self, v ) 2780 for c in self.callback: 2781 c( self.app, self, v )
2782 # callback() 2783 self._entry.connect( "changed", callback )
2784 # __setup_connections__() 2785 2786
2787 - def get_value( self ):
2788 return self._entry.get_text()
2789 # get_value() 2790 2791
2792 - def set_value( self, value ):
2793 self._entry.set_text( str( value ) )
2794 # set_value() 2795 2796
2797 - def __get_resize_mode__( self ):
2798 """Resize mode. 2799 2800 First tuple of tuple is about horizontal mode for label and entry. 2801 Second tuple of tuple is about vertical mode for label and entry. 2802 """ 2803 if self.multiline: 2804 return ( ( gtk.FILL, gtk.FILL | gtk.EXPAND ), 2805 ( gtk.FILL, gtk.FILL | gtk.EXPAND ) ) 2806 else: 2807 return ( ( gtk.FILL, gtk.FILL | gtk.EXPAND ), 2808 ( gtk.FILL, gtk.FILL ) )
2809 # __get_resize_mode__() 2810 # Entry 2811 2812
2813 -class Password( Entry ):
2814 """Password entry. 2815 2816 Like L{Entry}, but will show '*' instead of typed chars. 2817 """
2818 - def __init__( self, id, label="", value="", callback=None, 2819 persistent=False ):
2820 """Password constructor. 2821 2822 @param id: unique identifier. 2823 @param label: what to show on a label on the left side of the widget. 2824 @param value: initial content. 2825 @param callback: function (or list of functions) that will 2826 be called when this widget have its data changed. 2827 Function will receive as parameters: 2828 - App reference 2829 - Widget reference 2830 - new value 2831 @param persistent: if this widget should save its data between 2832 sessions. 2833 """ 2834 Entry.__init__( self, id, label, value, callback, persistent ) 2835 self._entry.set_visibility( False )
2836 # __init__() 2837 # Password 2838 2839
2840 -class Spin( _EGWidLabelEntry ):
2841 """Spin button entry. 2842 2843 Spin buttons are numeric user input that checks if value is inside 2844 a specified range. It also provides small buttons to help incrementing/ 2845 decrementing value. 2846 """ 2847 default_min = -1e60 2848 default_max = 1e60 2849 2850 value = _gen_ro_property( "value" ) 2851 min = _gen_ro_property( "min" ) 2852 max = _gen_ro_property( "max" ) 2853 step = _gen_ro_property( "step" ) 2854 digits = _gen_ro_property( "digits" ) 2855 2856 callback = _gen_ro_property( "callback" ) 2857
2858 - def __init__( self, id, label="", 2859 value=None, min=None, max=None, step=None, digits=3, 2860 callback=None, persistent=False ):
2861 """Spin constructor. 2862 2863 @param id: unique identifier. 2864 @param label: what to show on a label on the left side of the widget. 2865 @param value: initial content. 2866 @param min: minimum value. If None, L{default_min} will be used. 2867 @param max: maximum value. If None, L{default_max} will be used. 2868 @param step: step to use to decrement/increment using buttons. 2869 @param digits: how many digits to show. 2870 @param callback: function (or list of functions) that will 2871 be called when this widget have its data changed. 2872 Function will receive as parameters: 2873 - App reference 2874 - Widget reference 2875 - new value 2876 @param persistent: if this widget should save its data between 2877 sessions. 2878 """ 2879 self.value = value 2880 self.min = min 2881 self.max = max 2882 self.step = step 2883 self.digits = digits 2884 self.callback = _callback_tuple( callback ) 2885 2886 _EGWidLabelEntry.__init__( self, id, persistent, label ) 2887 2888 self.__setup_connections__()
2889 # __init__() 2890 2891
2892 - def __setup_gui__( self ):
2893 k = {} 2894 2895 if self.value is not None: 2896 k[ "value" ] = self.value 2897 2898 if self.min is not None: 2899 k[ "lower" ] = self.min 2900 else: 2901 k[ "lower" ] = self.default_min 2902 2903 if self.max is not None: 2904 k[ "upper" ] = self.max 2905 else: 2906 k[ "upper" ] = self.default_max 2907 2908 if self.step is not None: 2909 k[ "step_incr" ] = self.step 2910 k[ "page_incr" ] = self.step * 2 2911 else: 2912 k[ "step_incr" ] = 1 2913 k[ "page_incr" ] = 2 2914 2915 adj = gtk.Adjustment( **k ) 2916 self._entry = gtk.SpinButton( adjustment=adj, digits=self.digits ) 2917 self._entry.set_name( self.id ) 2918 self._entry.set_numeric( True ) 2919 self._entry.set_snap_to_ticks( False ) 2920 2921 _EGWidLabelEntry.__setup_gui__( self )
2922 # __setup_gui__() 2923 2924
2925 - def __setup_connections__( self ):
2926 - def callback( obj, *args ):
2927 v = self.get_value() 2928 self.app.data_changed( self, v ) 2929 for c in self.callback: 2930 c( self.app, self, v )
2931 # callback() 2932 self._entry.connect( "notify::value", callback )
2933 # __setup_connections__() 2934 2935
2936 - def set_value( self, value ):
2937 self._entry.set_value( float( value ) )
2938 # set_value() 2939 # Spin 2940 2941
2942 -class IntSpin( Spin ):
2943 """Integer-only Spin button. 2944 2945 Convenience widget that behaves like L{Spin} with digits set to 2946 zero and also ensure L{set_value} and L{get_value} operates on 2947 integers. 2948 """ 2949 default_min = -sys.maxint 2950 default_max = sys.maxint 2951
2952 - def __init__( self, id, label="", 2953 value=None, min=None, max=None, step=None, 2954 callback=None, persistent=False ):
2955 """Integer Spin constructor. 2956 2957 @param id: unique identifier. 2958 @param label: what to show on a label on the left side of the widget. 2959 @param value: initial content. 2960 @param min: minimum value. If None, L{default_min} will be used. 2961 @param max: maximum value. If None, L{default_max} will be used. 2962 @param step: step to use to decrement/increment using buttons. 2963 @param callback: function (or list of functions) that will 2964 be called when this widget have its data changed. 2965 Function will receive as parameters: 2966 - App reference 2967 - Widget reference 2968 - new value 2969 @param persistent: if this widget should save its data between 2970 sessions. 2971 """ 2972 if value is not None: 2973 value = int( value ) 2974 if min is not None: 2975 min = int( min ) 2976 if max is not None: 2977 max = int( max ) 2978 if step is not None: 2979 step = int( step ) 2980 Spin.__init__( self, id, label, value, min, max, step, 0, callback, 2981 persistent )
2982 # __init__() 2983 2984
2985 - def __setup_gui__( self ):
2986 if self.min is not None: 2987 vmin = self.min 2988 else: 2989 vmin = self.default_min 2990 2991 if self.max is not None: 2992 vmax = self.max 2993 else: 2994 vmax = self.default_max 2995 2996 if self.step is not None: 2997 sys.stderr.write( "Maemo HildonNumberEditor doesn't support step\n" ) 2998 2999 self._entry = hildon.NumberEditor( vmin, vmax ) 3000 self._entry.set_name( self.id ) 3001 if self.value: 3002 self._entry.set_value( self.value ) 3003 3004 _EGWidLabelEntry.__setup_gui__( self )
3005 # __setup_gui__() 3006 3007
3008 - def __setup_connections__( self ):
3009 - def callback( obj, *args ):
3010 v = self.get_value() 3011 self.app.data_changed( self, v ) 3012 for c in self.callback: 3013 c( self.app, self, v )
3014 # callback() 3015 self._entry.connect( "notify::value", callback )
3016 # __setup_connections__() 3017 3018
3019 - def get_value( self ):
3020 return int( Spin.get_value( self ) )
3021 # get_value() 3022 3023
3024 - def set_value( self, value ):
3025 self._entry.set_value( int( value ) )
3026 # set_value() 3027 # IntSpin 3028 3029
3030 -class UIntSpin( IntSpin ):
3031 """Unsigned integer-only Spin button. 3032 3033 Convenience widget that behaves like L{IntSpin} with minimum value 3034 always greater or equal zero. 3035 """ 3036 default_min = 0 3037
3038 - def __init__( self, id, label="", 3039 value=None, min=0, max=None, step=None, 3040 callback=None, persistent=False ):
3041 """Unsigned Integer Spin constructor. 3042 3043 @param id: unique identifier. 3044 @param label: what to show on a label on the left side of the widget. 3045 @param value: initial content. 3046 @param min: minimum value, must be greater or equal zero. 3047 @param max: maximum value. If None, L{default_max} will be used. 3048 @param step: step to use to decrement/increment using buttons. 3049 @param callback: function (or list of functions) that will 3050 be called when this widget have its data changed. 3051 Function will receive as parameters: 3052 - App reference 3053 - Widget reference 3054 - new value 3055 @param persistent: if this widget should save its data between 3056 sessions. 3057 """ 3058 if min < 0: 3059 raise ValueError( "UIntSpin cannot have min < 0!" ) 3060 Spin.__init__( self, id, label, value, min, max, step, 0, callback, 3061 persistent )
3062 # __init__() 3063 # UIntSpin 3064 3065
3066 -class Color( _EGWidLabelEntry ):
3067 """Button to select colors. 3068 3069 It show current/last selected color and may pop-up a new dialog to 3070 select a new one. 3071 """ 3072 color = _gen_ro_property( "color" ) 3073 callback = _gen_ro_property( "callback" ) 3074
3075 - def __init__( self, id, label="", color=0, 3076 callback=None, 3077 persistent=False ):
3078 """Color selector constructor. 3079 3080 @param id: unique identifier. 3081 @param label: what to show on a label on the left side of the widget. 3082 @param color: initial content. May be a triple with elements within 3083 the range 0-255, an string with color in HTML format or even 3084 a color present in X11's rgb.txt. 3085 @param callback: function (or list of functions) that will 3086 be called when this widget have its data changed. 3087 Function will receive as parameters: 3088 - App reference 3089 - Widget reference 3090 - new value 3091 @param persistent: if this widget should save its data between 3092 sessions. 3093 """ 3094 self.color = self.color_from( color ) 3095 self.callback = _callback_tuple( callback ) 3096 _EGWidLabelEntry.__init__( self, id, persistent, label ) 3097 3098 self.__setup_connections__()
3099 # __init__() 3100 3101
3102 - def color_from( color ):
3103 a = 255 3104 if isinstance( color, str ): 3105 try: 3106 c = gtk.gdk.color_parse( color ) 3107 r = int( c.red / 65535.0 * 255 ) 3108 g = int( c.green / 65535.0 * 255 ) 3109 b = int( c.blue / 65535.0 * 255 ) 3110 except ValueError, e: 3111 raise ValueError( "%s. color=%r" % ( e, color ) ) 3112 3113 if isinstance( color, int ): 3114 r = ( color >> 16 ) & 0xff 3115 g = ( color >> 8 ) & 0xff 3116 b = ( color & 0xff ) 3117 elif isinstance( color, ( tuple, list ) ): 3118 if len( color ) == 3: 3119 r, g, b = color 3120 else: 3121 a, r, g, b = color 3122 3123 return a, r, g, b
3124 # color_from() 3125 color_from = staticmethod( color_from ) 3126 3127
3128 - def __setup_gui__( self ):
3129 r = int( self.color[ 1 ] / 255.0 * 65535 ) 3130 g = int( self.color[ 2 ] / 255.0 * 65535 ) 3131 b = int( self.color[ 3 ] / 255.0 * 65535 ) 3132 3133 c = gtk.gdk.Color( r, g, b ) 3134 3135 self._entry = hildon.ColorButton() 3136 self._entry.set_color( c ) 3137 self._entry.set_name( self.id ) 3138 _EGWidLabelEntry.__setup_gui__( self )
3139 # __setup_gui__() 3140 3141
3142 - def __setup_connections__( self ):
3143 - def callback( obj, *args ):
3144 v = self.get_value() 3145 self.app.data_changed( self, v ) 3146 for c in self.callback: 3147 c( self.app, self, v )
3148 # callback() 3149 self._entry.connect( "notify::color", callback )
3150 # __setup_connections__() 3151 3152
3153 - def get_value( self ):
3154 """Return a tuple with ( alpha, red, green, blue ) 3155 each in 0-255 range. 3156 """ 3157 c = self._entry.get_color() 3158 r = int( c.red / 65535.0 * 255 ) 3159 g = int( c.green / 65535.0 * 255 ) 3160 b = int( c.blue / 65535.0 * 255 ) 3161 3162 return ( r, g, b )
3163 # get_value() 3164 3165
3166 - def set_value( self, value ):
3167 """ 3168 @param value: May be a triple with elements within 3169 the range 0-255, an string with color in HTML format or even 3170 a color present in X11's rgb.txt. 3171 """ 3172 a, r, g, b = self.color_from( value ) 3173 3174 r = int( r / 255.0 * 65535 ) 3175 g = int( g / 255.0 * 65535 ) 3176 b = int( b / 255.0 * 65535 ) 3177 3178 c = gtk.gdk.Color( r, g, b ) 3179 self._entry.set_color( c )
3180 # set_value() 3181 # Color 3182 3183
3184 -class Font( _EGWidLabelEntry ):
3185 """Button to select fonts. 3186 3187 It show current/last selected font and may pop-up a new dialog to 3188 select a new one. 3189 """ 3190 font = _gen_ro_property( "font" ) 3191 callback = _gen_ro_property( "callback" ) 3192
3193 - def __init__( self, id, label="", font="sans 12", callback=None, 3194 persistent=False ):
3195 """Font selector constructor. 3196 3197 @param id: unique identifier. 3198 @param label: what to show on a label on the left side of the widget. 3199 @param font: initial content. 3200 @param callback: function (or list of functions) that will 3201 be called when this widget have its data changed. 3202 Function will receive as parameters: 3203 - App reference 3204 - Widget reference 3205 - new value 3206 @param persistent: if this widget should save its data between 3207 sessions. 3208 """ 3209 self.font = font 3210 self.callback = _callback_tuple( callback ) 3211 _EGWidLabelEntry.__init__( self, id, persistent, label ) 3212 3213 self.__setup_connections__()
3214 # __init__() 3215 3216
3217 - def __setup_gui__( self ):
3218 self._entry = gtk.FontButton( self.font ) 3219 self._entry.set_name( self.id ) 3220 self._entry.set_show_style( True ) 3221 _EGWidLabelEntry.__setup_gui__( self )
3222 # __setup_gui__() 3223 3224
3225 - def __setup_connections__( self ):
3226 - def callback( obj ):
3227 v = self.get_value() 3228 self.app.data_changed( self, v ) 3229 for c in self.callback: 3230 c( self.app, self, v )
3231 # callback() 3232 self._entry.connect( "font-set", callback )
3233 # __setup_connections__() 3234 3235
3236 - def get_value( self ):
3237 return self._entry.get_font_name()
3238 # get_value() 3239 3240
3241 - def set_value( self, value ):
3242 self._entry.set_font_name( value )
3243 # set_value() 3244 # Font 3245 3246
3247 -class Selection( _EGWidLabelEntry ):
3248 """Selection box (aka Combo box). 3249 3250 Selection or combo box is an element that allow you to select one of 3251 various pre-defined values. 3252 """ 3253 options = _gen_ro_property( "options" ) 3254 active = _gen_ro_property( "active" ) 3255
3256 - def __init__( self, id, label="", options=None, active=None, 3257 callback=None, persistent=False ):
3258 """Selection constructor. 3259 3260 @param id: unique identifier. 3261 @param label: what to show on a label on the left side of the widget. 3262 @param options: list of possible values. 3263 @param active: selected element. 3264 @param callback: function (or list of functions) that will 3265 be called when this widget have its data changed. 3266 Function will receive as parameters: 3267 - App reference 3268 - Widget reference 3269 - new value 3270 @param persistent: if this widget should save its data between 3271 sessions. 3272 """ 3273 self.options = options or [] 3274 self.active = active 3275 self.callback = _callback_tuple( callback ) 3276 _EGWidLabelEntry.__init__( self, id, persistent, label ) 3277 3278 self.__setup_connections__()
3279 # __init__() 3280 3281
3282 - def __setup_gui__( self ):
3283 self._entry = gtk.combo_box_new_text() 3284 self._entry.set_name( self.id ) 3285 for i, o in enumerate( self.options ): 3286 self._entry.append_text( str( o ) ) 3287 if self.active == o: 3288 self._entry.set_active( i ) 3289 3290 _EGWidLabelEntry.__setup_gui__( self )
3291 # __setup_gui__() 3292 3293
3294 - def __setup_connections__( self ):
3295 - def callback( obj ):
3296 v = self.get_value() 3297 self.app.data_changed( self, v ) 3298 for c in self.callback: 3299 c( self.app, self, v )
3300 # callback() 3301 self._entry.connect( "changed", callback )
3302 # __setup_connections__() 3303 3304
3305 - def get_value( self ):
3306 return self._entry.get_active_text()
3307 # get_value() 3308 3309
3310 - def set_value( self, value ):
3311 for i, o in enumerate( self._entry.get_model() ): 3312 if o[ 0 ] == value: 3313 self._entry.set_active( i )
3314 # set_value() 3315 3316
3317 - def append( self, value, set_active=False ):
3318 """Append new value to available options. 3319 3320 @param value: string that is not already an option. 3321 3322 @raise: ValueError: if value is already an option. 3323 """ 3324 if value not in self.items(): 3325 self._entry.append_text( value ) 3326 if set_active: 3327 self.set_value( value ) 3328 else: 3329 raise ValueError( "value already in selection" )
3330 # append() 3331 3332
3333 - def prepend( self, value ):
3334 """Prepend new value to available options. 3335 3336 @param value: string that is not already an option. 3337 3338 @raise: ValueError: if value is already an option. 3339 """ 3340 if value not in self.items(): 3341 self._entry.prepend_text( value ) 3342 if set_active: 3343 self.set_value( value ) 3344 else: 3345 raise ValueError( "value already in selection" )
3346 # prepend() 3347 3348
3349 - def insert( self, position, value ):
3350 """Insert new option at position. 3351 3352 @param value: string that is not already an option. 3353 3354 @raise: ValueError: if value is already an option. 3355 """ 3356 if value not in self.items(): 3357 self._entry.insert_text( position, value ) 3358 if set_active: 3359 self.set_value( value ) 3360 else: 3361 raise ValueError( "value already in selection" )
3362 # insert() 3363 3364
3365 - def remove( self, value ):
3366 """Remove given value from available options. 3367 3368 @param value: string that is an option. 3369 3370 @raise ValueError: if value is not already an option. 3371 """ 3372 for i, o in enumerate( self._entry.get_model() ): 3373 if o[ 0 ] == value: 3374 self._entry.remove_text( i ) 3375 return 3376 3377 raise ValueError( "value not in selection" )
3378 # remove() 3379 3380
3381 - def items( self ):
3382 """Returns every item/option in this selection.""" 3383 return [ str( x[ 0 ] ) for x in self._entry.get_model() ]
3384 # items() 3385 options = items 3386 3387
3388 - def __len__( self ):
3389 return len( self._entry.get_model() )
3390 # __len__() 3391 3392
3393 - def __contains__( self, value ):
3394 return value in self.items()
3395 # __contains__() 3396 3397
3398 - def __iadd__( self, value ):
3399 """Same as L{append}""" 3400 self.append( value )
3401 # __iadd__() 3402 3403
3404 - def __isub__( self, value ):
3405 """Same as L{remove}""" 3406 self.remove( value )
3407 # __isub__() 3408 # Selection 3409 3410
3411 -class Progress( _EGWidLabelEntry ):
3412 """Progress bar.""" 3413 value = _gen_ro_property( "value" ) 3414
3415 - def __init__( self, id, label="", value=0.0 ):
3416 """Progress bar constructor. 3417 3418 0 <= value <= 1.0 3419 3420 @param id: unique identifier. 3421 @param label: what to show on a label on the left side of the widget. 3422 @param value: initial content ( 0.0 <= value <= 1.0 ) 3423 """ 3424 self.value = value 3425 _EGWidLabelEntry.__init__( self, id, False, label )
3426 # __init__() 3427
3428 - def __setup_gui__( self ):
3429 self._entry = gtk.ProgressBar() 3430 self._entry.set_name( self.id ) 3431 self.set_value( self.value ) 3432 _EGWidLabelEntry.__setup_gui__( self )
3433 # __setup_gui__() 3434 3435
3436 - def get_value( self ):
3437 return self._entry.get_fraction()
3438 # get_value() 3439 3440
3441 - def set_value( self, value ):
3442 if 1.0 < value <= 100.0: 3443 value /= 100.0 3444 elif not ( 0.0 <= value <= 1.0 ): 3445 raise ValueError( ( "Progress value of \"%s\" must be " 3446 "between 0.0 and 1.0!" ) % self.id ) 3447 self._entry.set_fraction( value ) 3448 self._entry.set_text( "%d%%" % ( int( value * 100 ), ) )
3449 # set_value() 3450 3451
3452 - def pulse( self ):
3453 """Animate progress bar.""" 3454 self._entry.pulse()
3455 # pulse() 3456 # Progress 3457 3458
3459 -class CheckBox( _EGDataWidget ):
3460 """Check box. 3461 3462 Check box is an component that have only two values: True (checked) or 3463 False (unchecked). 3464 """ 3465 state = _gen_ro_property( "state" ) 3466
3467 - def __init__( self, id, label="", state=False, callback=None, 3468 persistent=False ):
3469 """Check box constructor. 3470 3471 @param id: unique identifier. 3472 @param label: what to show on a label on the left side of the widget. 3473 @param state: initial state. 3474 @param callback: function (or list of functions) that will 3475 be called when this widget have its data changed. 3476 Function will receive as parameters: 3477 - App reference 3478 - Widget reference 3479 - new value 3480 @param persistent: if this widget should save its data between 3481 sessions. 3482 """ 3483 self.__label = label 3484 self.state = state 3485 self.callback = _callback_tuple( callback ) 3486 3487 _EGDataWidget.__init__( self, id, persistent ) 3488 3489 self.__setup_gui__() 3490 self.__setup_connections__()
3491 # __init__() 3492 3493
3494 - def __setup_gui__( self ):
3495 self._wid = gtk.CheckButton( self.__label ) 3496 self._wid.set_name( self.id ) 3497 self._wid.set_active( self.state ) 3498 self._widgets = ( self._wid, )
3499 # __setup_gui__() 3500 3501
3502 - def __setup_connections__( self ):
3503 - def callback( obj ):
3504 v = self.get_value() 3505 self.app.data_changed( self, v ) 3506 for c in self.callback: 3507 c( self.app, self, v )
3508 # callback() 3509 self._wid.connect( "toggled", callback )
3510 # __setup_connections__() 3511 3512
3513 - def __get_resize_mode__( self ):
3514 "Return a tuple with ( horizontal, vertical ) resize mode" 3515 return ( gtk.FILL, 0 )
3516 # __get_resize_mode__() 3517 3518
3519 - def get_value( self ):
3520 return self._wid.get_active()
3521 # get_value() 3522 3523
3524 - def set_value( self, value ):
3525 return self._wid.set_active( bool( value ) )
3526 # set_value() 3527 3528
3529 - def set_label( self, label ):
3530 if self.__label is None: 3531 raise ValueError( "You cannot change label of widget created " 3532 "without one. Create it with placeholder! " 3533 "(label='')" ) 3534 self.__label = label 3535 self._wid.set_label( self.__label )
3536 # set_label() 3537 3538
3539 - def get_label( self ):
3540 return self.__label
3541 # get_label() 3542 3543 label = property( get_label, set_label )
3544 # CheckBox 3545 3546
3547 -class Group( _EGWidget ):
3548 """Group of various components. 3549 3550 Group is a component that holds other components, always in a vertical 3551 layout. 3552 3553 Group has a frame and may show a label. 3554 """ 3555 children = _gen_ro_property( "children" ) 3556
3557 - def _get_app( self ):
3558 try: 3559 return self.__ro_app 3560 except AttributeError: 3561 return None
3562 # _get_app() 3563
3564 - def _set_app( self, value ):
3565 # We need to overload app setter in order to add 3566 # children widgets to application as soon as we know the app 3567 try: 3568 v = self.__ro_app 3569 except AttributeError: 3570 v = None 3571 if v is None: 3572 self.__ro_app = value 3573 self.__add_widgets_to_app__() 3574 else: 3575 raise Exception( "Read Only property 'app'." )
3576 # _set_app() 3577 app = property( _get_app, _set_app ) 3578 3579
3580 - def __init__( self, id, label="", children=None ):
3581 """Group constructor. 3582 3583 @param id: unique identified. 3584 @param label: displayed at top-left. 3585 @param children: a list of eagle widgets that this group contains. 3586 They're presented in vertical layout. 3587 """ 3588 _EGWidget.__init__( self, id ) 3589 self.__label = label 3590 self.children = children or tuple() 3591 3592 self.__setup_gui__()
3593 # __init__() 3594 3595
3596 - def __setup_gui__( self ):
3597 self._frame = gtk.Frame( self.__label ) 3598 self._frame.set_name( self.id ) 3599 self._contents = _Table( id=( "%s-contents" % self.id ), 3600 children=self.children ) 3601 self._frame.add( self._contents ) 3602 self._widgets = ( self._frame, )
3603 # __setup_gui__() 3604 3605
3606 - def __add_widgets_to_app__( self ):
3607 for w in self.children: 3608 self.app.__add_widget__( w )
3609 # __add_widgets_to_app__() 3610 3611
3612 - def __get_resize_mode__( self ):
3613 "Return a tuple with ( horizontal, vertical ) resize mode" 3614 return ( gtk.FILL | gtk.EXPAND, 0 )
3615 # __get_resize_mode__() 3616 3617
3618 - def set_label( self, label ):
3619 if self.__label is None: 3620 raise ValueError( "You cannot change label of widget created " 3621 "without one. Create it with placeholder! " 3622 "(label='')" ) 3623 self.__label = label 3624 self._frame.set_label( self.__label )
3625 # set_label() 3626 3627
3628 - def get_label( self ):
3629 return self.__label
3630 # get_label() 3631 3632 label = property( get_label, set_label )
3633 # Group 3634 3635
3636 -class Table( _EGWidget ):
3637 """Data table. 3638 3639 Each column should have only one type, it will be checked. 3640 Can be accessed as a python list: 3641 3642 >>> t = Table( 't', 'table', [ 1, 2, 3 ] ) 3643 >>> t[ 0 ] 3644 [ 1 ] 3645 >>> del t[ 1 ] 3646 >>> t[ : ] 3647 [ 1, 3 ] 3648 """ 3649 spacing = 3 3650 3651
3652 - class Row( object ):
3653 # Used to hide gtk.ListStore
3654 - def __init__( self, items ):
3655 self.__items = items
3656 # __init__() 3657 3658
3659 - def __str__( self ):
3660 return "[" + ", ".join( [ str( x ) for x in self.__items ] ) + "]"
3661 # __str__() 3662 __repr__ = __str__ 3663 3664
3665 - def __len__( self ):
3666 return len( self.__items )
3667 # __len__() 3668 3669
3670 - def __nonzero__( self ):
3671 return self.__items.__nonzero__()
3672 # __nonzero__() 3673 3674
3675 - def __getitem__( self, index ):
3676 return self.__items[ index ]
3677 # __getitem__() 3678 3679
3680 - def __setitem__( self, index, value ):
3681 self.__items[ index ] = value
3682 # __setitem__() 3683 3684
3685 - def __delitem__( self, index ):
3686 del self.__items[ index ]
3687 # __delitem__() 3688 3689
3690 - def __contains__( self, element ):
3691 return element in self.__items
3692 # __contains__() 3693 3694
3695 - def __getslice__( self, start, end ):
3696 slice = [] 3697 3698 l = len( self.__items ) 3699 while start < 0: 3700 start += l 3701 while end < 0: 3702 end += l 3703 3704 start = min( start, l ) 3705 end = min( end, l ) # l[ : ] -> l[ 0 : maxlistindex ] 3706 3707 for i in xrange( start, end ): 3708 slice.append( self.__items[ i ] ) 3709 return slice
3710 # __getslice__() 3711 3712
3713 - def __setslice__( self, start, end, items ):
3714 l = len( self.__items ) 3715 while start < 0: 3716 start += l 3717 while end < 0: 3718 end += l 3719 3720 start = min( start, l ) 3721 end = min( end, l ) # l[ : ] -> l[ 0 : maxlistindex ] 3722 3723 l2 = len( items ) 3724 if end - start > l2: 3725 end = start + l2 3726 3727 if self.__items.model._hid_row_changed is not None: 3728 hid_changed = self.__items.model._hid_row_changed 3729 self.__items.model.handler_block( hid_changed ) 3730 3731 j = 0 3732 for i in xrange( start, end ): 3733 self.__items[ i ] = items[ j ] 3734 j += 1 3735 3736 if self.__items.model._hid_row_changed is not None: 3737 hid_changed = self.__items.model._hid_row_changed 3738 self.__items.model.handler_unblock( hid_changed ) 3739 i = self.__items.iter 3740 p = self.__items.path 3741 self.__items.model.row_changed( p, i )
3742 3743 # __setslice__() 3744 # Row 3745 3746
3747 - class CellFormat( object ):
3748 __slots__ = ( "fgcolor", "bgcolor", "font", "bold", 3749 "italic", "underline", "strike" )
3750 - def __init__( self, **kargs ):
3751 for a in self.__slots__: 3752 v = kargs.get( a, None ) 3753 setattr( self, a, v )
3754 # __init__() 3755 # CellFormat() 3756 3757 3758
3759 - def __init__( self, id, label, items=None, types=None, 3760 headers=None, show_headers=True, editable=False, 3761 repositioning=False, expand_columns_indexes=None, 3762 cell_format_func=None, 3763 selection_callback=None, data_changed_callback=None ):
3764 """Table constructor. 3765 3766 @param id: unique identifier. 3767 @param label: what to show on table frame 3768 @param items: a list (single column) or list of lists (multiple 3769 columns) 3770 @param types: a list of types (str, int, long, float, unicode, bool) 3771 for columns, if omitted, will be guessed from items. 3772 @param headers: what to use as table header. 3773 @param show_headers: whenever to show table headers 3774 @param editable: if table is editable. If editable, user can change 3775 values inline or double-clicking, also edit buttons will 3776 show after the table. 3777 @param repositioning: allow items to be moved up and down. 3778 @param expand_columns_indexes: list of indexes that can expand size 3779 @param cell_format_func: if define, should return a CellFormat with 3780 properties to be applied to cell. Only non-None properties will 3781 be used. Function should have the following signature: 3782 def func( app, table, row, col, value ): 3783 return Table.CellFormat( ... ) 3784 where row and col are indexes in table. 3785 @param selection_callback: the function (or list of functions) to 3786 call when selection changes. Function will get as parameters: 3787 - App reference 3788 - Table reference 3789 - List of pairs ( index, row_contents ) 3790 @param data_changed_callback: the function (or list of functions) to 3791 call when data changes. Function will get as parameters: 3792 - App reference 3793 - Table reference 3794 - Pair ( index, row_contents ) 3795 3796 @warning: although this widget contains data, it's not a 3797 _EGDataWidget and thus will not notify application that 3798 data changed, also it cannot persist it's data 3799 automatically, if you wish, do it manually. This behavior 3800 may change in future if Table show to be useful as 3801 _EGDataWidget. 3802 """ 3803 _EGWidget.__init__( self, id ) 3804 self.editable = editable or False 3805 self.repositioning = repositioning or False 3806 self.__label = str( label or "" ) 3807 self.headers = headers or tuple() 3808 self.show_headers = bool( show_headers ) 3809 self.cell_format_func = cell_format_func 3810 3811 if isinstance( expand_columns_indexes, ( int, long ) ): 3812 expand_columns_indexes = ( expand_columns_indexes, ) 3813 elif isinstance( expand_columns_indexes, ( tuple, list ) ): 3814 expand_columns_indexes = tuple( expand_columns_indexes ) 3815 elif expand_columns_indexes is None: 3816 expand_columns_indexes = tuple() 3817 else: 3818 raise ValueError( \ 3819 "expand_columns_indexes must be a sequence of integers" ) 3820 self.expand_columns_indexes = expand_columns_indexes 3821 3822 if not ( types or items ): 3823 raise ValueError( "Must provide items or types!" ) 3824 elif not types: 3825 items = items or [] 3826 if not isinstance( items[ 0 ], ( list, tuple ) ): 3827 # just one column, convert to generic representation 3828 items = [ [ i ] for i in items ] 3829 3830 types = [ type( i ) for i in items[ 0 ] ] 3831 self.types = types 3832 self.items = items 3833 3834 self.selection_callback = _callback_tuple( selection_callback ) 3835 self.data_changed_callback = _callback_tuple( data_changed_callback ) 3836 3837 self.__setup_gui__() 3838 3839 self._model._hid_row_changed = None 3840 self._model._hid_row_deleted = None 3841 self._model._hid_row_inserted = None 3842 self.__setup_connections__() 3843 self.__setup_items__()
3844 # __init__() 3845 3846
3847 - def __setup_gui__( self ):
3848 self._frame = gtk.Frame( self.label ) 3849 self._frame.set_name( self.id ) 3850 3851 self._vbox = gtk.VBox( False, self.spacing ) 3852 self._vbox.set_border_width( self.spacing ) 3853 self._vbox.set_name( "vbox-%s" % self.id ) 3854 3855 self._frame.add( self._vbox ) 3856 self._widgets = ( self._frame, ) 3857 3858 self.__setup_table__() 3859 3860 if self.editable or self.repositioning: 3861 self._hbox = gtk.HBox( False, self.spacing ) 3862 self._vbox.pack_start( self._hbox, expand=False, fill=True ) 3863 3864 if self.editable: 3865 self._btn_add = gtk.Button( stock=gtk.STOCK_ADD ) 3866 self._btn_del = gtk.Button( stock=gtk.STOCK_REMOVE ) 3867 self._btn_edit = gtk.Button( stock=gtk.STOCK_EDIT ) 3868 3869 self._hbox.pack_start( self._btn_add ) 3870 self._hbox.pack_start( self._btn_del ) 3871 self._hbox.pack_start( self._btn_edit ) 3872 3873 if self.repositioning: 3874 if self.editable: 3875 self._hbox.pack_start( gtk.VSeparator() ) 3876 3877 self._btn_up = gtk.Button( stock=gtk.STOCK_GO_UP ) 3878 self._btn_down = gtk.Button( stock=gtk.STOCK_GO_DOWN ) 3879 3880 self._btn_up.set_sensitive( False ) 3881 self._btn_down.set_sensitive( False ) 3882 3883 self._hbox.pack_start( self._btn_up ) 3884 self._hbox.pack_start( self._btn_down )
3885 # __setup_gui__() 3886 3887
3888 - def __setup_connections__( self ):
3889 if self.data_changed_callback: 3890 self.__setup_connections_changed__() 3891 3892 if self.editable: 3893 self.__setup_connections_editable__() 3894 3895 if self.repositioning: 3896 self.__setup_connections_repositioning__() 3897 3898 if self.selection_callback: 3899 self.__setup_connections_selection__()
3900 # __setup_connections__() 3901 3902
3903 - def __setup_connections_changed__( self ):
3904 - def row_changed( model, path, itr ):
3905 index = path[ 0 ] 3906 v = ( index, Table.Row( model[ path ] ) ) 3907 for c in self.data_changed_callback: 3908 c( self.app, self, v )
3909 # row_changed() 3910 3911
3912 - def row_deleted( model, path ):
3913 index = path[ 0 ] 3914 v = ( index, None ) 3915 for c in self.data_changed_callback: 3916 c( self.app, self, v )
3917 # row_deleted() 3918 3919 c = self._model.connect 3920 self._model._hid_row_changed = c( "row-changed", row_changed ) 3921 self._model._hid_row_deleted = c( "row-deleted", row_deleted ) 3922 self._model._hid_row_inserted = c( "row-inserted", row_changed)
3923 # __setup_connections_changed__() 3924 3925
3927 - def edit_dialog( data ):
3928 title = "Edit data from table %s" % self.label 3929 buttons = ( gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, 3930 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT ) 3931 d = gtk.Dialog( title=title, 3932 flags=gtk.DIALOG_MODAL, 3933 buttons=buttons ) 3934 3935 settings = d.get_settings() 3936 try: 3937 settings.set_property( "gtk-button-images", False ) 3938 except: 3939 pass 3940 3941 d.set_default_response( gtk.RESPONSE_ACCEPT ) 3942 3943 l = len( data ) 3944 t = gtk.Table( l, 2 ) 3945 t.set_border_width( 0 ) 3946 w = [] 3947 for i, v in enumerate( data ): 3948 title = self._table.get_column( i ).get_title() 3949 label = gtk.Label( title ) 3950 label.set_justify( gtk.JUSTIFY_RIGHT ) 3951 label.set_alignment( xalign=1.0, yalign=0.5 ) 3952 3953 tp = self.types[ i ] 3954 if tp == bool: 3955 entry = gtk.CheckButton() 3956 entry.set_active( data[ i ] ) 3957 elif tp in ( int, long ): 3958 entry = gtk.SpinButton( digits=0 ) 3959 adj = entry.get_adjustment() 3960 adj.lower = Spin.default_min 3961 adj.upper = Spin.default_max 3962 adj.step_increment = 1 3963 adj.page_increment = 5 3964 entry.set_value( data[ i ] ) 3965 elif tp == float: 3966 entry = gtk.SpinButton( digits=6 ) 3967 adj = entry.get_adjustment() 3968 adj.lower = Spin.default_min 3969 adj.upper = Spin.default_max 3970 adj.step_increment = 1 3971 adj.page_increment = 5 3972 entry.set_value( data[ i ] ) 3973 elif tp in ( str, unicode ): 3974 entry = gtk.Entry() 3975 entry.set_text( data[ i ] ) 3976 else: 3977 try: 3978 name = tp.__name__ 3979 except: 3980 name = tp 3981 raise ValueError( "Unsuported column (%d) type: %s" % 3982 ( i, name ) ) 3983 3984 t.attach( label, 0, 1, i, i + 1, 3985 xoptions=gtk.FILL, 3986 xpadding=self.spacing, ypadding=self.spacing ) 3987 t.attach( entry, 1, 2, i, i + 1, 3988 xoptions=gtk.EXPAND|gtk.FILL, 3989 xpadding=self.spacing, ypadding=self.spacing ) 3990 w.append( entry ) 3991 3992 t.show_all() 3993 3994 sw = gtk.ScrolledWindow() 3995 sw.add_with_viewport( t ) 3996 sw.get_child().set_shadow_type( gtk.SHADOW_NONE ) 3997 d.vbox.pack_start( sw ) 3998 # Hack, disable scrollbars so we get the window to the 3999 # best size 4000 sw.set_policy( hscrollbar_policy=gtk.POLICY_NEVER, 4001 vscrollbar_policy=gtk.POLICY_NEVER ) 4002 d.show_all() 4003 # Scrollbars are automatic 4004 sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC, 4005 vscrollbar_policy=gtk.POLICY_AUTOMATIC ) 4006 4007 r = d.run() 4008 d.destroy() 4009 if r in ( gtk.RESPONSE_REJECT, gtk.RESPONSE_DELETE_EVENT ) : 4010 return None 4011 else: 4012 result = [] 4013 for i in xrange( len( data ) ): 4014 tp = self.types[ i ] 4015 wid = w[ i ] 4016 if tp == bool: 4017 r = bool( wid.get_active() ) 4018 elif tp in ( int, long ): 4019 r = tp( wid.get_value() ) 4020 elif tp == float: 4021 r = float( wid.get_value() ) 4022 elif tp in ( str, unicode ): 4023 r = tp( wid.get_text() ) 4024 else: 4025 try: 4026 name = tp.__name__ 4027 except: 4028 name = tp 4029 raise ValueError( \ 4030 "Unsuported column (%d) type: %s" % 4031 ( i, name ) ) 4032 result.append( r ) 4033 4034 return result
4035 # edit_dialog() 4036 4037
4038 - def clicked_add( button ):
4039 entry = [] 4040 for i, t in enumerate( self.types ): 4041 if t == bool: 4042 v = False 4043 elif t in ( int, long, float ): 4044 v = 0 4045 elif t in ( str, unicode ): 4046 v = '' 4047 else: 4048 try: 4049 name = t.__name__ 4050 except: 4051 name = t 4052 raise ValueError( "Unsuported column (%d) type: %s" % 4053 ( i, name ) ) 4054 entry.append( v ) 4055 result = edit_dialog( entry ) 4056 if result: 4057 self.append( result )
4058 # clicked_add() 4059 4060
4061 - def clicked_edit( button ):
4062 selected = self.selected() 4063 if not selected: 4064 return 4065 4066 for index, data in selected: 4067 print data 4068 result = edit_dialog( data ) 4069 if result: 4070 self[ index ] = result
4071 # clicked_edit() 4072 4073
4074 - def clicked_del( button ):
4075 selected = self.selected() 4076 if not selected: 4077 return 4078 4079 for index, data in selected: 4080 del self[ index ]
4081 # clicked_del() 4082 4083 self._btn_add.connect( "clicked", clicked_add ) 4084 self._btn_del.connect( "clicked", clicked_del ) 4085 self._btn_edit.connect( "clicked", clicked_edit ) 4086
4087 - def row_activated( treeview, path, column ):
4088 data = treeview.get_model()[ path ] 4089 result = edit_dialog( data ) 4090 if result: 4091 self[ path[ 0 ] ] = result
4092 # row_activated() 4093 4094 4095 self._table.connect( "row-activated", row_activated )
4096 # __setup_connections_editable__() 4097 4098
4100 - def selection_changed( selection ):
4101 result = self.selected() 4102 if not result: 4103 self._btn_up.set_sensitive( False ) 4104 self._btn_down.set_sensitive( False ) 4105 else: 4106 path = result[ 0 ][ 0 ] 4107 if path > 0: 4108 self._btn_up.set_sensitive( True ) 4109 else: 4110 self._btn_up.set_sensitive( False ) 4111 if path < len( self ) - 1: 4112 self._btn_down.set_sensitive( True ) 4113 else: 4114 self._btn_down.set_sensitive( False )
4115 # selection_changed() 4116
4117 - def move_up( button ):
4118 result = self.selected() 4119 a = result[ 0 ][ 0 ] 4120 if a <= 0: 4121 return 4122 4123 b = a - 1 4124 la = list( self[ a ] ) 4125 lb = list( self[ b ] ) 4126 self[ a ] = lb 4127 self[ b ] = la 4128 self.select( b )
4129 # move_up() 4130
4131 - def move_down( button ):
4132 result = self.selected() 4133 a = result[ 0 ][ 0 ] 4134 if a >= len( self ) - 1: 4135 return 4136 4137 b = a + 1 4138 la = list( self[ a ] ) 4139 lb = list( self[ b ] ) 4140 self[ a ] = lb 4141 self[ b ] = la 4142 self.select( b )
4143 # move_down() 4144 4145 selection = self._table.get_selection() 4146 selection.connect( "changed", selection_changed ) 4147 self._btn_up.connect( "clicked", move_up ) 4148 self._btn_down.connect( "clicked", move_down )
4149 # __setup_connections_repositioning__() 4150 4151
4153 - def selection_changed( selection ):
4154 result = self.selected() 4155 for c in self.selection_callback: 4156 c( self.app, self, result )
4157 # selection_changed() 4158 4159 selection = self._table.get_selection() 4160 selection.connect( "changed", selection_changed )
4161 # __setup_connections_selection__() 4162 4163
4164 - def __setup_table__( self ):
4165 self.__setup_model__() 4166 self._table = gtk.TreeView( self._model ) 4167 self._table.set_property( "allow-checkbox-mode", False ) 4168 self._table.set_name( "table-%s" % self.id ) 4169 self._table.get_selection().set_mode( gtk.SELECTION_MULTIPLE ) 4170
4171 - def column_clicked( column ):
4172 cid, order = self._model.get_sort_column_id() 4173 self._model.set_sort_column_id( cid, order )
4174 # column_clicked() 4175
4176 - def toggled( cell_render, path, col ):
4177 self._model[ path ][ col ] = not self._model[ path ][ col ]
4178 # toggled() 4179 4180
4181 - def edited( cell_render, path, text, col ):
4182 t = self.types[ col ] 4183 try: 4184 value = t( text ) 4185 except ValueError, e: 4186 name = t.__name__ 4187 error( "Invalid contents for column of type '%s': %s" % 4188 ( name, text ) ) 4189 else: 4190 self._model[ path ][ col ] = value
4191 # edited() 4192 4193 4194 for i, t in enumerate( self.types ): 4195 if t == bool: 4196 cell_rend = gtk.CellRendererToggle() 4197 props = { "active": i } 4198 if self.editable: 4199 cell_rend.set_property( "activatable", True) 4200 cell_rend.connect( "toggled", toggled, i ) 4201 4202 elif t in ( int, long, float, str, unicode ): 4203 cell_rend = gtk.CellRendererText() 4204 if self.editable: 4205 cell_rend.set_property( "editable", True ) 4206 cell_rend.connect( "edited", edited, i ) 4207 4208 props = { "text": i } 4209 if t in ( int, long, float ): 4210 cell_rend.set_property( "xalign", 1.0 ) 4211 else: 4212 try: 4213 name = t.__name__ 4214 except: 4215 name = t 4216 raise ValueError( "Unsuported column (%d) type: %s" % 4217 ( i, name ) ) 4218 4219 try: 4220 title = self.headers[ i ] 4221 except IndexError: 4222 title = "Col-%d (%s)" % ( i, t.__name__ ) 4223 4224 col = gtk.TreeViewColumn( title, cell_rend, **props ) 4225 col.set_resizable( True ) 4226 col.set_sort_column_id( i ) 4227 col.connect( "clicked", column_clicked ) 4228 if self.cell_format_func: 4229 def get_color( c ): 4230 return Canvas.__to_gtk_color__( Canvas.__color_from__( c ))
4231 # get_color() 4232 4233 def func( column, cell_renderer, model, itr, col_idx ): 4234 row_idx = model.get_path( itr )[ 0 ] 4235 value = model.get_value( itr, col_idx ) 4236 cf = self.cell_format_func( self.app, self, 4237 row_idx, col_idx, value ) 4238 if cf is None: 4239 cf = Table.CellFormat() 4240 4241 bgcolor = cf.bgcolor 4242 if bgcolor is not None: 4243 bgcolor = get_color( bgcolor ) 4244 else: 4245 bgcolor = self._table.style.base[ gtk.STATE_NORMAL ] 4246 cell_renderer.set_property( "cell-background-gdk", 4247 bgcolor ) 4248 if isinstance( cell_renderer, gtk.CellRendererText ): 4249 font = cf.font 4250 if font is not None: 4251 font = pango.FontDescription( font ) 4252 else: 4253 font = self.app._win.style.font_desc 4254 cell_renderer.set_property( "font-desc", font ) 4255 4256 fgcolor = cf.fgcolor 4257 if fgcolor is not None: 4258 fgcolor = get_color( fgcolor ) 4259 else: 4260 fgcolor = self._table.style.text[ gtk.STATE_NORMAL ] 4261 cell_renderer.set_property( "foreground-gdk", fgcolor ) 4262 4263 if cf.underline: 4264 underline = pango.UNDERLINE_SINGLE 4265 else: 4266 underline = pango.UNDERLINE_NONE 4267 cell_renderer.set_property( "underline", underline ) 4268 4269 if cf.bold: 4270 bold = pango.WEIGHT_BOLD 4271 else: 4272 bold = pango.WEIGHT_NORMAL 4273 cell_renderer.set_property( "weight", bold ) 4274 4275 if cf.italic: 4276 italic = pango.STYLE_ITALIC 4277 else: 4278 italic = pango.STYLE_NORMAL 4279 cell_renderer.set_property( "style", italic ) 4280 4281 cell_renderer.set_property( "strikethrough", 4282 bool( cf.strike ) )
4283 # func() 4284 col.set_cell_data_func( cell_rend, func, i ) 4285 # endif cell_format_func 4286 4287 if i in self.expand_columns_indexes: 4288 col.set_expand( True ) 4289 else: 4290 col.set_expand( False ) 4291 self._table.append_column( col ) 4292 4293 4294 self._table.set_headers_visible( self.show_headers ) 4295 self._table.set_headers_clickable( True ) 4296 self._table.set_reorderable( True ) 4297 self._table.set_enable_search( True ) 4298 4299 self._sw = gtk.ScrolledWindow() 4300 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC, 4301 vscrollbar_policy=gtk.POLICY_AUTOMATIC ) 4302 self._sw.set_shadow_type( gtk.SHADOW_IN ) 4303 self._sw.add( self._table ) 4304 self._vbox.pack_start( self._sw )
4305 # __setup_table__() 4306 4307
4308 - def __setup_items__( self ):
4309 if self.items: 4310 for row in self.items: 4311 self.append( row, select=False, autosize=False ) 4312 self.columns_autosize()
4313 # __setup_items__() 4314 4315 4316
4317 - def __setup_model__( self ):
4318 gtk_types = [] 4319 for i, t in enumerate( self.types ): 4320 if t == bool: 4321 gtk_types.append( gobject.TYPE_BOOLEAN ) 4322 elif t == int: 4323 gtk_types.append( gobject.TYPE_INT ) 4324 elif t == long: 4325 gtk_types.append( gobject.TYPE_LONG ) 4326 elif t == float: 4327 gtk_types.append( gobject.TYPE_FLOAT ) 4328 elif t in ( str, unicode ): 4329 gtk_types.append( gobject.TYPE_STRING ) 4330 else: 4331 try: 4332 name = t.__name__ 4333 except: 4334 name = t 4335 raise ValueError( "Unsuported column (%d) type: %s" % 4336 ( i, name ) ) 4337 self._model = gtk.ListStore( *gtk_types ) 4338
4339 - def sort_fn( model, itr1, itr2, id ):
4340 return cmp( model[ itr1 ][ id ], model[ itr2 ][ id ] )
4341 # sort_fn() 4342 4343 for i in xrange( len( self.types ) ): 4344 self._model.set_sort_func( i, sort_fn, i )
4345 # __setup_model__() 4346 4347
4348 - def __get_resize_mode__( self ):
4349 return ( gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND )
4350 # __get_resize_mode__() 4351 4352
4353 - def set_label( self, label ):
4354 self.__label = label 4355 self._frame.set_label( self.__label )
4356 # set_label() 4357 4358
4359 - def get_label( self ):
4360 return self.__label
4361 # get_label() 4362 4363 label = property( get_label, set_label ) 4364 4365
4366 - def columns_autosize( self ):
4367 self._table.columns_autosize()
4368 # columns_autosize() 4369 4370
4371 - def select( self, index ):
4372 selection = self._table.get_selection() 4373 selection.unselect_all() 4374 selection.select_path( index )
4375 # select() 4376 4377
4378 - def selected( self ):
4379 model, paths = self._table.get_selection().get_selected_rows() 4380 if paths: 4381 result = [] 4382 for p in paths: 4383 result.append( ( p[ 0 ], Table.Row( model[ p ] ) ) ) 4384 return result 4385 else: 4386 return None
4387 # selected() 4388 4389
4390 - def append( self, row, select=True, autosize=True ):
4391 if not isinstance( row, ( list, tuple ) ): 4392 row = ( row, ) 4393 4394 if self._model._hid_row_inserted is not None: 4395 self._model.handler_block( self._model._hid_row_inserted ) 4396 if self._model._hid_row_changed is not None: 4397 self._model.handler_block( self._model._hid_row_changed ) 4398 itr = self._model.append( row ) 4399 if self._model._hid_row_changed is not None: 4400 self._model.handler_unblock( self._model._hid_row_changed ) 4401 if self._model._hid_row_inserted is not None: 4402 self._model.handler_unblock( self._model._hid_row_inserted ) 4403 4404 self._model.row_changed( self._model.get_path( itr ), itr ) 4405 4406 if autosize: 4407 self._table.columns_autosize() 4408 if select: 4409 self._table.set_cursor( self._model[ itr ].path )
4410 # append() 4411 4412
4413 - def insert( self, index, row, select=True, autosize=True ):
4414 if not isinstance( row, ( list, tuple ) ): 4415 row = ( row, ) 4416 4417 if self._model._hid_row_inserted is not None: 4418 self._model.handler_block( self._model._hid_row_inserted ) 4419 if self._model._hid_row_changed is not None: 4420 self._model.handler_block( self._model._hid_row_changed ) 4421 itr = self._model.insert( index, row ) 4422 if self._model._hid_row_changed is not None: 4423 self._model.handler_unblock( self._model._hid_row_changed ) 4424 if self._model._hid_row_inserted is not None: 4425 self._model.handler_unblock( self._model._hid_row_inserted ) 4426 4427 self._model.row_changed( self._model.get_path( itr ), itr ) 4428 4429 if autosize: 4430 self._table.columns_autosize() 4431 if select: 4432 self._table.set_cursor( self._model[ itr ].path )
4433 # insert() 4434 4435
4436 - def __nonzero__( self ):
4437 return self._model.__nonzero__()
4438 # __nonzero__() 4439
4440 - def __len__( self ):
4441 return len( self._model )
4442 # __len__() 4443 4444
4445 - def __iadd__( self, other ):
4446 self.append( other ) 4447 return self
4448 # __iadd__() 4449 4450
4451 - def __setitem__( self, index, other ):
4452 if not isinstance( other, ( list, tuple, Table.Row ) ): 4453 other = ( other, ) 4454 try: 4455 if self._model._hid_row_inserted is not None: 4456 self._model.handler_block( self._model._hid_row_inserted ) 4457 if self._model._hid_row_changed is not None: 4458 self._model.handler_block( self._model._hid_row_changed ) 4459 self._model[ index ] = other 4460 if self._model._hid_row_changed is not None: 4461 self._model.handler_unblock( self._model._hid_row_changed ) 4462 if self._model._hid_row_inserted is not None: 4463 self._model.handler_unblock( self._model._hid_row_inserted ) 4464 4465 p = ( index, ) 4466 self._model.row_changed( p, self._model.get_iter( p ) ) 4467 4468 except TypeError, e: 4469 raise IndexError( "index out of range" )
4470 # __setitem__() 4471 4472
4473 - def __getitem__( self, index ):
4474 try: 4475 items = self._model[ index ] 4476 except TypeError, e: 4477 raise IndexError( "index out of range" ) 4478 4479 return Table.Row( items )
4480 # __getitem__() 4481 4482
4483 - def __delitem__( self, index ):
4484 try: 4485 del self._model[ index ] 4486 except TypeError, e: 4487 raise IndexError( "index out of range" )
4488 # __delitem__() 4489 4490
4491 - def __contains__( self, row ):
4492 for r in self._model: 4493 if row in r: 4494 return True 4495 return False
4496 # __contains__() 4497 4498
4499 - def __getslice__( self, start, end ):
4500 slice = [] 4501 4502 l = len( self._model ) 4503 while start < 0: 4504 start += l 4505 while end < 0: 4506 end += l 4507 4508 start = min( start, l ) 4509 end = min( end, l ) # l[ : ] -> l[ 0 : maxlistindex ] 4510 4511 for i in xrange( start, end ): 4512 slice.append( Table.Row( self._model[ i ] ) ) 4513 return slice
4514 # __getslice__() 4515 4516
4517 - def __setslice__( self, start, end, slice ):
4518 l = len( self._model ) 4519 while start < 0: 4520 start += l 4521 while end < 0: 4522 end += l 4523 4524 del self[ start : end ] 4525 4526 # just insert len( slice ) items 4527 l2 = len( slice ) 4528 if end - start > l2: 4529 end = start + l2 4530 for j, i in enumerate( xrange( start, end ) ): 4531 row = list( slice[ j ] ) 4532 4533 4534 # extend row if necessary 4535 lr = len( row ) 4536 lt = len( self.types ) 4537 if lr < lt: 4538 for i in xrange( lr, lt ): 4539 t = self.types[ i ] 4540 row.append( t() ) 4541 4542 self.insert( i, row, select=False, autosize=False )
4543 # __setslice__() 4544 4545
4546 - def __delslice__( self, start, end ):
4547 l = len( self._model ) 4548 while start < 0: 4549 start += l 4550 while end < 0: 4551 end += l 4552 4553 start = min( start, l ) 4554 end = min( end, l ) # l[ : ] -> l[ 0 : maxlistindex ] 4555 4556 while end > start: 4557 end -= 1 4558 del self._model[ end ]
4559 # __delslice__() 4560 # Table 4561 4562
4563 -class RichText( _EGWidget ):
4564 """A Rich Text viewer 4565 4566 Display text with basic formatting instructions. Formatting is 4567 done using a HTML subset. 4568 """ 4569
4570 - class Renderer( gtk.TextView ):
4571 """Specialized TextView to render formatted texts. 4572 4573 This class emits "follow-link" when user clicks somewhere. 4574 4575 It implements Writer interface as specified in standard library 4576 "formatter" module. 4577 """ 4578 bullet = None 4579 margin = 2 4580 signal_created = False 4581
4582 - def __init__( self, link_color="#0000ff", 4583 foreground=None, background=None, 4584 resource_provider=None ):
4585 """RichText.Renderer constructor. 4586 4587 @param link_color: color to use with links. String with color name 4588 or in internet format (3 pairs of RGB, in hexa, prefixed by 4589 #). 4590 @param foreground: default foreground color. Same spec as 4591 link_color. 4592 @param background: default background color. Same spec as 4593 link_color. 4594 @param resource_provider: function to provide unresolved resources. 4595 If some image could not be handled as a file, this function 4596 will be called and it should return an gtk.gdk.Pixbuf. 4597 Since http://url.com/file will always be unresolved, you 4598 may use this to provide remote file access to this class. 4599 """ 4600 self.link_color = link_color or "#0000ff" 4601 self.foreground = foreground 4602 self.background = background 4603 self.resource_provider = resource_provider 4604 self.hovering_over_link = False 4605 4606 b = gtk.TextBuffer() 4607 gtk.TextView.__init__( self, b ) 4608 4609 self.set_cursor_visible( False ) 4610 self.set_editable( False ) 4611 self.set_wrap_mode( gtk.WRAP_WORD ) 4612 self.set_left_margin( self.margin ) 4613 self.set_right_margin( self.margin ) 4614 4615 self.__setup_connections__() 4616 self.__setup_render__() 4617 self.__create_bullets__()
4618 # __init__() 4619 4620
4621 - def __create_bullets__( self ):
4622 klass = RichText.Renderer 4623 if klass.bullet is None: 4624 width = height = 16 4625 dx = dy = 4 4626 4627 colormap = gtk.gdk.colormap_get_system() 4628 visual = colormap.get_visual() 4629 4630 white = colormap.alloc_color( "#ffffff", True, True ) 4631 black = colormap.alloc_color( "#000000", True, True ) 4632 4633 pixmap = gtk.gdk.Pixmap( None, width, height, visual.depth ) 4634 white_gc = pixmap.new_gc( foreground=white, background=black, 4635 fill=gtk.gdk.SOLID, line_width=1, 4636 line_style=gtk.gdk.LINE_SOLID ) 4637 black_gc = pixmap.new_gc( foreground=black, background=white, 4638 fill=gtk.gdk.SOLID, line_width=1, 4639 line_style=gtk.gdk.LINE_SOLID ) 4640 pixmap.draw_rectangle( white_gc, True, 0, 0, width, height ) 4641 pixmap.draw_arc( black_gc, True, dx, dy, 4642 width - dx * 2, height - dy * 2, 4643 0, 23040 ) 4644 4645 4646 pixbuf = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, True, 8, 4647 width, height ) 4648 pixbuf = pixbuf.get_from_drawable( pixmap, colormap, 0, 0, 0, 0, 4649 width, height ) 4650 pixbuf = pixbuf.add_alpha( True, chr(255), chr(255), chr(255) ) 4651 klass.bullet = pixbuf
4652 # __create_bullets__() 4653 4654
4655 - def __setup_connections__( self ):
4656 hand_cursor = gtk.gdk.Cursor( gtk.gdk.HAND2 ) 4657 regular_cursor = gtk.gdk.Cursor( gtk.gdk.XTERM ) 4658 K_Return = gtk.gdk.keyval_from_name( "Return" ) 4659 K_KP_Enter = gtk.gdk.keyval_from_name( "KP_Enter" ) 4660 K_Home = gtk.gdk.keyval_from_name( "Home" ) 4661 K_End = gtk.gdk.keyval_from_name( "End" ) 4662 4663 if not self.__class__.signal_created: 4664 gobject.signal_new( "follow-link", RichText.Renderer, 4665 gobject.SIGNAL_RUN_LAST, 4666 gobject.TYPE_NONE, 4667 ( gobject.TYPE_STRING, 4668 gobject.TYPE_ULONG ) ) 4669 self.__class__.signal_created = True 4670 4678 # get_link() 4679 4684 # follow_if_link() 4685
4686 - def key_press_event( text_view, event ):
4687 if event.keyval in ( K_Return, K_KP_Enter ): 4688 b = text_view.get_buffer() 4689 itr = b.get_iter_at_mark( b.get_insert() ) 4690 follow_if_link( text_view, itr ) 4691 elif event.keyval == K_Home: 4692 itr = text_view.get_buffer().get_start_iter() 4693 text_view.scroll_to_iter( itr, 0.0, False ) 4694 elif event.keyval == K_End: 4695 itr = text_view.get_buffer().get_end_iter() 4696 text_view.scroll_to_iter( itr, 0.0, False )
4697 # key_press_event() 4698 self.connect( "key-press-event", key_press_event ) 4699
4700 - def event_after( text_view, event ):
4701 if event.type != gtk.gdk.BUTTON_RELEASE: 4702 return False 4703 4704 if event.button != 1: 4705 return False 4706 4707 b = text_view.get_buffer() 4708 4709 # we shouldn't follow a link if the user has selected something 4710 try: 4711 start, end = b.get_selection_bounds() 4712 except ValueError: 4713 pass 4714 else: 4715 if start.get_offset() != end.get_offset(): 4716 return False 4717 4718 x, y = text_view.window_to_buffer_coords( gtk.TEXT_WINDOW_WIDGET, 4719 int( event.x ), 4720 int( event.y ) ) 4721 itr = text_view.get_iter_at_location( x, y ) 4722 follow_if_link( text_view, itr ) 4723 return False
4724 # event_after() 4725 self.connect( "event-after", event_after ) 4726
4727 - def set_cursor_if_appropriate( text_view, x, y ):
4728 b = text_view.get_buffer() 4729 itr = text_view.get_iter_at_location( x, y ) 4730 4731 self.hovering_over_link = get_link( itr ) 4732 if self.hovering_over_link: 4733 cursor = hand_cursor 4734 else: 4735 cursor = regular_cursor 4736 4737 win = text_view.get_window( gtk.TEXT_WINDOW_TEXT ) 4738 win.set_cursor( cursor )
4739 # set_cursor_if_appropriate() 4740
4741 - def motion_notify_event( text_view, event ):
4742 x, y = text_view.window_to_buffer_coords( gtk.TEXT_WINDOW_WIDGET, 4743 int( event.x ), 4744 int( event.y ) ) 4745 set_cursor_if_appropriate( text_view, x, y ) 4746 text_view.window.get_pointer() 4747 return False
4748 # motion_notify_event() 4749 self.connect( "motion-notify-event", motion_notify_event ) 4750
4751 - def visibility_notify_event( text_view, event ):
4752 wx, wy, mod = text_view.window.get_pointer() 4753 x, y = text_view.window_to_buffer_coords( gtk.TEXT_WINDOW_WIDGET, 4754 wx, wy ) 4755 set_cursor_if_appropriate( text_view, x, y ) 4756 return False
4757 # visibility_notify_event() 4758 self.connect( "visibility-notify-event", visibility_notify_event ) 4759 4760
4761 - def after_realize( text_view ):
4762 colormap = self.get_colormap() 4763 if self.background: 4764 bg = colormap.alloc_color( self.background, True, True ) 4765 w = text_view.get_window( gtk.TEXT_WINDOW_TEXT ) 4766 w.set_background( bg ) 4767 w = text_view.get_window( gtk.TEXT_WINDOW_WIDGET ) 4768 w.set_background( bg )
4769 # after_realize() 4770 self.connect_after( "realize", after_realize )
4771 # __setup_connections__() 4772 4773
4774 - def __setup_render__( self ):
4775 self.buffer = self.get_buffer() 4776 itr = self.buffer.get_start_iter() 4777 4778 k = {} 4779 if self.foreground: 4780 k[ "foreground" ] = self.foreground 4781 4782 create_tag = self.buffer.create_tag 4783 self.tags = { 4784 "default": create_tag( "default", **k ), 4785 "bold": create_tag( "bold", weight=pango.WEIGHT_BOLD ), 4786 "italic": create_tag( "italic", style=pango.STYLE_ITALIC ), 4787 "link": create_tag( "link", foreground=self.link_color, 4788 underline=pango.UNDERLINE_SINGLE ), 4789 "h1": create_tag( "h1", scale=pango.SCALE_XX_LARGE ), 4790 "h2": create_tag( "h2", scale=pango.SCALE_X_LARGE ), 4791 "h3": create_tag( "h3", scale=pango.SCALE_LARGE ), 4792 "monospaced": create_tag( "monospaced", font="monospace" ), 4793 } 4794 self.tags[ "default" ].set_priority( 0 ) 4795 4796 self.font = [] 4797 self.link = [] 4798 self.margin = []
4799 # __setup_render__() 4800 4801
4802 - def send_paragraph( self, blankline ):
4803 if blankline: 4804 self.send_flowing_data( "\n" )
4805 # send_paragraph() 4806 4807
4808 - def send_line_break( self ):
4809 self.send_paragraph( 1 )
4810 # send_line_break() 4811 4812
4813 - def send_flowing_data( self, data ):
4814 itr = self.buffer.get_end_iter() 4815 t = [ self.tags[ "default" ] ] + self.font + self.link 4816 self.buffer.insert_with_tags( itr, data, *t )
4817 # send_flowing_data() 4818
4819 - def send_literal_data( self, data ):
4820 itr = self.buffer.get_end_iter() 4821 t = [ self.tags[ "default" ], self.tags[ "monospaced" ] ] + \ 4822 self.font + self.link 4823 self.buffer.insert_with_tags( itr, data, *t )
4824 # send_literal_data() 4825 4826
4827 - def send_hor_rule( self ):
4828 itr = self.buffer.get_end_iter() 4829 anchor = self.buffer.create_child_anchor( itr ) 4830 w = gtk.HSeparator()
4831 - def size_allocate( widget, rect ):
4832 lm = self.get_left_margin() 4833 rm = self.get_right_margin() 4834 width = max( rect.width - lm - rm - 1, 0 ) 4835 w.set_size_request( width, -1 )
4836 # size_allocate() 4837 4838 self.connect_after( "size-allocate", size_allocate ) 4839 self.add_child_at_anchor( w, anchor )
4840 # send_hor_rule() 4841 4842
4843 - def new_margin( self, margin, level ):
4844 itr = self.buffer.get_end_iter() 4845 self.margin.append( ( margin, level ) )
4846 # new_margin() 4847 4848
4849 - def send_label_data( self, data ):
4850 itr = self.buffer.get_end_iter() 4851 t = self.font + self.link + [ self.tags[ "bold" ] ] 4852 4853 margin, level = self.margin[ -1 ] 4854 self.buffer.insert_with_tags( itr, '\t' * level, *t ) 4855 if data == '*' and self.bullet: 4856 self.buffer.insert_pixbuf( itr, self.bullet ) 4857 else: 4858 self.buffer.insert_with_tags( itr, data, *t ) 4859 self.buffer.insert_with_tags( itr, ' ', *t )
4860 # send_label_data() 4861 4862
4863 - def add_image( self, filename, width, height ):
4864 try: 4865 pixbuf = gtk.gdk.pixbuf_new_from_file( filename ) 4866 except gobject.GError: 4867 if self.resource_provider: 4868 pixbuf = self.resource_provider( filename ) 4869 else: 4870 raise ValueError( "No resource provider for %r" % filename) 4871 4872 if not pixbuf: 4873 self.send_flowing_data( "[%s]" % filename ) 4874 return 4875 4876 ow = pixbuf.get_width() 4877 oh = pixbuf.get_height() 4878 p = float( ow ) / float( oh ) 4879 4880 if width > 0 and height < 1: 4881 height = int( width / p ) 4882 elif height > 0 and width < 1: 4883 width = int( height * p ) 4884 if width > 0 and height > 0: 4885 pixbuf = pixbuf.scale_simple( width, height, 4886 gtk.gdk.INTERP_BILINEAR ) 4887 itr = self.buffer.get_end_iter() 4888 self.buffer.insert_pixbuf( itr, pixbuf )
4889 # add_image() 4890 4891 4899 # new_link() 4900 4901 4904 # end_link() 4905 4906
4907 - def new_font( self, font ):
4908 if isinstance( font, ( tuple, list ) ): 4909 def append_unique( v ): 4910 f = self.font 4911 if v not in f: 4912 f.append( v )
4913 # append 4914 4915 size, is_italic, is_bold, is_tt = font 4916 if size == "h1": 4917 append_unique( self.tags[ "h1" ] ) 4918 elif size == "h2": 4919 append_unique( self.tags[ "h2" ] ) 4920 elif size == "h3": 4921 append_unique( self.tags[ "h3" ] ) 4922 4923 if is_italic: 4924 append_unique( self.tags[ "italic" ] ) 4925 if is_bold: 4926 append_unique( self.tags[ "bold" ] ) 4927 4928 elif isinstance( font, dict ): 4929 t = {} 4930 family = font.get( "family" ) 4931 size = font.get( "size" ) 4932 color = font.get( "color" ) 4933 background = font.get( "bgcolor" ) 4934 if family: 4935 t[ "family" ] = family 4936 if size: 4937 t[ "size-points" ] = int( size ) 4938 if color: 4939 t[ "foreground" ] = color 4940 if background: 4941 t[ "background" ] = background 4942 self.font.append( self.buffer.create_tag( None, **t ) ) 4943 else: 4944 self.font = []
4945 # new_font() 4946 4947
4948 - def goto( self, anchor ):
4949 mark = self.buffer.get_mark( anchor ) 4950 if mark is not None: 4951 self.scroll_mark_onscreen( mark ) 4952 else: 4953 raise ValueError( "Inexistent anchor: %r" % anchor )
4954 # goto() 4955 4956
4957 - def reset( self ):
4958 a = self.buffer.get_start_iter() 4959 b = self.buffer.get_end_iter() 4960 self.buffer.delete( a, b )
4961 # reset() 4962 # Renderer 4963 4964
4965 - class Parser( htmllib.HTMLParser ):
4966 """HTML subset parser"""
4967 - def anchor_bgn( self, href, name, type ):
4968 htmllib.HTMLParser.anchor_bgn( self, href, name, type ) 4969 self.formatter.push_link( href, name )
4970 # anchor_bgn() 4971 4972
4973 - def anchor_end( self ):
4974 self.formatter.pop_link()
4975 # anchor_end() 4976 4977
4978 - def handle_image( self, source, alt, ismap, align, width, height ):
4979 self.formatter.add_image( source, width, height )
4980 # handle_image() 4981 4982
4983 - def start_font( self, attrs ):
4984 k = dict( attrs ) 4985 self.formatter.push_font( k )
4986 # start_font() 4987 4988
4989 - def end_font( self ):
4990 self.formatter.pop_font()
4991 # end_font() 4992 # Parser 4993 4994
4995 - class Formatter( formatter.AbstractFormatter ):
4996 """HTML subset formatter"""
4997 - def add_image( self, filename, width, height ):
4998 self.writer.add_image( filename, width, height )
4999 # add_image() 5000 5003 # push_link() 5004 5007 # pop_link() 5008 5009
5010 - def push_font( self, font ):
5011 if isinstance( font, dict ): 5012 self.writer.new_font( font ) 5013 else: 5014 formatter.AbstractFormatter.push_font( self, font )
5015 # push_font() 5016 # Formatter 5017 5018 bgcolor = _gen_ro_property( "bgcolor" ) 5019 fgcolor = _gen_ro_property( "fgcolor" ) 5020 link_color = _gen_ro_property( "link_color" ) 5021 padding = 5 5022
5023 - def __init__( self, id, text="", label=None, link_color="blue", 5024 fgcolor=None, bgcolor=None, callback=None, 5025 img_provider=None ):
5026 """RichText constructor. 5027 5028 @param id: unique identifier. 5029 @param text: text to use in this viewer. 5030 @param label: label to display in the widget frame around the viewer. 5031 If None, no label or frame will be shown. 5032 @param link_color: color to use for links. 5033 @param fgcolor: color to use for foreground (text) 5034 @param bgcolor: color to use for background. 5035 @param callback: function (or list of functions) to call when 5036 user clicks a link. Links to anchor will automatically make 5037 the anchor/mark visible and then callback. Function will get 5038 as parameters: 5039 - App reference 5040 - RichText reference 5041 - href contents (string) 5042 - offset from buffer begin (integer) 5043 @param img_provider: if images could not be resolved, call this 5044 function. It should get an address (string) and return an 5045 eagle.Image. Eagle already provides a handle to addresses 5046 prefixed with "eagle://", the following part should be an 5047 eagle.Image id, and the image should be live (not garbage 5048 collected) when displaying it, so remember to keep a 5049 reference to it! You may use img_provider to download 5050 files from webservers and stuff like that. 5051 Function signature: 5052 def img_provider( filename ): 5053 return eagle.Image( ... ) 5054 """ 5055 _EGWidget.__init__( self, id ) 5056 self.__label = label 5057 self._callback = _callback_tuple( callback ) 5058 self.link_color = link_color 5059 self.foreground = fgcolor 5060 self.background = bgcolor 5061 self.img_provider = img_provider 5062 5063 self.__setup_gui__() 5064 self.__setup_parser__() 5065 self.__setup_connections__() 5066 self.set_text( text )
5067 # __init__() 5068 5069
5070 - def __setup_gui__( self ):
5071 - def img_provider( filename ):
5072 img = None 5073 if filename.startswith( "eagle://" ): 5074 id = filename[ len( "eagle://" ) : ] 5075 img = Image.__get_by_id__( id ) 5076 elif self.img_provider: 5077 img = self.img_provider( filename ) 5078 5079 if img: 5080 return img.__get_gtk_pixbuf__() 5081 else: 5082 error( "Could not find image %r" % filename )
5083 # img_provider() 5084 5085 self._sw = gtk.ScrolledWindow() 5086 self._renderer = RichText.Renderer( link_color=self.link_color, 5087 foreground=self.fgcolor, 5088 background=self.bgcolor, 5089 resource_provider=img_provider ) 5090 5091 self._sw.set_border_width( self.padding ) 5092 self._sw.set_shadow_type( gtk.SHADOW_IN ) 5093 if self.label is not None: 5094 self._frame = gtk.Frame( self.label ) 5095 self._frame.add( self._sw ) 5096 root = self._frame 5097 self._frame.set_shadow_type( gtk.SHADOW_OUT ) 5098 else: 5099 root = self._sw 5100 5101 self._sw.add( self._renderer ) 5102 self._sw.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC ) 5103 self._sw.show_all() 5104 self._widgets = ( root, )
5105 # __setup_gui__() 5106 5107
5108 - def __setup_parser__( self ):
5109 self._formatter = RichText.Formatter( self._renderer ) 5110 self._parser = RichText.Parser( self._formatter )
5111 # __setup_parser__() 5112 5113
5114 - def __setup_connections__( self ):
5115 - def callback( text_view, href, offset ):
5116 if href.startswith( "#" ): 5117 try: 5118 text_view.goto( href[ 1 : ] ) 5119 except ValueError, e: 5120 error( str( e ) ) 5121 5122 for c in self._callback: 5123 c( self.app, self, href, offset )
5124 # callback() 5125 self._renderer.connect( "follow-link", callback )
5126 # __setup_connections__() 5127 5128
5129 - def set_text( self, text ):
5130 """Replace current text""" 5131 self._text = text 5132 self._renderer.reset() 5133 self.__setup_parser__() 5134 self._parser.feed( self.text )
5135 # set_text() 5136 5137
5138 - def get_text( self ):
5139 """Return current text, with formatting tags""" 5140 return self._text
5141 # get_text() 5142 5143 text = property( get_text, set_text ) 5144 5145
5146 - def append( self, text ):
5147 self._text += text 5148 self._parser.feed( text )
5149 # append() 5150 5151
5152 - def set_label( self, label ):
5153 if self.__label is None: 5154 raise ValueError( "You cannot change label of widget created " 5155 "without one. Create it with placeholder! " 5156 "(label='')" ) 5157 self.__label = label 5158 self._frame.set_label( self.__label )
5159 # set_label() 5160 5161
5162 - def get_label( self ):
5163 return self.__label
5164 # get_label() 5165 5166 label = property( get_label, set_label ) 5167 5168
5169 - def __str__( self ):
5170 return "%s( id=%r, label=%r, link_color=%r, fgcolor=%r, bgcolor=%r )"%\ 5171 ( self.__class__.__name__, self.id, self.label, self.link_color, 5172 self.fgcolor, self.bgcolor )
5173 # __str__() 5174 __repr__ = __str__ 5175 # RichText 5176 5177
5178 -class Button( _EGWidget ):
5179 """A push button. 5180 """ 5181 stock = _gen_ro_property( "stock" ) 5182 callback = _gen_ro_property( "callback" ) 5183 5184 stock_items = ( 5185 "about", 5186 "help", 5187 "quit", 5188 "add", 5189 "remove", 5190 "refresh", 5191 "update", 5192 "yes", 5193 "no", 5194 "zoom_100", 5195 "zoom_in", 5196 "zoom_out", 5197 "zoom_fit", 5198 "undo", 5199 "execute", 5200 "stop", 5201 "open", 5202 "save", 5203 "save_as", 5204 "properties", 5205 "preferences", 5206 "print", 5207 "print_preview", 5208 "ok", 5209 "cancel", 5210 "apply", 5211 "close", 5212 "clear", 5213 "convert", 5214 "next", 5215 "back", 5216 "up", 5217 "down", 5218 "font", 5219 "color", 5220 ) 5221 5222 5223 _gtk_stock_map = { 5224 "about": gtk.STOCK_ABOUT, 5225 "help": gtk.STOCK_HELP, 5226 "quit": gtk.STOCK_QUIT, 5227 "add": gtk.STOCK_ADD, 5228 "remove": gtk.STOCK_REMOVE, 5229 "refresh": gtk.STOCK_REFRESH, 5230 "update": gtk.STOCK_REFRESH, 5231 "yes": gtk.STOCK_YES, 5232 "no": gtk.STOCK_NO, 5233 "zoom_100": gtk.STOCK_ZOOM_100, 5234 "zoom_in": gtk.STOCK_ZOOM_IN, 5235 "zoom_out": gtk.STOCK_ZOOM_OUT, 5236 "zoom_fit": gtk.STOCK_ZOOM_FIT, 5237 "undo": gtk.STOCK_UNDO, 5238 "execute": gtk.STOCK_EXECUTE, 5239 "stop": gtk.STOCK_STOP, 5240 "open": gtk.STOCK_OPEN, 5241 "save": gtk.STOCK_SAVE, 5242 "save_as": gtk.STOCK_SAVE_AS, 5243 "properties": gtk.STOCK_PROPERTIES, 5244 "preferences": gtk.STOCK_PREFERENCES, 5245 "print": gtk.STOCK_PRINT, 5246 "print_preview": gtk.STOCK_PRINT_PREVIEW, 5247 "ok": gtk.STOCK_OK, 5248 "cancel": gtk.STOCK_CANCEL, 5249 "apply": gtk.STOCK_APPLY, 5250 "close": gtk.STOCK_CLOSE, 5251 "clear": gtk.STOCK_CLEAR, 5252 "convert": gtk.STOCK_CONVERT, 5253 "next": gtk.STOCK_GO_FORWARD, 5254 "back": gtk.STOCK_GO_BACK, 5255 "up": gtk.STOCK_GO_UP, 5256 "down": gtk.STOCK_GO_DOWN, 5257 "font": gtk.STOCK_SELECT_FONT, 5258 "color": gtk.STOCK_SELECT_COLOR, 5259 } 5260
5261 - def __init__( self, id, label="", stock=None, callback=None ):
5262 """Push button constructor. 5263 5264 @param label: what text to show, if stock isn't provided. 5265 @param stock: optional. One of L{stock_items}. 5266 @param callback: the function (or list of functions) to call 5267 when button is pressed. Function will get as parameter: 5268 - App reference 5269 - Button reference 5270 """ 5271 self.label = label 5272 self.stock = stock 5273 self.callback = _callback_tuple( callback ) 5274 5275 # Check if provided stock items are implemented 5276 for i in self.stock_items: 5277 if i not in self._gtk_stock_map: 5278 print >> sys.stderr, \ 5279 "Stock item %s missing in implementation map!" % ( i, ) 5280 5281 _EGWidget.__init__( self, id ) 5282 5283 self.__setup_gui__() 5284 self.__setup_connections__()
5285 # __init__() 5286 5287
5288 - def __setup_gui__( self ):
5289 k = {} 5290 try: 5291 k[ "stock" ] = self._gtk_stock_map[ self.stock ] 5292 except KeyError: 5293 k[ "label" ] = self.label or self.stock 5294 5295 self._button = gtk.Button( **k ) 5296 self._button.set_name( self.id ) 5297 self._widgets = ( self._button, )
5298 # __setup_gui__() 5299 5300
5301 - def __setup_connections__( self ):
5302 - def callback( obj ):
5303 for c in self.callback: 5304 c( self.app, self )
5305 # callback() 5306 self._button.connect( "clicked", callback )
5307 # __setup_connections__() 5308 5309
5310 - def __get_resize_mode__( self ):
5311 "Return a tuple with ( horizontal, vertical ) resize mode" 5312 return ( gtk.FILL, 0 )
5313 # __get_resize_mode__() 5314 # Button 5315 5316
5317 -class AboutButton( Button, AutoGenId ):
5318 """Push button to show L{AboutDialog} of L{App}."""
5319 - def __init__( self, id=None ):
5320 """You may not provide id, it will be generated automatically"""
5321 - def show_about( app_id, wid_id ):
5322 self.app.show_about_dialog()
5323 # show_about() 5324 Button.__init__( self, id or self.__get_id__(), 5325 stock="about", callback=show_about )
5326 # __init__() 5327 # AboutButton 5328 5329
5330 -class CloseButton( Button, AutoGenId ):
5331 """Push button to close L{App}."""
5332 - def __init__( self, id=None ):
5333 """You may not provide id, it will be generated automatically"""
5334 - def close( app_id, wid_id ):
5335 self.app.close()
5336 # close() 5337 Button.__init__( self, id or self.__get_id__(), 5338 stock="close", callback=close )
5339 # __init__() 5340 # CloseButton 5341 5342
5343 -class QuitButton( Button, AutoGenId ):
5344 """Push button to quit all L{App}s."""
5345 - def __init__( self, id=None ):
5346 """You may not provide id, it will be generated automatically"""
5347 - def c( app_id, wid_id ):
5348 quit()
5349 # c() 5350 Button.__init__( self, id or self.__get_id__(), 5351 stock="quit", callback=c )
5352 # __init__() 5353 # QuitButton 5354 5355
5356 -class HelpButton( Button, AutoGenId ):
5357 """Push button to show L{HelpDialog} of L{App}."""
5358 - def __init__( self, id=None ):
5359 """You may not provide id, it will be generated automatically"""
5360 - def c( app_id, wid_id ):
5361 self.app.show_help_dialog()
5362 # c() 5363 Button.__init__( self, id or self.__get_id__(), 5364 stock="help", callback=c )
5365 # __init__() 5366 # HelpButton 5367 5368
5369 -class OpenFileButton( Button, AutoGenId ):
5370 """Push button to show dialog to choose an existing file."""
5371 - def __init__( self, id=None, 5372 filter=None, multiple=False, 5373 callback=None ):
5374 """Constructor. 5375 5376 @param id: may not be provided, it will be generated automatically. 5377 @param filter: filter files to show, see L{FileChooser}. 5378 @param multiple: enable selection of multiple files. 5379 @param callback: function (or list of functions) to call back 5380 when file is selected. Function will get as parameters: 5381 - app reference. 5382 - widget reference. 5383 - file name or file list (if multiple). 5384 5385 @see: L{FileChooser} 5386 """
5387 - def c( app_id, wid_id ):
5388 f = self.app.file_chooser( FileChooser.ACTION_OPEN, 5389 filter=filter, multiple=multiple ) 5390 if f is not None and callback: 5391 callback( self.app, self, f )
5392 # c() 5393 Button.__init__( self, id or self.__get_id__(), 5394 stock="open", callback=c )
5395 # __init__() 5396 # OpenFileButton 5397 5398
5399 -class SelectFolderButton( Button, AutoGenId ):
5400 """Push button to show dialog to choose an existing folder/directory."""
5401 - def __init__( self, id=None, callback=None ):
5402 """Constructor. 5403 5404 @param id: may not be provided, it will be generated automatically. 5405 @param callback: function (or list of functions) to call back 5406 when file is selected. Function will get as parameters: 5407 - app reference. 5408 - widget reference. 5409 - directory/folder name. 5410 5411 @see: L{FileChooser} 5412 """
5413 - def c( app_id, wid_id ):
5414 f = self.app.file_chooser( FileChooser.ACTION_SELECT_FOLDER ) 5415 if f is not None and callback: 5416 callback( self.app, self, f )
5417 # c() 5418 Button.__init__( self, id or self.__get_id__(), 5419 stock="open", callback=c )
5420 # __init__() 5421 # SelectFolderButton 5422 5423
5424 -class SaveFileButton( Button, AutoGenId ):
5425 """Push button to show dialog to choose a file to save."""
5426 - def __init__( self, id=None, filename=None, 5427 filter=None, callback=None ):
5428 """Constructor. 5429 5430 @param id: may not be provided, it will be generated automatically. 5431 @param filename: default filename. 5432 @param filter: filter files to show, see L{FileChooser}. 5433 @param callback: function (or list of functions) to call back 5434 when file is selected. Function will get as parameters: 5435 - app reference. 5436 - widget reference. 5437 - file name. 5438 5439 @see: L{FileChooser} 5440 """
5441 - def c( app_id, wid_id ):
5442 f = self.app.file_chooser( FileChooser.ACTION_SAVE, 5443 filename=filename, 5444 filter=filter ) 5445 if f is not None and callback: 5446 callback( self.app, self, f )
5447 # c() 5448 Button.__init__( self, id or self.__get_id__(), 5449 stock="save", callback=c )
5450 # __init__() 5451 # SaveFileButton 5452 5453
5454 -class PreferencesButton( Button, AutoGenId ):
5455 """Push button to show L{PreferencesDialog} of L{App}."""
5456 - def __init__( self, id=None ):
5457 """You may not provide id, it will be generated automatically"""
5458 - def c( app_id, wid_id ):
5459 f = self.app.show_preferences_dialog()
5460 # c() 5461 Button.__init__( self, id or self.__get_id__(), 5462 stock="preferences", callback=c )
5463 # __init__() 5464 # PreferencesButton 5465 5466
5467 -class HSeparator( _EGWidget, AutoGenId ):
5468 """Horizontal separator"""
5469 - def __init__( self, id=None ):
5470 """You may not provide id, it will be generated automatically""" 5471 _EGWidget.__init__( self, id or self.__get_id__() ) 5472 self._wid = gtk.HSeparator() 5473 self._wid.set_name( self.id ) 5474 self._widgets = ( self._wid, )
5475 # __init__() 5476 # HSeparator 5477 5478
5479 -class VSeparator( _EGWidget ):
5480 """Horizontal separator"""
5481 - def __init__( self, id=None ):
5482 """You may not provide id, it will be generated automatically""" 5483 _EGWidget.__init__( self, id or self.__get_id__() ) 5484 self._wid = gtk.VSeparator() 5485 self._wid.set_name( self.id ) 5486 self._widgets = ( self._wid, )
5487 # __init__() 5488 # VSeparator 5489 5490
5491 -class Label( _EGDataWidget, AutoGenId ):
5492 """Text label""" 5493 label = _gen_ro_property( "label" ) 5494 5495 LEFT = 0.0 5496 RIGHT = 1.0 5497 CENTER = 0.5 5498 TOP = 0.0 5499 MIDDLE = 0.5 5500 BOTTOM = 1.0 5501
5502 - def __init__( self, id=None, label="", 5503 halignment=LEFT, valignment=MIDDLE ):
5504 """Label constructor. 5505 5506 @param id: may not be provided, it will be generated automatically. 5507 @param label: what this label will show. 5508 @param halignment: horizontal alignment, like L{LEFT}, L{RIGHT} or 5509 L{CENTER}. 5510 @param valignment: vertical alignment, like L{TOP}, L{BOTTOM} or 5511 L{MIDDLE}. 5512 """ 5513 _EGDataWidget.__init__( self, id or self.__get_id__(), False ) 5514 self.label = label 5515 5516 self._wid = gtk.Label( self.label ) 5517 self._wid.set_name( self.id ) 5518 self._wid.set_alignment( xalign=halignment, yalign=valignment ) 5519 self._widgets = ( self._wid, )
5520 # __init__() 5521 5522
5523 - def get_value( self ):
5524 return self._wid.get_text()
5525 # get_value() 5526 5527
5528 - def set_value( self, value ):
5529 self._wid.set_text( str( value ) )
5530 # set_value() 5531 5532
5533 - def __str__( self ):
5534 return "%s( id=%r, label=%r )" % \ 5535 ( self.__class__.__name__, self.id, self.label )
5536 # __str__() 5537 5538
5539 - def __get_resize_mode__( self ):
5540 "Return a tuple with ( horizontal, vertical ) resize mode" 5541 return ( gtk.FILL, 0 )
5542 # __get_resize_mode__() 5543 # Label 5544 5545
5546 -def information( message ):
5547 """Show info message to user.""" 5548 5549 d = gtk.MessageDialog( type=gtk.MESSAGE_INFO, 5550 message_format=message, 5551 buttons=gtk.BUTTONS_CLOSE ) 5552 5553 settings = d.get_settings() 5554 try: 5555 settings.set_property( "gtk-button-images", False ) 5556 except: 5557 pass 5558 5559 d.run() 5560 d.destroy() 5561 return
5562 # information() 5563 info = information 5564 5565
5566 -def warning( message ):
5567 """Show warning message to user.""" 5568 5569 d = gtk.MessageDialog( type=gtk.MESSAGE_WARNING, 5570 message_format=message, 5571 buttons=gtk.BUTTONS_CLOSE ) 5572 5573 settings = d.get_settings() 5574 try: 5575 settings.set_property( "gtk-button-images", False ) 5576 except: 5577 pass 5578 5579 d.run() 5580 d.destroy() 5581 return
5582 # warning() 5583 warn = warning 5584 5585
5586 -def error( message ):
5587 """Show error message to user.""" 5588 5589 d = gtk.MessageDialog( type=gtk.MESSAGE_ERROR, 5590 message_format=message, 5591 buttons=gtk.BUTTONS_CLOSE ) 5592 5593 settings = d.get_settings() 5594 try: 5595 settings.set_property( "gtk-button-images", False ) 5596 except: 5597 pass 5598 5599 d.run() 5600 d.destroy() 5601 return
5602 # error() 5603 err = error 5604 5605
5606 -def yesno( message, yesdefault=False ):
5607 """Show yes/no message to user. 5608 5609 @param yesdefault: if yes should be the default action. 5610 """ 5611 5612 d = gtk.MessageDialog( type=gtk.MESSAGE_QUESTION, 5613 message_format=message, 5614 buttons=gtk.BUTTONS_YES_NO ) 5615 5616 settings = d.get_settings() 5617 try: 5618 settings.set_property( "gtk-button-images", False ) 5619 except: 5620 pass 5621 5622 if yesdefault: 5623 d.set_default_response( gtk.RESPONSE_YES ) 5624 else: 5625 d.set_default_response( gtk.RESPONSE_NO ) 5626 5627 r = d.run() 5628 d.destroy() 5629 5630 if r == gtk.RESPONSE_YES: 5631 return True 5632 elif r == gtk.RESPONSE_NO: 5633 return False 5634 else: 5635 return yesdefault
5636 # yesno() 5637 5638 5639
5640 -def confirm( message, okdefault=False ):
5641 """Show confirm message to user. 5642 5643 @param okdefault: if ok should be the default action. 5644 """ 5645 5646 d = gtk.MessageDialog( type=gtk.MESSAGE_QUESTION, 5647 message_format=message, 5648 buttons=gtk.BUTTONS_OK_CANCEL ) 5649 5650 settings = d.get_settings() 5651 try: 5652 settings.set_property( "gtk-button-images", False ) 5653 except: 5654 pass 5655 5656 if okdefault: 5657 d.set_default_response( gtk.RESPONSE_OK ) 5658 else: 5659 d.set_default_response( gtk.RESPONSE_CANCEL ) 5660 5661 r = d.run() 5662 d.destroy() 5663 5664 if r == gtk.RESPONSE_OK: 5665 return True 5666 elif r == gtk.RESPONSE_CANCEL: 5667 return False 5668 else: 5669 return okdefault
5670 # confirm() 5671 5672 5673
5674 -def run():
5675 """Enter the event loop""" 5676 try: 5677 gtk.main() 5678 except KeyboardInterrupt: 5679 raise SystemExit( "User quit using Control-C" )
5680 # run() 5681
5682 -def quit():
5683 """Quit the event loop""" 5684 gtk.main_quit()
5685 # quit() 5686 5687
5688 -def get_app_by_id( app_id ):
5689 """Given an App unique identifier, return the reference to it.""" 5690 if app_id is None: 5691 try: 5692 return _apps.values()[ 0 ] 5693 except IndexError, e: 5694 raise ValueError( "No application defined!" ) 5695 elif isinstance( app_id, ( str, unicode ) ): 5696 try: 5697 return _apps[ app_id ] 5698 except KeyError, e: 5699 raise ValueError( "Application id \"%s\" doesn't exists!" % \ 5700 app_id ) 5701 elif isinstance( app_id, App ): 5702 return app_id 5703 else: 5704 raise ValueError( "app_id must be string or App instance!" )
5705 # get_app_by_id() 5706 5707
5708 -def get_widget_by_id( widget_id, app_id=None ):
5709 """Given an Widget unique identifier, return the reference to it. 5710 5711 If app_id is not provided, will use the first App found. 5712 5713 @attention: Try to always provide app_id since it may lead to problems if 5714 your program have more than one App. 5715 """ 5716 app = get_app_by_id( app_id ) 5717 5718 if app: 5719 w = app.get_widget_by_id( widget_id ) 5720 if not w: 5721 raise ValueError( "Widget id \"%s\" doesn't exists!" % widget_id ) 5722 else: 5723 return w
5724 # get_widget_by_id() 5725 5726
5727 -def get_value( widget_id, app_id=None ):
5728 """Convenience function to get widget and call its get_value() method.""" 5729 try: 5730 wid = get_widget_by_id( widget_id, app_id ) 5731 return wid.get_value() 5732 except ValueError, e: 5733 raise ValueError( e )
5734 # get_value() 5735 5736
5737 -def set_value( widget_id, value, app_id=None ):
5738 """Convenience function to get widget and call its set_value() method.""" 5739 try: 5740 wid = get_widget_by_id( widget_id, app_id ) 5741 wid.set_value( value ) 5742 except ValueError, e: 5743 raise ValueError( e )
5744 # set_value() 5745 5746
5747 -def show( widget_id, app_id=None ):
5748 """Convenience function to get widget and call its show() method.""" 5749 try: 5750 wid = get_widget_by_id( widget_id, app_id ) 5751 wid.show() 5752 except ValueError, e: 5753 raise ValueError( e )
5754 # show() 5755 5756
5757 -def hide( widget_id, app_id=None ):
5758 """Convenience function to get widget and call its hide() method.""" 5759 try: 5760 wid = get_widget_by_id( widget_id, app_id ) 5761 wid.hide() 5762 except ValueError, e: 5763 raise ValueError( e )
5764 # hide() 5765 5766
5767 -def set_active( widget_id, active=True, app_id=None ):
5768 """Convenience function to get widget and call its set_active() method.""" 5769 try: 5770 wid = get_widget_by_id( widget_id, app_id ) 5771 wid.set_active( active ) 5772 except ValueError, e: 5773 raise ValueError( e )
5774 # set_active() 5775 5776
5777 -def set_inactive( widget_id, app_id=None ):
5778 """ 5779 Convenience function to get widget and call its set_inactive() method. 5780 """ 5781 try: 5782 wid = get_widget_by_id( widget_id, app_id ) 5783 wid.set_inactive() 5784 except ValueError, e: 5785 raise ValueError( e )
5786 # set_inactive() 5787 5788
5789 -def close( app_id=None ):
5790 """Convenience function to get app and call its close() method.""" 5791 try: 5792 app = get_app_by_id( app_id ) 5793 app.close() 5794 except ValueError, e: 5795 raise ValueError( e )
5796 # close() 5797