1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 __author__ = "Gustavo Sverzut Barbieri"
23 __author_email__ = "barbieri@gmail.com"
24 __license__ = "LGPL"
25 __url__ = "http://www.gustavobarbieri.com.br/eagle/"
26 __version__ = "0.5"
27 __revision__ = "$Rev: 50 $"
28 __description__ = """\
29 Eagle is an abstraction layer atop Graphical Toolkits focused on
30 making simple applications easy to build while powerful in features.
31 """
32 __long_description__ = """\
33 Eagle is an abstraction layer atop Graphical Toolkits focused on
34 making simple applications easy to build while powerful in features.
35
36 With Eagle you have many facilities to build application that needs
37 just some buttons, user input and a canvas to draw.
38
39 Canvas is really simple, what makes Eagle a great solution to
40 Computer Graphics and Image Processing software, the primary focus
41 of this library.
42
43 User input widgets are persistent, you just need to mark them
44 "persistent" or put them in the preferences area.
45
46 Eagle is not meant to be another Graphical Toolkit, you already
47 have a bunch of them, like Qt, Gtk, wxWidgets (to name just a few).
48 It's focused on applications that have few windows, with buttons,
49 simple user input and canvas. Widgets are laid out automatically
50 in 5 areas: left, right, top, bottom and center.
51
52 It provides useful widgets like: Color selector, Font selector,
53 Quit button, Preferences button and bialog, About dialog and Help
54 dialog.
55 """
56 __doc__ = __long_description__
57
58 __all__ = [
59 "run", "quit", "get_value", "set_value",
60 "get_app_by_id", "get_widget_by_id",
61 "show", "hide", "set_active", "set_inactive", "close",
62 "App",
63 "Entry", "Password",
64 "Spin", "IntSpin", "UIntSpin",
65 "CheckBox",
66 "Progress",
67 "Color", "Font",
68 "Button", "AboutButton", "CloseButton", "QuitButton", "HelpButton",
69 "OpenFileButton", "SelectFolderButton", "SaveFileButton",
70 "PreferencesButton",
71 "Selection",
72 "Group", "Table",
73 "HSeparator", "VSeparator",
74 "Label",
75 "Canvas", "Image",
76 "information", "info", "error", "err", "warning", "warn",
77 "yesno", "confirm",
78 "AboutDialog", "HelpDialog", "FileChooser",
79 "RichText",
80 ]
81
82 import os
83 import sys
84 import gc
85 import cPickle as pickle
86 import htmllib
87 import formatter
88 import weakref
89
90 try:
91 import pygtk
92 pygtk.require( "2.0" )
93 import gtk
94 import pango
95 import gobject
96 except ImportError, e:
97 sys.stderr.writelines(
98 ( "Missing module: ", str( e ), "\n",
99 "This module is part of pygtk (http://pygtk.org).\n",
100 ) )
101 sys.exit( -1 )
102
103 try:
104 import hildon
105 except ImportError, e:
106 sys.stderr.writelines(
107 ( "Missing module: ", str( e ), "\n",
108 "This module is part of pymaemo (http://pymaemo.sf.net).\n",
109 "HINT: Are you trying to use this on your desktop? If so use"
110 "eagle-gtk instead!\n"
111 ) )
112 sys.exit( -1 )
113
114 required_gtk = ( 2, 6, 0 )
115 m = gtk.check_version( *required_gtk )
116 if m:
117 sys.stderr.writelines(
118 ( "Error checking GTK version: %s\n"
119 "This system requires pygtk >= %s, you have %s installed.\n" )
120 % ( m,
121 ".".join( [ str( v ) for v in required_gtk ] ),
122 ".".join( [ str( v ) for v in gtk.pygtk_version ] )
123 ) )
124 sys.exit( -1 )
125
126 gtk.gdk.threads_init()
127
128 _apps = {}
129
130
132 """Generates a Read-Only property.
133
134 The generated property can be assigned only one value, if this value
135 is not None, it cannot be changed.
136 """
137 naming = "__ro_%s__" % ( name, )
139 try:
140 return getattr( self, naming )
141 except AttributeError:
142 return None
143
144 - def set( self, value ):
145 try:
146 v = getattr( self, naming )
147 except AttributeError:
148 v = None
149 if v is None:
150 setattr( self, naming, value )
151 else:
152 raise Exception( "Read Only property '%s'." % ( name, ) )
153
154 return property( get, set, None, doc )
155
156
157
159 if not isinstance( callback, ( tuple, list ) ):
160 if callback is None:
161 return tuple()
162 elif callable( callback ):
163 return ( callback, )
164 else:
165 raise TypeError( "Callback '%s' is not callable!" % ( callback, ) )
166 else:
167 for c in callback:
168 if not callable( c ):
169 raise TypeError( "Callback '%s' is not callable!" % ( c, ) )
170 return callback
171
172
173
175 if not isinstance( string, ( tuple, list ) ):
176 if string is None:
177 return tuple()
178 else:
179 return ( str( string ), )
180 else:
181 return tuple( [ str( s ) for s in string ] )
182
183
184
186 if not isinstance( obj, ( tuple, list ) ):
187 if obj is None:
188 return tuple()
189 else:
190 return ( obj, )
191 else:
192 return tuple( obj )
193
194
195
197 style = gtkwidget.get_style()
198 iconset = style.lookup_icon_set( stock_id )
199 if iconset:
200 icons = []
201 for s in iconset.get_sizes():
202 i = iconset.render_icon( style,
203 gtk.TEXT_DIR_NONE,
204 gtk.STATE_NORMAL,
205 s,
206 gtkwidget,
207 None
208 )
209 icons.append( i )
210 gtkwidget.set_icon_list( *icons )
211
212
213
215 """Internal widget to arrange components in tabular form.
216
217 @warning: never use it directly in Eagle applications!
218 """
219
220 padding = 3
221 id = _gen_ro_property( "id" )
222 children = _gen_ro_property( "children" )
223 horizontal = _gen_ro_property( "horizontal" )
224
225
226 - def __init__( self, id, children, horizontal=False ):
236
237
238
240 """Lay out components in a horizontal or vertical table."""
241 if not self.children:
242 return
243
244 n = len( self.children )
245
246 if self.horizontal:
247 self.resize( 2, n )
248 else:
249 self.resize( n, 2 )
250
251 for idx, c in enumerate( self.children ):
252 w = c.__get_widgets__()
253 xrm, yrm = c.__get_resize_mode__()
254
255 if len( w ) == 1:
256
257 if isinstance( xrm, ( tuple, list ) ):
258 xrm = xrm[ -1 ]
259 if isinstance( yrm, ( tuple, list ) ):
260 yrm = yrm[ -1 ]
261
262 if self.horizontal:
263 row0 = 0
264 row1 = 2
265 col0 = idx
266 col1 = idx + 1
267 else:
268 row0 = idx
269 row1 = idx + 1
270 col0 = 0
271 col1 = 2
272
273 self.attach( w[ 0 ], col0, col1, row0, row1,
274 xoptions=xrm,
275 yoptions=yrm,
276 xpadding=self.padding,
277 ypadding=self.padding )
278
279 elif len( w ) == 2:
280 if isinstance( xrm, int ):
281 xrm = ( xrm, xrm )
282 if isinstance( yrm, int ):
283 yrm = ( yrm, yrm )
284
285 if self.horizontal:
286 row0 = 0
287 row1 = 1
288 row2 = 1
289 row3 = 2
290 col0 = idx
291 col1 = idx + 1
292 col2 = idx
293 col3 = idx + 1
294 else:
295 row0 = idx
296 row1 = idx + 1
297 row2 = idx
298 row3 = idx + 1
299 col0 = 0
300 col1 = 1
301 col2 = 1
302 col3 = 2
303 self.attach( w[ 0 ], col0, col1, row0, row1,
304 xoptions=xrm[ 0 ],
305 yoptions=yrm[ 0 ],
306 xpadding=self.padding,
307 ypadding=self.padding )
308 self.attach( w[ 1 ], col2, col3, row2, row3,
309 xoptions=xrm[ 1 ],
310 yoptions=yrm[ 1 ],
311 xpadding=self.padding,
312 ypadding=self.padding )
313
314
315
318
319
320
321
322 -class _Panel( gtk.ScrolledWindow ):
367
368
369
370
372 """Internal widget to arrange components vertically.
373
374 @warning: never use it directly in Eagle applications!
375 """
376
377 _horizontal = False
378 _hscrollbar_policy = gtk.POLICY_NEVER
379 _vscrollbar_policy = gtk.POLICY_AUTOMATIC
380
381
382
384 """Internal widget to arrange components horizontally.
385
386 @warning: never use it directly in Eagle applications!
387 """
388
389 _horizontal = True
390 _hscrollbar_policy = gtk.POLICY_AUTOMATIC
391 _vscrollbar_policy = gtk.POLICY_NEVER
392
393
394
396 """The basic Eagle Object.
397
398 All eagle objects provides an attribute "id".
399
400 @warning: never use it directly in Eagle applications!
401 """
402
403 id = _gen_ro_property( "id" )
404
407
408
409
411 return "%s( id=%r )" % ( self.__class__.__name__, self.id )
412
413 __repr__ = __str__
414
415
416
418 """Mix-In to auto-generate ids.
419
420 @warning: never use it directly in Eagle applications!
421 """
422 last_id_num = 0
423
428
429 __get_id__ = classmethod( __get_id__ )
430
431
432
433 -class Image( _EGObject, AutoGenId ):
434 """
435 An image that can be loaded from files or binary data and saved to files.
436 """
437 _id2obj_ = weakref.WeakValueDictionary()
438
440 """Image constructor.
441
442 Images can be constructed in 2 ways using keyword arguments:
443 - from files, in this case you give it B{filename} keyword:
444
445 >>> Image( filename='myfile.png' )
446
447 - from raw data, in this case you need to provide at least
448 B{data}, B{width} and B{height} as arguments. Optional
449 arguments are I{depth}, I{has_alpha} and I{row_stride}.
450 See L{load_data()} for more information:
451
452 >>> Image( data=data, width=200, height=200, depth=32, has_alpha=False )
453
454 @see: L{load_data()}
455 @see: L{load_file()}
456 """
457 id = kargs.get( "id" ) or self.__get_id__()
458 _EGObject.__init__( self, id )
459
460 self._img = None
461
462 if "filename" in kargs:
463 self.load_file( kargs[ "filename" ] )
464 elif "data" in kargs and "width" in kargs and "height" in kargs:
465 k = { "data": kargs[ "data" ],
466 "width": kargs[ "width" ],
467 "height": kargs[ "height" ],
468 }
469 if "depth" in kargs:
470 k[ "depth" ] = kargs[ "depth" ]
471 if "has_alpha" in kargs:
472 k[ "has_alpha" ] = kargs[ "has_alpha" ]
473 if "rowstride" in kargs:
474 k[ "rowstride" ] = kargs[ "rowstride" ]
475 self.load_data( **k )
476 elif "__int_image__" in kargs:
477 if isinstance( kargs[ "__int_image__" ], gtk.gdk.Pixbuf ):
478 self._img = kargs[ "__int_image__" ]
479 else:
480 raise ValueError( "Wrong internal image given!" )
481 elif len( kargs ) > 0:
482 params = [ "%s=%r" % kv for kv in kargs.iteritems() ]
483 raise ValueError( "Unknow parameters: %s" % params )
484
485 Image._id2obj_[ self.id ] = self
486
487
488
490 return self._img
491
492
493
496
497 __get_by_id__ = classmethod( __get_by_id__ )
498
499
501 gc.collect()
502
503
504
505 - def save( self, filename, format=None, **options ):
506 """Save image to a file.
507
508 If format is not specified, it will be guessed from filename.
509
510 Format may be an extension or a mime type, see
511 L{get_writable_formats()}.
512
513 @see: L{get_writable_formats()}.
514 @raise Exception: if errors happened during write
515 @raise ValueError: if format is unsupported
516 """
517 if isinstance( filename, ( tuple, list ) ):
518 filename = os.path.join( *filename )
519
520 if format is None:
521 format = filename.split( os.path.extsep )[ -1 ]
522
523 format = format.lower()
524 t = None
525 for f in self.get_writable_formats():
526 if format == f[ "name" ] or \
527 format in f[ "extensions" ] or \
528 format in f[ "mime_types" ]:
529 t = f[ "name" ]
530 break
531 if t:
532 try:
533 self._img.save( filename, t, options )
534 except gobject.GError, e:
535 raise Exception( e )
536 else:
537 raise ValueError( "Unsupported file format: \"%s\"" % format )
538
539
540
542 """Get supported image format information.
543
544 @return: list of dicts with keys:
545 - B{name}: format name
546 - B{description}: format description
547 - B{extensions}: extensions that match format
548 - B{mime_types}: mime types that match format
549 - B{is_writable}: if it is possible to write in this format, otherwise
550 it's just readable
551 """
552 return gtk.gdk.pixbuf_get_formats()
553
554
555
566
567
568
570 """Load image from file given its filename.
571
572 filename may be a string or a tuple/list with path elements,
573 this helps your program to stay portable across different platforms.
574
575 >>> i = Image()
576 >>> i.load_file( 'img.png' )
577 >>> i.load_file( ( 'test', 'img.png' ) )
578 """
579 if isinstance( filename, ( tuple, list ) ):
580 filename = os.path.join( *filename )
581
582 try:
583 self._img = gtk.gdk.pixbuf_new_from_file( filename )
584 except gobject.GError, e:
585 raise Exception( e )
586
587
588
589 - def load_data( self, data, width, height,
590 depth=24, has_alpha=None, rowstride=None ):
591 """Load image from raw data.
592
593 If no value is provided as B{has_alpha}, then it's set to C{False}
594 if B{depth} is less or equal 24 or set to C{True} if depth is 32.
595
596 If no value is provided as B{rowstride}, then it's set to
597 M{width * depth / bits_per_sample}.
598
599 >>> i = Image()
600 >>> i.load_data( my_data1, 800, 600, depth=32, has_alpha=False )
601 >>> i.load_data( my_data2, 400, 300, depth=24 )
602 """
603 colorspace = gtk.gdk.COLORSPACE_RGB
604 bits_per_sample = 8
605
606 if has_alpha is None:
607 if depth <= 24:
608 has_alpha=False
609 else:
610 has_alpha=True
611
612 if rowstride is None:
613 rowstride = width * depth / bits_per_sample
614
615 if len( data ) < height * rowstride:
616 raise ValueError( ( "data must be at least "
617 "width * height * rowstride long."
618 "Values are: data size=%d, required=%d" ) %
619 ( len( data ), height * rowstride ) )
620
621 if isinstance( data, list ):
622
623 try:
624 import Numeric
625 data = Numeric.array( data, typecode=Numeric.Character )
626 except ImportError:
627 try:
628 import array
629 data = array.array( 'c', data )
630 except:
631 data = tuple( data )
632
633 self._img = gtk.gdk.pixbuf_new_from_data( data, colorspace,
634 has_alpha, bits_per_sample,
635 width, height, rowstride )
636
637
638
640 """Return raw data and information about this image.
641
642 @return: a tuple of:
643 - width
644 - height
645 - depth
646 - has alpha?
647 - rowstride
648 - raw pixel data
649 """
650 return ( self.get_width(), self.get_height(), self.get_depth(),
651 self.has_alpha(), self.get_rowstride(),
652 self._img.get_pixels() )
653
654
657
658
659
662
663
664
668
669
670
672 """Row stride is the allocated size of a row.
673
674 Generally, rowstride is the number of elements in a row multiplied
675 by the size of each element (bits per pixel).
676
677 But there are cases that there is more space left, a padding, to
678 align it to some boundary, so you may get different value for
679 row stride.
680 """
681 return self._img.get_rowstride()
682
683
684
688
689
690
692 """Bits per pixel"""
693 return self.get_n_channels() * self._img.get_bits_per_sample()
694
695 get_depth = get_bits_per_pixel
696
697
699 """If it has an alpha channel"""
700 return self._img.get_has_alpha()
701
702
703
704
705
764
765
766
767
797
798
799
800
802 """A window that displays information about the application.
803
804 @attention: avoid using this directly, use L{AboutButton} instead.
805 """
806 border = 3
807 spacing = 6
808 width = 600
809 height = 400
810 margin = 3
811
812 - def __init__( self, app,
813 title, author=None, description=None, help=None,
814 version=None, license=None, copyright=None ):
825
826
827
829 self._diag.destroy()
830
831
832
834 win = self.app.__get_window__()
835 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
836 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
837 self._diag = gtk.Dialog( title=( "About: %s" % self.title ),
838 parent=win,
839 flags=flags, buttons=btns )
840
841 settings = self._diag.get_settings()
842 try:
843 settings.set_property( "gtk-button-images", False )
844 except:
845 pass
846
847 self._diag.set_border_width( self.border )
848 self._diag.set_default_size( self.width, self.height )
849 self._diag.set_has_separator( False )
850 self._diag.vbox.set_spacing( self.spacing )
851
852 _set_icon_list( self._diag, gtk.STOCK_ABOUT )
853
854 self._text = RichText( id="About-%s" % self.app.id )
855 self._diag.vbox.pack_start( self._text._widgets[ 0 ], True, True )
856
857 self.__setup_text__()
858
859
860
861 - def __setup_text__( self ):
862 self._text.append( "<h1>%s</h1>" % self.title )
863
864 if self.version:
865 v = ".".join( self.version )
866 self._text.append( "<i>%s</i>" % v )
867
868 self._text.append( "<hr />" )
869
870 if self.description:
871 self._text.append( "<h2>Description</h2>" )
872 for l in self.description:
873 self._text.append( "<p>%s</p>" % l )
874
875 if self.license:
876 self._text.append( "<h2>License</h2><p>" )
877 self._text.append( ", ".join( self.license ) )
878 self._text.append( "</p>" )
879
880 if self.author:
881 if len( self.author ) == 1:
882 self._text.append( "<h2>Author</h2>" )
883 else:
884 self._text.append( "<h2>Authors</h2>" )
885
886 self._text.append( "<ul>" )
887 for a in self.author:
888 self._text.append( "<li>%s</li>" % a )
889 self._text.append( "</ul>" )
890
891 if self.help:
892 self._text.append( "<h2>Help</h2>" )
893 for l in self.help:
894 self._text.append( "<p>%s</p>" % l )
895
896 if self.copyright:
897 self._text.append( "<h2>Copyright</h2>" )
898 for l in self.copyright:
899 self._text.append( "<p>%s</p>" % l )
900
901
902
904 self._diag.show_all()
905 self._diag.run()
906 self._diag.hide()
907
908
909
910
912 """A window that displays application help.
913
914 @attention: avoid using this directly, use L{HelpButton} instead.
915 """
916 border = 3
917 spacing = 6
918 width = 600
919 height = 400
920 margin = 3
921
922 - def __init__( self, app, title, help=None ):
927
928
929
931 self._diag.destroy()
932
933
934
936 win = self.app.__get_window__()
937 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
938 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
939 self._diag = gtk.Dialog( title=( "Help: %s" % self.title ),
940 parent=win,
941 flags=flags, buttons=btns )
942
943 settings = self._diag.get_settings()
944 try:
945 settings.set_property( "gtk-button-images", False )
946 except:
947 pass
948
949 self._diag.set_border_width( self.border )
950 self._diag.set_default_size( self.width, self.height )
951 self._diag.set_has_separator( False )
952 self._diag.vbox.set_spacing( self.spacing )
953 _set_icon_list( self._diag, gtk.STOCK_HELP )
954
955 self._text = RichText( id="About-%s" % self.app.id )
956 self._diag.vbox.pack_start( self._text._widgets[ 0 ], True, True )
957
958 self.__setup_text__()
959
960
961
962 - def __setup_text__( self ):
963 self._text.append( "<h1>%s</h1>" % self.title )
964 self._text.append( "<h2>Help</h2>" )
965 for l in self.help:
966 self._text.append( "<p>%s</p>" % l )
967
968
969
971 self._diag.show_all()
972 self._diag.run()
973 self._diag.hide()
974
975
976
977
979 """A dialog to choose a file.
980
981 @attention: avoid using this directly, use L{App.file_chooser},
982 L{OpenFileButton}, L{SaveFileButton} or L{SelectFolderButton} instead.
983 """
984 ACTION_OPEN = 0
985 ACTION_SAVE = 1
986 ACTION_SELECT_FOLDER = 2
987 ACTION_CREATE_FOLDER = 3
988
989 - def __init__( self, app, action, filename=None,
990 title=None, filter=None, multiple=False ):
991 """Dialog to choose files.
992
993 filter may be a single pattern (ie: '*.png'), mime type
994 (ie: 'text/html') or a list of patterns or mime types or
995 a list of lists, each sub list with a filter name and mime type/
996 patterns accepted. Examples:
997 [ [ 'Images', '*.ppm', 'image/jpeg', 'image/png' ],
998 [ 'Text', '*.text', 'text/plain' ],
999 ]
1000 """
1001 _EGWidget.__init__( self, self.__get_id__(), app )
1002 self.action = action
1003 self.filter = filter
1004 self.multiple = multiple or False
1005 self.filename = filename
1006 self.title = title or self.__gen_title__()
1007
1008 if self.multiple:
1009 warn( "Maemo doesn't support multiple file selection!" )
1010
1011 self.__setup_gui__()
1012
1013
1014
1023
1024
1025
1027 self._diag.destroy()
1028
1029
1030
1032 win = self.app.__get_window__()
1033 a = { self.ACTION_OPEN: gtk.FILE_CHOOSER_ACTION_OPEN,
1034 self.ACTION_SAVE: gtk.FILE_CHOOSER_ACTION_SAVE,
1035 self.ACTION_SELECT_FOLDER: gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
1036 self.ACTION_CREATE_FOLDER: gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER,
1037 }.get( self.action, gtk.FILE_CHOOSER_ACTION_OPEN )
1038
1039 self._diag = hildon.FileChooserDialog( parent=win,
1040 action=a )
1041 _set_icon_list( self._diag, gtk.STOCK_OPEN )
1042 if self.filter:
1043 sys.stderr.write( "Maemo HildonFileChooserDialog doesn't support "
1044 "filters" )
1045 if self.filename:
1046 self._diag.set_filename( self.filename )
1047
1048
1049
1051 self._diag.show_all()
1052 r = self._diag.run()
1053 self._diag.hide()
1054 if r == gtk.RESPONSE_OK:
1055 return self._diag.get_filename()
1056 else:
1057 return None
1058
1059
1060
1061
1063 """A dialog to present user with preferences.
1064
1065 Preferences is another L{App} area, just like C{left}, C{right}, C{center},
1066 C{top} or C{bottom}, but will be displayed in a separate window.
1067
1068 @attention: avoid using this directly, use L{PreferencesButton} instead.
1069 """
1075
1076
1077
1079 self._diag.destroy()
1080
1081
1082
1084 win = self.app.__get_window__()
1085 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
1086 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
1087 self._diag = gtk.Dialog( title=( "Preferences: %s" % self.app.title ),
1088 parent=win,
1089 flags=flags, buttons=btns )
1090
1091 settings = self._diag.get_settings()
1092 try:
1093 settings.set_property( "gtk-button-images", False )
1094 except:
1095 pass
1096
1097 self._diag.set_default_size( 400, 300 )
1098 _set_icon_list( self._diag, gtk.STOCK_PREFERENCES )
1099
1100 self._sw = gtk.ScrolledWindow()
1101 self._diag.get_child().pack_start( self._sw, expand=True, fill=True )
1102
1103 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
1104 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
1105
1106 self._tab = _Table( self.id, self.children )
1107 self._sw.add_with_viewport( self._tab )
1108 self._sw.get_child().set_shadow_type( gtk.SHADOW_NONE )
1109 self._sw.set_shadow_type( gtk.SHADOW_NONE )
1110
1111
1112
1118
1119
1120
1122 self._diag.show_all()
1123 self._diag.run()
1124 self._diag.hide()
1125
1126
1127
1128
1130 """Dialog to show uncaught exceptions.
1131
1132 This dialog shows information about uncaught exceptions and also save
1133 the traceback to a file.
1134 """
1135
1136 border = 3
1137 spacing = 6
1138 width = 600
1139 height = 400
1140 margin = 3
1141
1145
1146
1147
1149 b = ( gtk.STOCK_QUIT, gtk.RESPONSE_CLOSE )
1150 self._diag = gtk.Dialog( "Application Crashed!",
1151 parent=None,
1152 flags=gtk.DIALOG_MODAL,
1153 buttons=b )
1154
1155 settings = self._diag.get_settings()
1156 try:
1157 settings.set_property( "gtk-button-images", False )
1158 except:
1159 pass
1160
1161 self._diag.set_border_width( self.border )
1162 self._diag.set_default_size( self.width, self.height )
1163 self._diag.set_has_separator( False )
1164 self._diag.vbox.set_spacing( self.spacing )
1165
1166 self._hbox1 = gtk.HBox()
1167
1168 self._label1 = gtk.Label( "<b>Exception type:</b>" )
1169 self._label1.set_use_markup( True )
1170 self._hbox1.pack_start( self._label1, False, False, self.spacing )
1171 self._label1.show()
1172
1173 self._exctype = gtk.Label()
1174 self._hbox1.pack_start( self._exctype, False, True )
1175 self._exctype.show()
1176
1177 self._diag.vbox.pack_start( self._hbox1, False, False )
1178 self._hbox1.show()
1179
1180 self._hbox2 = gtk.HBox()
1181
1182 self._label2 = gtk.Label( "<b>This info was saved to:</b>" )
1183 self._label2.set_use_markup( True )
1184 self._hbox2.pack_start( self._label2, False, False, self.spacing )
1185 self._label2.show()
1186
1187 self._save_name = gtk.Label()
1188 self._hbox2.pack_start( self._save_name, False, True )
1189 self._save_name.show()
1190
1191 self._diag.vbox.pack_start( self._hbox2, False, False )
1192 self._hbox2.show()
1193
1194 self._sw = gtk.ScrolledWindow()
1195 self._sw.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC )
1196 self._sw.set_shadow_type( gtk.SHADOW_IN )
1197 self._text = gtk.TextView()
1198 self._text.set_editable( False )
1199 self._text.set_cursor_visible( False )
1200 self._text.set_wrap_mode( gtk.WRAP_WORD )
1201 self._text.set_left_margin( self.margin )
1202 self._text.set_right_margin( self.margin )
1203
1204 self._sw.add( self._text )
1205 self._text.show()
1206 self._diag.vbox.pack_start( self._sw, expand=True, fill=True )
1207 self._sw.show()
1208 self.__setup_text__()
1209
1210
1211
1212 - def __setup_text__( self ):
1213 self._buf = self._text.get_buffer()
1214 self._buf.create_tag( "label", weight=pango.WEIGHT_BOLD )
1215 self._buf.create_tag( "code", foreground="gray25",
1216 family="monospace" )
1217 self._buf.create_tag( "exc", foreground="#880000",
1218 weight=pango.WEIGHT_BOLD )
1219
1220
1221
1223 import traceback
1224 self._exctype.set_text( str( exctype ) )
1225 self.print_tb( tb )
1226
1227 lines = traceback.format_exception_only( exctype, value )
1228 msg = lines[ 0 ]
1229 result = msg.split( ' ', 1 )
1230 if len( result ) == 1:
1231 msg = result[ 0 ]
1232 arguments = ""
1233 else:
1234 msg, arguments = result
1235
1236 self._insert_text( "\n" )
1237 self._insert_text( msg, "exc" )
1238 self._insert_text( " " )
1239 self._insert_text( arguments )
1240
1241
1242
1244 import traceback
1245 import time
1246 progname = os.path.split( sys.argv[ 0 ] )[ -1 ]
1247 filename = "%s-%s-%s.tb" % ( progname,
1248 os.getuid(),
1249 int( time.time() ) )
1250 filename = os.path.join( os.path.sep, "tmp", filename )
1251 f = open( filename, "wb" )
1252 try:
1253 os.chmod( filename, 0600 )
1254 except:
1255 pass
1256
1257 for e in traceback.format_exception( exctype, value, tb ):
1258 f.write( e )
1259 f.close()
1260 self._save_name.set_text( filename )
1261 sys.stderr.write( "Traceback saved to '%s'.\n" % filename )
1262
1263
1264
1266 import linecache
1267
1268 if limit is None:
1269 if hasattr( sys, "tracebacklimit" ):
1270 limit = sys.tracebacklimit
1271 n = 0
1272 while tb is not None and ( limit is None or n < limit ):
1273 f = tb.tb_frame
1274 lineno = tb.tb_lineno
1275 co = f.f_code
1276 filename = co.co_filename
1277 name = co.co_name
1278 self._print_file( filename, lineno, name )
1279 line = linecache.getline( filename, lineno )
1280 if line:
1281 self._insert_text( " " + line.strip() + "\n\n", "code" )
1282 tb = tb.tb_next
1283 n = n+1
1284
1285
1286
1287 - def _insert_text( self, text, *tags ):
1288 end_iter = self._buf.get_end_iter()
1289 self._buf.insert_with_tags_by_name( end_iter, text, *tags )
1290
1291
1292
1306
1307
1309 import pdb
1310 pdb.pm()
1311
1312
1313
1314
1315 - def run( self, error=None ):
1316 r = self._diag.run()
1317 if r == gtk.RESPONSE_CLOSE or gtk.RESPONSE_DELETE_EVENT:
1318 raise SystemExit( error )
1319
1320
1321
1331
1332 except_hook = staticmethod( except_hook )
1333
1334 sys.excepthook = DebugDialog.except_hook
1335
1336
1337 -class _EGWidLabelEntry( _EGDataWidget ):
1338 """Widget that holds a label and an associated Entry.
1339
1340 @note: _EGWidLabelEntry must B{NOT} be used directly! You should use
1341 a widget that specialize this instead.
1342
1343 @attention: B{Widget Developers:} You must setup an instance attribute
1344 C{_entry} before using it, since this will be set as mnemonic for this
1345 label and also returned in L{__get_widgets__}().
1346 """
1347 - def __init__( self, id, persistent, label="" ):
1348 _EGDataWidget.__init__( self, id, persistent )
1349 self.__label = label
1350 self.__setup_gui__()
1351
1352
1353
1354 - def __setup_gui__( self ):
1355 if self.__label is not None:
1356 self._label = gtk.Label( self.__label )
1357 self._label.set_justify( gtk.JUSTIFY_RIGHT )
1358 self._label.set_alignment( xalign=1.0, yalign=0.5 )
1359 self._label.set_mnemonic_widget( self._entry )
1360 self._widgets = ( self._label, self._entry )
1361 else:
1362 self._widgets = ( self._entry, )
1363
1364
1365
1366 - def get_value( self ):
1367 return self._entry.get_value()
1368
1369
1370
1371 - def set_value( self, value ):
1372 self._entry.set_value( value )
1373
1374
1375
1376 - def set_label( self, label ):
1377 if self.__label is None:
1378 raise ValueError( "You cannot change label of widget created "
1379 "without one. Create it with placeholder! "
1380 "(label='')" )
1381 self.__label = label
1382 self._label.set_text( self.__label )
1383
1384
1385
1386 - def get_label( self ):
1387 return self.__label
1388
1389
1390 label = property( get_label, set_label )
1391
1392
1393 - def __str__( self ):
1394 return "%s( id=%r, label=%r, value=%r )" % \
1395 ( self.__class__.__name__, self.id, self.label,
1396 self.get_value() )
1397
1398 __repr__ = __str__
1399
1400
1402 """Resize mode.
1403
1404 First tuple of tuple is about horizontal mode for label and entry.
1405 Second tuple of tuple is about vertical mode for label and entry.
1406 """
1407 return ( ( 0, gtk.FILL | gtk.EXPAND ),
1408 ( 0, 0 ) )
1409
1410
1411
1412
1413 -class App( _EGObject, AutoGenId ):
1414 """An application window.
1415
1416 This is the base of Eagle programs, since it will hold every graphical
1417 component.
1418
1419 An App window is split in 5 areas:
1420 - left
1421 - right
1422 - center
1423 - top
1424 - bottom
1425 the first 3 have a vertical layout, the other have horizontal layout.
1426 Every area has its own scroll bars that are shown automatically when
1427 need.
1428
1429 Also provided is an extra area, that is shown in another window. This is
1430 the preferences area. It have a vertical layout and components that
1431 hold data are made persistent automatically. You should use
1432 L{PreferencesButton} to show this area.
1433
1434 Extra information like author, description, help, version, license and
1435 copyright are used in specialized dialogs. You may show these dialogs
1436 with L{AboutButton} and L{HelpButton}.
1437
1438 Widgets can be reach with L{get_widget_by_id}, example:
1439 >>> app = App( "My App", left=Entry( id="entry" ) )
1440 >>> app.get_widget_by_id( "entry" )
1441 Entry( id='entry', label='entry', value='' )
1442
1443 You may also reach widgets using dict-like syntax, but with the
1444 special case for widgets that hold data, these will be provided
1445 using their L{set_data<_EGDataWidget.set_data>} and
1446 L{get_data<_EGDataWidget.get_data>}, it make things easier, but
1447 B{be careful to don't misuse it!}. Example:
1448
1449 >>> app= App( "My App", left=Entry( id="entry" ),
1450 ... right=Canvas( "canvas", 300, 300 ) )
1451 >>> app[ "entry" ]
1452 ''
1453 >>> app[ "entry" ] = "text"
1454 >>> app[ "entry" ]
1455 'text'
1456 >>> app[ "canvas" ]
1457 Canvas( id='canvas', width=300, height=300, label='' )
1458 >>> app[ "canvas" ].draw_text( "hello" )
1459 >>> app[ "entry" ].get_value() # will fail, since it's a data widget
1460
1461 """
1462 border_width = 0
1463 spacing = 3
1464
1465 title = _gen_ro_property( "title" )
1466 left = _gen_ro_property( "left" )
1467 right = _gen_ro_property( "right" )
1468 top = _gen_ro_property( "top" )
1469 bottom = _gen_ro_property( "bottom" )
1470 center = _gen_ro_property( "center" )
1471 preferences = _gen_ro_property( "preferences" )
1472 statusbar = _gen_ro_property( "statusbar" )
1473 _widgets = _gen_ro_property( "_widgets" )
1474
1475 - def __init__( self, title, id=None,
1476 center=None, left=None, right=None, top=None, bottom=None,
1477 preferences=None,
1478 quit_callback=None, data_changed_callback=None,
1479 author=None, description=None, help=None, version=None,
1480 license=None, copyright=None,
1481 statusbar=False ):
1482 """App Constructor.
1483
1484 @param title: application name, to be displayed in the title bar.
1485 @param id: unique id to this application, or None to generate one
1486 automatically.
1487 @param center: list of widgets to be laid out vertically in the
1488 window's center.
1489 @param left: list of widgets to be laid out vertically in the
1490 window's left side.
1491 @param right: list of widgets to be laid out vertically in the
1492 window's right side.
1493 @param top: list of widgets to be laid out horizontally in the
1494 window's top.
1495 @param bottom: list of widgets to be laid out horizontally in the
1496 window's bottom.
1497 @param preferences: list of widgets to be laid out vertically in
1498 another window, this can be shown with L{PreferencesButton}.
1499 @param statusbar: if C{True}, an statusbar will be available and
1500 usable with L{status_message} method.
1501 @param author: the application author or list of author, used in
1502 L{AboutDialog}, this can be shown with L{AboutButton}.
1503 @param description: application description, used in L{AboutDialog}.
1504 @param help: help text, used in L{AboutDialog} and L{HelpDialog}, this
1505 can be shown with L{HelpButton}.
1506 @param version: application version, used in L{AboutDialog}.
1507 @param license: application license, used in L{AboutDialog}.
1508 @param copyright: application copyright, used in L{AboutDialog}.
1509 @param quit_callback: function (or list of functions) that will be
1510 called when application is closed. Function will receive as
1511 parameter the reference to App. If return value is False,
1512 it will abort closing the window.
1513 @param data_changed_callback: function (or list of functions) that will
1514 be called when some widget that holds data have its data
1515 changed. Function will receive as parameters:
1516 - App reference
1517 - Widget reference
1518 - new value
1519 """
1520 _EGObject.__init__( self, id )
1521 self.title = title
1522 self.left = left
1523 self.right = right
1524 self.top = top
1525 self.bottom = bottom
1526 self.center = center
1527 self.preferences = preferences
1528 self.author = _str_tuple( author )
1529 self.description = _str_tuple( description )
1530 self.help = _str_tuple( help )
1531 self.version = _str_tuple( version )
1532 self.license = _str_tuple( license )
1533 self.copyright = _str_tuple( copyright )
1534 self.statusbar = statusbar
1535 self._widgets = {}
1536
1537 self.quit_callback = _callback_tuple( quit_callback )
1538 self.data_changed_callback = _callback_tuple( data_changed_callback )
1539
1540 self.__add_to_app_list__()
1541 self.__setup_gui__()
1542 self.__setup_connections__()
1543 self.load()
1544
1545
1546
1553
1554
1555
1557 w = self.get_widget_by_id( name )
1558 if w is None:
1559 raise ValueError( "Could not find any widget with id=%r" % name )
1560 elif isinstance( w, _EGDataWidget ):
1561 return w.set_value( value )
1562 else:
1563 raise TypeError(
1564 "Could not set value of widget '%s' of type '%s'." % \
1565 ( name, type( w ).__name__ ) )
1566
1567
1568
1576
1577
1578
1580 """Show L{AboutDialog} of this App."""
1581 diag = AboutDialog( app=self,
1582 title=self.title,
1583 author=self.author,
1584 description=self.description,
1585 help=self.help,
1586 version=self.version,
1587 license=self.license,
1588 copyright=self.copyright,
1589 )
1590 diag.run()
1591
1592
1593
1595 """Show L{HelpDialog} of this App."""
1596 diag = HelpDialog( app=self,
1597 title=self.title,
1598 help=self.help,
1599 )
1600 diag.run()
1601
1602
1603
1604 - def file_chooser( self, action, filename=None,
1605 filter=None, multiple=False ):
1606 """Show L{FileChooser} and return selected file(s).
1607
1608 @param action: must be one of ACTION_* as defined in L{FileChooser}.
1609 @param filter: a pattern (ie: '*.png'), mime type or a list.
1610
1611 @see: L{FileChooser}
1612 """
1613 diag = FileChooser( app=self, action=action,
1614 filename=filename, filter=filter,
1615 multiple=multiple )
1616 return diag.run()
1617
1618
1619
1621 """Show L{PreferencesDialog} associated with this App."""
1622 return self._preferences.run()
1623
1624
1625
1627 return self._win
1628
1629
1630
1632 if not self.id:
1633 self.id = self.__get_id__()
1634
1635 if self.id in _apps:
1636 raise ValueError( "App id '%s' already existent!" % self.id )
1637
1638 _apps[ self.id ] = self
1639
1640
1641
1663
1664
1665
1667 self._win = hildon.App()
1668 self._win.set_name( self.id )
1669 self._win.set_title( self.title )
1670
1671 self._mv = hildon.AppView( self.title )
1672 self._win.set_appview( self._mv )
1673 self._mv.set_fullscreen_key_allowed( True )
1674
1675 self._top_layout = gtk.VBox( False )
1676 self._mv.add( self._top_layout )
1677
1678 self._hbox = gtk.HBox( False, self.spacing )
1679 self._hbox.set_border_width( self.border_width )
1680 self._top_layout.pack_start( self._hbox, expand=True, fill=True )
1681
1682 self.__setup_menus__()
1683 self.__setup_gui_left__()
1684 self.__setup_gui_right__()
1685 self.__setup_gui_center__()
1686 self.__setup_gui_top__()
1687 self.__setup_gui_bottom__()
1688 self.__setup_gui_preferences__()
1689
1690 self._vbox = gtk.VBox( False, self.spacing )
1691
1692 has_top = bool( self._top.__get_widgets__() )
1693 has_bottom = bool( self._bottom.__get_widgets__() )
1694 has_left = bool( self._left.__get_widgets__() )
1695 has_right = bool( self._right.__get_widgets__() )
1696 has_center = bool( self._center.__get_widgets__() )
1697
1698 expand_top = False
1699 expand_bottom = False
1700 expand_left = False
1701 expand_right = False
1702 expand_center = has_center
1703
1704
1705 if has_left and not has_center:
1706 expand_left = True
1707 if has_right and not has_center:
1708 expand_right = True
1709
1710
1711 if has_top and not has_center:
1712 expand_top = True
1713 if has_bottom and not has_center:
1714 expand_bottom = True
1715
1716
1717 has_vl = has_top or has_center or has_bottom
1718 if has_vl:
1719 if has_top:
1720 self._vbox.pack_start( self._top, expand_top, True )
1721 if has_center or has_bottom:
1722 self._vbox.pack_start( gtk.HSeparator(), False, True )
1723
1724 if has_center:
1725 self._vbox.pack_start( self._center, expand_center, True )
1726 if has_bottom:
1727 self._vbox.pack_start( gtk.HSeparator(), False, True )
1728
1729 if has_bottom:
1730 self._vbox.pack_start( self._bottom, expand_bottom, True )
1731
1732
1733
1734 if has_left:
1735 self._hbox.pack_start( self._left, expand_left, True )
1736 if has_vl or has_right:
1737 self._hbox.pack_start( gtk.VSeparator(), False, True )
1738
1739 if has_vl:
1740 self._hbox.pack_start( self._vbox, True, True )
1741 if has_right:
1742 self._hbox.pack_start( gtk.VSeparator(), False, True )
1743
1744 if has_right:
1745 self._hbox.pack_start( self._right, expand_right, True )
1746
1747
1748 if self.statusbar:
1749 self._statusbar = gtk.Statusbar()
1750 self._statusbar_ctx = self._statusbar.get_context_id( self.title )
1751 self._statusbar.set_has_resize_grip( True )
1752 self._top_layout.pack_end( self._statusbar,
1753 expand=False, fill=True )
1754
1755 settings = self._win.get_settings()
1756 try:
1757 settings.set_property( "gtk-button-images", False )
1758 except:
1759 pass
1760 self._win.show_all()
1761
1762
1763
1765 self._menu = self._mv.get_menu()
1766
1767 mi = gtk.MenuItem( "Help" )
1768 mi.connect( "activate", lambda *x: self.show_help_dialog() )
1769 self._menu.append( mi )
1770
1771 mi = gtk.MenuItem( "About" )
1772 mi.connect( "activate", lambda *x: self.show_about_dialog() )
1773 self._menu.append( mi )
1774
1775 mi = gtk.MenuItem( "Preferences" )
1776 mi.connect( "activate", lambda *x: self.show_preferences_dialog() )
1777 self._menu.append( mi )
1778
1779 mi = gtk.MenuItem( "Close" )
1780 mi.connect( "activate", lambda *x: self.close() )
1781 self._menu.append( mi )
1782
1783 mi = gtk.MenuItem( "Quit" )
1784 mi.connect( "activate", lambda *x: quit() )
1785 self._menu.append( mi )
1786
1787 self._menu.show_all()
1788
1789
1790
1793
1794
1795
1798
1799
1800
1803
1804
1805
1808
1809
1810
1813
1814
1815
1819
1820
1821
1824
1825
1826
1828 """Notify that widget changed it's value.
1829
1830 Probably you will not need to call this directly.
1831 """
1832 self.save()
1833 for c in self.data_changed_callback:
1834 c( self, widget, value )
1835
1836
1837
1839 self.save()
1840
1841 for c in self.quit_callback:
1842 if not c( self ):
1843 return False
1844
1845 del _apps[ self.id ]
1846 if not _apps:
1847 gtk.main_quit()
1848
1849 return True
1850
1851
1852
1854 if self.__do_close__():
1855 return False
1856 else:
1857 return True
1858
1859
1860
1863 if not os.path.exists( d ):
1864 mkdir( os.path.dirname( d ) )
1865 os.mkdir( d )
1866
1867
1868 fname = "%s.save_data" % self.id
1869 home = os.environ.get( "HOME", "." )
1870 binname = os.path.realpath( sys.argv[ 0 ] )[ 1 : ]
1871 d = os.path.join( home, ".eagle", binname )
1872
1873 mkdir( d )
1874
1875 return os.path.join( d, fname )
1876
1877
1878
1880 """Save data from widgets to file.
1881
1882 Probably you will not need to call this directly.
1883 """
1884 d = {}
1885 for id, w in self._widgets.iteritems():
1886 if isinstance( w, _EGDataWidget ) and w.persistent:
1887 d[ id ] = w.get_value()
1888
1889 if d:
1890 f = open( self.__persistence_filename__(), "wb" )
1891 pickle.dump( d, f, pickle.HIGHEST_PROTOCOL )
1892 f.close()
1893
1894
1895
1897 """Load data to widgets from file.
1898
1899 Probably you will not need to call this directly.
1900 """
1901 try:
1902 f = open( self.__persistence_filename__(), "rb" )
1903 except IOError:
1904 return
1905
1906 d = pickle.load( f )
1907 f.close()
1908
1909 for id, v in d.iteritems():
1910 try:
1911 w = self._widgets[ id ]
1912 except KeyError:
1913 w = None
1914 if isinstance( w, _EGDataWidget ) and w.persistent:
1915 w.set_value( v )
1916
1917
1918
1920 """Close application window."""
1921 if self.__do_close__():
1922 self._win.destroy()
1923
1924
1925
1927 """Display a message in status bar and retrieve its identifier for
1928 later removal.
1929
1930 @see: L{remove_status_message}
1931 @note: this only active if statusbar=True
1932 """
1933 if self.statusbar:
1934 return self._statusbar.push( self._statusbar_ctx, message )
1935 else:
1936 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
1937
1938
1939
1941 """Remove a previously displayed message.
1942
1943 @see: L{status_message}
1944 @note: this only active if statusbar=True
1945 """
1946 if self.statusbar:
1947 self._statusbar.remove( self._statusbar_ctx, message_id )
1948 else:
1949 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
1950
1951
1952
1954 """Register a function to be called after a given timeout/interval.
1955
1956 @param interval: milliseconds between calls.
1957 @param callback: function to call back. This function gets as
1958 argument the app reference and must return C{True} to
1959 keep running, if C{False} is returned, it will not be
1960 called anymore.
1961 @return: id number to be used in L{remove_event_source}
1962 """
1963 - def wrap( *args ):
1965
1966 return gobject.timeout_add( interval, wrap )
1967
1968
1969
1971 """Register a function to be called when system is idle.
1972
1973 System is idle if there is no other event pending.
1974
1975 @param callback: function to call back. This function gets as
1976 argument the app reference and must return C{True} to
1977 keep running, if C{False} is returned, it will not be
1978 called anymore.
1979 @return: id number to be used in L{remove_event_source}
1980 """
1981 - def wrap( *args ):
1983
1984 return gobject.idle_add( wrap )
1985
1986
1987
1988
1989 - def io_watch( self, file, callback,
1990 on_in=False, on_out=False, on_urgent=False, on_error=False,
1991 on_hungup=False ):
1992 """Register a function to be called after an Input/Output event.
1993
1994 @param file: any file object or file descriptor (integer).
1995 @param callback: function to be called back, parameters will be the
1996 application that generated the event, the file that triggered
1997 it and on_in, on_out, on_urgent, on_error or on_hungup,
1998 being True those that triggered the event.
1999 The function must return C{True} to be called back again,
2000 otherwise it is automatically removed.
2001 @param on_in: there is data to read.
2002 @param on_out: data can be written without blocking.
2003 @param on_urgent: there is urgent data to read.
2004 @param on_error: error condition.
2005 @param on_hungup: hung up (the connection has been broken, usually for
2006 pipes and sockets).
2007 @return: id number to be used in L{remove_event_source}
2008 """
2009 - def wrap( source, cb_condition ):
2010 on_in = bool( cb_condition & gobject.IO_IN )
2011 on_out = bool( cb_condition & gobject.IO_OUT )
2012 on_urgent = bool( cb_condition & gobject.IO_PRI )
2013 on_error = bool( cb_condition & gobject.IO_ERR )
2014 on_hungup = bool( cb_condition & gobject.IO_HUP )
2015 return callback( self, source, on_in=on_in,
2016 on_out=on_out, on_urgent=on_urgent,
2017 on_error=on_error, on_hungup=on_hungup )
2018
2019
2020 condition = 0
2021 if on_in:
2022 condition |= gobject.IO_IN
2023 if on_out:
2024 condition |= gobject.IO_OUT
2025 if on_urgent:
2026 condition |= gobject.IO_PRI
2027 if on_error:
2028 condition |= gobject.IO_ERR
2029 if on_hungup:
2030 condition |= gobject.IO_HUP
2031 return gobject.io_add_watch( file, condition, wrap )
2032
2033
2034
2036 """Remove an event generator like those created by L{timeout_add},
2037 L{idle_add} or L{io_watch}.
2038
2039 @param event_id: value returned from L{timeout_add},
2040 L{idle_add} or L{io_watch}.
2041
2042 @return: C{True} if it was removed.
2043 """
2044 return gobject.source_remove( event_id )
2045
2046
2047
2048
2050 """The drawing area.
2051
2052 Eagle's drawing area (Canvas) is provided with a frame and an optional
2053 label, together with scrollbars, to make it fit everywhere.
2054
2055 """
2056 padding = 5
2057 bgcolor= "black"
2058
2059 LEFT = -1
2060 CENTER = 0
2061 RIGHT = 1
2062
2063 FONT_OPTION_BOLD = 1
2064 FONT_OPTION_OBLIQUE = 2
2065 FONT_OPTION_ITALIC = 4
2066
2067 FONT_NAME_NORMAL = "normal"
2068 FONT_NAME_SERIF = "serif"
2069 FONT_NAME_SANS = "sans"
2070 FONT_NAME_MONO = "monospace"
2071
2072 MOUSE_BUTTON_1 = 1
2073 MOUSE_BUTTON_2 = 2
2074 MOUSE_BUTTON_3 = 4
2075 MOUSE_BUTTON_4 = 8
2076 MOUSE_BUTTON_5 = 16
2077
2078 label = _gen_ro_property( "label" )
2079
2080 - def __init__( self, id, width, height, label="", bgcolor=None,
2081 scrollbars=True, callback=None ):
2082 """Canvas Constructor.
2083
2084 @param id: unique identifier.
2085 @param width: width of the drawing area in pixels, widget can be
2086 larger or smaller because and will use scrollbars if need.
2087 @param height: height of the drawing area in pixels, widget can be
2088 larger or smaller because and will use scrollbars if need.
2089 @param label: label to display in the widget frame around the
2090 drawing area. If None, no label or frame will be shown.
2091 @param bgcolor: color to paint background.
2092 @param scrollbars: whenever to use scrollbars and make canvas
2093 fit small places.
2094 @param callback: function (or list of functions) to call when
2095 mouse state changed in the drawing area. Function will get
2096 as parameters:
2097 - App reference
2098 - Canvas reference
2099 - Button state (or'ed MOUSE_BUTTON_*)
2100 - horizontal positon (x)
2101 - vertical positon (y)
2102
2103 @todo: honor the alpha value while drawing colors.
2104 """
2105 _EGWidget.__init__( self, id )
2106 self.__label = label
2107 self.width = width
2108 self.height = height
2109 self.scrollbars = scrollbars
2110
2111 self._pixmap = None
2112 self._callback = _callback_tuple( callback )
2113
2114
2115
2116
2117 self._style = None
2118 self._fg_gc_normal = None
2119 self._bg_gc_normal = None
2120
2121 if bgcolor is not None:
2122 self.bgcolor = self.__color_from__( bgcolor )
2123
2124 self.__setup_gui__( width, height )
2125 self.__setup_connections__()
2126
2127
2128
2130 self._sw = gtk.ScrolledWindow()
2131 self._area = gtk.DrawingArea()
2132
2133 self._sw.set_border_width( self.padding )
2134
2135 if self.label is not None:
2136 self._frame = gtk.Frame( self.label )
2137 self._frame.add( self._sw )
2138 self._frame.set_shadow_type( gtk.SHADOW_OUT )
2139 root = self._frame
2140 else:
2141 root = self._sw
2142
2143 self._area.set_size_request( width, height )
2144 self._sw.add_with_viewport( self._area )
2145 if self.scrollbars:
2146 policy = gtk.POLICY_AUTOMATIC
2147 border = gtk.SHADOW_IN
2148 else:
2149 policy = gtk.POLICY_NEVER
2150 border = gtk.SHADOW_NONE
2151
2152 self._sw.set_policy( hscrollbar_policy=policy,
2153 vscrollbar_policy=policy )
2154 self._sw.child.set_shadow_type( border )
2155 self._sw.show_all()
2156
2157 self._widgets = ( root, )
2158
2159
2160
2162 self._style = self._area.get_style()
2163 self._fg_gc_normal = self._style.fg_gc[ gtk.STATE_NORMAL ]
2164 self._bg_gc_normal = self._style.bg_gc[ gtk.STATE_NORMAL ]
2165
2166
2167
2175
2176 self._area.connect( "configure_event", configure_event )
2177
2178
2180 x , y, width, height = event.area
2181 gc = widget.get_style().fg_gc[ gtk.STATE_NORMAL ]
2182 widget.window.draw_drawable( gc, self._pixmap, x, y, x, y,
2183 width, height )
2184 return False
2185
2186 self._area.connect( "expose_event", expose_event )
2187
2188
2202
2203
2204 buttons_map = {
2205 1: self.MOUSE_BUTTON_1,
2206 2: self.MOUSE_BUTTON_2,
2207 3: self.MOUSE_BUTTON_3,
2208 4: self.MOUSE_BUTTON_4,
2209 5: self.MOUSE_BUTTON_5,
2210 }
2211
2223
2224 if self._callback:
2225 self._area.connect( "button_press_event", button_press_event )
2226
2227
2239
2240 if self._callback:
2241 self._area.connect( "button_release_event", button_release_event )
2242
2243
2245 if self._pixmap is None:
2246 return True
2247
2248 if event.is_hint:
2249 x, y, state = event.window.get_pointer()
2250 else:
2251 x = event.x
2252 y = event.y
2253 state = event.state
2254
2255
2256 btns = get_buttons( state )
2257 x = int( x )
2258 y = int( y )
2259
2260 if btns:
2261 for c in self._callback:
2262 c( self.app, self, btns, x, y )
2263
2264 return True
2265
2266 if self._callback:
2267 self._area.connect( "motion_notify_event", motion_notify_event )
2268
2269
2270
2271 self._area.set_events( gtk.gdk.EXPOSURE_MASK |
2272 gtk.gdk.LEAVE_NOTIFY_MASK |
2273 gtk.gdk.BUTTON_PRESS_MASK |
2274 gtk.gdk.BUTTON_RELEASE_MASK |
2275 gtk.gdk.POINTER_MOTION_MASK |
2276 gtk.gdk.POINTER_MOTION_HINT_MASK )
2277
2278
2279
2281 return ( gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND )
2282
2283
2284
2285
2287 """Convert from color to internal representation.
2288
2289 Gets a string, integer or tuple/list arguments and converts into
2290 internal color representation.
2291 """
2292 a = 255
2293
2294 if isinstance( color, str ):
2295 try:
2296 c = gtk.gdk.color_parse( color )
2297 r = int( c.red / 65535.0 * 255 )
2298 g = int( c.green / 65535.0 * 255 )
2299 b = int( c.blue / 65535.0 * 255 )
2300 except ValueError, e:
2301 raise ValueError( "%s. color=%r" % ( e, color ) )
2302 elif isinstance( color, gtk.gdk.Color ):
2303 r = int( color.red / 65535.0 * 255 )
2304 g = int( color.green / 65535.0 * 255 )
2305 b = int( color.blue / 65535.0 * 255 )
2306 elif isinstance( color, int ):
2307 r = ( color >> 16 ) & 0xff
2308 g = ( color >> 8 ) & 0xff
2309 b = ( color & 0xff )
2310 elif isinstance( color, ( tuple, list ) ):
2311 if len( color) == 3:
2312 r, g, b = color
2313 else:
2314 a, r, g, b = color
2315
2316 return a, r, g, b
2317
2318 __color_from__ = staticmethod( __color_from__ )
2319
2320
2322 r = int( color[ 1 ] / 255.0 * 65535 )
2323 g = int( color[ 2 ] / 255.0 * 65535 )
2324 b = int( color[ 3 ] / 255.0 * 65535 )
2325 return gtk.gdk.Color( r, g, b )
2326
2327 __to_gtk_color__ = staticmethod( __to_gtk_color__ )
2328
2329
2352
2353
2354
2355 - def resize( self, width, height ):
2356 """Resize the drawing area."""
2357 old = self._pixmap
2358 self._pixmap = gtk.gdk.Pixmap( self._area.window, width, height )
2359 if old is None:
2360
2361 self.clear()
2362 else:
2363
2364 w, h = old.get_size()
2365 self._pixmap.draw_drawable( self._fg_gc_normal, old,
2366 0, 0, 0, 0, w, h )
2367
2368
2369
2370 - def draw_image( self, image, x=0, y=0,
2371 width=None, height=None,
2372 src_x=0, src_y=0 ):
2373 """Draw image on canvas.
2374
2375 By default it draws entire image at top canvas corner.
2376
2377 You may restrict which image areas to use with src_x, src_y, width
2378 and height.
2379
2380 You may choose position on canvas with x and y.
2381 """
2382 if not isinstance( image, Image ):
2383 raise TypeError( ( "image must be instance of Image class, "
2384 "but %s found!" ) % ( type( image ).__name__ ) )
2385
2386 p = image.__get_gtk_pixbuf__()
2387
2388 if src_x >= p.get_width():
2389 raise ValueError( "src_x is greater or equal width!" )
2390
2391 if src_y >= p.get_height():
2392 raise ValueError( "src_y is greater or equal height!" )
2393
2394 if width is None or width < 1:
2395 width = p.get_width()
2396
2397 if height is None or height < 1:
2398 height = p.get_height()
2399
2400 if src_x + width > p.get_width():
2401 width = p.get_width() - src_x
2402 if src_y + height > p.get_height():
2403 height = p.get_height() - src_y
2404
2405 self._pixmap.draw_pixbuf( self._fg_gc_normal,
2406 p, src_x, src_y, x, y, width, height )
2407 self._area.queue_draw_area( x, y, width, width )
2408
2409
2410
2411 - def draw_text( self, text, x=0, y=0,
2412 fgcolor=None, bgcolor=None,
2413 font_name=None, font_size=None, font_options=0,
2414 font_family=None,
2415 width=None, wrap_word=False,
2416 alignment=LEFT, justify=True ):
2417 """Draw text on canvas.
2418
2419 By default text is draw with current font and colors at top canvas
2420 corner.
2421
2422 You may limit width providing a value and choose if it should wrap
2423 at words (wrap_word=True) or characters (wrap_word=False).
2424
2425
2426 Colors can be specified with fgcolor an bgcolor. If not provided, the
2427 system foreground color is used and no background color is used.
2428
2429 Font name, family, size and options may be specified using
2430 font_name, font_family, font_size and font_options, respectively.
2431 Try to avoid using system specific font fames, use those provided
2432 by FONT_NAME_*.
2433
2434 Font options is OR'ed values from FONT_OPTIONS_*.
2435
2436 Font name is a string that have all the information, like
2437 "sans bold 12". This is returned by L{Font}.
2438
2439 Text alignment is one of LEFT, RIGHT or CENTER.
2440 """
2441 if fgcolor is not None:
2442 fgcolor = self.__to_gtk_color__( self.__color_from__( fgcolor ) )
2443 if bgcolor is not None:
2444 bgcolor = self.__to_gtk_color__( self.__color_from__( bgcolor ) )
2445
2446 layout = self._area.create_pango_layout( text )
2447 if width is not None:
2448 layout.set_width( width * pango.SCALE )
2449 if wrap_word:
2450 layout.set_wrap( pango.WRAP_WORD )
2451
2452 layout.set_justify( justify )
2453 alignment = { self.LEFT: pango.ALIGN_LEFT,
2454 self.CENTER: pango.ALIGN_CENTER,
2455 self.RIGHT: pango.ALIGN_RIGHT }.get( alignment,
2456 pango.ALIGN_CENTER )
2457 layout.set_alignment( alignment )
2458
2459 if font_name or font_size or font_options or font_family:
2460 if font_name:
2461 fd = pango.FontDescription( font_name )
2462 else:
2463 fd = layout.get_context().get_font_description()
2464
2465 if font_size:
2466 fd.set_size( font_size * pango.SCALE)
2467 if font_options:
2468 if font_options & self.FONT_OPTION_BOLD:
2469 fd.set_weight( pango.WEIGHT_BOLD )
2470 if font_options & self.FONT_OPTION_ITALIC:
2471 fd.set_style( pango.STYLE_ITALIC )
2472 if font_options & self.FONT_OPTION_OBLIQUE:
2473 fd.set_style( pango.STYLE_OBLIQUE )
2474 layout.set_font_description( fd )
2475
2476 self._pixmap.draw_layout( self._fg_gc_normal, x, y, layout,
2477 fgcolor, bgcolor )
2478 w, h = layout.get_pixel_size()
2479 self._area.queue_draw_area( x, y, w, h )
2480
2481
2482
2483
2489
2490
2491
2493 """Draw points.
2494
2495 Efficient way to draw more than one point with the same
2496 characteristics.
2497 """
2498 gc = self.__configure_gc__( fgcolor=color )
2499 self._pixmap.draw_points( gc, points )
2500 w, h = self._pixmap.get_size()
2501 self._area.queue_draw_area( 0, 0, w, h )
2502
2503
2504
2505 - def draw_line( self, x0, y0, x1, y1, color=None, size=1 ):
2506 """Draw line."""
2507 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2508 self._pixmap.draw_line( gc, x0, y0, x1, y1 )
2509
2510 size2 = size * 2
2511
2512 w, h = abs( x1 - x0 ) + size2, abs( y1 - y0 ) + size2
2513 x, y = max( min( x0, x1 ) - size, 0 ), max( min( y0, y1 ) - size, 0 )
2514 self._area.queue_draw_area( x, y, w, h )
2515
2516
2517
2519 """Draw line segments.
2520
2521 Efficient way to draw more than one line with the same
2522 characteristics.
2523
2524 Lines are not connected, use L{draw_lines} instead.
2525 """
2526 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2527 self._pixmap.draw_segments( gc, segments )
2528 w, h = self._pixmap.get_size()
2529 self._area.queue_draw_area( 0, 0, w, h )
2530
2531
2532
2533 - def draw_lines( self, points, color=None, size=1 ):
2534 """Draw lines connecting points.
2535
2536 Points are connected using lines, but first and last points
2537 are not connected, use L{draw_polygon} instead.
2538 """
2539 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2540 self._pixmap.draw_lines( gc, points )
2541 w, h = self._pixmap.get_size()
2542 self._area.queue_draw_area( 0, 0, w, h )
2543
2544
2545
2546 - def draw_rectangle( self, x, y, width, height, color=None, size=1,
2547 fillcolor=None, filled=False ):
2548 """Draw rectagle.
2549
2550 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2551
2552 If L{color} is provided, it will draw the rectangle's frame, even
2553 if L{filled} is C{True}.
2554 """
2555 if filled:
2556 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2557 self._pixmap.draw_rectangle( gc, True, x, y, width, height )
2558
2559 if size > 0:
2560 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2561 self._pixmap.draw_rectangle( gc, False, x, y, width, height )
2562 else:
2563 size = 0
2564
2565 half = size / 2
2566 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2567
2568
2569
2570 - def draw_arc( self, x, y, width, height, start_angle, end_angle,
2571 color=None, size=1, fillcolor=None, filled=False ):
2572 """Draw arc on canvas.
2573
2574 Arc will be the part of an ellipse that starts at ( L{x}, L{y} )
2575 and have size of L{width}xL{height}.
2576
2577 L{start_angle} and L{end_angle} are in radians, starts from the
2578 positive x-axis and are counter-clockwise.
2579
2580 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2581
2582 If L{color} is provided, it will draw the arc's frame, even
2583 if L{filled} is C{True}. Frame here is just the curve, not the
2584 straight lines that are limited by L{start_angle} and L{end_angle}.
2585 """
2586
2587 mult = 180.0 / 3.1415926535897931 * 64.0
2588 start_angle = int( mult * start_angle )
2589 end_angle = int( mult * end_angle )
2590
2591 if filled:
2592 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2593 self._pixmap.draw_arc( gc, True, x, y, width, height,
2594 start_angle, end_angle )
2595 if size > 0:
2596 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2597 self._pixmap.draw_arc( gc, False, x, y, width, height,
2598 start_angle, end_angle )
2599 else:
2600 size = 0
2601
2602 half = size / 2
2603 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2604
2605
2606
2607 - def draw_polygon( self, points, color=None, size=1,
2608 fillcolor=None, filled=False ):
2609 """Draw polygon on canvas.
2610
2611 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2612
2613 If L{color} is provided, it will draw the polygon's frame, even
2614 if L{filled} is C{True}.
2615 """
2616 if filled:
2617 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2618 self._pixmap.draw_polygon( gc, True, points )
2619
2620 if size > 0:
2621 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2622 self._pixmap.draw_polygon( gc, False, points )
2623 else:
2624 size = 0
2625
2626 w, h = self._pixmap.get_size()
2627 self._area.queue_draw_area( 0, 0, w, h )
2628
2629
2630
2632 """Clear using bgcolor."""
2633 self.fill( self.bgcolor )
2634
2635
2636
2637 - def fill( self, color ):
2641
2642
2643
2646
2647
2648
2650 """Get the L{Image} that represents this drawing area."""
2651 w, h = self._pixmap.get_size()
2652 img = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, True, 8, w, h )
2653 img.get_from_drawable( self._pixmap, self._area.get_colormap(),
2654 0, 0, 0, 0, w, h )
2655 return Image( __int_image__=img )
2656
2657
2658
2660 if self.__label is None:
2661 raise ValueError( "You cannot change label of widget created "
2662 "without one. Create it with placeholder! "
2663 "(label='')" )
2664 self.__label = label
2665 self._frame.set_label( self.__label )
2666
2667
2668
2670 return self.__label
2671
2672
2673 label = property( get_label, set_label )
2674
2675
2677 return "%s( id=%r, width=%r, height=%r, label=%r )" % \
2678 ( self.__class__.__name__, self.id, self.width, self.height,
2679 self.label )
2680
2681 __repr__ = __str__
2682
2683
2684
2685 -class _MultiLineEntry( gtk.ScrolledWindow ):
2686 - def __init__( self ):
2687 gtk.ScrolledWindow.__init__( self )
2688 self.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
2689 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
2690 self.set_shadow_type( gtk.SHADOW_IN )
2691
2692 self.textview = gtk.TextView()
2693 self.add( self.textview )
2694
2695 self.textview.set_editable( True )
2696 self.textview.set_cursor_visible( True )
2697 self.textview.set_left_margin( 2 )
2698 self.textview.set_right_margin( 2 )
2699 self.textview.get_buffer().connect( "changed", self.__emit_changed__ )
2700
2701
2702
2703 - def __emit_changed__( self, *args ):
2704 self.emit( "changed" )
2705
2706
2707
2708 - def set_text( self, value ):
2709 self.textview.get_buffer().set_text( value )
2710
2711
2712
2713 - def get_text( self ):
2714 b = self.textview.get_buffer()
2715 return b.get_text( b.get_start_iter(), b.get_end_iter() )
2716
2717
2718 gobject.signal_new( "changed",
2719 _MultiLineEntry,
2720 gobject.SIGNAL_RUN_LAST,
2721 gobject.TYPE_NONE,
2722 tuple() )
2723
2724
2725 -class Entry( _EGWidLabelEntry ):
2726 """Text entry.
2727
2728 The simplest user input component. Its contents are free-form and not
2729 filtered/masked.
2730 """
2731 value = _gen_ro_property( "value" )
2732 callback = _gen_ro_property( "callback" )
2733 multiline = _gen_ro_property( "multiline" )
2734
2735 - def __init__( self, id, label="", value="", callback=None,
2736 persistent=False, multiline=False ):
2737 """Entry constructor.
2738
2739 @param id: unique identifier.
2740 @param label: what to show on a label on the left side of the widget.
2741 @param value: initial content.
2742 @param callback: function (or list of functions) that will
2743 be called when this widget have its data changed.
2744 Function will receive as parameters:
2745 - App reference
2746 - Widget reference
2747 - new value
2748 @param persistent: if this widget should save its data between
2749 sessions.
2750 @param multiline: if this entry can be multi-line.
2751 """
2752 self.value = value
2753 self.callback = _callback_tuple( callback )
2754 self.multiline = bool( multiline )
2755
2756 _EGWidLabelEntry.__init__( self, id, persistent, label )
2757
2758 self.__setup_gui__()
2759 self.__setup_connections__()
2760
2761
2762
2763 - def __setup_gui__( self ):
2764 if self.multiline:
2765 self._entry = _MultiLineEntry()
2766 else:
2767 self._entry = gtk.Entry()
2768
2769 self._entry.set_name( self.id )
2770 self._entry.set_text( self.value )
2771
2772 _EGWidLabelEntry.__setup_gui__( self )
2773
2774
2775
2777 - def callback( obj ):
2778 v = self.get_value()
2779 self.app.data_changed( self, v )
2780 for c in self.callback:
2781 c( self.app, self, v )
2782
2783 self._entry.connect( "changed", callback )
2784
2785
2786
2787 - def get_value( self ):
2788 return self._entry.get_text()
2789
2790
2791
2792 - def set_value( self, value ):
2793 self._entry.set_text( str( value ) )
2794
2795
2796
2798 """Resize mode.
2799
2800 First tuple of tuple is about horizontal mode for label and entry.
2801 Second tuple of tuple is about vertical mode for label and entry.
2802 """
2803 if self.multiline:
2804 return ( ( gtk.FILL, gtk.FILL | gtk.EXPAND ),
2805 ( gtk.FILL, gtk.FILL | gtk.EXPAND ) )
2806 else:
2807 return ( ( gtk.FILL, gtk.FILL | gtk.EXPAND ),
2808 ( gtk.FILL, gtk.FILL ) )
2809
2810
2811
2812
2814 """Password entry.
2815
2816 Like L{Entry}, but will show '*' instead of typed chars.
2817 """
2818 - def __init__( self, id, label="", value="", callback=None,
2819 persistent=False ):
2820 """Password constructor.
2821
2822 @param id: unique identifier.
2823 @param label: what to show on a label on the left side of the widget.
2824 @param value: initial content.
2825 @param callback: function (or list of functions) that will
2826 be called when this widget have its data changed.
2827 Function will receive as parameters:
2828 - App reference
2829 - Widget reference
2830 - new value
2831 @param persistent: if this widget should save its data between
2832 sessions.
2833 """
2834 Entry.__init__( self, id, label, value, callback, persistent )
2835 self._entry.set_visibility( False )
2836
2837
2838
2839
2840 -class Spin( _EGWidLabelEntry ):
2841 """Spin button entry.
2842
2843 Spin buttons are numeric user input that checks if value is inside
2844 a specified range. It also provides small buttons to help incrementing/
2845 decrementing value.
2846 """
2847 default_min = -1e60
2848 default_max = 1e60
2849
2850 value = _gen_ro_property( "value" )
2851 min = _gen_ro_property( "min" )
2852 max = _gen_ro_property( "max" )
2853 step = _gen_ro_property( "step" )
2854 digits = _gen_ro_property( "digits" )
2855
2856 callback = _gen_ro_property( "callback" )
2857
2858 - def __init__( self, id, label="",
2859 value=None, min=None, max=None, step=None, digits=3,
2860 callback=None, persistent=False ):
2861 """Spin constructor.
2862
2863 @param id: unique identifier.
2864 @param label: what to show on a label on the left side of the widget.
2865 @param value: initial content.
2866 @param min: minimum value. If None, L{default_min} will be used.
2867 @param max: maximum value. If None, L{default_max} will be used.
2868 @param step: step to use to decrement/increment using buttons.
2869 @param digits: how many digits to show.
2870 @param callback: function (or list of functions) that will
2871 be called when this widget have its data changed.
2872 Function will receive as parameters:
2873 - App reference
2874 - Widget reference
2875 - new value
2876 @param persistent: if this widget should save its data between
2877 sessions.
2878 """
2879 self.value = value
2880 self.min = min
2881 self.max = max
2882 self.step = step
2883 self.digits = digits
2884 self.callback = _callback_tuple( callback )
2885
2886 _EGWidLabelEntry.__init__( self, id, persistent, label )
2887
2888 self.__setup_connections__()
2889
2890
2891
2893 k = {}
2894
2895 if self.value is not None:
2896 k[ "value" ] = self.value
2897
2898 if self.min is not None:
2899 k[ "lower" ] = self.min
2900 else:
2901 k[ "lower" ] = self.default_min
2902
2903 if self.max is not None:
2904 k[ "upper" ] = self.max
2905 else:
2906 k[ "upper" ] = self.default_max
2907
2908 if self.step is not None:
2909 k[ "step_incr" ] = self.step
2910 k[ "page_incr" ] = self.step * 2
2911 else:
2912 k[ "step_incr" ] = 1
2913 k[ "page_incr" ] = 2
2914
2915 adj = gtk.Adjustment( **k )
2916 self._entry = gtk.SpinButton( adjustment=adj, digits=self.digits )
2917 self._entry.set_name( self.id )
2918 self._entry.set_numeric( True )
2919 self._entry.set_snap_to_ticks( False )
2920
2921 _EGWidLabelEntry.__setup_gui__( self )
2922
2923
2924
2931
2932 self._entry.connect( "notify::value", callback )
2933
2934
2935
2938
2939
2940
2941
2943 """Integer-only Spin button.
2944
2945 Convenience widget that behaves like L{Spin} with digits set to
2946 zero and also ensure L{set_value} and L{get_value} operates on
2947 integers.
2948 """
2949 default_min = -sys.maxint
2950 default_max = sys.maxint
2951
2952 - def __init__( self, id, label="",
2953 value=None, min=None, max=None, step=None,
2954 callback=None, persistent=False ):
2955 """Integer Spin constructor.
2956
2957 @param id: unique identifier.
2958 @param label: what to show on a label on the left side of the widget.
2959 @param value: initial content.
2960 @param min: minimum value. If None, L{default_min} will be used.
2961 @param max: maximum value. If None, L{default_max} will be used.
2962 @param step: step to use to decrement/increment using buttons.
2963 @param callback: function (or list of functions) that will
2964 be called when this widget have its data changed.
2965 Function will receive as parameters:
2966 - App reference
2967 - Widget reference
2968 - new value
2969 @param persistent: if this widget should save its data between
2970 sessions.
2971 """
2972 if value is not None:
2973 value = int( value )
2974 if min is not None:
2975 min = int( min )
2976 if max is not None:
2977 max = int( max )
2978 if step is not None:
2979 step = int( step )
2980 Spin.__init__( self, id, label, value, min, max, step, 0, callback,
2981 persistent )
2982
2983
2984
2986 if self.min is not None:
2987 vmin = self.min
2988 else:
2989 vmin = self.default_min
2990
2991 if self.max is not None:
2992 vmax = self.max
2993 else:
2994 vmax = self.default_max
2995
2996 if self.step is not None:
2997 sys.stderr.write( "Maemo HildonNumberEditor doesn't support step\n" )
2998
2999 self._entry = hildon.NumberEditor( vmin, vmax )
3000 self._entry.set_name( self.id )
3001 if self.value:
3002 self._entry.set_value( self.value )
3003
3004 _EGWidLabelEntry.__setup_gui__( self )
3005
3006
3007
3014
3015 self._entry.connect( "notify::value", callback )
3016
3017
3018
3021
3022
3023
3026
3027
3028
3029
3031 """Unsigned integer-only Spin button.
3032
3033 Convenience widget that behaves like L{IntSpin} with minimum value
3034 always greater or equal zero.
3035 """
3036 default_min = 0
3037
3038 - def __init__( self, id, label="",
3039 value=None, min=0, max=None, step=None,
3040 callback=None, persistent=False ):
3041 """Unsigned Integer Spin constructor.
3042
3043 @param id: unique identifier.
3044 @param label: what to show on a label on the left side of the widget.
3045 @param value: initial content.
3046 @param min: minimum value, must be greater or equal zero.
3047 @param max: maximum value. If None, L{default_max} will be used.
3048 @param step: step to use to decrement/increment using buttons.
3049 @param callback: function (or list of functions) that will
3050 be called when this widget have its data changed.
3051 Function will receive as parameters:
3052 - App reference
3053 - Widget reference
3054 - new value
3055 @param persistent: if this widget should save its data between
3056 sessions.
3057 """
3058 if min < 0:
3059 raise ValueError( "UIntSpin cannot have min < 0!" )
3060 Spin.__init__( self, id, label, value, min, max, step, 0, callback,
3061 persistent )
3062
3063
3064
3065
3066 -class Color( _EGWidLabelEntry ):
3067 """Button to select colors.
3068
3069 It show current/last selected color and may pop-up a new dialog to
3070 select a new one.
3071 """
3072 color = _gen_ro_property( "color" )
3073 callback = _gen_ro_property( "callback" )
3074
3075 - def __init__( self, id, label="", color=0,
3076 callback=None,
3077 persistent=False ):
3078 """Color selector constructor.
3079
3080 @param id: unique identifier.
3081 @param label: what to show on a label on the left side of the widget.
3082 @param color: initial content. May be a triple with elements within
3083 the range 0-255, an string with color in HTML format or even
3084 a color present in X11's rgb.txt.
3085 @param callback: function (or list of functions) that will
3086 be called when this widget have its data changed.
3087 Function will receive as parameters:
3088 - App reference
3089 - Widget reference
3090 - new value
3091 @param persistent: if this widget should save its data between
3092 sessions.
3093 """
3094 self.color = self.color_from( color )
3095 self.callback = _callback_tuple( callback )
3096 _EGWidLabelEntry.__init__( self, id, persistent, label )
3097
3098 self.__setup_connections__()
3099
3100
3101
3103 a = 255
3104 if isinstance( color, str ):
3105 try:
3106 c = gtk.gdk.color_parse( color )
3107 r = int( c.red / 65535.0 * 255 )
3108 g = int( c.green / 65535.0 * 255 )
3109 b = int( c.blue / 65535.0 * 255 )
3110 except ValueError, e:
3111 raise ValueError( "%s. color=%r" % ( e, color ) )
3112
3113 if isinstance( color, int ):
3114 r = ( color >> 16 ) & 0xff
3115 g = ( color >> 8 ) & 0xff
3116 b = ( color & 0xff )
3117 elif isinstance( color, ( tuple, list ) ):
3118 if len( color ) == 3:
3119 r, g, b = color
3120 else:
3121 a, r, g, b = color
3122
3123 return a, r, g, b
3124
3125 color_from = staticmethod( color_from )
3126
3127
3129 r = int( self.color[ 1 ] / 255.0 * 65535 )
3130 g = int( self.color[ 2 ] / 255.0 * 65535 )
3131 b = int( self.color[ 3 ] / 255.0 * 65535 )
3132
3133 c = gtk.gdk.Color( r, g, b )
3134
3135 self._entry = hildon.ColorButton()
3136 self._entry.set_color( c )
3137 self._entry.set_name( self.id )
3138 _EGWidLabelEntry.__setup_gui__( self )
3139
3140
3141
3148
3149 self._entry.connect( "notify::color", callback )
3150
3151
3152
3154 """Return a tuple with ( alpha, red, green, blue )
3155 each in 0-255 range.
3156 """
3157 c = self._entry.get_color()
3158 r = int( c.red / 65535.0 * 255 )
3159 g = int( c.green / 65535.0 * 255 )
3160 b = int( c.blue / 65535.0 * 255 )
3161
3162 return ( r, g, b )
3163
3164
3165
3167 """
3168 @param value: May be a triple with elements within
3169 the range 0-255, an string with color in HTML format or even
3170 a color present in X11's rgb.txt.
3171 """
3172 a, r, g, b = self.color_from( value )
3173
3174 r = int( r / 255.0 * 65535 )
3175 g = int( g / 255.0 * 65535 )
3176 b = int( b / 255.0 * 65535 )
3177
3178 c = gtk.gdk.Color( r, g, b )
3179 self._entry.set_color( c )
3180
3181
3182
3183
3184 -class Font( _EGWidLabelEntry ):
3185 """Button to select fonts.
3186
3187 It show current/last selected font and may pop-up a new dialog to
3188 select a new one.
3189 """
3190 font = _gen_ro_property( "font" )
3191 callback = _gen_ro_property( "callback" )
3192
3193 - def __init__( self, id, label="", font="sans 12", callback=None,
3194 persistent=False ):
3195 """Font selector constructor.
3196
3197 @param id: unique identifier.
3198 @param label: what to show on a label on the left side of the widget.
3199 @param font: initial content.
3200 @param callback: function (or list of functions) that will
3201 be called when this widget have its data changed.
3202 Function will receive as parameters:
3203 - App reference
3204 - Widget reference
3205 - new value
3206 @param persistent: if this widget should save its data between
3207 sessions.
3208 """
3209 self.font = font
3210 self.callback = _callback_tuple( callback )
3211 _EGWidLabelEntry.__init__( self, id, persistent, label )
3212
3213 self.__setup_connections__()
3214
3215
3216
3222
3223
3224
3231
3232 self._entry.connect( "font-set", callback )
3233
3234
3235
3237 return self._entry.get_font_name()
3238
3239
3240
3242 self._entry.set_font_name( value )
3243
3244
3245
3246
3248 """Selection box (aka Combo box).
3249
3250 Selection or combo box is an element that allow you to select one of
3251 various pre-defined values.
3252 """
3253 options = _gen_ro_property( "options" )
3254 active = _gen_ro_property( "active" )
3255
3256 - def __init__( self, id, label="", options=None, active=None,
3257 callback=None, persistent=False ):
3258 """Selection constructor.
3259
3260 @param id: unique identifier.
3261 @param label: what to show on a label on the left side of the widget.
3262 @param options: list of possible values.
3263 @param active: selected element.
3264 @param callback: function (or list of functions) that will
3265 be called when this widget have its data changed.
3266 Function will receive as parameters:
3267 - App reference
3268 - Widget reference
3269 - new value
3270 @param persistent: if this widget should save its data between
3271 sessions.
3272 """
3273 self.options = options or []
3274 self.active = active
3275 self.callback = _callback_tuple( callback )
3276 _EGWidLabelEntry.__init__( self, id, persistent, label )
3277
3278 self.__setup_connections__()
3279
3280
3281
3283 self._entry = gtk.combo_box_new_text()
3284 self._entry.set_name( self.id )
3285 for i, o in enumerate( self.options ):
3286 self._entry.append_text( str( o ) )
3287 if self.active == o:
3288 self._entry.set_active( i )
3289
3290 _EGWidLabelEntry.__setup_gui__( self )
3291
3292
3293
3300
3301 self._entry.connect( "changed", callback )
3302
3303
3304
3306 return self._entry.get_active_text()
3307
3308
3309
3311 for i, o in enumerate( self._entry.get_model() ):
3312 if o[ 0 ] == value:
3313 self._entry.set_active( i )
3314
3315
3316
3317 - def append( self, value, set_active=False ):
3318 """Append new value to available options.
3319
3320 @param value: string that is not already an option.
3321
3322 @raise: ValueError: if value is already an option.
3323 """
3324 if value not in self.items():
3325 self._entry.append_text( value )
3326 if set_active:
3327 self.set_value( value )
3328 else:
3329 raise ValueError( "value already in selection" )
3330
3331
3332
3334 """Prepend new value to available options.
3335
3336 @param value: string that is not already an option.
3337
3338 @raise: ValueError: if value is already an option.
3339 """
3340 if value not in self.items():
3341 self._entry.prepend_text( value )
3342 if set_active:
3343 self.set_value( value )
3344 else:
3345 raise ValueError( "value already in selection" )
3346
3347
3348
3349 - def insert( self, position, value ):
3350 """Insert new option at position.
3351
3352 @param value: string that is not already an option.
3353
3354 @raise: ValueError: if value is already an option.
3355 """
3356 if value not in self.items():
3357 self._entry.insert_text( position, value )
3358 if set_active:
3359 self.set_value( value )
3360 else:
3361 raise ValueError( "value already in selection" )
3362
3363
3364
3366 """Remove given value from available options.
3367
3368 @param value: string that is an option.
3369
3370 @raise ValueError: if value is not already an option.
3371 """
3372 for i, o in enumerate( self._entry.get_model() ):
3373 if o[ 0 ] == value:
3374 self._entry.remove_text( i )
3375 return
3376
3377 raise ValueError( "value not in selection" )
3378
3379
3380
3382 """Returns every item/option in this selection."""
3383 return [ str( x[ 0 ] ) for x in self._entry.get_model() ]
3384
3385 options = items
3386
3387
3389 return len( self._entry.get_model() )
3390
3391
3392
3395
3396
3397
3401
3402
3403
3407
3408
3409
3410
3412 """Progress bar."""
3413 value = _gen_ro_property( "value" )
3414
3415 - def __init__( self, id, label="", value=0.0 ):
3416 """Progress bar constructor.
3417
3418 0 <= value <= 1.0
3419
3420 @param id: unique identifier.
3421 @param label: what to show on a label on the left side of the widget.
3422 @param value: initial content ( 0.0 <= value <= 1.0 )
3423 """
3424 self.value = value
3425 _EGWidLabelEntry.__init__( self, id, False, label )
3426
3427
3433
3434
3435
3437 return self._entry.get_fraction()
3438
3439
3440
3442 if 1.0 < value <= 100.0:
3443 value /= 100.0
3444 elif not ( 0.0 <= value <= 1.0 ):
3445 raise ValueError( ( "Progress value of \"%s\" must be "
3446 "between 0.0 and 1.0!" ) % self.id )
3447 self._entry.set_fraction( value )
3448 self._entry.set_text( "%d%%" % ( int( value * 100 ), ) )
3449
3450
3451
3453 """Animate progress bar."""
3454 self._entry.pulse()
3455
3456
3457
3458
3460 """Check box.
3461
3462 Check box is an component that have only two values: True (checked) or
3463 False (unchecked).
3464 """
3465 state = _gen_ro_property( "state" )
3466
3467 - def __init__( self, id, label="", state=False, callback=None,
3468 persistent=False ):
3469 """Check box constructor.
3470
3471 @param id: unique identifier.
3472 @param label: what to show on a label on the left side of the widget.
3473 @param state: initial state.
3474 @param callback: function (or list of functions) that will
3475 be called when this widget have its data changed.
3476 Function will receive as parameters:
3477 - App reference
3478 - Widget reference
3479 - new value
3480 @param persistent: if this widget should save its data between
3481 sessions.
3482 """
3483 self.__label = label
3484 self.state = state
3485 self.callback = _callback_tuple( callback )
3486
3487 _EGDataWidget.__init__( self, id, persistent )
3488
3489 self.__setup_gui__()
3490 self.__setup_connections__()
3491
3492
3493
3495 self._wid = gtk.CheckButton( self.__label )
3496 self._wid.set_name( self.id )
3497 self._wid.set_active( self.state )
3498 self._widgets = ( self._wid, )
3499
3500
3501
3508
3509 self._wid.connect( "toggled", callback )
3510
3511
3512
3514 "Return a tuple with ( horizontal, vertical ) resize mode"
3515 return ( gtk.FILL, 0 )
3516
3517
3518
3520 return self._wid.get_active()
3521
3522
3523
3526
3527
3528
3530 if self.__label is None:
3531 raise ValueError( "You cannot change label of widget created "
3532 "without one. Create it with placeholder! "
3533 "(label='')" )
3534 self.__label = label
3535 self._wid.set_label( self.__label )
3536
3537
3538
3540 return self.__label
3541
3542
3543 label = property( get_label, set_label )
3544
3545
3546
3547 -class Group( _EGWidget ):
3548 """Group of various components.
3549
3550 Group is a component that holds other components, always in a vertical
3551 layout.
3552
3553 Group has a frame and may show a label.
3554 """
3555 children = _gen_ro_property( "children" )
3556
3558 try:
3559 return self.__ro_app
3560 except AttributeError:
3561 return None
3562
3563
3565
3566
3567 try:
3568 v = self.__ro_app
3569 except AttributeError:
3570 v = None
3571 if v is None:
3572 self.__ro_app = value
3573 self.__add_widgets_to_app__()
3574 else:
3575 raise Exception( "Read Only property 'app'." )
3576
3577 app = property( _get_app, _set_app )
3578
3579
3580 - def __init__( self, id, label="", children=None ):
3581 """Group constructor.
3582
3583 @param id: unique identified.
3584 @param label: displayed at top-left.
3585 @param children: a list of eagle widgets that this group contains.
3586 They're presented in vertical layout.
3587 """
3588 _EGWidget.__init__( self, id )
3589 self.__label = label
3590 self.children = children or tuple()
3591
3592 self.__setup_gui__()
3593
3594
3595
3597 self._frame = gtk.Frame( self.__label )
3598 self._frame.set_name( self.id )
3599 self._contents = _Table( id=( "%s-contents" % self.id ),
3600 children=self.children )
3601 self._frame.add( self._contents )
3602 self._widgets = ( self._frame, )
3603
3604
3605
3609
3610
3611
3613 "Return a tuple with ( horizontal, vertical ) resize mode"
3614 return ( gtk.FILL | gtk.EXPAND, 0 )
3615
3616
3617
3619 if self.__label is None:
3620 raise ValueError( "You cannot change label of widget created "
3621 "without one. Create it with placeholder! "
3622 "(label='')" )
3623 self.__label = label
3624 self._frame.set_label( self.__label )
3625
3626
3627
3629 return self.__label
3630
3631
3632 label = property( get_label, set_label )
3633
3634
3635
3636 -class Table( _EGWidget ):
3637 """Data table.
3638
3639 Each column should have only one type, it will be checked.
3640 Can be accessed as a python list:
3641
3642 >>> t = Table( 't', 'table', [ 1, 2, 3 ] )
3643 >>> t[ 0 ]
3644 [ 1 ]
3645 >>> del t[ 1 ]
3646 >>> t[ : ]
3647 [ 1, 3 ]
3648 """
3649 spacing = 3
3650
3651
3652 - class Row( object ):
3653
3655 self.__items = items
3656
3657
3658
3660 return "[" + ", ".join( [ str( x ) for x in self.__items ] ) + "]"
3661
3662 __repr__ = __str__
3663
3664
3666 return len( self.__items )
3667
3668
3669
3672
3673
3674
3676 return self.__items[ index ]
3677
3678
3679
3681 self.__items[ index ] = value
3682
3683
3684
3686 del self.__items[ index ]
3687
3688
3689
3691 return element in self.__items
3692
3693
3694
3696 slice = []
3697
3698 l = len( self.__items )
3699 while start < 0:
3700 start += l
3701 while end < 0:
3702 end += l
3703
3704 start = min( start, l )
3705 end = min( end, l )
3706
3707 for i in xrange( start, end ):
3708 slice.append( self.__items[ i ] )
3709 return slice
3710
3711
3712
3714 l = len( self.__items )
3715 while start < 0:
3716 start += l
3717 while end < 0:
3718 end += l
3719
3720 start = min( start, l )
3721 end = min( end, l )
3722
3723 l2 = len( items )
3724 if end - start > l2:
3725 end = start + l2
3726
3727 if self.__items.model._hid_row_changed is not None:
3728 hid_changed = self.__items.model._hid_row_changed
3729 self.__items.model.handler_block( hid_changed )
3730
3731 j = 0
3732 for i in xrange( start, end ):
3733 self.__items[ i ] = items[ j ]
3734 j += 1
3735
3736 if self.__items.model._hid_row_changed is not None:
3737 hid_changed = self.__items.model._hid_row_changed
3738 self.__items.model.handler_unblock( hid_changed )
3739 i = self.__items.iter
3740 p = self.__items.path
3741 self.__items.model.row_changed( p, i )
3742
3743
3744
3745
3746
3754
3755
3756
3757
3758
3759 - def __init__( self, id, label, items=None, types=None,
3760 headers=None, show_headers=True, editable=False,
3761 repositioning=False, expand_columns_indexes=None,
3762 cell_format_func=None,
3763 selection_callback=None, data_changed_callback=None ):
3764 """Table constructor.
3765
3766 @param id: unique identifier.
3767 @param label: what to show on table frame
3768 @param items: a list (single column) or list of lists (multiple
3769 columns)
3770 @param types: a list of types (str, int, long, float, unicode, bool)
3771 for columns, if omitted, will be guessed from items.
3772 @param headers: what to use as table header.
3773 @param show_headers: whenever to show table headers
3774 @param editable: if table is editable. If editable, user can change
3775 values inline or double-clicking, also edit buttons will
3776 show after the table.
3777 @param repositioning: allow items to be moved up and down.
3778 @param expand_columns_indexes: list of indexes that can expand size
3779 @param cell_format_func: if define, should return a CellFormat with
3780 properties to be applied to cell. Only non-None properties will
3781 be used. Function should have the following signature:
3782 def func( app, table, row, col, value ):
3783 return Table.CellFormat( ... )
3784 where row and col are indexes in table.
3785 @param selection_callback: the function (or list of functions) to
3786 call when selection changes. Function will get as parameters:
3787 - App reference
3788 - Table reference
3789 - List of pairs ( index, row_contents )
3790 @param data_changed_callback: the function (or list of functions) to
3791 call when data changes. Function will get as parameters:
3792 - App reference
3793 - Table reference
3794 - Pair ( index, row_contents )
3795
3796 @warning: although this widget contains data, it's not a
3797 _EGDataWidget and thus will not notify application that
3798 data changed, also it cannot persist it's data
3799 automatically, if you wish, do it manually. This behavior
3800 may change in future if Table show to be useful as
3801 _EGDataWidget.
3802 """
3803 _EGWidget.__init__( self, id )
3804 self.editable = editable or False
3805 self.repositioning = repositioning or False
3806 self.__label = str( label or "" )
3807 self.headers = headers or tuple()
3808 self.show_headers = bool( show_headers )
3809 self.cell_format_func = cell_format_func
3810
3811 if isinstance( expand_columns_indexes, ( int, long ) ):
3812 expand_columns_indexes = ( expand_columns_indexes, )
3813 elif isinstance( expand_columns_indexes, ( tuple, list ) ):
3814 expand_columns_indexes = tuple( expand_columns_indexes )
3815 elif expand_columns_indexes is None:
3816 expand_columns_indexes = tuple()
3817 else:
3818 raise ValueError( \
3819 "expand_columns_indexes must be a sequence of integers" )
3820 self.expand_columns_indexes = expand_columns_indexes
3821
3822 if not ( types or items ):
3823 raise ValueError( "Must provide items or types!" )
3824 elif not types:
3825 items = items or []
3826 if not isinstance( items[ 0 ], ( list, tuple ) ):
3827
3828 items = [ [ i ] for i in items ]
3829
3830 types = [ type( i ) for i in items[ 0 ] ]
3831 self.types = types
3832 self.items = items
3833
3834 self.selection_callback = _callback_tuple( selection_callback )
3835 self.data_changed_callback = _callback_tuple( data_changed_callback )
3836
3837 self.__setup_gui__()
3838
3839 self._model._hid_row_changed = None
3840 self._model._hid_row_deleted = None
3841 self._model._hid_row_inserted = None
3842 self.__setup_connections__()
3843 self.__setup_items__()
3844
3845
3846
3848 self._frame = gtk.Frame( self.label )
3849 self._frame.set_name( self.id )
3850
3851 self._vbox = gtk.VBox( False, self.spacing )
3852 self._vbox.set_border_width( self.spacing )
3853 self._vbox.set_name( "vbox-%s" % self.id )
3854
3855 self._frame.add( self._vbox )
3856 self._widgets = ( self._frame, )
3857
3858 self.__setup_table__()
3859
3860 if self.editable or self.repositioning:
3861 self._hbox = gtk.HBox( False, self.spacing )
3862 self._vbox.pack_start( self._hbox, expand=False, fill=True )
3863
3864 if self.editable:
3865 self._btn_add = gtk.Button( stock=gtk.STOCK_ADD )
3866 self._btn_del = gtk.Button( stock=gtk.STOCK_REMOVE )
3867 self._btn_edit = gtk.Button( stock=gtk.STOCK_EDIT )
3868
3869 self._hbox.pack_start( self._btn_add )
3870 self._hbox.pack_start( self._btn_del )
3871 self._hbox.pack_start( self._btn_edit )
3872
3873 if self.repositioning:
3874 if self.editable:
3875 self._hbox.pack_start( gtk.VSeparator() )
3876
3877 self._btn_up = gtk.Button( stock=gtk.STOCK_GO_UP )
3878 self._btn_down = gtk.Button( stock=gtk.STOCK_GO_DOWN )
3879
3880 self._btn_up.set_sensitive( False )
3881 self._btn_down.set_sensitive( False )
3882
3883 self._hbox.pack_start( self._btn_up )
3884 self._hbox.pack_start( self._btn_down )
3885
3886
3887
3900
3901
3902
3905 index = path[ 0 ]
3906 v = ( index, Table.Row( model[ path ] ) )
3907 for c in self.data_changed_callback:
3908 c( self.app, self, v )
3909
3910
3911
3913 index = path[ 0 ]
3914 v = ( index, None )
3915 for c in self.data_changed_callback:
3916 c( self.app, self, v )
3917
3918
3919 c = self._model.connect
3920 self._model._hid_row_changed = c( "row-changed", row_changed )
3921 self._model._hid_row_deleted = c( "row-deleted", row_deleted )
3922 self._model._hid_row_inserted = c( "row-inserted", row_changed)
3923
3924
3925
3928 title = "Edit data from table %s" % self.label
3929 buttons = ( gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
3930 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT )
3931 d = gtk.Dialog( title=title,
3932 flags=gtk.DIALOG_MODAL,
3933 buttons=buttons )
3934
3935 settings = d.get_settings()
3936 try:
3937 settings.set_property( "gtk-button-images", False )
3938 except:
3939 pass
3940
3941 d.set_default_response( gtk.RESPONSE_ACCEPT )
3942
3943 l = len( data )
3944 t = gtk.Table( l, 2 )
3945 t.set_border_width( 0 )
3946 w = []
3947 for i, v in enumerate( data ):
3948 title = self._table.get_column( i ).get_title()
3949 label = gtk.Label( title )
3950 label.set_justify( gtk.JUSTIFY_RIGHT )
3951 label.set_alignment( xalign=1.0, yalign=0.5 )
3952
3953 tp = self.types[ i ]
3954 if tp == bool:
3955 entry = gtk.CheckButton()
3956 entry.set_active( data[ i ] )
3957 elif tp in ( int, long ):
3958 entry = gtk.SpinButton( digits=0 )
3959 adj = entry.get_adjustment()
3960 adj.lower = Spin.default_min
3961 adj.upper = Spin.default_max
3962 adj.step_increment = 1
3963 adj.page_increment = 5
3964 entry.set_value( data[ i ] )
3965 elif tp == float:
3966 entry = gtk.SpinButton( digits=6 )
3967 adj = entry.get_adjustment()
3968 adj.lower = Spin.default_min
3969 adj.upper = Spin.default_max
3970 adj.step_increment = 1
3971 adj.page_increment = 5
3972 entry.set_value( data[ i ] )
3973 elif tp in ( str, unicode ):
3974 entry = gtk.Entry()
3975 entry.set_text( data[ i ] )
3976 else:
3977 try:
3978 name = tp.__name__
3979 except:
3980 name = tp
3981 raise ValueError( "Unsuported column (%d) type: %s" %
3982 ( i, name ) )
3983
3984 t.attach( label, 0, 1, i, i + 1,
3985 xoptions=gtk.FILL,
3986 xpadding=self.spacing, ypadding=self.spacing )
3987 t.attach( entry, 1, 2, i, i + 1,
3988 xoptions=gtk.EXPAND|gtk.FILL,
3989 xpadding=self.spacing, ypadding=self.spacing )
3990 w.append( entry )
3991
3992 t.show_all()
3993
3994 sw = gtk.ScrolledWindow()
3995 sw.add_with_viewport( t )
3996 sw.get_child().set_shadow_type( gtk.SHADOW_NONE )
3997 d.vbox.pack_start( sw )
3998
3999
4000 sw.set_policy( hscrollbar_policy=gtk.POLICY_NEVER,
4001 vscrollbar_policy=gtk.POLICY_NEVER )
4002 d.show_all()
4003
4004 sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
4005 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
4006
4007 r = d.run()
4008 d.destroy()
4009 if r in ( gtk.RESPONSE_REJECT, gtk.RESPONSE_DELETE_EVENT ) :
4010 return None
4011 else:
4012 result = []
4013 for i in xrange( len( data ) ):
4014 tp = self.types[ i ]
4015 wid = w[ i ]
4016 if tp == bool:
4017 r = bool( wid.get_active() )
4018 elif tp in ( int, long ):
4019 r = tp( wid.get_value() )
4020 elif tp == float:
4021 r = float( wid.get_value() )
4022 elif tp in ( str, unicode ):
4023 r = tp( wid.get_text() )
4024 else:
4025 try:
4026 name = tp.__name__
4027 except:
4028 name = tp
4029 raise ValueError( \
4030 "Unsuported column (%d) type: %s" %
4031 ( i, name ) )
4032 result.append( r )
4033
4034 return result
4035
4036
4037
4039 entry = []
4040 for i, t in enumerate( self.types ):
4041 if t == bool:
4042 v = False
4043 elif t in ( int, long, float ):
4044 v = 0
4045 elif t in ( str, unicode ):
4046 v = ''
4047 else:
4048 try:
4049 name = t.__name__
4050 except:
4051 name = t
4052 raise ValueError( "Unsuported column (%d) type: %s" %
4053 ( i, name ) )
4054 entry.append( v )
4055 result = edit_dialog( entry )
4056 if result:
4057 self.append( result )
4058
4059
4060
4062 selected = self.selected()
4063 if not selected:
4064 return
4065
4066 for index, data in selected:
4067 print data
4068 result = edit_dialog( data )
4069 if result:
4070 self[ index ] = result
4071
4072
4073
4081
4082
4083 self._btn_add.connect( "clicked", clicked_add )
4084 self._btn_del.connect( "clicked", clicked_del )
4085 self._btn_edit.connect( "clicked", clicked_edit )
4086
4088 data = treeview.get_model()[ path ]
4089 result = edit_dialog( data )
4090 if result:
4091 self[ path[ 0 ] ] = result
4092
4093
4094
4095 self._table.connect( "row-activated", row_activated )
4096
4097
4098
4101 result = self.selected()
4102 if not result:
4103 self._btn_up.set_sensitive( False )
4104 self._btn_down.set_sensitive( False )
4105 else:
4106 path = result[ 0 ][ 0 ]
4107 if path > 0:
4108 self._btn_up.set_sensitive( True )
4109 else:
4110 self._btn_up.set_sensitive( False )
4111 if path < len( self ) - 1:
4112 self._btn_down.set_sensitive( True )
4113 else:
4114 self._btn_down.set_sensitive( False )
4115
4116
4118 result = self.selected()
4119 a = result[ 0 ][ 0 ]
4120 if a <= 0:
4121 return
4122
4123 b = a - 1
4124 la = list( self[ a ] )
4125 lb = list( self[ b ] )
4126 self[ a ] = lb
4127 self[ b ] = la
4128 self.select( b )
4129
4130
4132 result = self.selected()
4133 a = result[ 0 ][ 0 ]
4134 if a >= len( self ) - 1:
4135 return
4136
4137 b = a + 1
4138 la = list( self[ a ] )
4139 lb = list( self[ b ] )
4140 self[ a ] = lb
4141 self[ b ] = la
4142 self.select( b )
4143
4144
4145 selection = self._table.get_selection()
4146 selection.connect( "changed", selection_changed )
4147 self._btn_up.connect( "clicked", move_up )
4148 self._btn_down.connect( "clicked", move_down )
4149
4150
4151
4154 result = self.selected()
4155 for c in self.selection_callback:
4156 c( self.app, self, result )
4157
4158
4159 selection = self._table.get_selection()
4160 selection.connect( "changed", selection_changed )
4161
4162
4163
4165 self.__setup_model__()
4166 self._table = gtk.TreeView( self._model )
4167 self._table.set_property( "allow-checkbox-mode", False )
4168 self._table.set_name( "table-%s" % self.id )
4169 self._table.get_selection().set_mode( gtk.SELECTION_MULTIPLE )
4170
4172 cid, order = self._model.get_sort_column_id()
4173 self._model.set_sort_column_id( cid, order )
4174
4175
4176 - def toggled( cell_render, path, col ):
4177 self._model[ path ][ col ] = not self._model[ path ][ col ]
4178
4179
4180
4181 - def edited( cell_render, path, text, col ):
4182 t = self.types[ col ]
4183 try:
4184 value = t( text )
4185 except ValueError, e:
4186 name = t.__name__
4187 error( "Invalid contents for column of type '%s': %s" %
4188 ( name, text ) )
4189 else:
4190 self._model[ path ][ col ] = value
4191
4192
4193
4194 for i, t in enumerate( self.types ):
4195 if t == bool:
4196 cell_rend = gtk.CellRendererToggle()
4197 props = { "active": i }
4198 if self.editable:
4199 cell_rend.set_property( "activatable", True)
4200 cell_rend.connect( "toggled", toggled, i )
4201
4202 elif t in ( int, long, float, str, unicode ):
4203 cell_rend = gtk.CellRendererText()
4204 if self.editable:
4205 cell_rend.set_property( "editable", True )
4206 cell_rend.connect( "edited", edited, i )
4207
4208 props = { "text": i }
4209 if t in ( int, long, float ):
4210 cell_rend.set_property( "xalign", 1.0 )
4211 else:
4212 try:
4213 name = t.__name__
4214 except:
4215 name = t
4216 raise ValueError( "Unsuported column (%d) type: %s" %
4217 ( i, name ) )
4218
4219 try:
4220 title = self.headers[ i ]
4221 except IndexError:
4222 title = "Col-%d (%s)" % ( i, t.__name__ )
4223
4224 col = gtk.TreeViewColumn( title, cell_rend, **props )
4225 col.set_resizable( True )
4226 col.set_sort_column_id( i )
4227 col.connect( "clicked", column_clicked )
4228 if self.cell_format_func:
4229 def get_color( c ):
4230 return Canvas.__to_gtk_color__( Canvas.__color_from__( c ))
4231
4232
4233 def func( column, cell_renderer, model, itr, col_idx ):
4234 row_idx = model.get_path( itr )[ 0 ]
4235 value = model.get_value( itr, col_idx )
4236 cf = self.cell_format_func( self.app, self,
4237 row_idx, col_idx, value )
4238 if cf is None:
4239 cf = Table.CellFormat()
4240
4241 bgcolor = cf.bgcolor
4242 if bgcolor is not None:
4243 bgcolor = get_color( bgcolor )
4244 else:
4245 bgcolor = self._table.style.base[ gtk.STATE_NORMAL ]
4246 cell_renderer.set_property( "cell-background-gdk",
4247 bgcolor )
4248 if isinstance( cell_renderer, gtk.CellRendererText ):
4249 font = cf.font
4250 if font is not None:
4251 font = pango.FontDescription( font )
4252 else:
4253 font = self.app._win.style.font_desc
4254 cell_renderer.set_property( "font-desc", font )
4255
4256 fgcolor = cf.fgcolor
4257 if fgcolor is not None:
4258 fgcolor = get_color( fgcolor )
4259 else:
4260 fgcolor = self._table.style.text[ gtk.STATE_NORMAL ]
4261 cell_renderer.set_property( "foreground-gdk", fgcolor )
4262
4263 if cf.underline:
4264 underline = pango.UNDERLINE_SINGLE
4265 else:
4266 underline = pango.UNDERLINE_NONE
4267 cell_renderer.set_property( "underline", underline )
4268
4269 if cf.bold:
4270 bold = pango.WEIGHT_BOLD
4271 else:
4272 bold = pango.WEIGHT_NORMAL
4273 cell_renderer.set_property( "weight", bold )
4274
4275 if cf.italic:
4276 italic = pango.STYLE_ITALIC
4277 else:
4278 italic = pango.STYLE_NORMAL
4279 cell_renderer.set_property( "style", italic )
4280
4281 cell_renderer.set_property( "strikethrough",
4282 bool( cf.strike ) )
4283
4284 col.set_cell_data_func( cell_rend, func, i )
4285
4286
4287 if i in self.expand_columns_indexes:
4288 col.set_expand( True )
4289 else:
4290 col.set_expand( False )
4291 self._table.append_column( col )
4292
4293
4294 self._table.set_headers_visible( self.show_headers )
4295 self._table.set_headers_clickable( True )
4296 self._table.set_reorderable( True )
4297 self._table.set_enable_search( True )
4298
4299 self._sw = gtk.ScrolledWindow()
4300 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
4301 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
4302 self._sw.set_shadow_type( gtk.SHADOW_IN )
4303 self._sw.add( self._table )
4304 self._vbox.pack_start( self._sw )