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