1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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()
116
117 _apps = {}
118
119
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, )
128 try:
129 return getattr( self, naming )
130 except AttributeError:
131 return None
132
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
143 return property( get, set, None, doc )
144
145
146
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
161
162
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
172
173
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
183
184
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
201
202
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 ):
225
226
227
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
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
303
304
307
308
309
310
311 -class _Panel( gtk.ScrolledWindow ):
356
357
358
359
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
370
371
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
382
383
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
396
397
398
400 return "%s( id=%r )" % ( self.__class__.__name__, self.id )
401
402 __repr__ = __str__
403
404
405
407 """Mix-In to auto-generate ids.
408
409 @warning: never use it directly in Eagle applications!
410 """
411 last_id_num = 0
412
417
418 __get_id__ = classmethod( __get_id__ )
419
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
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
476
477
479 return self._img
480
481
482
485
486 __get_by_id__ = classmethod( __get_by_id__ )
487
488
490 gc.collect()
491
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
528
529
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
543
544
555
556
557
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
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
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
626
627
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
643
646
647
648
651
652
653
657
658
659
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
672
673
677
678
679
681 """Bits per pixel"""
682 return self.get_n_channels() * self._img.get_bits_per_sample()
683
684 get_depth = get_bits_per_pixel
685
686
688 """If it has an alpha channel"""
689 return self._img.get_has_alpha()
690
691
692
693
694
753
754
755
756
786
787
788
789
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 ):
814
815
816
818 self._diag.destroy()
819
820
821
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
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
884
885
887 self._diag.show_all()
888 self._diag.run()
889 self._diag.hide()
890
891
892
893
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 ):
910
911
912
914 self._diag.destroy()
915
916
917
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
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
944
945
947 self._diag.show_all()
948 self._diag.run()
949 self._diag.hide()
950
951
952
953
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
986
987
996
997
998
1000 self._diag.destroy()
1001
1002
1003
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
1054
1055
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
1065
1066
1067
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 """
1081
1082
1083
1085 self._diag.destroy()
1086
1087
1088
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
1110
1111
1117
1118
1119
1121 self._diag.show_all()
1122 self._diag.run()
1123 self._diag.hide()
1124
1125
1126
1127
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
1135 border = 12
1136 spacing = 6
1137 width = 600
1138 height = 400
1139 margin = 6
1140
1144
1145
1146
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
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
1212
1213
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
1233
1234
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
1255
1256
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
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
1283
1284
1298
1299
1301 import pdb
1302 pdb.pm()
1303
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
1312
1313
1323
1324 except_hook = staticmethod( except_hook )
1325
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
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
1356
1357
1358 - def get_value( self ):
1359 return self._entry.get_value()
1360
1361
1362
1363 - def set_value( self, value ):
1364 self._entry.set_value( value )
1365
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
1376
1377
1378 - def get_label( self ):
1379 return self.__label
1380
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
1390 __repr__ = __str__
1391
1392
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
1402
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
1537
1538
1545
1546
1547
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
1559
1560
1568
1569
1570
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
1584
1585
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
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
1610
1611
1613 """Show L{PreferencesDialog} associated with this App."""
1614 return self._preferences.run()
1615
1616
1617
1619 return self._win
1620
1621
1622
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
1632
1633
1655
1656
1657
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
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
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
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
1721
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
1745
1746
1749
1750
1751
1754
1755
1756
1759
1760
1761
1764
1765
1766
1769
1770
1771
1775
1776
1777
1780
1781
1782
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
1792
1793
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
1807
1808
1810 if self.__do_close__():
1811 return False
1812 else:
1813 return True
1814
1815
1816
1819 if not os.path.exists( d ):
1820 mkdir( os.path.dirname( d ) )
1821 os.mkdir( d )
1822
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
1833
1834
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
1850
1851
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
1873
1874
1876 """Close application window."""
1877 if self.__do_close__():
1878 self._win.destroy()
1879
1880
1881
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
1894
1895
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
1907
1908
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 ):
1921
1922 return gobject.timeout_add( interval, wrap )
1923
1924
1925
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 ):
1939
1940 return gobject.idle_add( wrap )
1941
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
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
1989
1990
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
2002
2003
2004
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
2071
2072
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
2083
2084
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
2115
2116
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
2122
2123
2131
2132 self._area.connect( "configure_event", configure_event )
2133
2134
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
2142 self._area.connect( "expose_event", expose_event )
2143
2144
2158
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
2179
2180 if self._callback:
2181 self._area.connect( "button_press_event", button_press_event )
2182
2183
2195
2196 if self._callback:
2197 self._area.connect( "button_release_event", button_release_event )
2198
2199
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
2222 if self._callback:
2223 self._area.connect( "motion_notify_event", motion_notify_event )
2224
2225
2226
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
2234
2235
2237 return ( gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND )
2238
2239
2240
2241
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
2274 __color_from__ = staticmethod( __color_from__ )
2275
2276
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
2283 __to_gtk_color__ = staticmethod( __to_gtk_color__ )
2284
2285
2308
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
2317 self.clear()
2318 else:
2319
2320 w, h = old.get_size()
2321 self._pixmap.draw_drawable( self._fg_gc_normal, old,
2322 0, 0, 0, 0, w, h )
2323
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
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
2437
2438
2439
2445
2446
2447
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
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
2472
2473
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
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
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
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
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
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
2585
2586
2588 """Clear using bgcolor."""
2589 self.fill( self.bgcolor )
2590
2591
2592
2593 - def fill( self, color ):
2597
2598
2599
2602
2603
2604
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
2613
2614
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
2623
2624
2626 return self.__label
2627
2628
2629 label = property( get_label, set_label )
2630
2631
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
2637 __repr__ = __str__
2638
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
2657
2658
2659 - def __emit_changed__( self, *args ):
2660 self.emit( "changed" )
2661
2662
2663
2664 - def set_text( self, value ):
2665 self.textview.get_buffer().set_text( value )
2666
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
2673
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
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
2730
2731
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
2739 self._entry.connect( "changed", callback )
2740
2741
2742
2743 - def get_value( self ):
2744 return self._entry.get_text()
2745
2746
2747
2748 - def set_value( self, value ):
2749 self._entry.set_text( str( value ) )
2750
2751
2752
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
2766
2767
2768
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
2793
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
2846
2847
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
2879
2880
2887
2888 self._entry.connect( "value-changed", callback )
2889
2890
2891
2894
2895
2896
2897
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
2939
2940
2943
2944
2945
2948
2949
2950
2951
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
2985
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
3026
3027
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
3051 color_from = staticmethod( color_from )
3052
3053
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
3069
3070
3077
3078 self._entry.connect( "color-set", callback )
3079
3080
3081
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
3097
3098
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
3116
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
3150
3151
3157
3158
3159
3166
3167 self._entry.connect( "font-set", callback )
3168
3169
3170
3172 return self._entry.get_font_name()
3173
3174
3175
3177 self._entry.set_font_name( value )
3178
3179
3180
3181
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
3215
3216
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
3227
3228
3235
3236 self._entry.connect( "changed", callback )
3237
3238
3239
3241 return self._entry.get_active_text()
3242
3243
3244
3246 for i, o in enumerate( self._entry.get_model() ):
3247 if o[ 0 ] == value:
3248 self._entry.set_active( i )
3249
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
3266
3267
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
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
3298
3299
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
3314
3315
3317 """Returns every item/option in this selection."""
3318 return [ str( x[ 0 ] ) for x in self._entry.get_model() ]
3319
3320 options = items
3321
3322
3324 return len( self._entry.get_model() )
3325
3326
3327
3330
3331
3332
3336
3337
3338
3342
3343
3344
3345
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
3362
3368
3369
3370
3372 return self._entry.get_fraction()
3373
3374
3375
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
3385
3386
3388 """Animate progress bar."""
3389 self._entry.pulse()
3390
3391
3392
3393
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
3427
3428
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
3435
3436
3443
3444 self._wid.connect( "toggled", callback )
3445
3446
3447
3449 "Return a tuple with ( horizontal, vertical ) resize mode"
3450 return ( gtk.FILL, 0 )
3451
3452
3453
3455 return self._wid.get_active()
3456
3457
3458
3461
3462
3463
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
3472
3473
3475 return self.__label
3476
3477
3478 label = property( get_label, set_label )
3479
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
3493 try:
3494 return self.__ro_app
3495 except AttributeError:
3496 return None
3497
3498
3500
3501
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
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
3529
3530
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
3539
3540
3544
3545
3546
3548 "Return a tuple with ( horizontal, vertical ) resize mode"
3549 return ( gtk.FILL | gtk.EXPAND, 0 )
3550
3551
3552
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
3561
3562
3564 return self.__label
3565
3566
3567 label = property( get_label, set_label )
3568
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
3590 self.__items = items
3591
3592
3593
3595 return "[" + ", ".join( [ str( x ) for x in self.__items ] ) + "]"
3596
3597 __repr__ = __str__
3598
3599
3601 return len( self.__items )
3602
3603
3604
3607
3608
3609
3611 return self.__items[ index ]
3612
3613
3614
3616 self.__items[ index ] = value
3617
3618
3619
3621 del self.__items[ index ]
3622
3623
3624
3626 return element in self.__items
3627
3628
3629
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 )
3641
3642 for i in xrange( start, end ):
3643 slice.append( self.__items[ i ] )
3644 return slice
3645
3646
3647
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 )
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
3679
3680
3681
3689
3690
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
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
3780
3781
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
3821
3822
3835
3836
3837
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
3845
3846
3848 index = path[ 0 ]
3849 v = ( index, None )
3850 for c in self.data_changed_callback:
3851 c( self.app, self, v )
3852
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
3859
3860
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
3927
3928 sw.set_policy( hscrollbar_policy=gtk.POLICY_NEVER,
3929 vscrollbar_policy=gtk.POLICY_NEVER )
3930 d.show_all()
3931
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
3964
3965
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
3987
3988
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
4000
4001
4009
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
4016 data = treeview.get_model()[ path ]
4017 result = edit_dialog( data )
4018 if result:
4019 self[ path[ 0 ] ] = result
4020
4021
4022
4023 self._table.connect( "row-activated", row_activated )
4024
4025
4026
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
4044
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
4058
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
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
4078
4079
4082 result = self.selected()
4083 for c in self.selection_callback:
4084 c( self.app, self, result )
4085
4086
4087 selection = self._table.get_selection()
4088 selection.connect( "changed", selection_changed )
4089
4090
4091
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
4099 cid, order = self._model.get_sort_column_id()
4100 self._model.set_sort_column_id( cid, order )
4101
4102
4103 - def toggled( cell_render, path, col ):
4104 self._model[ path ][ col ] = not self._model[ path ][ col ]
4105
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
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
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
4212 col.set_cell_data_func( cell_rend, func, i )
4213
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 )