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.7"
27 __revision__ = "$Rev: 94 $"
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", "Tabs", "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 "ExpandPolicy",
81 ]
82
83 import os
84 import sys
85 import gc
86 import cPickle as pickle
87 import htmllib
88 import formatter
89 import weakref
90
91 try:
92 import pygtk
93 pygtk.require( "2.0" )
94 import gtk
95 import pango
96 import gobject
97 except ImportError, e:
98 sys.stderr.writelines(
99 ( "Missing module: ", str( e ), "\n",
100 "This module is part of pygtk (http://pygtk.org).\n",
101 ) )
102 sys.exit( -1 )
103
104 required_gtk = ( 2, 6, 0 )
105 m = gtk.check_version( *required_gtk )
106 if m:
107 sys.stderr.writelines(
108 ( "Error checking GTK version: %s\n"
109 "This system requires pygtk >= %s, you have %s installed.\n" )
110 % ( m,
111 ".".join( [ str( v ) for v in required_gtk ] ),
112 ".".join( [ str( v ) for v in gtk.pygtk_version ] )
113 ) )
114 sys.exit( -1 )
115
116
117 if not sys.platform.startswith( "win" ):
118 gtk.gdk.threads_init()
119
120 _apps = {}
121
122
124 """Generates a Read-Only property.
125
126 The generated property can be assigned only one value, if this value
127 is not None, it cannot be changed.
128 """
129 naming = "__ro_%s__" % ( name, )
131 try:
132 return getattr( self, naming )
133 except AttributeError:
134 return None
135
136 - def set( self, value ):
137 try:
138 v = getattr( self, naming )
139 except AttributeError:
140 v = None
141 if v is None:
142 setattr( self, naming, value )
143 else:
144 raise Exception( "Read Only property '%s'." % ( name, ) )
145
146 return property( get, set, None, doc )
147
148
149
151 if not isinstance( callback, ( tuple, list ) ):
152 if callback is None:
153 return tuple()
154 elif callable( callback ):
155 return ( callback, )
156 else:
157 raise TypeError( "Callback '%s' is not callable!" % ( callback, ) )
158 else:
159 for c in callback:
160 if not callable( c ):
161 raise TypeError( "Callback '%s' is not callable!" % ( c, ) )
162 return callback
163
164
165
167 if not isinstance( string, ( tuple, list ) ):
168 if string is None:
169 return tuple()
170 else:
171 return ( str( string ), )
172 else:
173 return tuple( [ str( s ) for s in string ] )
174
175
176
178 if not isinstance( obj, ( tuple, list ) ):
179 if obj is None:
180 return tuple()
181 else:
182 return ( obj, )
183 else:
184 return tuple( obj )
185
186
187
189 style = gtkwidget.get_style()
190 iconset = style.lookup_icon_set( stock_id )
191 if iconset:
192 icons = []
193 for s in iconset.get_sizes():
194 i = iconset.render_icon( style,
195 gtk.TEXT_DIR_NONE,
196 gtk.STATE_NORMAL,
197 s,
198 gtkwidget,
199 None
200 )
201 icons.append( i )
202 gtkwidget.set_icon_list( *icons )
203
204
205
207 __slots__ = ( "fill", "expand" )
208 - def __init__( self, expand=False, fill=True ):
209 self.expand = expand
210 self.fill = fill
211
212
213
215 p = 0
216 if self.expand:
217 p |= gtk.EXPAND
218 if self.fill:
219 p |= gtk.FILL
220 return p
221
222
223
225 return "ExpandRule( expand=%s, fill=%s )" % \
226 ( self.expand, self.fill )
227
228 __repr__ = __str__
229
230
233 Rule = _ExpandRule
234 __slots__ = ( "horizontal", "vertical" )
235
238 if isinstance( arg, _ExpandRule ):
239 return arg
240 elif isinstance( arg, ( tuple, list ) ):
241 return _ExpandRule( *arg )
242 elif isinstance( arg, dict ):
243 return _ExpandRule( **arg )
244
245
246 h = kargs.get( "horizontal", None )
247 if h is not None:
248 self.horizontal = conv_arg( h )
249 else:
250 self.horizontal = _ExpandRule()
251
252 v = kargs.get( "vertical", None )
253 if v is not None:
254 self.vertical = conv_arg( v )
255 else:
256 self.vertical = _ExpandRule()
257
258
259
261 return "%s( horizontal=%s, vertical=%s )" % \
262 ( self.__class__.__name__, self.horizontal, self.vertical )
263
264 __repr__ = __str__
265
266
267
268 - class All( Policy ):
272
273
274
279
280
281
286
287
288
293
294
295
300
301
302
307
308
309
310 - class Fill( Policy ):
314
315
316
317
318
320 """Internal widget to arrange components in tabular form.
321
322 @warning: never use it directly in Eagle applications!
323 """
324
325 padding = 3
326 id = _gen_ro_property( "id" )
327 children = _gen_ro_property( "children" )
328 horizontal = _gen_ro_property( "horizontal" )
329
330
331 - def __init__( self, id, children, horizontal=False ):
341
342
343
345 """Lay out components in a horizontal or vertical table."""
346 if not self.children:
347 return
348
349 n = len( self.children )
350
351 if self.horizontal:
352 self.resize( 2, n )
353 else:
354 self.resize( n, 2 )
355
356 if self.horizontal:
357 orientation = _EGWidget.ORIENTATION_HORIZONTAL
358 else:
359 orientation = _EGWidget.ORIENTATION_VERTICAL
360
361 for idx, c in enumerate( self.children ):
362 c.__configure_orientation__( orientation )
363 w = c.__get_widgets__()
364 policy = c.expand_policy
365
366 if len( w ) == 1:
367
368 if isinstance( policy, ( tuple, list ) ):
369 policy = policy[ -1 ]
370
371 xrm = policy.horizontal.__get_gtk_resize_policy__()
372 yrm = policy.vertical.__get_gtk_resize_policy__()
373
374 if self.horizontal:
375 row0 = 0
376 row1 = 2
377 col0 = idx
378 col1 = idx + 1
379 else:
380 row0 = idx
381 row1 = idx + 1
382 col0 = 0
383 col1 = 2
384
385 self.attach( w[ 0 ], col0, col1, row0, row1,
386 xoptions=xrm,
387 yoptions=yrm,
388 xpadding=self.padding,
389 ypadding=self.padding )
390
391 elif len( w ) == 2:
392 if not isinstance( policy, ( tuple, list ) ):
393 policy = ( policy, policy )
394
395 xrm = ( policy[ 0 ].horizontal.__get_gtk_resize_policy__(),
396 policy[ 1 ].horizontal.__get_gtk_resize_policy__() )
397 yrm = ( policy[ 0 ].vertical.__get_gtk_resize_policy__(),
398 policy[ 1 ].vertical.__get_gtk_resize_policy__() )
399
400 if self.horizontal:
401 row0 = 0
402 row1 = 1
403 row2 = 1
404 row3 = 2
405 col0 = idx
406 col1 = idx + 1
407 col2 = idx
408 col3 = idx + 1
409 else:
410 row0 = idx
411 row1 = idx + 1
412 row2 = idx
413 row3 = idx + 1
414 col0 = 0
415 col1 = 1
416 col2 = 1
417 col3 = 2
418 self.attach( w[ 0 ], col0, col1, row0, row1,
419 xoptions=xrm[ 0 ],
420 yoptions=yrm[ 0 ],
421 xpadding=self.padding,
422 ypadding=self.padding )
423 self.attach( w[ 1 ], col2, col3, row2, row3,
424 xoptions=xrm[ 1 ],
425 yoptions=yrm[ 1 ],
426 xpadding=self.padding,
427 ypadding=self.padding )
428
429
430
433
434
435
436
437 -class _Panel( gtk.ScrolledWindow ):
482
483
484
485
487 """Internal widget to arrange components vertically.
488
489 @warning: never use it directly in Eagle applications!
490 """
491
492 _horizontal = False
493 _hscrollbar_policy = gtk.POLICY_NEVER
494 _vscrollbar_policy = gtk.POLICY_AUTOMATIC
495
496
497
499 """Internal widget to arrange components horizontally.
500
501 @warning: never use it directly in Eagle applications!
502 """
503
504 _horizontal = True
505 _hscrollbar_policy = gtk.POLICY_AUTOMATIC
506 _vscrollbar_policy = gtk.POLICY_NEVER
507
508
509
511 """The basic Eagle Object.
512
513 All eagle objects provides an attribute "id".
514
515 @warning: never use it directly in Eagle applications!
516 """
517
518 id = _gen_ro_property( "id" )
519
522
523
524
526 return "%s( id=%r )" % ( self.__class__.__name__, self.id )
527
528 __repr__ = __str__
529
530
531
533 """Mix-In to auto-generate ids.
534
535 @warning: never use it directly in Eagle applications!
536 """
537 last_id_num = 0
538
543
544 __get_id__ = classmethod( __get_id__ )
545
546
547
548 -class Image( _EGObject, AutoGenId ):
549 """
550 An image that can be loaded from files or binary data and saved to files.
551 """
552 _id2obj_ = weakref.WeakValueDictionary()
553
555 """Image constructor.
556
557 Images can be constructed in 2 ways using keyword arguments:
558 - from files, in this case you give it B{filename} keyword:
559
560 >>> Image( filename='myfile.png' )
561
562 - from raw data, in this case you need to provide at least
563 B{data}, B{width} and B{height} as arguments. Optional
564 arguments are I{depth}, I{has_alpha} and I{row_stride}.
565 See L{load_data()} for more information:
566
567 >>> Image( data=data, width=200, height=200, depth=32, has_alpha=False )
568
569 @see: L{load_data()}
570 @see: L{load_file()}
571 """
572 id = kargs.get( "id" ) or self.__get_id__()
573 _EGObject.__init__( self, id )
574
575 self._img = None
576
577 if "filename" in kargs:
578 self.load_file( kargs[ "filename" ] )
579 elif "data" in kargs and "width" in kargs and "height" in kargs:
580 k = { "data": kargs[ "data" ],
581 "width": kargs[ "width" ],
582 "height": kargs[ "height" ],
583 }
584 if "depth" in kargs:
585 k[ "depth" ] = kargs[ "depth" ]
586 if "has_alpha" in kargs:
587 k[ "has_alpha" ] = kargs[ "has_alpha" ]
588 if "rowstride" in kargs:
589 k[ "rowstride" ] = kargs[ "rowstride" ]
590 self.load_data( **k )
591 elif "__int_image__" in kargs:
592 if isinstance( kargs[ "__int_image__" ], gtk.gdk.Pixbuf ):
593 self._img = kargs[ "__int_image__" ]
594 else:
595 raise ValueError( "Wrong internal image given!" )
596 elif len( kargs ) > 0:
597 params = [ "%s=%r" % kv for kv in kargs.iteritems() ]
598 raise ValueError( "Unknow parameters: %s" % params )
599
600 Image._id2obj_[ self.id ] = self
601
602
603
605 return self._img
606
607
608
611
612 __get_by_id__ = classmethod( __get_by_id__ )
613
614
616 gc.collect()
617
618
619
620 - def save( self, filename, format=None, **options ):
621 """Save image to a file.
622
623 If format is not specified, it will be guessed from filename.
624
625 Format may be an extension or a mime type, see
626 L{get_writable_formats()}.
627
628 @see: L{get_writable_formats()}.
629 @raise Exception: if errors happened during write
630 @raise ValueError: if format is unsupported
631 """
632 if isinstance( filename, ( tuple, list ) ):
633 filename = os.path.join( *filename )
634
635 if format is None:
636 format = filename.split( os.path.extsep )[ -1 ]
637
638 format = format.lower()
639 t = None
640 for f in self.get_writable_formats():
641 if format == f[ "name" ] or \
642 format in f[ "extensions" ] or \
643 format in f[ "mime_types" ]:
644 t = f[ "name" ]
645 break
646 if t:
647 try:
648 self._img.save( filename, t, options )
649 except gobject.GError, e:
650 raise Exception( e )
651 else:
652 raise ValueError( "Unsupported file format: \"%s\"" % format )
653
654
655
657 """Get supported image format information.
658
659 @return: list of dicts with keys:
660 - B{name}: format name
661 - B{description}: format description
662 - B{extensions}: extensions that match format
663 - B{mime_types}: mime types that match format
664 - B{is_writable}: if it is possible to write in this format, otherwise
665 it's just readable
666 """
667 return gtk.gdk.pixbuf_get_formats()
668
669
670
681
682
683
685 """Load image from file given its filename.
686
687 filename may be a string or a tuple/list with path elements,
688 this helps your program to stay portable across different platforms.
689
690 >>> i = Image()
691 >>> i.load_file( 'img.png' )
692 >>> i.load_file( ( 'test', 'img.png' ) )
693 """
694 if isinstance( filename, ( tuple, list ) ):
695 filename = os.path.join( *filename )
696
697 try:
698 self._img = gtk.gdk.pixbuf_new_from_file( filename )
699 except gobject.GError, e:
700 raise Exception( e )
701
702
703
704 - def load_data( self, data, width, height,
705 depth=24, has_alpha=None, rowstride=None ):
706 """Load image from raw data.
707
708 If no value is provided as B{has_alpha}, then it's set to C{False}
709 if B{depth} is less or equal 24 or set to C{True} if depth is 32.
710
711 If no value is provided as B{rowstride}, then it's set to
712 M{width * depth / bits_per_sample}.
713
714 >>> i = Image()
715 >>> i.load_data( my_data1, 800, 600, depth=32, has_alpha=False )
716 >>> i.load_data( my_data2, 400, 300, depth=24 )
717 """
718 colorspace = gtk.gdk.COLORSPACE_RGB
719 bits_per_sample = 8
720
721 if has_alpha is None:
722 if depth <= 24:
723 has_alpha=False
724 else:
725 has_alpha=True
726
727 if rowstride is None:
728 rowstride = width * depth / bits_per_sample
729
730 if len( data ) < height * rowstride:
731 raise ValueError( ( "data must be at least "
732 "width * height * rowstride long."
733 "Values are: data size=%d, required=%d" ) %
734 ( len( data ), height * rowstride ) )
735
736 if isinstance( data, list ):
737
738 try:
739 import Numeric
740 data = Numeric.array( data, typecode=Numeric.Character )
741 except ImportError:
742 try:
743 import array
744 data = array.array( 'c', data )
745 except:
746 data = tuple( data )
747
748 self._img = gtk.gdk.pixbuf_new_from_data( data, colorspace,
749 has_alpha, bits_per_sample,
750 width, height, rowstride )
751
752
753
755 """Return raw data and information about this image.
756
757 @return: a tuple of:
758 - width
759 - height
760 - depth
761 - has alpha?
762 - rowstride
763 - raw pixel data
764 """
765 return ( self.get_width(), self.get_height(), self.get_depth(),
766 self.has_alpha(), self.get_rowstride(),
767 self._img.get_pixels() )
768
769
772
773
774
777
778
779
783
784
785
787 """Row stride is the allocated size of a row.
788
789 Generally, rowstride is the number of elements in a row multiplied
790 by the size of each element (bits per pixel).
791
792 But there are cases that there is more space left, a padding, to
793 align it to some boundary, so you may get different value for
794 row stride.
795 """
796 return self._img.get_rowstride()
797
798
799
803
804
805
807 """Bits per pixel"""
808 return self.get_n_channels() * self._img.get_bits_per_sample()
809
810 get_depth = get_bits_per_pixel
811
812
814 """If it has an alpha channel"""
815 return self._img.get_has_alpha()
816
817
818
819
820
882
883
884
885
915
916
917
918
920 """A window that displays information about the application.
921
922 @attention: avoid using this directly, use L{AboutButton} instead.
923 """
924 border = 12
925 spacing = 6
926 width = 600
927 height = 400
928 margin = 6
929
930 - def __init__( self, app,
931 title, author=None, description=None, help=None,
932 version=None, license=None, copyright=None ):
943
944
945
947 self._diag.destroy()
948
949
950
952 win = self.app.__get_window__()
953 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
954 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
955 self._diag = gtk.Dialog( title=( "About: %s" % self.title ),
956 parent=win,
957 flags=flags, buttons=btns )
958
959 self._diag.set_border_width( self.border )
960 self._diag.set_default_size( self.width, self.height )
961 self._diag.set_has_separator( False )
962 self._diag.vbox.set_spacing( self.spacing )
963
964 _set_icon_list( self._diag, gtk.STOCK_ABOUT )
965
966 self._text = RichText( id="About-%s" % self.app.id )
967 self._diag.vbox.pack_start( self._text._widgets[ 0 ], True, True )
968
969 self.__setup_text__()
970
971
972
973 - def __setup_text__( self ):
974 self._text.append( "<h1>%s</h1>" % self.title )
975
976 if self.version:
977 v = ".".join( self.version )
978 self._text.append( "<i>%s</i>" % v )
979
980 self._text.append( "<hr />" )
981
982 if self.description:
983 self._text.append( "<h2>Description</h2>" )
984 for l in self.description:
985 self._text.append( "<p>%s</p>" % l )
986
987 if self.license:
988 self._text.append( "<h2>License</h2><p>" )
989 self._text.append( ", ".join( self.license ) )
990 self._text.append( "</p>" )
991
992 if self.author:
993 if len( self.author ) == 1:
994 self._text.append( "<h2>Author</h2>" )
995 else:
996 self._text.append( "<h2>Authors</h2>" )
997
998 self._text.append( "<ul>" )
999 for a in self.author:
1000 self._text.append( "<li>%s</li>" % a )
1001 self._text.append( "</ul>" )
1002
1003 if self.help:
1004 self._text.append( "<h2>Help</h2>" )
1005 for l in self.help:
1006 self._text.append( "<p>%s</p>" % l )
1007
1008 if self.copyright:
1009 self._text.append( "<h2>Copyright</h2>" )
1010 for l in self.copyright:
1011 self._text.append( "<p>%s</p>" % l )
1012
1013
1014
1016 self._diag.show_all()
1017 self._diag.run()
1018 self._diag.hide()
1019
1020
1021
1022
1024 """A window that displays application help.
1025
1026 @attention: avoid using this directly, use L{HelpButton} instead.
1027 """
1028 border = 12
1029 spacing = 6
1030 width = 600
1031 height = 400
1032 margin = 6
1033
1034 - def __init__( self, app, title, help=None ):
1039
1040
1041
1043 self._diag.destroy()
1044
1045
1046
1048 win = self.app.__get_window__()
1049 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
1050 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
1051 self._diag = gtk.Dialog( title=( "Help: %s" % self.title ),
1052 parent=win,
1053 flags=flags, buttons=btns )
1054 self._diag.set_border_width( self.border )
1055 self._diag.set_default_size( self.width, self.height )
1056 self._diag.set_has_separator( False )
1057 self._diag.vbox.set_spacing( self.spacing )
1058 _set_icon_list( self._diag, gtk.STOCK_HELP )
1059
1060 self._text = RichText( id="About-%s" % self.app.id )
1061 self._diag.vbox.pack_start( self._text._widgets[ 0 ], True, True )
1062
1063 self.__setup_text__()
1064
1065
1066
1067 - def __setup_text__( self ):
1068 self._text.append( "<h1>%s</h1>" % self.title )
1069 self._text.append( "<h2>Help</h2>" )
1070 for l in self.help:
1071 self._text.append( "<p>%s</p>" % l )
1072
1073
1074
1076 self._diag.show_all()
1077 self._diag.run()
1078 self._diag.hide()
1079
1080
1081
1082
1084 """A dialog to choose a file.
1085
1086 @attention: avoid using this directly, use L{App.file_chooser},
1087 L{OpenFileButton}, L{SaveFileButton} or L{SelectFolderButton} instead.
1088 """
1089 ACTION_OPEN = 0
1090 ACTION_SAVE = 1
1091 ACTION_SELECT_FOLDER = 2
1092 ACTION_CREATE_FOLDER = 3
1093
1094 - def __init__( self, app, action, filename=None,
1095 title=None, filter=None, multiple=False ):
1096 """Dialog to choose files.
1097
1098 filter may be a single pattern (ie: '*.png'), mime type
1099 (ie: 'text/html') or a list of patterns or mime types or
1100 a list of lists, each sub list with a filter name and mime type/
1101 patterns accepted. Examples:
1102 [ [ 'Images', '*.ppm', 'image/jpeg', 'image/png' ],
1103 [ 'Text', '*.text', 'text/plain' ],
1104 ]
1105 """
1106 _EGWidget.__init__( self, self.__get_id__(), app )
1107 self.action = action
1108 self.filter = filter
1109 self.multiple = multiple or False
1110 self.filename = filename
1111 self.title = title or self.__gen_title__()
1112
1113 self.__setup_gui__()
1114
1115
1116
1125
1126
1127
1129 self._diag.destroy()
1130
1131
1132
1134 win = self.app.__get_window__()
1135 a = { self.ACTION_OPEN: gtk.FILE_CHOOSER_ACTION_OPEN,
1136 self.ACTION_SAVE: gtk.FILE_CHOOSER_ACTION_SAVE,
1137 self.ACTION_SELECT_FOLDER: gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
1138 self.ACTION_CREATE_FOLDER: gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER,
1139 }.get( self.action, gtk.FILE_CHOOSER_ACTION_OPEN )
1140
1141 b = ( gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT,
1142 gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL )
1143 self._diag = gtk.FileChooserDialog( title=self.title,
1144 parent=win,
1145 action=a,
1146 buttons=b )
1147 _set_icon_list( self._diag, gtk.STOCK_OPEN )
1148 self._diag.set_select_multiple( self.multiple )
1149 if self.filter:
1150 if isinstance( self.filter, ( tuple, list ) ):
1151 for f in self.filter:
1152 filter = gtk.FileFilter()
1153 if isinstance( f, ( tuple, list ) ):
1154 filter.set_name( f[ 0 ] )
1155 for e in f[ 1 : ]:
1156 if '/' in e:
1157 filter.add_mime_type( e )
1158 else:
1159 filter.add_pattern( e )
1160 elif isinstance( f, ( str, unicode ) ):
1161 filter.set_name( f )
1162 if '/' in f:
1163 filter.add_mime_type( f )
1164 else:
1165 filter.add_pattern( f )
1166 else:
1167 raise ValueError( "invalid filter!" )
1168 self._diag.add_filter( filter )
1169
1170 elif isinstance( self.filter, ( str, unicode ) ):
1171 filter = gtk.FileFilter()
1172 filter.set_name( self.filter )
1173 if '/' in self.filter:
1174 filter.add_mime_type( self.filter )
1175 else:
1176 filter.add_pattern( self.filter )
1177 self._diag.set_filter( filter )
1178 else:
1179 raise ValueError( "invalid filter!" )
1180 if self.filename:
1181 self._diag.set_filename( self.filename )
1182
1183
1184
1186 self._diag.show_all()
1187 r = self._diag.run()
1188 self._diag.hide()
1189 if r == gtk.RESPONSE_ACCEPT:
1190 return self._diag.get_filename()
1191 else:
1192 return None
1193
1194
1195
1196
1198 """A dialog to present user with preferences.
1199
1200 Preferences is another L{App} area, just like C{left}, C{right}, C{center},
1201 C{top} or C{bottom}, but will be displayed in a separate window.
1202
1203 @attention: avoid using this directly, use L{PreferencesButton} instead.
1204 """
1210
1211
1212
1214 self._diag.destroy()
1215
1216
1217
1219 win = self.app.__get_window__()
1220 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
1221 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
1222 self._diag = gtk.Dialog( title=( "Preferences: %s" % self.app.title ),
1223 parent=win,
1224 flags=flags, buttons=btns )
1225 self._diag.set_default_size( 400, 300 )
1226 _set_icon_list( self._diag, gtk.STOCK_PREFERENCES )
1227
1228 self._sw = gtk.ScrolledWindow()
1229 self._diag.get_child().pack_start( self._sw, expand=True, fill=True )
1230
1231 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
1232 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
1233
1234 self._tab = _Table( self.id, self.children )
1235 self._sw.add_with_viewport( self._tab )
1236 self._sw.get_child().set_shadow_type( gtk.SHADOW_NONE )
1237 self._sw.set_shadow_type( gtk.SHADOW_NONE )
1238
1239
1240
1246
1247
1248
1250 self._diag.show_all()
1251 self._diag.run()
1252 self._diag.hide()
1253
1254
1255
1256
1258 """Dialog to show uncaught exceptions.
1259
1260 This dialog shows information about uncaught exceptions and also save
1261 the traceback to a file.
1262 """
1263
1264 border = 12
1265 spacing = 6
1266 width = 600
1267 height = 400
1268 margin = 6
1269
1273
1274
1275
1277 b = ( gtk.STOCK_QUIT, gtk.RESPONSE_CLOSE )
1278 self._diag = gtk.Dialog( "Application Crashed!",
1279 parent=None,
1280 flags=gtk.DIALOG_MODAL,
1281 buttons=b )
1282 self._diag.set_border_width( self.border )
1283 self._diag.set_default_size( self.width, self.height )
1284 self._diag.set_has_separator( False )
1285 self._diag.vbox.set_spacing( self.spacing )
1286
1287 self._hbox1 = gtk.HBox()
1288
1289 self._label1 = gtk.Label( "<b>Exception type:</b>" )
1290 self._label1.set_use_markup( True )
1291 self._hbox1.pack_start( self._label1, False, False, self.spacing )
1292 self._label1.show()
1293
1294 self._exctype = gtk.Label()
1295 self._hbox1.pack_start( self._exctype, False, True )
1296 self._exctype.show()
1297
1298 self._diag.vbox.pack_start( self._hbox1, False, False )
1299 self._hbox1.show()
1300
1301 self._hbox2 = gtk.HBox()
1302
1303 self._label2 = gtk.Label( "<b>This info was saved to:</b>" )
1304 self._label2.set_use_markup( True )
1305 self._hbox2.pack_start( self._label2, False, False, self.spacing )
1306 self._label2.show()
1307
1308 self._save_name = gtk.Label()
1309 self._hbox2.pack_start( self._save_name, False, True )
1310 self._save_name.show()
1311
1312 self._diag.vbox.pack_start( self._hbox2, False, False )
1313 self._hbox2.show()
1314
1315 self._sw = gtk.ScrolledWindow()
1316 self._sw.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC )
1317 self._sw.set_shadow_type( gtk.SHADOW_IN )
1318 self._text = gtk.TextView()
1319 self._text.set_editable( False )
1320 self._text.set_cursor_visible( False )
1321 self._text.set_wrap_mode( gtk.WRAP_WORD )
1322 self._text.set_left_margin( self.margin )
1323 self._text.set_right_margin( self.margin )
1324
1325 self._sw.add( self._text )
1326 self._text.show()
1327 self._diag.vbox.pack_start( self._sw, expand=True, fill=True )
1328 self._sw.show()
1329 self.__setup_text__()
1330
1331
1332
1333 - def __setup_text__( self ):
1334 self._buf = self._text.get_buffer()
1335 self._buf.create_tag( "label", weight=pango.WEIGHT_BOLD )
1336 self._buf.create_tag( "code", foreground="gray25",
1337 family="monospace" )
1338 self._buf.create_tag( "exc", foreground="#880000",
1339 weight=pango.WEIGHT_BOLD )
1340
1341
1342
1344 import traceback
1345 self._exctype.set_text( str( exctype ) )
1346 self.print_tb( tb )
1347
1348 lines = traceback.format_exception_only( exctype, value )
1349 msg = lines[ 0 ]
1350 result = msg.split( ' ', 1 )
1351 if len( result ) == 1:
1352 msg = result[ 0 ]
1353 arguments = ""
1354 else:
1355 msg, arguments = result
1356
1357 self._insert_text( "\n" )
1358 self._insert_text( msg, "exc" )
1359 self._insert_text( " " )
1360 self._insert_text( arguments )
1361
1362
1363
1365 import traceback
1366 import time
1367 progname = os.path.split( sys.argv[ 0 ] )[ -1 ]
1368 filename = "%s-%s-%s.tb" % ( progname,
1369 os.getuid(),
1370 int( time.time() ) )
1371 filename = os.path.join( os.path.sep, "tmp", filename )
1372 f = open( filename, "wb" )
1373 try:
1374 os.chmod( filename, 0600 )
1375 except:
1376 pass
1377
1378 for e in traceback.format_exception( exctype, value, tb ):
1379 f.write( e )
1380 f.close()
1381 self._save_name.set_text( filename )
1382 sys.stderr.write( "Traceback saved to '%s'.\n" % filename )
1383
1384
1385
1387 import linecache
1388
1389 if limit is None:
1390 if hasattr( sys, "tracebacklimit" ):
1391 limit = sys.tracebacklimit
1392 n = 0
1393 while tb is not None and ( limit is None or n < limit ):
1394 f = tb.tb_frame
1395 lineno = tb.tb_lineno
1396 co = f.f_code
1397 filename = co.co_filename
1398 name = co.co_name
1399 self._print_file( filename, lineno, name )
1400 line = linecache.getline( filename, lineno )
1401 if line:
1402 self._insert_text( " " + line.strip() + "\n\n", "code" )
1403 tb = tb.tb_next
1404 n = n+1
1405
1406
1407
1408 - def _insert_text( self, text, *tags ):
1409 end_iter = self._buf.get_end_iter()
1410 self._buf.insert_with_tags_by_name( end_iter, text, *tags )
1411
1412
1413
1427
1428
1430 import pdb
1431 pdb.pm()
1432
1433
1434
1435
1436 - def run( self, error=None ):
1437 r = self._diag.run()
1438 if r == gtk.RESPONSE_CLOSE or gtk.RESPONSE_DELETE_EVENT:
1439 raise SystemExit( error )
1440
1441
1442
1452
1453 except_hook = staticmethod( except_hook )
1454
1455 sys.excepthook = DebugDialog.except_hook
1456
1457
1458 -class _EGWidLabelEntry( _EGDataWidget ):
1459 """Widget that holds a label and an associated Entry.
1460
1461 @note: _EGWidLabelEntry must B{NOT} be used directly! You should use
1462 a widget that specialize this instead.
1463
1464 @attention: B{Widget Developers:} You must setup an instance attribute
1465 C{_entry} before using it, since this will be set as mnemonic for this
1466 label and also returned in L{__get_widgets__}().
1467 """
1468
1469 - def __init__( self, id, persistent, label="", expand_policy=None ):
1470 """
1471 @param expand_policy: one or two ExpandPolicy elements. If just
1472 one is provided, it will be used for both inner elements.
1473 If two are provided, first will be used for label and
1474 second for entry.
1475 """
1476 if expand_policy is None:
1477 expand_policy = ( ExpandPolicy.Fill(),
1478 ExpandPolicy.Horizontal() )
1479 elif not isinstance( expand_policy, ( list, tuple ) ):
1480 expand_policy = ( expand_policy, expand_policy )
1481
1482 _EGDataWidget.__init__( self, id, persistent,
1483 expand_policy=expand_policy )
1484 self.__label = label
1485 self.__setup_gui__()
1486
1487
1488
1489 - def __setup_gui__( self ):
1490 if self.__label is not None:
1491 self._label = gtk.Label( self.__label )
1492 self._label.set_mnemonic_widget( self._entry )
1493 self._widgets = ( self._label, self._entry )
1494 else:
1495 self._widgets = ( self._entry, )
1496
1497
1498
1500 if self.label:
1501 if setting == self.ORIENTATION_VERTICAL:
1502 self._label.set_justify( gtk.JUSTIFY_RIGHT )
1503 self._label.set_alignment( xalign=1.0, yalign=0.5 )
1504 elif setting == self.ORIENTATION_HORIZONTAL:
1505 self._label.set_justify( gtk.JUSTIFY_LEFT )
1506 self._label.set_alignment( xalign=0.0, yalign=1.0 )
1507
1508
1509
1510 - def get_value( self ):
1511 return self._entry.get_value()
1512
1513
1514
1515 - def set_value( self, value ):
1516 self._entry.set_value( value )
1517
1518
1519
1520 - def set_label( self, label ):
1521 if self.__label is None:
1522 raise ValueError( "You cannot change label of widget created "
1523 "without one. Create it with placeholder! "
1524 "(label='')" )
1525 self.__label = label
1526 self._label.set_text( self.__label )
1527
1528
1529
1530 - def get_label( self ):
1531 return self.__label
1532
1533
1534 label = property( get_label, set_label )
1535
1536
1537 - def __str__( self ):
1538 return "%s( id=%r, label=%r, value=%r )" % \
1539 ( self.__class__.__name__, self.id, self.label,
1540 self.get_value() )
1541
1542 __repr__ = __str__
1543
1544
1545
1546 -class App( _EGObject, AutoGenId ):
1547 """An application window.
1548
1549 This is the base of Eagle programs, since it will hold every graphical
1550 component.
1551
1552 An App window is split in 5 areas:
1553 - left
1554 - right
1555 - center
1556 - top
1557 - bottom
1558 the first 3 have a vertical layout, the other have horizontal layout.
1559 Every area has its own scroll bars that are shown automatically when
1560 need.
1561
1562 Also provided is an extra area, that is shown in another window. This is
1563 the preferences area. It have a vertical layout and components that
1564 hold data are made persistent automatically. You should use
1565 L{PreferencesButton} to show this area.
1566
1567 Extra information like author, description, help, version, license and
1568 copyright are used in specialized dialogs. You may show these dialogs
1569 with L{AboutButton} and L{HelpButton}.
1570
1571 Widgets can be reach with L{get_widget_by_id}, example:
1572 >>> app = App( "My App", left=Entry( id="entry" ) )
1573 >>> app.get_widget_by_id( "entry" )
1574 Entry( id='entry', label='entry', value='' )
1575
1576 You may also reach widgets using dict-like syntax, but with the
1577 special case for widgets that hold data, these will be provided
1578 using their L{set_data<_EGDataWidget.set_data>} and
1579 L{get_data<_EGDataWidget.get_data>}, it make things easier, but
1580 B{be careful to don't misuse it!}. Example:
1581
1582 >>> app= App( "My App", left=Entry( id="entry" ),
1583 ... right=Canvas( "canvas", 300, 300 ) )
1584 >>> app[ "entry" ]
1585 ''
1586 >>> app[ "entry" ] = "text"
1587 >>> app[ "entry" ]
1588 'text'
1589 >>> app[ "canvas" ]
1590 Canvas( id='canvas', width=300, height=300, label='' )
1591 >>> app[ "canvas" ].draw_text( "hello" )
1592 >>> app[ "entry" ].get_value() # will fail, since it's a data widget
1593
1594 """
1595 border_width = 10
1596 spacing = 3
1597
1598 title = _gen_ro_property( "title" )
1599 left = _gen_ro_property( "left" )
1600 right = _gen_ro_property( "right" )
1601 top = _gen_ro_property( "top" )
1602 bottom = _gen_ro_property( "bottom" )
1603 center = _gen_ro_property( "center" )
1604 preferences = _gen_ro_property( "preferences" )
1605 statusbar = _gen_ro_property( "statusbar" )
1606 _widgets = _gen_ro_property( "_widgets" )
1607
1608 - def __init__( self, title, id=None,
1609 center=None, left=None, right=None, top=None, bottom=None,
1610 preferences=None, window_size=( 800, 600 ),
1611 quit_callback=None, data_changed_callback=None,
1612 author=None, description=None, help=None, version=None,
1613 license=None, copyright=None,
1614 statusbar=False ):
1615 """App Constructor.
1616
1617 @param title: application name, to be displayed in the title bar.
1618 @param id: unique id to this application, or None to generate one
1619 automatically.
1620 @param center: list of widgets to be laid out vertically in the
1621 window's center.
1622 @param left: list of widgets to be laid out vertically in the
1623 window's left side.
1624 @param right: list of widgets to be laid out vertically in the
1625 window's right side.
1626 @param top: list of widgets to be laid out horizontally in the
1627 window's top.
1628 @param bottom: list of widgets to be laid out horizontally in the
1629 window's bottom.
1630 @param preferences: list of widgets to be laid out vertically in
1631 another window, this can be shown with L{PreferencesButton}.
1632 @param window_size: tuple of ( width, height ) or None to use the
1633 minimum size.
1634 @param statusbar: if C{True}, an statusbar will be available and
1635 usable with L{status_message} method.
1636 @param author: the application author or list of author, used in
1637 L{AboutDialog}, this can be shown with L{AboutButton}.
1638 @param description: application description, used in L{AboutDialog}.
1639 @param help: help text, used in L{AboutDialog} and L{HelpDialog}, this
1640 can be shown with L{HelpButton}.
1641 @param version: application version, used in L{AboutDialog}.
1642 @param license: application license, used in L{AboutDialog}.
1643 @param copyright: application copyright, used in L{AboutDialog}.
1644 @param quit_callback: function (or list of functions) that will be
1645 called when application is closed. Function will receive as
1646 parameter the reference to App. If return value is False,
1647 it will abort closing the window.
1648 @param data_changed_callback: function (or list of functions) that will
1649 be called when some widget that holds data have its data
1650 changed. Function will receive as parameters:
1651 - App reference
1652 - Widget reference
1653 - new value
1654 """
1655 _EGObject.__init__( self, id )
1656 self.title = title
1657 self.left = left
1658 self.right = right
1659 self.top = top
1660 self.bottom = bottom
1661 self.center = center
1662 self.preferences = preferences
1663 self.window_size = window_size
1664 self.author = _str_tuple( author )
1665 self.description = _str_tuple( description )
1666 self.help = _str_tuple( help )
1667 self.version = _str_tuple( version )
1668 self.license = _str_tuple( license )
1669 self.copyright = _str_tuple( copyright )
1670 self.statusbar = statusbar
1671 self._widgets = {}
1672
1673 self.quit_callback = _callback_tuple( quit_callback )
1674 self.data_changed_callback = _callback_tuple( data_changed_callback )
1675
1676 self.__add_to_app_list__()
1677 self.__setup_gui__()
1678 self.__setup_connections__()
1679 self.load()
1680
1681
1682
1689
1690
1691
1693 w = self.get_widget_by_id( name )
1694 if w is None:
1695 raise ValueError( "Could not find any widget with id=%r" % name )
1696 elif isinstance( w, _EGDataWidget ):
1697 return w.set_value( value )
1698 else:
1699 raise TypeError(
1700 "Could not set value of widget '%s' of type '%s'." % \
1701 ( name, type( w ).__name__ ) )
1702
1703
1704
1712
1713
1714
1716 """Show L{AboutDialog} of this App."""
1717 diag = AboutDialog( app=self,
1718 title=self.title,
1719 author=self.author,
1720 description=self.description,
1721 help=self.help,
1722 version=self.version,
1723 license=self.license,
1724 copyright=self.copyright,
1725 )
1726 diag.run()
1727
1728
1729
1731 """Show L{HelpDialog} of this App."""
1732 diag = HelpDialog( app=self,
1733 title=self.title,
1734 help=self.help,
1735 )
1736 diag.run()
1737
1738
1739
1740 - def file_chooser( self, action, filename=None,
1741 filter=None, multiple=False ):
1742 """Show L{FileChooser} and return selected file(s).
1743
1744 @param action: must be one of ACTION_* as defined in L{FileChooser}.
1745 @param filter: a pattern (ie: '*.png'), mime type or a list.
1746
1747 @see: L{FileChooser}
1748 """
1749 diag = FileChooser( app=self, action=action,
1750 filename=filename, filter=filter,
1751 multiple=multiple )
1752 return diag.run()
1753
1754
1755
1757 """Show L{PreferencesDialog} associated with this App."""
1758 return self._preferences.run()
1759
1760
1761
1763 return self._win
1764
1765
1766
1768 if not self.id:
1769 self.id = self.__get_id__()
1770
1771 if self.id in _apps:
1772 raise ValueError( "App id '%s' already existent!" % self.id )
1773
1774 _apps[ self.id ] = self
1775
1776
1777
1799
1800
1801
1803 self._win = gtk.Window( gtk.WINDOW_TOPLEVEL )
1804 self._win.set_name( self.id )
1805 self._win.set_title( self.title )
1806 if self.window_size:
1807 self._win.set_default_size( *self.window_size )
1808
1809 self._top_layout = gtk.VBox( False )
1810 self._win.add( self._top_layout )
1811
1812 self._vbox = gtk.VBox( False, self.spacing )
1813 self._hbox = gtk.HBox( False, self.spacing )
1814 self._hbox.set_border_width( self.border_width )
1815 self._top_layout.pack_start( self._vbox, expand=True, fill=True )
1816
1817 self.__setup_gui_left__()
1818 self.__setup_gui_right__()
1819 self.__setup_gui_center__()
1820 self.__setup_gui_top__()
1821 self.__setup_gui_bottom__()
1822 self.__setup_gui_preferences__()
1823
1824 has_top = bool( self._top.__get_widgets__() )
1825 has_bottom = bool( self._bottom.__get_widgets__() )
1826 has_left = bool( self._left.__get_widgets__() )
1827 has_right = bool( self._right.__get_widgets__() )
1828 has_center = bool( self._center.__get_widgets__() )
1829
1830 expand_top = False
1831 expand_bottom = False
1832 expand_left = False
1833 expand_right = False
1834 expand_center = has_center
1835 has_hl = has_left or has_center or has_right
1836
1837
1838 if has_left and not has_center:
1839 expand_left = True
1840 if has_right and not has_center:
1841 expand_right = True
1842
1843
1844 if has_top and not has_hl:
1845 expand_top = True
1846 if has_bottom and not has_hl:
1847 expand_bottom = True
1848
1849
1850 if has_hl:
1851 if has_left:
1852 self._hbox.pack_start( self._left, expand_left, True )
1853 if has_center or has_right:
1854 self._hbox.pack_start( gtk.VSeparator(), False, True )
1855
1856 if has_center:
1857 self._hbox.pack_start( self._center, expand_center, True )
1858 if has_right:
1859 self._hbox.pack_start( gtk.VSeparator(), False, True )
1860
1861 if has_right:
1862 self._hbox.pack_start( self._right, expand_right, True )
1863
1864
1865
1866 if has_top:
1867 self._vbox.pack_start( self._top, expand_top, True )
1868 if has_hl or has_bottom:
1869 self._vbox.pack_start( gtk.HSeparator(), False, True )
1870
1871 if has_hl:
1872 self._vbox.pack_start( self._hbox, True, True )
1873 if has_bottom:
1874 self._vbox.pack_start( gtk.HSeparator(), False, True )
1875
1876 if has_bottom:
1877 self._vbox.pack_start( self._bottom, expand_bottom, True )
1878
1879
1880 if self.statusbar:
1881 self._statusbar = gtk.Statusbar()
1882 self._statusbar_ctx = self._statusbar.get_context_id( self.title )
1883 self._statusbar.set_has_resize_grip( True )
1884 self._top_layout.pack_end( self._statusbar,
1885 expand=False, fill=True )
1886
1887 self._win.show_all()
1888
1889
1890
1893
1894
1895
1898
1899
1900
1903
1904
1905
1908
1909
1910
1913
1914
1915
1919
1920
1921
1924
1925
1926
1928 """Notify that widget changed it's value.
1929
1930 Probably you will not need to call this directly.
1931 """
1932 self.save()
1933 for c in self.data_changed_callback:
1934 c( self, widget, value )
1935
1936
1937
1939 self.save()
1940
1941 for c in self.quit_callback:
1942 if not c( self ):
1943 return False
1944
1945 del _apps[ self.id ]
1946 if not _apps:
1947 gtk.main_quit()
1948
1949 return True
1950
1951
1952
1954 if self.__do_close__():
1955 return False
1956 else:
1957 return True
1958
1959
1960
1962 fname = "%s.save_data" % self.id
1963
1964 if sys.platform.startswith( "win" ):
1965 appdata = os.environ.get( "APPDATA", "C:" )
1966 binname = os.path.realpath( sys.argv[ 0 ] ).replace( ":", "" )
1967 d = os.path.join( appdata, "Eagle", binname )
1968 else:
1969 home = os.environ.get( "HOME", "." )
1970 binname = os.path.realpath( sys.argv[ 0 ] )[ 1 : ]
1971 d = os.path.join( home, ".eagle", binname )
1972
1973 if not os.path.exists( d ):
1974 os.makedirs( d )
1975
1976 return os.path.join( d, fname )
1977
1978
1979
1981 """Save data from widgets to file.
1982
1983 Probably you will not need to call this directly.
1984 """
1985 d = {}
1986 for id, w in self._widgets.iteritems():
1987 if isinstance( w, _EGDataWidget ) and w.persistent:
1988 d[ id ] = w.get_value()
1989
1990 if d:
1991 f = open( self.__persistence_filename__(), "wb" )
1992 pickle.dump( d, f, pickle.HIGHEST_PROTOCOL )
1993 f.close()
1994
1995
1996
1998 """Load data to widgets from file.
1999
2000 Probably you will not need to call this directly.
2001 """
2002 try:
2003 f = open( self.__persistence_filename__(), "rb" )
2004 except IOError:
2005 return
2006
2007 d = pickle.load( f )
2008 f.close()
2009
2010 for id, v in d.iteritems():
2011 try:
2012 w = self._widgets[ id ]
2013 except KeyError:
2014 w = None
2015 if isinstance( w, _EGDataWidget ) and w.persistent:
2016 w.set_value( v )
2017
2018
2019
2021 """Close application window."""
2022 if self.__do_close__():
2023 self._win.destroy()
2024
2025
2026
2028 """Display a message in status bar and retrieve its identifier for
2029 later removal.
2030
2031 @see: L{remove_status_message}
2032 @note: this only active if statusbar=True
2033 """
2034 if self.statusbar:
2035 return self._statusbar.push( self._statusbar_ctx, message )
2036 else:
2037 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
2038
2039
2040
2042 """Remove a previously displayed message.
2043
2044 @see: L{status_message}
2045 @note: this only active if statusbar=True
2046 """
2047 if self.statusbar:
2048 self._statusbar.remove( self._statusbar_ctx, message_id )
2049 else:
2050 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
2051
2052
2053
2055 """Register a function to be called after a given timeout/interval.
2056
2057 @param interval: milliseconds between calls.
2058 @param callback: function to call back. This function gets as
2059 argument the app reference and must return C{True} to
2060 keep running, if C{False} is returned, it will not be
2061 called anymore.
2062 @return: id number to be used in L{remove_event_source}
2063 """
2064 - def wrap( *args ):
2066
2067 return gobject.timeout_add( interval, wrap )
2068
2069
2070
2072 """Register a function to be called when system is idle.
2073
2074 System is idle if there is no other event pending.
2075
2076 @param callback: function to call back. This function gets as
2077 argument the app reference and must return C{True} to
2078 keep running, if C{False} is returned, it will not be
2079 called anymore.
2080 @return: id number to be used in L{remove_event_source}
2081 """
2082 - def wrap( *args ):
2084
2085 return gobject.idle_add( wrap )
2086
2087
2088
2089
2090 - def io_watch( self, file, callback,
2091 on_in=False, on_out=False, on_urgent=False, on_error=False,
2092 on_hungup=False ):
2093 """Register a function to be called after an Input/Output event.
2094
2095 @param file: any file object or file descriptor (integer).
2096 @param callback: function to be called back, parameters will be the
2097 application that generated the event, the file that triggered
2098 it and on_in, on_out, on_urgent, on_error or on_hungup,
2099 being True those that triggered the event.
2100 The function must return C{True} to be called back again,
2101 otherwise it is automatically removed.
2102 @param on_in: there is data to read.
2103 @param on_out: data can be written without blocking.
2104 @param on_urgent: there is urgent data to read.
2105 @param on_error: error condition.
2106 @param on_hungup: hung up (the connection has been broken, usually for
2107 pipes and sockets).
2108 @return: id number to be used in L{remove_event_source}
2109 """
2110 - def wrap( source, cb_condition ):
2111 on_in = bool( cb_condition & gobject.IO_IN )
2112 on_out = bool( cb_condition & gobject.IO_OUT )
2113 on_urgent = bool( cb_condition & gobject.IO_PRI )
2114 on_error = bool( cb_condition & gobject.IO_ERR )
2115 on_hungup = bool( cb_condition & gobject.IO_HUP )
2116 return callback( self, source, on_in=on_in,
2117 on_out=on_out, on_urgent=on_urgent,
2118 on_error=on_error, on_hungup=on_hungup )
2119
2120
2121 condition = 0
2122 if on_in:
2123 condition |= gobject.IO_IN
2124 if on_out:
2125 condition |= gobject.IO_OUT
2126 if on_urgent:
2127 condition |= gobject.IO_PRI
2128 if on_error:
2129 condition |= gobject.IO_ERR
2130 if on_hungup:
2131 condition |= gobject.IO_HUP
2132 return gobject.io_add_watch( file, condition, wrap )
2133
2134
2135
2137 """Remove an event generator like those created by L{timeout_add},
2138 L{idle_add} or L{io_watch}.
2139
2140 @param event_id: value returned from L{timeout_add},
2141 L{idle_add} or L{io_watch}.
2142
2143 @return: C{True} if it was removed.
2144 """
2145 return gobject.source_remove( event_id )
2146
2147
2148
2149
2151 """The drawing area.
2152
2153 Eagle's drawing area (Canvas) is provided with a frame and an optional
2154 label, together with scrollbars, to make it fit everywhere.
2155
2156 """
2157 padding = 5
2158 bgcolor= "black"
2159
2160 LEFT = -1
2161 CENTER = 0
2162 RIGHT = 1
2163
2164 FONT_OPTION_BOLD = 1
2165 FONT_OPTION_OBLIQUE = 2
2166 FONT_OPTION_ITALIC = 4
2167
2168 FONT_NAME_NORMAL = "normal"
2169 FONT_NAME_SERIF = "serif"
2170 FONT_NAME_SANS = "sans"
2171 FONT_NAME_MONO = "monospace"
2172
2173 MOUSE_BUTTON_1 = 1
2174 MOUSE_BUTTON_2 = 2
2175 MOUSE_BUTTON_3 = 4
2176 MOUSE_BUTTON_4 = 8
2177 MOUSE_BUTTON_5 = 16
2178
2179 label = _gen_ro_property( "label" )
2180
2181 - def __init__( self, id, width, height, label="", bgcolor=None,
2182 scrollbars=True, callback=None, expand_policy=None ):
2183 """Canvas Constructor.
2184
2185 @param id: unique identifier.
2186 @param width: width of the drawing area in pixels, widget can be
2187 larger or smaller because and will use scrollbars if need.
2188 @param height: height of the drawing area in pixels, widget can be
2189 larger or smaller because and will use scrollbars if need.
2190 @param label: label to display in the widget frame around the
2191 drawing area. If None, no label or frame will be shown.
2192 @param bgcolor: color to paint background.
2193 @param scrollbars: whenever to use scrollbars and make canvas
2194 fit small places.
2195 @param callback: function (or list of functions) to call when
2196 mouse state changed in the drawing area. Function will get
2197 as parameters:
2198 - App reference
2199 - Canvas reference
2200 - Button state (or'ed MOUSE_BUTTON_*)
2201 - horizontal positon (x)
2202 - vertical positon (y)
2203 @param expand_policy: how this widget should fit space, see
2204 L{ExpandPolicy.Policy.Rule}.
2205
2206 @todo: honor the alpha value while drawing colors.
2207 """
2208 _EGWidget.__init__( self, id, expand_policy=expand_policy )
2209 self.__label = label
2210 self.width = width
2211 self.height = height
2212 self.scrollbars = scrollbars
2213
2214 self._pixmap = None
2215 self._callback = _callback_tuple( callback )
2216
2217
2218
2219
2220 self._style = None
2221 self._fg_gc_normal = None
2222 self._bg_gc_normal = None
2223
2224 if bgcolor is not None:
2225 self.bgcolor = self.__color_from__( bgcolor )
2226
2227 self.__setup_gui__( width, height )
2228 self.__setup_connections__()
2229
2230
2231
2233 self._sw = gtk.ScrolledWindow()
2234 self._area = gtk.DrawingArea()
2235
2236 self._sw.set_border_width( self.padding )
2237
2238 if self.label is not None:
2239 self._frame = gtk.Frame( self.label )
2240 self._frame.add( self._sw )
2241 self._frame.set_shadow_type( gtk.SHADOW_OUT )
2242 root = self._frame
2243 else:
2244 root = self._sw
2245
2246 self._area.set_size_request( width, height )
2247 self._sw.add_with_viewport( self._area )
2248 if self.scrollbars:
2249 policy = gtk.POLICY_AUTOMATIC
2250 border = gtk.SHADOW_IN
2251 else:
2252 policy = gtk.POLICY_NEVER
2253 border = gtk.SHADOW_NONE
2254
2255 self._sw.set_policy( hscrollbar_policy=policy,
2256 vscrollbar_policy=policy )
2257 self._sw.child.set_shadow_type( border )
2258 self._sw.show_all()
2259
2260 self._widgets = ( root, )
2261
2262
2263
2265 self._style = self._area.get_style()
2266 self._fg_gc_normal = self._style.fg_gc[ gtk.STATE_NORMAL ]
2267 self._bg_gc_normal = self._style.bg_gc[ gtk.STATE_NORMAL ]
2268
2269
2270
2278
2279 self._area.connect( "configure_event", configure_event )
2280
2281
2283 x , y, width, height = event.area
2284 gc = widget.get_style().fg_gc[ gtk.STATE_NORMAL ]
2285 widget.window.draw_drawable( gc, self._pixmap, x, y, x, y,
2286 width, height )
2287 return False
2288
2289 self._area.connect( "expose_event", expose_event )
2290
2291
2305
2306
2307 buttons_map = {
2308 1: self.MOUSE_BUTTON_1,
2309 2: self.MOUSE_BUTTON_2,
2310 3: self.MOUSE_BUTTON_3,
2311 4: self.MOUSE_BUTTON_4,
2312 5: self.MOUSE_BUTTON_5,
2313 }
2314
2326
2327 if self._callback:
2328 self._area.connect( "button_press_event", button_press_event )
2329
2330
2342
2343 if self._callback:
2344 self._area.connect( "button_release_event", button_release_event )
2345
2346
2348 if self._pixmap is None:
2349 return True
2350
2351 if event.is_hint:
2352 x, y, state = event.window.get_pointer()
2353 else:
2354 x = event.x
2355 y = event.y
2356 state = event.state
2357
2358
2359 btns = get_buttons( state )
2360 x = int( x )
2361 y = int( y )
2362
2363 if btns:
2364 for c in self._callback:
2365 c( self.app, self, btns, x, y )
2366
2367 return True
2368
2369 if self._callback:
2370 self._area.connect( "motion_notify_event", motion_notify_event )
2371
2372
2373
2374 self._area.set_events( gtk.gdk.EXPOSURE_MASK |
2375 gtk.gdk.LEAVE_NOTIFY_MASK |
2376 gtk.gdk.BUTTON_PRESS_MASK |
2377 gtk.gdk.BUTTON_RELEASE_MASK |
2378 gtk.gdk.POINTER_MOTION_MASK |
2379 gtk.gdk.POINTER_MOTION_HINT_MASK )
2380
2381
2382
2383
2385 """Convert from color to internal representation.
2386
2387 Gets a string, integer or tuple/list arguments and converts into
2388 internal color representation.
2389 """
2390 a = 255
2391
2392 if isinstance( color, str ):
2393 try:
2394 c = gtk.gdk.color_parse( color )
2395 r = int( c.red / 65535.0 * 255 )
2396 g = int( c.green / 65535.0 * 255 )
2397 b = int( c.blue / 65535.0 * 255 )
2398 except ValueError, e:
2399 raise ValueError( "%s. color=%r" % ( e, color ) )
2400 elif isinstance( color, gtk.gdk.Color ):
2401 r = int( color.red / 65535.0 * 255 )
2402 g = int( color.green / 65535.0 * 255 )
2403 b = int( color.blue / 65535.0 * 255 )
2404 elif isinstance( color, int ):
2405 r = ( color >> 16 ) & 0xff
2406 g = ( color >> 8 ) & 0xff
2407 b = ( color & 0xff )
2408 elif isinstance( color, ( tuple, list ) ):
2409 if len( color) == 3:
2410 r, g, b = color
2411 else:
2412 a, r, g, b = color
2413
2414 return a, r, g, b
2415
2416 __color_from__ = staticmethod( __color_from__ )
2417
2418
2420 r = int( color[ 1 ] / 255.0 * 65535 )
2421 g = int( color[ 2 ] / 255.0 * 65535 )
2422 b = int( color[ 3 ] / 255.0 * 65535 )
2423 return gtk.gdk.Color( r, g, b )
2424
2425 __to_gtk_color__ = staticmethod( __to_gtk_color__ )
2426
2427
2450
2451
2452
2453 - def resize( self, width, height ):
2454 """Resize the drawing area."""
2455 old = self._pixmap
2456 self._pixmap = gtk.gdk.Pixmap( self._area.window, width, height )
2457 if old is None:
2458
2459 self.clear()
2460 else:
2461
2462 w, h = old.get_size()
2463 self._pixmap.draw_drawable( self._fg_gc_normal, old,
2464 0, 0, 0, 0, w, h )
2465
2466
2467
2468 - def draw_image( self, image, x=0, y=0,
2469 width=None, height=None,
2470 src_x=0, src_y=0 ):
2471 """Draw image on canvas.
2472
2473 By default it draws entire image at top canvas corner.
2474
2475 You may restrict which image areas to use with src_x, src_y, width
2476 and height.
2477
2478 You may choose position on canvas with x and y.
2479 """
2480 if not isinstance( image, Image ):
2481 raise TypeError( ( "image must be instance of Image class, "
2482 "but %s found!" ) % ( type( image ).__name__ ) )
2483
2484 p = image.__get_gtk_pixbuf__()
2485
2486 if src_x >= p.get_width():
2487 raise ValueError( "src_x is greater or equal width!" )
2488
2489 if src_y >= p.get_height():
2490 raise ValueError( "src_y is greater or equal height!" )
2491
2492 if width is None or width < 1:
2493 width = p.get_width()
2494
2495 if height is None or height < 1:
2496 height = p.get_height()
2497
2498 if src_x + width > p.get_width():
2499 width = p.get_width() - src_x
2500 if src_y + height > p.get_height():
2501 height = p.get_height() - src_y
2502
2503 self._pixmap.draw_pixbuf( self._fg_gc_normal,
2504 p, src_x, src_y, x, y, width, height )
2505 self._area.queue_draw_area( x, y, width, width )
2506
2507
2508
2509 - def draw_text( self, text, x=0, y=0,
2510 fgcolor=None, bgcolor=None,
2511 font_name=None, font_size=None, font_options=0,
2512 font_family=None,
2513 width=None, wrap_word=False,
2514 alignment=LEFT, justify=True ):
2515 """Draw text on canvas.
2516
2517 By default text is draw with current font and colors at top canvas
2518 corner.
2519
2520 You may limit width providing a value and choose if it should wrap
2521 at words (wrap_word=True) or characters (wrap_word=False).
2522
2523
2524 Colors can be specified with fgcolor an bgcolor. If not provided, the
2525 system foreground color is used and no background color is used.
2526
2527 Font name, family, size and options may be specified using
2528 font_name, font_family, font_size and font_options, respectively.
2529 Try to avoid using system specific font fames, use those provided
2530 by FONT_NAME_*.
2531
2532 Font options is OR'ed values from FONT_OPTIONS_*.
2533
2534 Font name is a string that have all the information, like
2535 "sans bold 12". This is returned by L{Font}.
2536
2537 Text alignment is one of LEFT, RIGHT or CENTER.
2538 """
2539 if fgcolor is not None:
2540 fgcolor = self.__to_gtk_color__( self.__color_from__( fgcolor ) )
2541 if bgcolor is not None:
2542 bgcolor = self.__to_gtk_color__( self.__color_from__( bgcolor ) )
2543
2544 layout = self._area.create_pango_layout( text )
2545 if width is not None:
2546 layout.set_width( width * pango.SCALE )
2547 if wrap_word:
2548 layout.set_wrap( pango.WRAP_WORD )
2549
2550 layout.set_justify( justify )
2551 alignment = { self.LEFT: pango.ALIGN_LEFT,
2552 self.CENTER: pango.ALIGN_CENTER,
2553 self.RIGHT: pango.ALIGN_RIGHT }.get( alignment,
2554 pango.ALIGN_CENTER )
2555 layout.set_alignment( alignment )
2556
2557 if font_name or font_size or font_options or font_family:
2558 if font_name:
2559 fd = pango.FontDescription( font_name )
2560 else:
2561 fd = layout.get_context().get_font_description()
2562
2563 if font_size:
2564 fd.set_size( font_size * pango.SCALE)
2565 if font_options:
2566 if font_options & self.FONT_OPTION_BOLD:
2567 fd.set_weight( pango.WEIGHT_BOLD )
2568 if font_options & self.FONT_OPTION_ITALIC:
2569 fd.set_style( pango.STYLE_ITALIC )
2570 if font_options & self.FONT_OPTION_OBLIQUE:
2571 fd.set_style( pango.STYLE_OBLIQUE )
2572 layout.set_font_description( fd )
2573
2574 self._pixmap.draw_layout( self._fg_gc_normal, x, y, layout,
2575 fgcolor, bgcolor )
2576 w, h = layout.get_pixel_size()
2577 self._area.queue_draw_area( x, y, w, h )
2578
2579
2580
2581
2587
2588
2589
2591 """Draw points.
2592
2593 Efficient way to draw more than one point with the same
2594 characteristics.
2595 """
2596 gc = self.__configure_gc__( fgcolor=color )
2597 self._pixmap.draw_points( gc, points )
2598 w, h = self._pixmap.get_size()
2599 self._area.queue_draw_area( 0, 0, w, h )
2600
2601
2602
2603 - def draw_line( self, x0, y0, x1, y1, color=None, size=1 ):
2604 """Draw line."""
2605 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2606 self._pixmap.draw_line( gc, x0, y0, x1, y1 )
2607
2608 size2 = size * 2
2609
2610 w, h = abs( x1 - x0 ) + size2, abs( y1 - y0 ) + size2
2611 x, y = max( min( x0, x1 ) - size, 0 ), max( min( y0, y1 ) - size, 0 )
2612 self._area.queue_draw_area( x, y, w, h )
2613
2614
2615
2617 """Draw line segments.
2618
2619 Efficient way to draw more than one line with the same
2620 characteristics.
2621
2622 Lines are not connected, use L{draw_lines} instead.
2623 """
2624 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2625 self._pixmap.draw_segments( gc, segments )
2626 w, h = self._pixmap.get_size()
2627 self._area.queue_draw_area( 0, 0, w, h )
2628
2629
2630
2631 - def draw_lines( self, points, color=None, size=1 ):
2632 """Draw lines connecting points.
2633
2634 Points are connected using lines, but first and last points
2635 are not connected, use L{draw_polygon} instead.
2636 """
2637 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2638 self._pixmap.draw_lines( gc, points )
2639 w, h = self._pixmap.get_size()
2640 self._area.queue_draw_area( 0, 0, w, h )
2641
2642
2643
2644 - def draw_rectangle( self, x, y, width, height, color=None, size=1,
2645 fillcolor=None, filled=False ):
2646 """Draw rectagle.
2647
2648 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2649
2650 If L{color} is provided, it will draw the rectangle's frame, even
2651 if L{filled} is C{True}.
2652 """
2653 if filled:
2654 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2655 self._pixmap.draw_rectangle( gc, True, x, y, width, height )
2656
2657 if size > 0:
2658 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2659 self._pixmap.draw_rectangle( gc, False, x, y, width, height )
2660 else:
2661 size = 0
2662
2663 half = size / 2
2664 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2665
2666
2667
2668 - def draw_arc( self, x, y, width, height, start_angle, end_angle,
2669 color=None, size=1, fillcolor=None, filled=False ):
2670 """Draw arc on canvas.
2671
2672 Arc will be the part of an ellipse that starts at ( L{x}, L{y} )
2673 and have size of L{width}xL{height}.
2674
2675 L{start_angle} and L{end_angle} are in radians, starts from the
2676 positive x-axis and are counter-clockwise.
2677
2678 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2679
2680 If L{color} is provided, it will draw the arc's frame, even
2681 if L{filled} is C{True}. Frame here is just the curve, not the
2682 straight lines that are limited by L{start_angle} and L{end_angle}.
2683 """
2684
2685 mult = 180.0 / 3.1415926535897931 * 64.0
2686 start_angle = int( mult * start_angle )
2687 end_angle = int( mult * end_angle )
2688
2689 if filled:
2690 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2691 self._pixmap.draw_arc( gc, True, x, y, width, height,
2692 start_angle, end_angle )
2693 if size > 0:
2694 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2695 self._pixmap.draw_arc( gc, False, x, y, width, height,
2696 start_angle, end_angle )
2697 else:
2698 size = 0
2699
2700 half = size / 2
2701 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2702
2703
2704
2705 - def draw_polygon( self, points, color=None, size=1,
2706 fillcolor=None, filled=False ):
2707 """Draw polygon on canvas.
2708
2709 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2710
2711 If L{color} is provided, it will draw the polygon's frame, even
2712 if L{filled} is C{True}.
2713 """
2714 if filled:
2715 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2716 self._pixmap.draw_polygon( gc, True, points )
2717
2718 if size > 0:
2719 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2720 self._pixmap.draw_polygon( gc, False, points )
2721 else:
2722 size = 0
2723
2724 w, h = self._pixmap.get_size()
2725 self._area.queue_draw_area( 0, 0, w, h )
2726
2727
2728
2730 """Clear using bgcolor."""
2731 self.fill( self.bgcolor )
2732
2733
2734
2735 - def fill( self, color ):
2739
2740
2741
2744
2745
2746
2748 """Get the L{Image} that represents this drawing area."""
2749 w, h = self._pixmap.get_size()
2750 img = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, True, 8, w, h )
2751 img.get_from_drawable( self._pixmap, self._area.get_colormap(),
2752 0, 0, 0, 0, w, h )
2753 return Image( __int_image__=img )
2754
2755
2756
2758 if self.__label is None:
2759 raise ValueError( "You cannot change label of widget created "
2760 "without one. Create it with placeholder! "
2761 "(label='')" )
2762 self.__label = label
2763 self._frame.set_label( self.__label )
2764
2765
2766
2768 return self.__label
2769
2770
2771 label = property( get_label, set_label )
2772
2773
2775 return "%s( id=%r, width=%r, height=%r, label=%r )" % \
2776 ( self.__class__.__name__, self.id, self.width, self.height,
2777 self.label )
2778
2779 __repr__ = __str__
2780
2781
2782
2783 -class _MultiLineEntry( gtk.ScrolledWindow ):
2784 - def __init__( self ):
2785 gtk.ScrolledWindow.__init__( self )
2786 self.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
2787 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
2788 self.set_shadow_type( gtk.SHADOW_IN )
2789
2790 self.textview = gtk.TextView()
2791 self.add( self.textview )
2792
2793 self.textview.set_editable( True )
2794 self.textview.set_cursor_visible( True )
2795 self.textview.set_left_margin( 2 )
2796 self.textview.set_right_margin( 2 )
2797 self.textview.get_buffer().connect( "changed", self.__emit_changed__ )
2798
2799
2800
2801 - def __emit_changed__( self, *args ):
2802 self.emit( "changed" )
2803
2804
2805
2806 - def set_text( self, value ):
2807 self.textview.get_buffer().set_text( value )
2808
2809
2810
2811 - def get_text( self ):
2812 b = self.textview.get_buffer()
2813 return b.get_text( b.get_start_iter(), b.get_end_iter() )
2814
2815
2816
2817 - def set_editable( self, setting ):
2818 self.textview.set_editable( setting )
2819
2820
2821 gobject.signal_new( "changed",
2822 _MultiLineEntry,
2823 gobject.SIGNAL_RUN_LAST,
2824 gobject.TYPE_NONE,
2825 tuple() )
2826
2827
2828 -class Entry( _EGWidLabelEntry ):
2829 """Text entry.
2830
2831 The simplest user input component. Its contents are free-form and not
2832 filtered/masked.
2833 """
2834 value = _gen_ro_property( "value" )
2835 callback = _gen_ro_property( "callback" )
2836 multiline = _gen_ro_property( "multiline" )
2837
2838
2839 - def __init__( self, id, label="", value="", callback=None,
2840 editable=True, persistent=False, multiline=False,
2841 expand_policy=None ):
2842 """Entry constructor.
2843
2844 @param id: unique identifier.
2845 @param label: what to show on a label on the left side of the widget.
2846 @param value: initial content.
2847 @param editable: if this field is editable by user.
2848 @param callback: function (or list of functions) that will
2849 be called when this widget have its data changed.
2850 Function will receive as parameters:
2851 - App reference
2852 - Widget reference
2853 - new value
2854 @param persistent: if this widget should save its data between
2855 sessions.
2856 @param multiline: if this entry can be multi-line.
2857 @param expand_policy: how this widget should fit space, see
2858 L{ExpandPolicy.Policy.Rule}.
2859 """
2860 self.value = value
2861 self.callback = _callback_tuple( callback )
2862 self.multiline = bool( multiline )
2863 self._editable = editable
2864
2865 if expand_policy is None:
2866 if self.multiline:
2867 p = ExpandPolicy.All()
2868 else:
2869 p = ExpandPolicy.Horizontal()
2870
2871 expand_policy = ( ExpandPolicy.Fill(), p )
2872
2873 _EGWidLabelEntry.__init__( self, id, persistent, label,
2874 expand_policy=expand_policy )
2875
2876 self.__setup_gui__()
2877 self.__setup_connections__()
2878
2879
2880
2881 - def __setup_gui__( self ):
2882 if self.multiline:
2883 self._entry = _MultiLineEntry()
2884 else:
2885 self._entry = gtk.Entry()
2886
2887 self._entry.set_name( self.id )
2888 self._entry.set_text( self.value )
2889 self.set_editable( self._editable )
2890
2891 _EGWidLabelEntry.__setup_gui__( self )
2892
2893
2894
2900
2901
2902
2904 - def callback( obj ):
2905 v = self.get_value()
2906 self.app.data_changed( self, v )
2907 for c in self.callback:
2908 c( self.app, self, v )
2909
2910 self._entry.connect( "changed", callback )
2911
2912
2913
2914 - def get_value( self ):
2915 return self._entry.get_text()
2916
2917
2918
2919 - def set_value( self, value ):
2920 self._entry.set_text( str( value ) )
2921
2922
2923
2924 - def set_editable( self, value ):
2925 self._editable = bool( value )
2926 self._entry.set_editable( self._editable )
2927
2928
2929
2930 - def get_editable( self ):
2931 return self._editable
2932
2933
2934 editable = property( get_editable, set_editable )
2935
2936
2937
2939 """Password entry.
2940
2941 Like L{Entry}, but will show '*' instead of typed chars.
2942 """
2943 - def __init__( self, id, label="", value="", callback=None,
2944 persistent=False, expand_policy=None ):
2945 """Password constructor.
2946
2947 @param id: unique identifier.
2948 @param label: what to show on a label on the left side of the widget.
2949 @param value: initial content.
2950 @param callback: function (or list of functions) that will
2951 be called when this widget have its data changed.
2952 Function will receive as parameters:
2953 - App reference
2954 - Widget reference
2955 - new value
2956 @param persistent: if this widget should save its data between
2957 sessions.
2958 @param expand_policy: how this widget should fit space, see
2959 L{ExpandPolicy.Policy.Rule}.
2960 """
2961 Entry.__init__( self, id, label, value, callback, persistent,
2962 expand_policy=expand_policy )
2963 self._entry.set_visibility( False )
2964
2965
2966
2967
2968 -class Spin( _EGWidLabelEntry ):
2969 """Spin button entry.
2970
2971 Spin buttons are numeric user input that checks if value is inside
2972 a specified range. It also provides small buttons to help incrementing/
2973 decrementing value.
2974 """
2975 default_min = -1e60
2976 default_max = 1e60
2977
2978 value = _gen_ro_property( "value" )
2979 min = _gen_ro_property( "min" )
2980 max = _gen_ro_property( "max" )
2981 step = _gen_ro_property( "step" )
2982 digits = _gen_ro_property( "digits" )
2983
2984 callback = _gen_ro_property( "callback" )
2985
2986 - def __init__( self, id, label="",
2987 value=None, min=None, max=None, step=None, digits=3,
2988 callback=None, persistent=False, expand_policy=None ):
2989 """Spin constructor.
2990
2991 @param id: unique identifier.
2992 @param label: what to show on a label on the left side of the widget.
2993 @param value: initial content.
2994 @param min: minimum value. If None, L{default_min} will be used.
2995 @param max: maximum value. If None, L{default_max} will be used.
2996 @param step: step to use to decrement/increment using buttons.
2997 @param digits: how many digits to show.
2998 @param callback: function (or list of functions) that will
2999 be called when this widget have its data changed.
3000 Function will receive as parameters:
3001 - App reference
3002 - Widget reference
3003 - new value
3004 @param persistent: if this widget should save its data between
3005 sessions.
3006 @param expand_policy: how this widget should fit space, see
3007 L{ExpandPolicy.Policy.Rule}.
3008 """
3009 self.value = value
3010 self.min = min
3011 self.max = max
3012 self.step = step
3013 self.digits = digits
3014 self.callback = _callback_tuple( callback )
3015
3016 _EGWidLabelEntry.__init__( self, id, persistent, label,
3017 expand_policy=expand_policy )
3018
3019 self.__setup_connections__()
3020
3021
3022
3024 k = {}
3025
3026 if self.value is not None:
3027 k[ "value" ] = self.value
3028
3029 if self.min is not None:
3030 k[ "lower" ] = self.min
3031 else:
3032 k[ "lower" ] = self.default_min
3033
3034 if self.max is not None:
3035 k[ "upper" ] = self.max
3036 else:
3037 k[ "upper" ] = self.default_max
3038
3039 if self.step is not None:
3040 k[ "step_incr" ] = self.step
3041 k[ "page_incr" ] = self.step * 2
3042 else:
3043 k[ "step_incr" ] = 1
3044 k[ "page_incr" ] = 2
3045
3046 adj = gtk.Adjustment( **k )
3047 self._entry = gtk.SpinButton( adjustment=adj, digits=self.digits )
3048 self._entry.set_name( self.id )
3049 self._entry.set_numeric( True )
3050 self._entry.set_snap_to_ticks( False )
3051
3052 _EGWidLabelEntry.__setup_gui__( self )
3053
3054
3055
3062
3063 self._entry.connect( "value-changed", callback )
3064
3065
3066
3069
3070
3071
3072
3074 """Integer-only Spin button.
3075
3076 Convenience widget that behaves like L{Spin} with digits set to
3077 zero and also ensure L{set_value} and L{get_value} operates on
3078 integers.
3079 """
3080 default_min = -sys.maxint
3081 default_max = sys.maxint
3082
3083 - def __init__( self, id, label="",
3084 value=None, min=None, max=None, step=None,
3085 callback=None, persistent=False, expand_policy=None ):
3086 """Integer Spin constructor.
3087
3088 @param id: unique identifier.
3089 @param label: what to show on a label on the left side of the widget.
3090 @param value: initial content.
3091 @param min: minimum value. If None, L{default_min} will be used.
3092 @param max: maximum value. If None, L{default_max} will be used.
3093 @param step: step to use to decrement/increment using buttons.
3094 @param callback: function (or list of functions) that will
3095 be called when this widget have its data changed.
3096 Function will receive as parameters:
3097 - App reference
3098 - Widget reference
3099 - new value
3100 @param persistent: if this widget should save its data between
3101 sessions.
3102 @param expand_policy: how this widget should fit space, see
3103 L{ExpandPolicy.Policy.Rule}.
3104 """
3105 if value is not None:
3106 value = int( value )
3107 if min is not None:
3108 min = int( min )
3109 if max is not None:
3110 max = int( max )
3111 if step is not None:
3112 step = int( step )
3113 Spin.__init__( self, id, label, value, min, max, step, 0, callback,
3114 persistent, expand_policy=expand_policy )
3115
3116
3117
3120
3121
3122
3125
3126
3127
3128
3130 """Unsigned integer-only Spin button.
3131
3132 Convenience widget that behaves like L{IntSpin} with minimum value
3133 always greater or equal zero.
3134 """
3135 default_min = 0
3136
3137 - def __init__( self, id, label="",
3138 value=None, min=0, max=None, step=None,
3139 callback=None, persistent=False, expand_policy=None ):
3140 """Unsigned Integer Spin constructor.
3141
3142 @param id: unique identifier.
3143 @param label: what to show on a label on the left side of the widget.
3144 @param value: initial content.
3145 @param min: minimum value, must be greater or equal zero.
3146 @param max: maximum value. If None, L{default_max} will be used.
3147 @param step: step to use to decrement/increment using buttons.
3148 @param callback: function (or list of functions) that will
3149 be called when this widget have its data changed.
3150 Function will receive as parameters:
3151 - App reference
3152 - Widget reference
3153 - new value
3154 @param persistent: if this widget should save its data between
3155 sessions.
3156 @param expand_policy: how this widget should fit space, see
3157 L{ExpandPolicy.Policy.Rule}.
3158 """
3159 if min < 0:
3160 raise ValueError( "UIntSpin cannot have min < 0!" )
3161 Spin.__init__( self, id, label, value, min, max, step, 0, callback,
3162 persistent, expand_policy=expand_policy )
3163
3164
3165
3166
3167 -class Color( _EGWidLabelEntry ):
3168 """Button to select colors.
3169
3170 It show current/last selected color and may pop-up a new dialog to
3171 select a new one.
3172 """
3173 color = _gen_ro_property( "color" )
3174 use_alpha = _gen_ro_property( "use_alpha" )
3175 callback = _gen_ro_property( "callback" )
3176
3177 - def __init__( self, id, label="", color=0, use_alpha=False,
3178 callback=None, persistent=False, expand_policy=None ):
3179 """Color selector constructor.
3180
3181 @param id: unique identifier.
3182 @param label: what to show on a label on the left side of the widget.
3183 @param color: initial content. May be a triple with elements within
3184 the range 0-255, an string with color in HTML format or even
3185 a color present in X11's rgb.txt.
3186 @param use_alpha: if the alpha channel should be used, it will be
3187 the first value in the tuple representing the color.
3188 @param callback: function (or list of functions) that will
3189 be called when this widget have its data changed.
3190 Function will receive as parameters:
3191 - App reference
3192 - Widget reference
3193 - new value
3194 @param persistent: if this widget should save its data between
3195 sessions.
3196 @param expand_policy: how this widget should fit space, see
3197 L{ExpandPolicy.Policy.Rule}.
3198 """
3199 self.color = self.color_from( color )
3200 self.use_alpha = use_alpha
3201 self.callback = _callback_tuple( callback )
3202 _EGWidLabelEntry.__init__( self, id, persistent, label,
3203 expand_policy=expand_policy )
3204
3205 self.__setup_connections__()
3206
3207
3208
3210 a = 255
3211 if isinstance( color, str ):
3212 try:
3213 c = gtk.gdk.color_parse( color )
3214 r = int( c.red / 65535.0 * 255 )
3215 g = int( c.green / 65535.0 * 255 )
3216 b = int( c.blue / 65535.0 * 255 )
3217 except ValueError, e:
3218 raise ValueError( "%s. color=%r" % ( e, color ) )
3219
3220 if isinstance( color, int ):
3221 r = ( color >> 16 ) & 0xff
3222 g = ( color >> 8 ) & 0xff
3223 b = ( color & 0xff )
3224 elif isinstance( color, ( tuple, list ) ):
3225 if len( color ) == 3:
3226 r, g, b = color
3227 else:
3228 a, r, g, b = color
3229
3230 return a, r, g, b
3231
3232 color_from = staticmethod( color_from )
3233
3234
3236 r = int( self.color[ 1 ] / 255.0 * 65535 )
3237 g = int( self.color[ 2 ] / 255.0 * 65535 )
3238 b = int( self.color[ 3 ] / 255.0 * 65535 )
3239
3240 c = gtk.gdk.Color( r, g, b )
3241
3242 self._entry = gtk.ColorButton( c )
3243 self._entry.set_name( self.id )
3244 self._entry.set_use_alpha( self.use_alpha )
3245 if self.use_alpha:
3246 alpha = int( self.color[ 0 ] / 255.0 * 65535 )
3247 self._entry.set_alpha( alpha )
3248 _EGWidLabelEntry.__setup_gui__( self )
3249
3250
3251
3258
3259 self._entry.connect( "color-set", callback )
3260
3261
3262
3264 """Return a tuple with ( alpha, red, green, blue )
3265 ( alpha, red, green, blue ) (use_alpha=True), each in 0-255 range.
3266 """
3267 c = self._entry.get_color()
3268 r = int( c.red / 65535.0 * 255 )
3269 g = int( c.green / 65535.0 * 255 )
3270 b = int( c.blue / 65535.0 * 255 )
3271
3272 if self.use_alpha:
3273 a = int( self._entry.get_alpha() / 65535.0 * 255 )
3274 return ( a, r, g, b )
3275 else:
3276 return ( r, g, b )
3277
3278
3279
3281 """
3282 @param value: May be a triple with elements within
3283 the range 0-255, an string with color in HTML format or even
3284 a color present in X11's rgb.txt.
3285 """
3286 a, r, g, b = self.color_from( value )
3287 if self.use_alpha:
3288 self._entry.set_alpha( int( a / 255.0 * 65535.0 ) )
3289
3290 r = int( r / 255.0 * 65535 )
3291 g = int( g / 255.0 * 65535 )
3292 b = int( b / 255.0 * 65535 )
3293
3294 c = gtk.gdk.Color( r, g, b )
3295 self._entry.set_color( c )
3296
3297
3298
3299
3300 -class Font( _EGWidLabelEntry ):
3301 """Button to select fonts.
3302
3303 It show current/last selected font and may pop-up a new dialog to
3304 select a new one.
3305 """
3306 font = _gen_ro_property( "font" )
3307 callback = _gen_ro_property( "callback" )
3308
3309 - def __init__( self, id, label="", font="sans 12", callback=None,
3310 persistent=False, expand_policy=None ):
3311 """Font selector constructor.
3312
3313 @param id: unique identifier.
3314 @param label: what to show on a label on the left side of the widget.
3315 @param font: initial content.
3316 @param callback: function (or list of functions) that will
3317 be called when this widget have its data changed.
3318 Function will receive as parameters:
3319 - App reference
3320 - Widget reference
3321 - new value
3322 @param persistent: if this widget should save its data between
3323 sessions.
3324 @param expand_policy: how this widget should fit space, see
3325 L{ExpandPolicy.Policy.Rule}.
3326 """
3327 self.font = font
3328 self.callback = _callback_tuple( callback )
3329 _EGWidLabelEntry.__init__( self, id, persistent, label,
3330 expand_policy=expand_policy )
3331
3332 self.__setup_connections__()
3333
3334
3335
3341
3342
3343
3350
3351 self._entry.connect( "font-set", callback )
3352
3353
3354
3356 return self._entry.get_font_name()
3357
3358
3359
3361 self._entry.set_font_name( value )
3362
3363
3364
3365
3367 """Selection box (aka Combo box).
3368
3369 Selection or combo box is an element that allow you to select one of
3370 various pre-defined values.
3371 """
3372 options = _gen_ro_property( "options" )
3373 active = _gen_ro_property( "active" )
3374
3375 - def __init__( self, id, label="", options=None, active=None,
3376 callback=None, persistent=False, expand_policy=None ):
3377 """Selection constructor.
3378
3379 @param id: unique identifier.
3380 @param label: what to show on a label on the left side of the widget.
3381 @param options: list of possible values.
3382 @param active: selected element.
3383 @param callback: function (or list of functions) that will
3384 be called when this widget have its data changed.
3385 Function will receive as parameters:
3386 - App reference
3387 - Widget reference
3388 - new value
3389 @param persistent: if this widget should save its data between
3390 sessions.
3391 @param expand_policy: how this widget should fit space, see
3392 L{ExpandPolicy.Policy.Rule}.
3393 """
3394 self.options = options or []
3395 self.active = active
3396 self.callback = _callback_tuple( callback )
3397 _EGWidLabelEntry.__init__( self, id, persistent, label,
3398 expand_policy=expand_policy )
3399
3400 self.__setup_connections__()
3401
3402
3403
3405 self._entry = gtk.combo_box_new_text()
3406 self._entry.set_name( self.id )
3407 for i, o in enumerate( self.options ):
3408 self._entry.append_text( str( o ) )
3409 if self.active == o:
3410 self._entry.set_active( i )
3411
3412 _EGWidLabelEntry.__setup_gui__( self )
3413
3414
3415
3422
3423 self._entry.connect( "changed", callback )
3424
3425
3426
3428 return self._entry.get_active_text()
3429
3430
3431
3433 for i, o in enumerate( self._entry.get_model() ):
3434 if o[ 0 ] == value:
3435 self._entry.set_active( i )
3436
3437
3438
3439 - def append( self, value, set_active=False ):
3440 """Append new value to available options.
3441
3442 @param value: string that is not already an option.
3443
3444 @raise: ValueError: if value is already an option.
3445 """
3446 if value not in self.items():
3447 self._entry.append_text( value )
3448 if set_active:
3449 self.set_value( value )
3450 else:
3451 raise ValueError( "value already in selection" )
3452
3453
3454
3456 """Prepend new value to available options.
3457
3458 @param value: string that is not already an option.
3459
3460 @raise: ValueError: if value is already an option.
3461 """
3462 if value not in self.items():
3463 self._entry.prepend_text( value )
3464 if set_active:
3465 self.set_value( value )
3466 else:
3467 raise ValueError( "value already in selection" )
3468
3469
3470
3471 - def insert( self, position, value ):
3472 """Insert new option at position.
3473
3474 @param value: string that is not already an option.
3475
3476 @raise: ValueError: if value is already an option.
3477 """
3478 if value not in self.items():
3479 self._entry.insert_text( position, value )
3480 if set_active:
3481 self.set_value( value )
3482 else:
3483 raise ValueError( "value already in selection" )
3484
3485
3486
3488 """Remove given value from available options.
3489
3490 @param value: string that is an option.
3491
3492 @raise ValueError: if value is not already an option.
3493 """
3494 for i, o in enumerate( self._entry.get_model() ):
3495 if o[ 0 ] == value:
3496 self._entry.remove_text( i )
3497 return
3498
3499 raise ValueError( "value not in selection" )
3500
3501
3502
3504 """Returns every item/option in this selection."""
3505 return [ str( x[ 0 ] ) for x in self._entry.get_model() ]
3506
3507 options = items
3508
3509
3511 return len( self._entry.get_model() )
3512
3513
3514
3517
3518
3519
3523
3524
3525
3529
3530
3531
3532
3534 """Progress bar."""
3535 value = _gen_ro_property( "value" )
3536
3537 - def __init__( self, id, label="", value=0.0, expand_policy=None ):
3538 """Progress bar constructor.
3539
3540 0 <= value <= 1.0
3541
3542 @param id: unique identifier.
3543 @param label: what to show on a label on the left side of the widget.
3544 @param value: initial content ( 0.0 <= value <= 1.0 )
3545 @param expand_policy: how this widget should fit space, see
3546 L{ExpandPolicy.Policy.Rule}.
3547 """
3548 self.value = value
3549 _EGWidLabelEntry.__init__( self, id, False, label,
3550 expand_policy=expand_policy )
3551
3552
3558
3559
3560
3562 return self._entry.get_fraction()
3563
3564
3565
3567 if 1.0 < value <= 100.0:
3568 value /= 100.0
3569 elif not ( 0.0 <= value <= 1.0 ):
3570 raise ValueError( ( "Progress value of \"%s\" must be "
3571 "between 0.0 and 1.0!" ) % self.id )
3572 self._entry.set_fraction( value )
3573 self._entry.set_text( "%d%%" % ( int( value * 100 ), ) )
3574
3575
3576
3578 """Animate progress bar."""
3579 self._entry.pulse()
3580
3581
3582
3583
3585 """Check box.
3586
3587 Check box is an component that have only two values: True (checked) or
3588 False (unchecked).
3589 """
3590 state = _gen_ro_property( "state" )
3591
3592 - def __init__( self, id, label="", state=False, callback=None,
3593 persistent=False, expand_policy=None ):
3594 """Check box constructor.
3595
3596 @param id: unique identifier.
3597 @param label: what to show on a label on the left side of the widget.
3598 @param state: initial state.
3599 @param callback: function (or list of functions) that will
3600 be called when this widget have its data changed.
3601 Function will receive as parameters:
3602 - App reference
3603 - Widget reference
3604 - new value
3605 @param persistent: if this widget should save its data between
3606 sessions.
3607 @param expand_policy: how this widget should fit space, see
3608 L{ExpandPolicy.Policy.Rule}.
3609 """
3610 self.__label = label
3611 self.state = state
3612 self.callback = _callback_tuple( callback )
3613
3614 if expand_policy is None:
3615 expand_policy = ExpandPolicy.Fill()
3616
3617 _EGDataWidget.__init__( self, id, persistent,
3618 expand_policy=expand_policy )
3619
3620 self.__setup_gui__()
3621 self.__setup_connections__()
3622
3623
3624
3626 self._wid = gtk.CheckButton( self.__label )
3627 self._wid.set_name( self.id )
3628 self._wid.set_active( self.state )
3629 self._widgets = ( self._wid, )
3630
3631
3632
3639
3640 self._wid.connect( "toggled", callback )
3641
3642
3643
3645 return self._wid.get_active()
3646
3647
3648
3651
3652
3653
3655 if self.__label is None:
3656 raise ValueError( "You cannot change label of widget created "
3657 "without one. Create it with placeholder! "
3658 "(label='')" )
3659 self.__label = label
3660 self._wid.set_label( self.__label )
3661
3662
3663
3665 return self.__label
3666
3667
3668 label = property( get_label, set_label )
3669
3670
3671
3672 -class Group( _EGWidget ):
3673 """Group of various components.
3674
3675 Group is a component that holds other components, always in a vertical
3676 layout.
3677
3678 Group has a frame and may show a label.
3679 """
3680 children = _gen_ro_property( "children" )
3681 horizontal = _gen_ro_property( "horizontal" )
3682
3683 BORDER_NONE = gtk.SHADOW_NONE
3684 BORDER_IN = gtk.SHADOW_IN
3685 BORDER_OUT = gtk.SHADOW_OUT
3686 BORDER_ETCHED_IN = gtk.SHADOW_ETCHED_IN
3687 BORDER_ETCHED_OUT = gtk.SHADOW_ETCHED_OUT
3688
3690 try:
3691 return self.__ro_app
3692 except AttributeError:
3693 return None
3694
3695
3697
3698
3699 try:
3700 v = self.__ro_app
3701 except AttributeError:
3702 v = None
3703 if v is None:
3704 self.__ro_app = value
3705 self.__add_widgets_to_app__()
3706 else:
3707 raise Exception( "Read Only property 'app'." )
3708
3709 app = property( _get_app, _set_app )
3710
3711
3712 - def __init__( self, id, label="", children=None, horizontal=False,
3713 border=BORDER_ETCHED_IN, expand_policy=None ):
3714 """Group constructor.
3715
3716 @param id: unique identified.
3717 @param label: displayed at top-left.
3718 @param children: a list of eagle widgets that this group contains.
3719 @param horizontal: if widgets should be laid out horizontally.
3720 @param border: can be one of Group.BORDER_* values or None to
3721 disable border and label completely. Note that some themes
3722 may have different appearance for borders and some may not
3723 respect BORDER_NONE, so if you really want no border, use
3724 None to disable it. Note that Groups without borders cannot
3725 have one added later.
3726 @param expand_policy: how this widget should fit space, see
3727 L{ExpandPolicy.Policy.Rule}.
3728 """
3729 if expand_policy is None:
3730 expand_policy = ExpandPolicy.Horizontal()
3731
3732 _EGWidget.__init__( self, id, expand_policy=expand_policy )
3733 self.__label = label
3734 self.children = _obj_tuple( children )
3735 self.horizontal = bool( horizontal )
3736 self.__border = border
3737
3738 self.__setup_gui__()
3739
3740
3741
3743 self._contents = _Table( id=( "%s-contents" % self.id ),
3744 children=self.children,
3745 horizontal=self.horizontal )
3746
3747 if self.border is not None:
3748 self._frame = gtk.Frame( self.__label )
3749 self._frame.set_name( self.id )
3750 self._frame.set_shadow_type( self.border )
3751 self._frame.add( self._contents )
3752 root = self._frame
3753 else:
3754 root = self._contents
3755
3756 self._widgets = ( root, )
3757
3758
3759
3763
3764
3765
3767 if self.__label is None:
3768 raise ValueError( "You cannot change label of widget created "
3769 "without one. Create it with placeholder! "
3770 "(label='')" )
3771 self.__label = label
3772 if self.border is not None:
3773 self._frame.set_label( self.__label )
3774
3775
3776
3778 return self.__label
3779
3780
3781 label = property( get_label, set_label )
3782
3783
3785 if self.__border is None:
3786 raise ValueError( "You cannot change border of widget created "
3787 "without one." )
3788
3789 if border is not None:
3790 self._frame.set_shadow_type( border )
3791 else:
3792 raise ValueError( "You cannot remove widget border" )
3793
3794 self.__border = border
3795
3796
3797
3799 return self.__border
3800
3801
3802 border = property( get_border, set_border )
3803
3804
3805
3806 -class Tabs( _EGWidget ):
3807 """Present widgets in Tabs.
3808
3809 This is also known as TabControl, TabWidget, Notebook, etc in
3810 other toolkits.
3811
3812 Tabs are composed from Pages (L{Tabs.Page}), which behave just
3813 like L{Group}s and hold a list of widgets.
3814
3815 Pages can be accessed by their index number (integer) or label,
3816 using the dictionary syntax or L{Tabs.get_page()} method.
3817 """
3818
3819 - class Page( _EGWidget, AutoGenId ):
3820 """Page in Tabs component.
3821
3822 Pages must have a name and optionally an id, otherwise one id
3823 will be automatically generated.
3824
3825 It behaves just like L{Group} component.
3826 """
3827 spacing = 3
3828
3829 children = _gen_ro_property( "children" )
3830 horizontal = _gen_ro_property( "horizontal" )
3831 parent = _gen_ro_property( "parent" )
3832
3833 - def __init__( self, id=None, label="", children=None,
3834 horizontal=False ):
3835 """Tabs.Page constructor.
3836
3837 @param id: may not be provided, it will be generated automatically.
3838 @param label: displayed as Page label.
3839 @param children: a list of eagle widgets that this page contains.
3840 @param horizontal: if widgets should be laid out horizontally.
3841 """
3842 _EGWidget.__init__( self, id or self.__get_id__() )
3843 self.__label = label or ""
3844 self.horizontal = bool( horizontal )
3845 self.children = _obj_tuple( children )
3846 self.parent = None
3847
3848 self.__setup_gui__()
3849
3850
3851
3852 - def _get_app( self ):
3853 try:
3854 return self.__ro_app
3855 except AttributeError:
3856 return None
3857
3858
3859
3860 - def _set_app( self, value ):
3861
3862
3863 try:
3864 v = self.__ro_app
3865 except AttributeError:
3866 v = None
3867 if v is None:
3868 self.__ro_app = value
3869 self.__add_widgets_to_app__()
3870 else:
3871 raise Exception( "Read Only property 'app'." )
3872
3873
3874 app = property( _get_app, _set_app )
3875
3876
3880
3881
3882
3883 - def __setup_gui__( self ):
3884 self._wid = _Table( id=( "%s-contents" % self.id ),
3885 children=self.children,
3886 horizontal=self.horizontal )
3887 self._widgets = ( self._wid, )
3888
3889
3890
3891 - def set_label( self, label ):
3892 if label is None:
3893 raise ValueError( "You cannot have 'None' labels for "
3894 "Tabs.Page!" )
3895 self.__label = str( label )
3896
3897 if self.parent is not None:
3898 self.parent.__set_page_label__( self, self.label )
3899
3900
3901
3902 - def get_label( self ):
3903 return self.__label
3904
3905
3906 label = property( get_label, set_label )
3907
3908
3909 - def focus( self ):
3911
3912
3913
3914
3915 children = _gen_ro_property( "children" )
3916
3917
3918 - def __init__( self, id, children=None, expand_policy=None ):
3919 """Tabs constructor.
3920
3921 @param id: unique identified.
3922 @param children: an iterable with L{Tabs.Page}
3923 @param expand_policy: how this widget should fit space, see
3924 L{ExpandPolicy.Policy.Rule}.
3925 """
3926 if expand_policy is None:
3927 expand_policy = ExpandPolicy.All()
3928
3929 _EGWidget.__init__( self, id, expand_policy=expand_policy )
3930 if not children:
3931 self.children = tuple()
3932 else:
3933 lst = []
3934 for i, widget in enumerate( children ):
3935 if isinstance( widget, Tabs.Page ):
3936 lst.append( widget )
3937 else:
3938 sys.stderr.write( ( "children #%d (%s) is not "
3939 "instance of Tabs.Page, but %s" ) %
3940 ( i, widget, type( widget ).__name__ ))
3941 self.children = tuple( lst )
3942
3943 self.__setup_gui__()
3944
3945
3946
3954
3955
3956
3958 try:
3959 return self.__ro_app
3960 except AttributeError:
3961 return None
3962
3963
3964
3966
3967
3968 try:
3969 v = self.__ro_app
3970 except AttributeError:
3971 v = None
3972 if v is None:
3973 self.__ro_app = value
3974 self.__add_widgets_to_app__()
3975 else:
3976 raise Exception( "Read Only property 'app'." )
3977
3978
3979 app = property( _get_app, _set_app )
3980
3981
3985
3986
3987
3988 - def __set_page_label__( self, page, value ):
3989 self._wid.set_tab_label( page._widgets[ 0 ], gtk.Label( value ) )
3990
3991
3992
3993 - def __focus_page__( self, page ):
3994 for index, p in enumerate( self.children ):
3995 if p == page:
3996 self._wid.set_current_page( index )
3997 return
3998 raise ValueError( "Page '%s' doesn't exist in this Tab." % ( page, ) )
3999
4000
4001
4002 - def focus_page( self, index_or_name_or_page ):
4003 """Make given page visible."""
4004 if not isinstance( index_or_name_or_page, Tabs.Page ):
4005 index_or_name = index_or_name_or_page
4006 page = self.get_page( index_or_name )
4007 else:
4008 page = index_or_name_or_page
4009 page.focus()
4010
4011
4012
4013 - def get_page( self, index_or_name ):
4014 """Get the Tabs.Page given its index or name.
4015
4016 @raise KeyError if index_or_name doesn't exists.
4017 """
4018 if isinstance( index_or_name, basestring ):
4019 name = index_or_name
4020 for w in self.children:
4021 if w.label == name:
4022 return w
4023 raise KeyError( "No page labeled '%s'" % name )
4024 else:
4025 index = index_or_name
4026 try:
4027 return self.children[ index ]
4028 except IndexError, e:
4029 raise KeyError( "No page numbered %s" % index )
4030
4031
4032
4034 """Same as L{Tabs.get_page()}.
4035
4036 @raise KeyError see L{Tabs.get_page()}
4037 @see L{Tabs.get_page()}
4038 """
4039 return self.get_page( name )
4040
4041
4042
4044 """Set L{Tabs.Page.label} of a page get using 'name' for
4045 L{Tabs.get_page()}.
4046
4047 @raise KeyError see L{Tabs.get_page()}
4048 @see L{Tabs.get_page()}
4049 """
4050 page = self[ name ]
4051 page.label = value
4052
4053
4054
4055
4056 -class Table( _EGWidget ):
4057 """Data table.
4058
4059 Each column should have only one type, it will be checked.
4060 Can be accessed as a python list:
4061
4062 >>> t = Table( 't', 'table', [ 1, 2, 3 ] )
4063 >>> t[ 0 ]
4064 [ 1 ]
4065 >>> del t[ 1 ]
4066 >>> t[ : ]
4067 [ 1, 3 ]
4068 """
4069 spacing = 3
4070
4071
4072 - class Row( object ):
4073
4075 self.__items = items
4076
4077
4078
4080 return "[" + ", ".join( [ str( x ) for x in self.__items ] ) + "]"
4081
4082 __repr__ = __str__
4083
4084
4086 return len( self.__items )
4087
4088
4089
4092
4093
4094
4096 return self.__items[ index ]
4097
4098
4099
4101 self.__items[ index ] = value
4102
4103
4104
4106 del self.__items[ index ]
4107
4108
4109
4111 return element in self.__items
4112
4113
4114
4116 slice = []
4117
4118 l = len( self.__items )
4119 while start < 0:
4120 start += l
4121 while end < 0:
4122 end += l
4123
4124 start = min( start, l )
4125 end = min( end, l )
4126
4127 for i in xrange( start, end ):
4128 slice.append( self.__items[ i ] )
4129 return slice
4130
4131
4132
4134 l = len( self.__items )
4135 while start < 0:
4136 start += l
4137 while end < 0:
4138 end += l
4139
4140 start = min( start, l )
4141 end = min( end, l )
4142
4143 l2 = len( items )
4144 if end - start > l2:
4145 end = start + l2
4146
4147 if self.__items.model._hid_row_changed is not None:
4148 hid_changed = self.__items.model._hid_row_changed
4149 self.__items.model.handler_block( hid_changed )
4150
4151 j = 0
4152 for i in xrange( start, end ):
4153 self.__items[ i ] = items[ j ]
4154 j += 1
4155
4156 if self.__items.model._hid_row_changed is not None:
4157 hid_changed = self.__items.model._hid_row_changed
4158 self.__items.model.handler_unblock( hid_changed )
4159 i = self.__items.iter
4160 p = self.__items.path
4161 self.__items.model.row_changed( p, i )
4162
4163
4164
4165
4166
4174
4175
4176
4177
4178
4179 - def __init__( self, id, label="", items=None, types=None,
4180 headers=None, show_headers=True, editable=False,
4181 repositioning=False, expand_columns_indexes=None,
4182 hidden_columns_indexes=None, cell_format_func=None,
4183 selection_callback=None, data_changed_callback=None,
4184 expand_policy=None ):
4185 """Table constructor.
4186
4187 @param id: unique identifier.
4188 @param label: what to show on table frame
4189 @param items: a list (single column) or list of lists (multiple
4190 columns)
4191 @param types: a list of types (str, int, long, float, unicode, bool)
4192 for columns, if omitted, will be guessed from items.
4193 @param headers: what to use as table header.
4194 @param show_headers: whenever to show table headers
4195 @param editable: if table is editable. If editable, user can change
4196 values inline or double-clicking, also edit buttons will
4197 show after the table.
4198 @param repositioning: allow items to be moved up and down.
4199 @param expand_columns_indexes: list of indexes that can expand size
4200 @param cell_format_func: if define, should return a CellFormat with
4201 properties to be applied to cell. Only non-None properties will
4202 be used. Function should have the following signature:
4203 def func( app, table, row, col, value ):
4204 return Table.CellFormat( ... )
4205 where row and col are indexes in table.
4206 @param selection_callback: the function (or list of functions) to
4207 call when selection changes. Function will get as parameters:
4208 - App reference
4209 - Table reference
4210 - List of pairs ( index, row_contents )
4211 @param data_changed_callback: the function (or list of functions) to
4212 call when data changes. Function will get as parameters:
4213 - App reference
4214 - Table reference
4215 - Pair ( index, row_contents )
4216 @param expand_policy: how this widget should fit space, see
4217 L{ExpandPolicy.Policy.Rule}.
4218
4219 @warning: although this widget contains data, it's not a
4220 _EGDataWidget and thus will not notify application that
4221 data changed, also it cannot persist it's data
4222 automatically, if you wish, do it manually. This behavior
4223 may change in future if Table show to be useful as
4224 _EGDataWidget.
4225 """
4226 if expand_policy is None:
4227 expand_policy = ExpandPolicy.All()
4228
4229 _EGWidget.__init__( self, id, expand_policy=expand_policy )
4230 self.editable = editable or False
4231 self.repositioning = repositioning or False
4232 self.__label = label
4233 self.headers = headers or tuple()
4234 self.show_headers = bool( show_headers )
4235 self.cell_format_func = cell_format_func
4236
4237 if isinstance( expand_columns_indexes, ( int, long ) ):
4238 expand_columns_indexes = ( expand_columns_indexes, )
4239 elif isinstance( expand_columns_indexes, ( tuple, list ) ):
4240 expand_columns_indexes = tuple( expand_columns_indexes )
4241 elif expand_columns_indexes is None:
4242 expand_columns_indexes = tuple()
4243 else:
4244 raise ValueError( \
4245 "expand_columns_indexes must be a sequence of integers" )
4246 self.expand_columns_indexes = expand_columns_indexes
4247
4248 if isinstance( hidden_columns_indexes, ( int, long ) ):
4249 hidden_columns_indexes = ( hidden_columns_indexes, )
4250 elif isinstance( hidden_columns_indexes, ( tuple, list ) ):
4251 hidden_columns_indexes = tuple( hidden_columns_indexes )
4252 elif hidden_columns_indexes is None:
4253 hidden_columns_indexes = tuple()
4254 else:
4255 raise ValueError( \
4256 "hidden_columns_indexes must be a sequence of integers" )
4257 self.hidden_columns_indexes = hidden_columns_indexes
4258
4259 if not ( types or items ):
4260 raise ValueError( "Must provide items or types!" )
4261 elif not types:
4262 items = items or []
4263 if not isinstance( items[ 0 ], ( list, tuple ) ):
4264
4265 items = [ [ i ] for i in items ]
4266
4267 types = [ type( i ) for i in items[ 0 ] ]
4268 self.types = types
4269 self.items = items
4270
4271 self.selection_callback = _callback_tuple( selection_callback )
4272 self.data_changed_callback = _callback_tuple( data_changed_callback )
4273
4274 self.__setup_gui__()
4275
4276 self._model._hid_row_changed = None
4277 self._model._hid_row_deleted = None
4278 self._model._hid_row_inserted = None
4279 self.__setup_connections__()
4280 self.__setup_items__()
4281
4282
4283
4285 self._vbox = gtk.VBox( False, self.spacing )
4286 self._vbox.set_border_width( self.spacing )
4287 self._vbox.set_name( "vbox-%s" % self.id )
4288
4289 if self.label is not None:
4290 self._frame = gtk.Frame( self.label )
4291 self._frame.set_name( self.id )
4292 self._frame.add( self._vbox )
4293 root = self._frame
4294 else:
4295 root = self._vbox
4296 self._widgets = ( root, )
4297
4298 self.__setup_table__()
4299
4300 if self.editable or self.repositioning:
4301 self._hbox = gtk.HBox( False, self.spacing )
4302 self._vbox.pack_start( self._hbox, expand=False, fill=True )
4303
4304 if self.editable:
4305 self._btn_add = gtk.Button( stock=gtk.STOCK_ADD )
4306 self._btn_del = gtk.Button( stock=gtk.STOCK_REMOVE )
4307 self._btn_edit = gtk.Button( stock=gtk.STOCK_EDIT )
4308
4309 self._hbox.pack_start( self._btn_add )
4310 self._hbox.pack_start( self._btn_del )
4311 self._hbox.pack_start( self._btn_edit )
4312
4313 if self.repositioning:
4314 if self.editable:
4315 self._hbox.pack_start( gtk.VSeparator() )
4316
4317 self._btn_up = gtk.Button( stock=gtk.STOCK_GO_UP )
4318 self._btn_down = gtk.Button( stock=gtk.STOCK_GO_DOWN )
4319
4320 self._btn_up.set_sensitive( False )
4321 self._btn_down.set_sensitive( False )
4322
4323 self._hbox.pack_start( self._btn_up )
4324 self._hbox.pack_start( self._btn_down )
4325
4326
4327
4340
4341
4342
4345 index = path[ 0 ]
4346 v = ( index, Table.Row( model[ path ] ) )
4347 for c in self.data_changed_callback:
4348 c( self.app, self, v )
4349
4350
4351
4353 index = path[ 0 ]
4354 v = ( index, None )
4355 for c in self.data_changed_callback:
4356 c( self.app, self, v )
4357
4358
4359 c = self._model.connect
4360 self._model._hid_row_changed = c( "row-changed", row_changed )
4361 self._model._hid_row_deleted = c( "row-deleted", row_deleted )
4362 self._model._hid_row_inserted = c( "row-inserted", row_changed)
4363
4364
4365
4368 title = "Edit data from table %s" % ( self.label or self.id )
4369 buttons = ( gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
4370 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT )
4371 d = gtk.Dialog( title=title,
4372 flags=gtk.DIALOG_MODAL,
4373 buttons=buttons )
4374 d.set_default_response( gtk.RESPONSE_ACCEPT )
4375
4376 l = len( data )
4377 t = gtk.Table( l, 2 )
4378 t.set_border_width( 0 )
4379 w = {}
4380 for i, v in enumerate( data ):
4381 if i in self.hidden_columns_indexes:
4382 continue
4383
4384 title = self._table.get_column( i ).get_title()
4385 label = gtk.Label( "%s:" % title )
4386 label.set_justify( gtk.JUSTIFY_RIGHT )
4387 label.set_alignment( xalign=1.0, yalign=0.5 )
4388
4389 tp = self.types[ i ]
4390 if tp == bool:
4391 entry = gtk.CheckButton()
4392 entry.set_active( data[ i ] )
4393 elif tp in ( int, long ):
4394 entry = gtk.SpinButton( digits=0 )
4395 adj = entry.get_adjustment()
4396 adj.lower = Spin.default_min
4397 adj.upper = Spin.default_max
4398 adj.step_increment = 1
4399 adj.page_increment = 5
4400 entry.set_value( data[ i ] )
4401 elif tp == float:
4402 entry = gtk.SpinButton( digits=6 )
4403 adj = entry.get_adjustment()
4404 adj.lower = Spin.default_min
4405 adj.upper = Spin.default_max
4406 adj.step_increment = 1
4407 adj.page_increment = 5
4408 entry.set_value( data[ i ] )
4409 elif tp in ( str, unicode ):
4410 entry = gtk.Entry()
4411 entry.set_text( data[ i ] )
4412 elif tp == Image:
4413 entry = gtk.Image()
4414 entry.set_from_pixbuf( data[ i ].__get_gtk_pixbuf__() )
4415 else:
4416 entry = gtk.Label( str( data[ i ] ) )
4417
4418 t.attach( label, 0, 1, i, i + 1,
4419 xoptions=gtk.FILL,
4420 xpadding=self.spacing, ypadding=self.spacing )
4421 t.attach( entry, 1, 2, i, i + 1,
4422 xoptions=gtk.EXPAND|gtk.FILL,
4423 xpadding=self.spacing, ypadding=self.spacing )
4424 w[ i ] = entry
4425
4426 t.show_all()
4427
4428 sw = gtk.ScrolledWindow()
4429 sw.add_with_viewport( t )
4430 sw.get_child().set_shadow_type( gtk.SHADOW_NONE )
4431 d.vbox.pack_start( sw )
4432
4433
4434 sw.set_policy( hscrollbar_policy=gtk.POLICY_NEVER,
4435 vscrollbar_policy=gtk.POLICY_NEVER )
4436 d.show_all()
4437
4438 sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
4439 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
4440
4441 r = d.run()
4442 d.destroy()
4443 if r in ( gtk.RESPONSE_REJECT, gtk.RESPONSE_DELETE_EVENT ) :
4444 return None
4445 else:
4446 result = []
4447 for i in xrange( len( data ) ):
4448 tp = self.types[ i ]
4449
4450 if i not in w:
4451 r = data[ i ]
4452 else:
4453 wid = w[ i ]
4454
4455 if tp == bool:
4456 r = bool( wid.get_active() )
4457 elif tp in ( int, long ):
4458 r = tp( wid.get_value() )
4459 elif tp == float:
4460 r = float( wid.get_value() )
4461 elif tp in ( str, unicode ):
4462 r = tp( wid.get_text() )
4463 else:
4464 r = data[ i ]
4465 result.append( r )
4466
4467 return result
4468
4469
4470
4472 entry = []
4473 for i, t in enumerate( self.types ):
4474 if t == bool:
4475 v = False
4476 elif t in ( int, long, float ):
4477 v = 0
4478 elif t in ( str, unicode ):
4479 v = ''
4480 else:
4481 try:
4482 v = t.default_value()
4483 except AttributeError:
4484 try:
4485 name = t.__name__
4486 except:
4487 name = t
4488 raise ValueError( ( "Unsuported column (%d) type: %s."
4489 " Type should provide "
4490 "default_value() static method." )
4491 % ( i, name ) )
4492 entry.append( v )
4493 result = edit_dialog( entry )
4494 if result:
4495 self.append( result )
4496
4497
4498
4500 selected = self.selected()
4501 if not selected:
4502 return
4503
4504 for index, data in selected:
4505 print data
4506 result = edit_dialog( data )
4507 if result:
4508 self[ index ] = result
4509
4510
4511
4519
4520
4521 self._btn_add.connect( "clicked", clicked_add )
4522 self._btn_del.connect( "clicked", clicked_del )
4523 self._btn_edit.connect( "clicked", clicked_edit )
4524
4526 data = treeview.get_model()[ path ]
4527 result = edit_dialog( data )
4528 if result:
4529 self[ path[ 0 ] ] = result
4530
4531
4532
4533 self._table.connect( "row-activated", row_activated )
4534
4535
4536
4539 result = self.selected()
4540 if not result:
4541 self._btn_up.set_sensitive( False )
4542 self._btn_down.set_sensitive( False )
4543 else:
4544 path = result[ 0 ][ 0 ]
4545 if path > 0:
4546 self._btn_up.set_sensitive( True )
4547 else:
4548 self._btn_up.set_sensitive( False )
4549 if path < len( self ) - 1:
4550 self._btn_down.set_sensitive( True )
4551 else:
4552 self._btn_down.set_sensitive( False )
4553
4554
4556 result = self.selected()
4557 a = result[ 0 ][ 0 ]
4558 if a <= 0:
4559 return
4560
4561 b = a - 1
4562 la = list( self[ a ] )
4563 lb = list( self[ b ] )
4564 self[ a ] = lb
4565 self[ b ] = la
4566 self.select( b )
4567
4568
4570 result = self.selected()
4571 a = result[ 0 ][ 0 ]
4572 if a >= len( self ) - 1:
4573 return
4574
4575 b = a + 1
4576 la = list( self[ a ] )
4577 lb = list( self[ b ] )
4578 self[ a ] = lb
4579 self[ b ] = la
4580 self.select( b )
4581
4582
4583 selection = self._table.get_selection()
4584 selection.connect( "changed", selection_changed )
4585 self._btn_up.connect( "clicked", move_up )
4586 self._btn_down.connect( "clicked", move_down )
4587
4588
4589
4592 result = self.selected()
4593 for c in self.selection_callback:
4594 c( self.app, self, result )
4595
4596
4597 selection = self._table.get_selection()
4598 selection.connect( "changed", selection_changed )
4599
4600
4601
4713
4714
4715
4717 self.__setup_model__()
4718 self._table = gtk.TreeView( self._model )
4719 self._table.set_name( "table-%s" % self.id )
4720 self._table.get_selection().set_mode( gtk.SELECTION_MULTIPLE )
4721
4723 cid, order = self._model.get_sort_column_id()
4724 self._model.set_sort_column_id( cid, order )
4725
4726
4727 - def toggled( cell_render, path, col ):
4728 self._model[ path ][ col ] = not self._model[ path ][ col ]
4729
4730
4731
4732 - def edited( cell_render, path, text, col ):
4733 t = self.types[ col ]
4734 try:
4735 value = t( text )
4736 except ValueError, e:
4737 name = t.__name__
4738 error( "Invalid contents for column of type '%s': %s" %
4739 ( name, text ) )
4740 else:
4741 self._model[ path ][ col ] = value
4742
4743
4744
4745 for i, t in enumerate( self.types ):
4746 if t == bool:
4747 cell_rend = gtk.CellRendererToggle()
4748 props = { "active": i }
4749 cell_rend.model_view_conv = None
4750 if self.editable:
4751 cell_rend.set_property( "activatable", True)
4752 cell_rend.connect( "toggled", toggled, i )
4753
4754 elif t in ( int, long, float, str, unicode ):
4755 cell_rend = gtk.CellRendererText()
4756 props = { "text": i }
4757 cell_rend.model_view_conv = None
4758 if self.editable:
4759 cell_rend.set_property( "editable", True )
4760 cell_rend.connect( "edited", edited, i )
4761
4762 if t in ( int, long, float ):
4763 cell_rend.set_property( "xalign", 1.0 )
4764 elif t == Image:
4765 cell_rend = gtk.CellRendererPixbuf()
4766 cell_rend.model_view_conv = lambda x: x.__get_gtk_pixbuf__()
4767 props = {}
4768 else:
4769 cell_rend = gtk.CellRendererText()
4770 props = {}
4771 cell_rend.model_view_conv = str
4772
4773 try:
4774 title = self.headers[ i ]
4775 except IndexError:
4776 title = "Col-%d (%s)" % ( i, t.__name__ )
4777
4778 col = gtk.TreeViewColumn( title, cell_rend, **props )
4779 col.set_resizable( True )
4780 col.set_sort_column_id( i )
4781 col.connect( "clicked", column_clicked )
4782
4783 if self.cell_format_func:
4784 f = self.__create_column_cell_format_func__( col, cell_rend )
4785 col.set_cell_data_func( cell_rend, f, i )
4786
4787 elif callable( cell_rend.model_view_conv ):
4788 def f( column, cell_rend, model, iter, model_idx ):
4789 o = model[ iter ][ model_idx ]
4790 v = cell_rend.model_view_conv( o )
4791 if isinstance( cell_rend, gtk.CellRendererText ):
4792 cell_rend.set_property( "text", v )
4793 elif isinstance( cell_rend, gtk.CellRendererToggle ):
4794 cell_rend.set_property( "active", v )
4795 elif isinstance( cell_rend, gtk.CellRendererPixbuf ):
4796 cell_rend.set_property( "pixbuf", v )
4797
4798 col.set_cell_data_func( cell_rend, f, i )
4799
4800
4801 if i in self.expand_columns_indexes:
4802 col.set_expand( True )
4803 else:
4804 col.set_expand( False )
4805
4806 if i not in self.hidden_columns_indexes:
4807 col.set_visible( True )
4808 else:
4809 col.set_visible( False )
4810
4811 self._table.append_column( col )
4812
4813
4814 self._table.set_headers_visible( self.show_headers )
4815 self._table.set_headers_clickable( True )
4816 self._table.set_reorderable( self.repositioning )
4817 self._table.set_enable_search( True )
4818
4819 self._sw = gtk.ScrolledWindow()
4820 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
4821 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
4822 self._sw.set_shadow_type( gtk.SHADOW_IN )
4823 self._sw.add( self._table )
4824 self._vbox.pack_start( self._sw )
4825
4826
4827
4833
4834
4835
4836
4838 gtk_types = []
4839 for i, t in enumerate( self.types ):
4840 if t == bool:
4841 gtk_types.append( gobject.TYPE_BOOLEAN )
4842 elif t == int:
4843 gtk_types.append( gobject.TYPE_INT )
4844 elif t == long:
4845 gtk_types.append( gobject.TYPE_LONG )
4846 elif t == float:
4847 gtk_types.append( gobject.TYPE_FLOAT )
4848 elif t in ( str, unicode ):
4849 gtk_types.append( gobject.TYPE_STRING )
4850 else:
4851 gtk_types.append( gobject.TYPE_PYOBJECT )
4852
4853 self._model = gtk.ListStore( *gtk_types )
4854
4855 - def sort_fn( model, itr1, itr2, id ):
4856 return cmp( model[ itr1 ][ id ], model[ itr2 ][ id ] )
4857
4858
4859 for i in xrange( len( self.types ) ):
4860 self._model.set_sort_func( i, sort_fn, i )
4861
4862
4863
4865 if self.__label is None:
4866 raise ValueError( "You cannot change label of widget created "
4867 "without one. Create it with placeholder! "
4868 "(label='')" )
4869 self.__label = label
4870 self._frame.set_label( self.__label )
4871
4872
4873
4875 return self.__label
4876
4877
4878 label = property( get_label, set_label )
4879
4880
4883
4884
4885
4887 selection = self._table.get_selection()
4888 selection.unselect_all()
4889 selection.select_path( index )
4890
4891
4892
4894 model, paths = self._table.get_selection().get_selected_rows()
4895 if paths:
4896 result = []
4897 for p in paths:
4898 result.append( ( p[ 0 ], Table.Row( model[ p ] ) ) )
4899 return result
4900 else:
4901 return None
4902
4903
4904
4905 - def append( self, row, select=True, autosize=True ):
4906 if not isinstance( row, ( list, tuple ) ):
4907 row = ( row, )
4908
4909 if self._model._hid_row_inserted is not None:
4910 self._model.handler_block( self._model._hid_row_inserted )
4911 if self._model._hid_row_changed is not None:
4912 self._model.handler_block( self._model._hid_row_changed )
4913 itr = self._model.append( row )
4914 if self._model._hid_row_changed is not None:
4915 self._model.handler_unblock( self._model._hid_row_changed )
4916 if self._model._hid_row_inserted is not None:
4917 self._model.handler_unblock( self._model._hid_row_inserted )
4918
4919 self._model.row_changed( self._model.get_path( itr ), itr )
4920
4921 if autosize:
4922 self._table.columns_autosize()
4923 if select:
4924 self._table.set_cursor( self._model[ itr ].path )
4925
4926
4927
4928 - def insert( self, index, row, select=True, autosize=True ):
4929 if not isinstance( row, ( list, tuple ) ):
4930 row = ( row, )
4931
4932 if self._model._hid_row_inserted is not None:
4933 self._model.handler_block( self._model._hid_row_inserted )
4934 if self._model._hid_row_changed is not None:
4935 self._model.handler_block( self._model._hid_row_changed )
4936 itr = self._model.insert( index, row )
4937 if self._model._hid_row_changed is not None:
4938 self._model.handler_unblock( self._model._hid_row_changed )
4939 if self._model._hid_row_inserted is not None:
4940 self._model.handler_unblock( self._model._hid_row_inserted )
4941
4942 self._model.row_changed( self._model.get_path( itr ), itr )
4943
4944 if autosize:
4945 self._table.columns_autosize()
4946 if select:
4947 self._table.set_cursor( self._model[ itr ].path )
4948
4949
4950
4953
4954
4956 return len( self._model )
4957
4958
4959
4961 self.append( other )
4962 return self
4963
4964
4965
4967 if not isinstance( other, ( list, tuple, Table.Row ) ):
4968 other = ( other, )
4969 try:
4970 if self._model._hid_row_inserted is not None:
4971 self._model.handler_block( self._model._hid_row_inserted )
4972 if self._model._hid_row_changed is not None:
4973 self._model.handler_block( self._model._hid_row_changed )
4974 self._model[ index ] = other
4975 if self._model._hid_row_changed is not None:
4976 self._model.handler_unblock( self._model._hid_row_changed )
4977 if self._model._hid_row_inserted is not None:
4978 self._model.handler_unblock( self._model._hid_row_inserted )
4979
4980 p = ( index, )
4981 self._model.row_changed( p, self._model.get_iter( p ) )
4982
4983 except TypeError, e:
4984 raise IndexError( "index out of range" )
4985
4986
4987
4989 try:
4990 items = self._model[ index ]
4991 except TypeError, e:
4992 raise IndexError( "index out of range" )
4993
4994 return Table.Row( items )
4995
4996
4997
4999 try:
5000 del self._model[ index ]
5001 except TypeError, e:
5002 raise IndexError( "index out of range" )
5003
5004
5005
5007 for r in self._model:
5008 if row in r:
5009 return True
5010 return False
5011
5012
5013
5015 slice = []
5016
5017 l = len( self._model )
5018 while start < 0:
5019 start += l
5020 while end < 0:
5021 end += l
5022
5023 start = min( start, l )
5024 end = min( end, l )
5025
5026 for i in xrange( start, end ):
5027 slice.append( Table.Row( self._model[ i ] ) )
5028 return slice
5029
5030
5031
5033 l = len( self._model )
5034 while start < 0:
5035 start += l
5036 while end < 0:
5037 end += l
5038
5039 del self[ start : end ]
5040
5041
5042 l2 = len( slice )
5043 if end - start > l2:
5044 end = start + l2
5045 for j, i in enumerate( xrange( start, end ) ):
5046 row = list( slice[ j ] )
5047
5048
5049
5050 lr = len( row )
5051 lt = len( self.types )
5052 if lr < lt:
5053 for i in xrange( lr, lt ):
5054 t = self.types[ i ]
5055 row.append( t() )
5056
5057 self.insert( i, row, select=False, autosize=False )
5058
5059
5060
5062 l = len( self._model )
5063 while start < 0:
5064 start += l
5065 while end < 0:
5066 end += l
5067
5068 start = min( start, l )
5069 end = min( end, l )
5070
5071 while end > start:
5072 end -= 1
5073 del self._model[ end ]