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.2"
27 __revision__ = "$Rev: 20 $"
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 ]
80
81 import os
82 import sys
83 import gc
84 import cPickle as pickle
85
86 try:
87 import pygtk
88 pygtk.require( "2.0" )
89 import gtk
90 import pango
91 import gobject
92 except ImportError, e:
93 sys.stderr.writelines(
94 ( "Missing module: ", str( e ), "\n",
95 "This module is part of pygtk (http://pygtk.org).\n",
96 ) )
97 sys.exit( -1 )
98
99 try:
100 import hildon
101 except ImportError, e:
102 sys.stderr.writelines(
103 ( "Missing module: ", str( e ), "\n",
104 "This module is part of pymaemo (http://pymaemo.sf.net).\n",
105 "HINT: Are you trying to use this on your desktop? If so use"
106 "eagle-gtk instead!\n"
107 ) )
108 sys.exit( -1 )
109
110 required_gtk = ( 2, 6, 0 )
111 m = gtk.check_version( *required_gtk )
112 if m:
113 sys.stderr.writelines(
114 ( "Error checking GTK version: %s\n"
115 "This system requires pygtk >= %s, you have %s installed.\n" )
116 % ( m,
117 ".".join( [ str( v ) for v in required_gtk ] ),
118 ".".join( [ str( v ) for v in gtk.pygtk_version ] )
119 ) )
120 sys.exit( -1 )
121
122 gtk.gdk.threads_init()
123
124 _apps = {}
125
126
128 """Generates a Read-Only property.
129
130 The generated property can be assigned only one value, if this value
131 is not None, it cannot be changed.
132 """
133 naming = "__ro_%s__" % ( name, )
135 try:
136 return getattr( self, naming )
137 except AttributeError:
138 return None
139
140 - def set( self, value ):
141 try:
142 v = getattr( self, naming )
143 except AttributeError:
144 v = None
145 if v is None:
146 setattr( self, naming, value )
147 else:
148 raise Exception( "Read Only property '%s'." % ( name, ) )
149
150 return property( get, set, None, doc )
151
152
153
155 if not isinstance( callback, ( tuple, list ) ):
156 if callback is None:
157 return tuple()
158 elif callable( callback ):
159 return ( callback, )
160 else:
161 raise TypeError( "Callback '%s' is not callable!" % ( callback, ) )
162 else:
163 for c in callback:
164 if not callable( c ):
165 raise TypeError( "Callback '%s' is not callable!" % ( c, ) )
166 return callback
167
168
169
171 if not isinstance( string, ( tuple, list ) ):
172 if string is None:
173 return tuple()
174 else:
175 return ( str( string ), )
176 else:
177 return tuple( [ str( s ) for s in string ] )
178
179
180
182 if not isinstance( obj, ( tuple, list ) ):
183 if obj is None:
184 return tuple()
185 else:
186 return ( obj, )
187 else:
188 return tuple( obj )
189
190
191
193 style = gtkwidget.get_style()
194 iconset = style.lookup_icon_set( stock_id )
195 if iconset:
196 icons = []
197 for s in iconset.get_sizes():
198 i = iconset.render_icon( style,
199 gtk.TEXT_DIR_NONE,
200 gtk.STATE_NORMAL,
201 s,
202 gtkwidget,
203 None
204 )
205 icons.append( i )
206 gtkwidget.set_icon_list( *icons )
207
208
209
211 """Internal widget to arrange components in tabular form.
212
213 @warning: never use it directly in Eagle applications!
214 """
215
216 padding = 3
217 id = _gen_ro_property( "id" )
218 children = _gen_ro_property( "children" )
219 horizontal = _gen_ro_property( "horizontal" )
220
221
222 - def __init__( self, id, children, horizontal=False ):
232
233
234
236 """Lay out components in a horizontal or vertical table."""
237 if not self.children:
238 return
239
240 n = len( self.children )
241
242 if self.horizontal:
243 self.resize( 2, n )
244 else:
245 self.resize( n, 2 )
246
247 for idx, c in enumerate( self.children ):
248 w = c.__get_widgets__()
249 xrm, yrm = c.__get_resize_mode__()
250
251 if len( w ) == 1:
252 if self.horizontal:
253 row0 = 0
254 row1 = 2
255 col0 = idx
256 col1 = idx + 1
257 else:
258 row0 = idx
259 row1 = idx + 1
260 col0 = 0
261 col1 = 2
262
263 self.attach( w[ 0 ], col0, col1, row0, row1,
264 xoptions=xrm,
265 yoptions=yrm,
266 xpadding=self.padding,
267 ypadding=self.padding )
268
269 elif len( w ) == 2:
270 if isinstance( xrm, int ):
271 xrm = ( xrm, xrm )
272 if isinstance( yrm, int ):
273 yrm = ( yrm, yrm )
274
275 if self.horizontal:
276 row0 = 0
277 row1 = 1
278 row2 = 1
279 row3 = 2
280 col0 = idx
281 col1 = idx + 1
282 col2 = idx
283 col3 = idx + 1
284 else:
285 row0 = idx
286 row1 = idx + 1
287 row2 = idx
288 row3 = idx + 1
289 col0 = 0
290 col1 = 1
291 col2 = 1
292 col3 = 2
293 self.attach( w[ 0 ], col0, col1, row0, row1,
294 xoptions=xrm[ 0 ],
295 yoptions=yrm[ 0 ],
296 xpadding=self.padding,
297 ypadding=self.padding )
298 self.attach( w[ 1 ], col2, col3, row2, row3,
299 xoptions=xrm[ 1 ],
300 yoptions=yrm[ 1 ],
301 xpadding=self.padding,
302 ypadding=self.padding )
303
304
305
308
309
310
311
312 -class _Panel( gtk.ScrolledWindow ):
357
358
359
360
362 """Internal widget to arrange components vertically.
363
364 @warning: never use it directly in Eagle applications!
365 """
366
367 _horizontal = False
368 _hscrollbar_policy = gtk.POLICY_NEVER
369 _vscrollbar_policy = gtk.POLICY_AUTOMATIC
370
371
372
374 """Internal widget to arrange components horizontally.
375
376 @warning: never use it directly in Eagle applications!
377 """
378
379 _horizontal = True
380 _hscrollbar_policy = gtk.POLICY_AUTOMATIC
381 _vscrollbar_policy = gtk.POLICY_NEVER
382
383
384
386 """The basic Eagle Object.
387
388 All eagle objects provides an attribute "id".
389
390 @warning: never use it directly in Eagle applications!
391 """
392
393 id = _gen_ro_property( "id" )
394
397
398
399
401 return "%s( id=%r )" % ( self.__class__.__name__, self.id )
402
403 __repr__ = __str__
404
405
406
408 """Mix-In to auto-generate ids.
409
410 @warning: never use it directly in Eagle applications!
411 """
412 last_id_num = 0
413
418
419 __get_id__ = classmethod( __get_id__ )
420
421
422
423 -class Image( _EGObject, AutoGenId ):
424 """
425 An image that can be loaded from files or binary data and saved to files.
426 """
427
429 """Image constructor.
430
431 Images can be constructed in 2 ways using keyword arguments:
432 - from files, in this case you give it B{filename} keyword:
433
434 >>> Image( filename='myfile.png' )
435
436 - from raw data, in this case you need to provide at least
437 B{data}, B{width} and B{height} as arguments. Optional
438 arguments are I{depth}, I{has_alpha} and I{row_stride}.
439 See L{load_data()} for more information:
440
441 >>> Image( data=data, width=200, height=200, depth=32, has_alpha=False )
442
443 @see: L{load_data()}
444 @see: L{load_file()}
445 """
446 _EGObject.__init__( self, self.__get_id__() )
447
448 self._img = None
449
450 if "filename" in kargs:
451 self.load_file( kargs[ "filename" ] )
452 elif "data" in kargs and "width" in kargs and "height" in kargs:
453 k = { "data": kargs[ "data" ],
454 "width": kargs[ "width" ],
455 "height": kargs[ "height" ],
456 }
457 if "depth" in kargs:
458 k[ "depth" ] = kargs[ "depth" ]
459 if "has_alpha" in kargs:
460 k[ "has_alpha" ] = kargs[ "has_alpha" ]
461 if "rowstride" in kargs:
462 k[ "rowstride" ] = kargs[ "rowstride" ]
463 self.load_data( **k )
464 elif "__int_image__" in kargs:
465 if isinstance( kargs[ "__int_image__" ], gtk.gdk.Pixbuf ):
466 self._img = kargs[ "__int_image__" ]
467 else:
468 raise ValueError( "Wrong internal image given!" )
469 elif len( kargs ) > 0:
470 params = [ "%s=%r" % kv for kv in kargs.iteritems() ]
471 raise ValueError( "Unknow parameters: %s" % params )
472
473
474
476 return self._img
477
478
479
481 gc.collect()
482
483
484
485 - def save( self, filename, format=None, **options ):
486 """Save image to a file.
487
488 If format is not specified, it will be guessed from filename.
489
490 Format may be an extension or a mime type, see
491 L{get_writable_formats()}.
492
493 @see: L{get_writable_formats()}.
494 @raise Exception: if errors happened during write
495 @raise ValueError: if format is unsupported
496 """
497 if isinstance( filename, ( tuple, list ) ):
498 filename = os.path.join( *filename )
499
500 if format is None:
501 format = filename.split( os.path.extsep )[ -1 ]
502
503 format = format.lower()
504 t = None
505 for f in self.get_writable_formats():
506 if format == f[ "name" ] or \
507 format in f[ "extensions" ] or \
508 format in f[ "mime_types" ]:
509 t = f[ "name" ]
510 break
511 if t:
512 try:
513 self._img.save( filename, t, options )
514 except gobject.GError, e:
515 raise Exception( e )
516 else:
517 raise ValueError( "Unsupported file format: \"%s\"" % format )
518
519
520
522 """Get supported image format information.
523
524 @return: list of dicts with keys:
525 - B{name}: format name
526 - B{description}: format description
527 - B{extensions}: extensions that match format
528 - B{mime_types}: mime types that match format
529 - B{is_writable}: if it is possible to write in this format, otherwise
530 it's just readable
531 """
532 return gtk.gdk.pixbuf_get_formats()
533
534
535
546
547
548
550 """Load image from file given its filename.
551
552 filename may be a string or a tuple/list with path elements,
553 this helps your program to stay portable across different platforms.
554
555 >>> i = Image()
556 >>> i.load_file( 'img.png' )
557 >>> i.load_file( ( 'test', 'img.png' ) )
558 """
559 if isinstance( filename, ( tuple, list ) ):
560 filename = os.path.join( *filename )
561
562 try:
563 self._img = gtk.gdk.pixbuf_new_from_file( filename )
564 except gobject.GError, e:
565 raise Exception( e )
566
567
568
569 - def load_data( self, data, width, height,
570 depth=24, has_alpha=None, rowstride=None ):
571 """Load image from raw data.
572
573 If no value is provided as B{has_alpha}, then it's set to C{False}
574 if B{depth} is less or equal 24 or set to C{True} if depth is 32.
575
576 If no value is provided as B{rowstride}, then it's set to
577 M{width * depth / bits_per_sample}.
578
579 >>> i = Image()
580 >>> i.load_data( my_data1, 800, 600, depth=32, has_alpha=False )
581 >>> i.load_data( my_data2, 400, 300, depth=24 )
582 """
583 colorspace = gtk.gdk.COLORSPACE_RGB
584 bits_per_sample = 8
585
586 if has_alpha is None:
587 if depth <= 24:
588 has_alpha=False
589 else:
590 has_alpha=True
591
592 if rowstride is None:
593 rowstride = width * depth / bits_per_sample
594
595 if len( data ) < height * rowstride:
596 raise ValueError( ( "data must be at least "
597 "width * height * rowstride long."
598 "Values are: data size=%d, required=%d" ) %
599 ( len( data ), height * rowstride ) )
600
601 if isinstance( data, list ):
602
603 try:
604 import Numeric
605 data = Numeric.array( data, typecode=Numeric.Character )
606 except ImportError:
607 try:
608 import array
609 data = array.array( 'c', data )
610 except:
611 data = tuple( data )
612
613 self._img = gtk.gdk.pixbuf_new_from_data( data, colorspace,
614 has_alpha, bits_per_sample,
615 width, height, rowstride )
616
617
618
620 """Return raw data and information about this image.
621
622 @return: a tuple of:
623 - width
624 - height
625 - depth
626 - has alpha?
627 - rowstride
628 - raw pixel data
629 """
630 return ( self.get_width(), self.get_height(), self.get_depth(),
631 self.has_alpha(), self.get_rowstride(),
632 self._img.get_pixels() )
633
634
637
638
639
642
643
644
648
649
650
652 """Row stride is the allocated size of a row.
653
654 Generally, rowstride is the number of elements in a row multiplied
655 by the size of each element (bits per pixel).
656
657 But there are cases that there is more space left, a padding, to
658 align it to some boundary, so you may get different value for
659 row stride.
660 """
661 return self._img.get_rowstride()
662
663
664
668
669
670
672 """Bits per pixel"""
673 return self.get_n_channels() * self._img.get_bits_per_sample()
674
675 get_depth = get_bits_per_pixel
676
677
679 """If it has an alpha channel"""
680 return self._img.get_has_alpha()
681
682
683
684
685
744
745
746
747
775
776
777
778
780 """A window that displays information about the application.
781
782 @attention: avoid using this directly, use L{AboutButton} instead.
783 """
784 border = 12
785 spacing = 6
786 width = 600
787 height = 400
788 margin = 6
789
790 - def __init__( self, app,
791 title, author=None, description=None, help=None,
792 version=None, license=None, copyright=None ):
803
804
805
807 self._diag.destroy()
808
809
810
812 win = self.app.__get_window__()
813 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
814 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
815 self._diag = gtk.Dialog( title=( "About: %s" % self.title ),
816 parent=win,
817 flags=flags, buttons=btns )
818
819 self._diag.set_border_width( self.border )
820 self._diag.set_default_size( self.width, self.height )
821 self._diag.set_has_separator( False )
822 self._diag.vbox.set_spacing( self.spacing )
823
824 _set_icon_list( self._diag, gtk.STOCK_ABOUT )
825
826 self._sw = gtk.ScrolledWindow()
827 self._diag.vbox.pack_start( self._sw, expand=True, fill=True )
828
829 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
830 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
831 self._sw.set_shadow_type( gtk.SHADOW_IN )
832
833 self._text = gtk.TextView()
834 self._sw.add( self._text )
835 self._text.set_editable( False )
836 self._text.set_cursor_visible( False )
837 self._text.set_wrap_mode( gtk.WRAP_WORD )
838 self._text.set_left_margin( self.margin )
839 self._text.set_right_margin( self.margin )
840
841 self.__setup_text__()
842
843
844
845 - def __setup_text__( self ):
846 buf = self._text.get_buffer()
847 big = buf.create_tag( "big", scale=pango.SCALE_XX_LARGE )
848 bold = buf.create_tag( "bold", weight=pango.WEIGHT_BOLD )
849 italic = buf.create_tag( "italic", style=pango.STYLE_ITALIC )
850 i = buf.get_start_iter()
851 ins = buf.insert_with_tags
852
853 ins( i, self.title, big, bold )
854
855 if self.version:
856 ins( i, "\n" )
857 ins( i, ".".join( self.version ), italic )
858
859 ins( i, "\n\n" )
860
861 if self.description:
862 for l in self.description:
863 ins( i, l )
864 ins( i, "\n" )
865 ins( i, "\n\n" )
866
867 if self.license:
868 ins( i, "License: ", bold )
869 ins( i, ", ".join( self.license ) )
870 ins( i, "\n" )
871
872 if self.author:
873 if len( self.author ) == 1:
874 ins( i, "Author:\n", bold )
875 else:
876 ins( i, "Authors:\n", bold )
877 for a in self.author:
878 ins( i, "\t" )
879 ins( i, a )
880 ins( i, "\n" )
881 ins( i, "\n\n" )
882
883 if self.help:
884 ins( i, "Help:\n", bold )
885 for l in self.help:
886 ins( i, l )
887 ins( i, "\n" )
888 ins( i, "\n\n" )
889
890 if self.copyright:
891 ins( i, "Copyright:\n", bold )
892 for l in self.copyright:
893 ins( i, l )
894 ins( i, "\n" )
895 ins( i, "\n\n" )
896
897
898
900 self._diag.show_all()
901 self._diag.run()
902 self._diag.hide()
903
904
905
906
908 """A window that displays application help.
909
910 @attention: avoid using this directly, use L{HelpButton} instead.
911 """
912 border = 12
913 spacing = 6
914 width = 600
915 height = 400
916 margin = 6
917
918 - def __init__( self, app, title, help=None ):
923
924
925
927 self._diag.destroy()
928
929
930
932 win = self.app.__get_window__()
933 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
934 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
935 self._diag = gtk.Dialog( title=( "Help: %s" % self.title ),
936 parent=win,
937 flags=flags, buttons=btns )
938 self._diag.set_border_width( self.border )
939 self._diag.set_default_size( self.width, self.height )
940 self._diag.set_has_separator( False )
941 self._diag.vbox.set_spacing( self.spacing )
942 _set_icon_list( self._diag, gtk.STOCK_HELP )
943
944 self._sw = gtk.ScrolledWindow()
945 self._diag.get_child().pack_start( self._sw, expand=True, fill=True )
946
947 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
948 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
949 self._sw.set_shadow_type( gtk.SHADOW_IN )
950
951 self._text = gtk.TextView()
952 self._sw.add( self._text )
953 self._text.set_editable( False )
954 self._text.set_cursor_visible( False )
955 self._text.set_wrap_mode( gtk.WRAP_WORD )
956 self._text.set_left_margin( self.margin )
957 self._text.set_right_margin( self.margin )
958
959 self.__setup_text__()
960
961
962
963 - def __setup_text__( self ):
964 buf = self._text.get_buffer()
965 big = buf.create_tag( "big", scale=pango.SCALE_XX_LARGE )
966 bold = buf.create_tag( "bold", weight=pango.WEIGHT_BOLD )
967 italic = buf.create_tag( "italic", style=pango.STYLE_ITALIC )
968 i = buf.get_start_iter()
969 ins = buf.insert_with_tags
970
971 ins( i, self.title, big, bold )
972 ins( i, "\n\n" )
973
974 ins( i, "Help:\n", bold )
975 for l in self.help:
976 ins( i, l )
977 ins( i, "\n" )
978
979
980
982 self._diag.show_all()
983 self._diag.run()
984 self._diag.hide()
985
986
987
988
990 """A dialog to choose a file.
991
992 @attention: avoid using this directly, use L{App.file_chooser},
993 L{OpenFileButton}, L{SaveFileButton} or L{SelectFolderButton} instead.
994 """
995 ACTION_OPEN = 0
996 ACTION_SAVE = 1
997 ACTION_SELECT_FOLDER = 2
998 ACTION_CREATE_FOLDER = 3
999
1000 - def __init__( self, app, action, filename=None,
1001 title=None, filter=None, multiple=False ):
1002 """Dialog to choose files.
1003
1004 filter may be a single pattern (ie: '*.png'), mime type
1005 (ie: 'text/html') or a list of patterns or mime types or
1006 a list of lists, each sub list with a filter name and mime type/
1007 patterns accepted. Examples:
1008 [ [ 'Images', '*.ppm', 'image/jpeg', 'image/png' ],
1009 [ 'Text', '*.text', 'text/plain' ],
1010 ]
1011 """
1012 _EGWidget.__init__( self, self.__get_id__(), app )
1013 self.action = action
1014 self.filter = filter
1015 self.multiple = multiple or False
1016 self.filename = filename
1017 self.title = title or self.__gen_title__()
1018
1019 if self.multiple:
1020 warn( "Maemo doesn't support multiple file selection!" )
1021
1022 self.__setup_gui__()
1023
1024
1025
1034
1035
1036
1038 self._diag.destroy()
1039
1040
1041
1043 win = self.app.__get_window__()
1044 a = { self.ACTION_OPEN: gtk.FILE_CHOOSER_ACTION_OPEN,
1045 self.ACTION_SAVE: gtk.FILE_CHOOSER_ACTION_SAVE,
1046 self.ACTION_SELECT_FOLDER: gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
1047 self.ACTION_CREATE_FOLDER: gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER,
1048 }.get( self.action, gtk.FILE_CHOOSER_ACTION_OPEN )
1049
1050 self._diag = hildon.FileChooserDialog( parent=win,
1051 action=a )
1052 _set_icon_list( self._diag, gtk.STOCK_OPEN )
1053 if self.filter:
1054 if isinstance( self.filter, ( tuple, list ) ):
1055 for f in self.filter:
1056 filter = gtk.FileFilter()
1057 if isinstance( f, ( tuple, list ) ):
1058 filter.set_name( f[ 0 ] )
1059 for e in f[ 1 : ]:
1060 if '/' in e:
1061 filter.add_mime_type( e )
1062 else:
1063 filter.add_pattern( e )
1064 elif isinstance( f, ( str, unicode ) ):
1065 filter.set_name( f )
1066 if '/' in f:
1067 filter.add_mime_type( f )
1068 else:
1069 filter.add_pattern( f )
1070 else:
1071 raise ValueError( "invalid filter!" )
1072 self._diag.add_filter( filter )
1073
1074 elif isinstance( self.filter, ( str, unicode ) ):
1075 filter = gtk.FileFilter()
1076 filter.set_name( self.filter )
1077 if '/' in self.filter:
1078 filter.add_mime_type( self.filter )
1079 else:
1080 filter.add_pattern( self.filter )
1081 self._diag.set_filter( filter )
1082 else:
1083 raise ValueError( "invalid filter!" )
1084 if self.filename:
1085 self._diag.set_filename( self.filename )
1086
1087
1088
1090 self._diag.show_all()
1091 r = self._diag.run()
1092 self._diag.hide()
1093 if r == gtk.RESPONSE_OK:
1094 return self._diag.get_filename()
1095 else:
1096 return None
1097
1098
1099
1100
1102 """A dialog to present user with preferences.
1103
1104 Preferences is another L{App} area, just like C{left}, C{right}, C{center},
1105 C{top} or C{bottom}, but will be displayed in a separate window.
1106
1107 @attention: avoid using this directly, use L{PreferencesButton} instead.
1108 """
1114
1115
1116
1118 self._diag.destroy()
1119
1120
1121
1123 win = self.app.__get_window__()
1124 btns = ( gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE )
1125 flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
1126 self._diag = gtk.Dialog( title=( "Preferences: %s" % self.app.title ),
1127 parent=win,
1128 flags=flags, buttons=btns )
1129 self._diag.set_default_size( 400, 300 )
1130 _set_icon_list( self._diag, gtk.STOCK_PREFERENCES )
1131
1132 self._sw = gtk.ScrolledWindow()
1133 self._diag.get_child().pack_start( self._sw, expand=True, fill=True )
1134
1135 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
1136 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
1137
1138 self._tab = _Table( self.id, self.children )
1139 self._sw.add_with_viewport( self._tab )
1140 self._sw.get_child().set_shadow_type( gtk.SHADOW_NONE )
1141 self._sw.set_shadow_type( gtk.SHADOW_NONE )
1142
1143
1144
1150
1151
1152
1154 self._diag.show_all()
1155 self._diag.run()
1156 self._diag.hide()
1157
1158
1159
1160
1162 """Dialog to show uncaught exceptions.
1163
1164 This dialog shows information about uncaught exceptions and also save
1165 the traceback to a file.
1166 """
1167
1168 border = 12
1169 spacing = 6
1170 width = 600
1171 height = 400
1172 margin = 6
1173
1177
1178
1179
1181 b = ( gtk.STOCK_QUIT, gtk.RESPONSE_CLOSE )
1182 self._diag = gtk.Dialog( "Application Crashed!",
1183 parent=None,
1184 flags=gtk.DIALOG_MODAL,
1185 buttons=b )
1186 self._diag.set_border_width( self.border )
1187 self._diag.set_default_size( self.width, self.height )
1188 self._diag.set_has_separator( False )
1189 self._diag.vbox.set_spacing( self.spacing )
1190
1191 self._hbox1 = gtk.HBox()
1192
1193 self._label1 = gtk.Label( "<b>Exception type:</b>" )
1194 self._label1.set_use_markup( True )
1195 self._hbox1.pack_start( self._label1, False, False, self.spacing )
1196 self._label1.show()
1197
1198 self._exctype = gtk.Label()
1199 self._hbox1.pack_start( self._exctype, False, True )
1200 self._exctype.show()
1201
1202 self._diag.vbox.pack_start( self._hbox1, False, False )
1203 self._hbox1.show()
1204
1205 self._hbox2 = gtk.HBox()
1206
1207 self._label2 = gtk.Label( "<b>This info was saved to:</b>" )
1208 self._label2.set_use_markup( True )
1209 self._hbox2.pack_start( self._label2, False, False, self.spacing )
1210 self._label2.show()
1211
1212 self._save_name = gtk.Label()
1213 self._hbox2.pack_start( self._save_name, False, True )
1214 self._save_name.show()
1215
1216 self._diag.vbox.pack_start( self._hbox2, False, False )
1217 self._hbox2.show()
1218
1219 self._sw = gtk.ScrolledWindow()
1220 self._sw.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC )
1221 self._sw.set_shadow_type( gtk.SHADOW_IN )
1222 self._text = gtk.TextView()
1223 self._text.set_editable( False )
1224 self._text.set_cursor_visible( False )
1225 self._text.set_wrap_mode( gtk.WRAP_WORD )
1226 self._text.set_left_margin( self.margin )
1227 self._text.set_right_margin( self.margin )
1228
1229 self._sw.add( self._text )
1230 self._text.show()
1231 self._diag.vbox.pack_start( self._sw, expand=True, fill=True )
1232 self._sw.show()
1233 self.__setup_text__()
1234
1235
1236
1237 - def __setup_text__( self ):
1238 self._buf = self._text.get_buffer()
1239 self._buf.create_tag( "label", weight=pango.WEIGHT_BOLD )
1240 self._buf.create_tag( "code", foreground="gray25",
1241 family="monospace" )
1242 self._buf.create_tag( "exc", foreground="#880000",
1243 weight=pango.WEIGHT_BOLD )
1244
1245
1246
1248 import traceback
1249 self._exctype.set_text( str( exctype ) )
1250 self.print_tb( tb )
1251
1252 lines = traceback.format_exception_only( exctype, value )
1253 msg = lines[ 0 ]
1254 result = msg.split( ' ', 1 )
1255 if len( result ) == 1:
1256 msg = result[ 0 ]
1257 arguments = ""
1258 else:
1259 msg, arguments = result
1260
1261 self._insert_text( "\n" )
1262 self._insert_text( msg, "exc" )
1263 self._insert_text( " " )
1264 self._insert_text( arguments )
1265
1266
1267
1269 import traceback
1270 import time
1271 progname = os.path.split( sys.argv[ 0 ] )[ -1 ]
1272 filename = "%s-%s-%s.tb" % ( progname,
1273 os.getuid(),
1274 int( time.time() ) )
1275 filename = os.path.join( os.path.sep, "tmp", filename )
1276 f = open( filename, "wb" )
1277 try:
1278 os.chmod( filename, 0600 )
1279 except:
1280 pass
1281
1282 for e in traceback.format_exception( exctype, value, tb ):
1283 f.write( e )
1284 f.close()
1285 self._save_name.set_text( filename )
1286 sys.stderr.write( "Traceback saved to '%s'.\n" % filename )
1287
1288
1289
1291 import linecache
1292
1293 if limit is None:
1294 if hasattr( sys, "tracebacklimit" ):
1295 limit = sys.tracebacklimit
1296 n = 0
1297 while tb is not None and ( limit is None or n < limit ):
1298 f = tb.tb_frame
1299 lineno = tb.tb_lineno
1300 co = f.f_code
1301 filename = co.co_filename
1302 name = co.co_name
1303 self._print_file( filename, lineno, name )
1304 line = linecache.getline( filename, lineno )
1305 if line:
1306 self._insert_text( " " + line.strip() + "\n\n", "code" )
1307 tb = tb.tb_next
1308 n = n+1
1309
1310
1311
1312 - def _insert_text( self, text, *tags ):
1313 end_iter = self._buf.get_end_iter()
1314 self._buf.insert_with_tags_by_name( end_iter, text, *tags )
1315
1316
1317
1331
1332
1334 import pdb
1335 pdb.pm()
1336
1337
1338
1339
1340 - def run( self, error=None ):
1341 r = self._diag.run()
1342 if r == gtk.RESPONSE_CLOSE or gtk.RESPONSE_DELETE_EVENT:
1343 raise SystemExit( error )
1344
1345
1346
1356
1357 except_hook = staticmethod( except_hook )
1358
1359 sys.excepthook = DebugDialog.except_hook
1360
1361
1362 -class _EGWidLabelEntry( _EGDataWidget ):
1363 """Widget that holds a label and an associated Entry.
1364
1365 @note: _EGWidLabelEntry must B{NOT} be used directly! You should use
1366 a widget that specialize this instead.
1367
1368 @attention: B{Widget Developers:} You must setup an instance attribute
1369 C{_entry} before using it, since this will be set as mnemonic for this
1370 label and also returned in L{__get_widgets__}().
1371 """
1372 label = _gen_ro_property( "label" )
1373
1374 - def __init__( self, id, persistent, label="" ):
1378
1379
1380
1381 - def __setup_gui__( self ):
1382 self._label = gtk.Label( self.label )
1383 self._label.set_justify( gtk.JUSTIFY_RIGHT )
1384 self._label.set_alignment( xalign=1.0, yalign=0.5 )
1385 self._label.set_mnemonic_widget( self._entry )
1386 self._widgets = ( self._label, self._entry )
1387
1388
1389
1390 - def get_value( self ):
1391 return self._entry.get_value()
1392
1393
1394
1395 - def set_value( self, value ):
1396 self._entry.set_value( value )
1397
1398
1399
1400 - def __str__( self ):
1401 return "%s( id=%r, label=%r, value=%r )" % \
1402 ( self.__class__.__name__, self.id, self.label,
1403 self.get_value() )
1404
1405 __repr__ = __str__
1406
1407
1408
1409 -class App( _EGObject, AutoGenId ):
1410 """An application window.
1411
1412 This is the base of Eagle programs, since it will hold every graphical
1413 component.
1414
1415 An App window is split in 5 areas:
1416 - left
1417 - right
1418 - center
1419 - top
1420 - bottom
1421 the first 3 have a vertical layout, the other have horizontal layout.
1422 Every area has its own scroll bars that are shown automatically when
1423 need.
1424
1425 Also provided is an extra area, that is shown in another window. This is
1426 the preferences area. It have a vertical layout and components that
1427 hold data are made persistent automatically. You should use
1428 L{PreferencesButton} to show this area.
1429
1430 Extra information like author, description, help, version, license and
1431 copyright are used in specialized dialogs. You may show these dialogs
1432 with L{AboutButton} and L{HelpButton}.
1433
1434 Widgets can be reach with L{get_widget_by_id}, example:
1435 >>> app = App( "My App", left=Entry( id="entry" ) )
1436 >>> app.get_widget_by_id( "entry" )
1437 Entry( id='entry', label='entry', value='' )
1438
1439 You may also reach widgets using dict-like syntax, but with the
1440 special case for widgets that hold data, these will be provided
1441 using their L{set_data<_EGDataWidget.set_data>} and
1442 L{get_data<_EGDataWidget.get_data>}, it make things easier, but
1443 B{be careful to don't misuse it!}. Example:
1444
1445 >>> app= App( "My App", left=Entry( id="entry" ),
1446 ... right=Canvas( "canvas", 300, 300 ) )
1447 >>> app[ "entry" ]
1448 ''
1449 >>> app[ "entry" ] = "text"
1450 >>> app[ "entry" ]
1451 'text'
1452 >>> app[ "canvas" ]
1453 Canvas( id='canvas', width=300, height=300, label='' )
1454 >>> app[ "canvas" ].draw_text( "hello" )
1455 >>> app[ "entry" ].get_value() # will fail, since it's a data widget
1456
1457 """
1458 border_width = 10
1459 spacing = 3
1460
1461 title = _gen_ro_property( "title" )
1462 left = _gen_ro_property( "left" )
1463 right = _gen_ro_property( "right" )
1464 top = _gen_ro_property( "top" )
1465 bottom = _gen_ro_property( "bottom" )
1466 center = _gen_ro_property( "center" )
1467 preferences = _gen_ro_property( "preferences" )
1468 statusbar = _gen_ro_property( "statusbar" )
1469 _widgets = _gen_ro_property( "_widgets" )
1470
1471 - def __init__( self, title, id=None,
1472 center=None, left=None, right=None, top=None, bottom=None,
1473 preferences=None,
1474 quit_callback=None, data_changed_callback=None,
1475 author=None, description=None, help=None, version=None,
1476 license=None, copyright=None,
1477 statusbar=False ):
1478 """App Constructor.
1479
1480 @param title: application name, to be displayed in the title bar.
1481 @param id: unique id to this application, or None to generate one
1482 automatically.
1483 @param center: list of widgets to be laid out vertically in the
1484 window's center.
1485 @param left: list of widgets to be laid out vertically in the
1486 window's left side.
1487 @param right: list of widgets to be laid out vertically in the
1488 window's right side.
1489 @param top: list of widgets to be laid out horizontally in the
1490 window's top.
1491 @param bottom: list of widgets to be laid out horizontally in the
1492 window's bottom.
1493 @param preferences: list of widgets to be laid out vertically in
1494 another window, this can be shown with L{PreferencesButton}.
1495 @param statusbar: if C{True}, an statusbar will be available and
1496 usable with L{status_message} method.
1497 @param author: the application author or list of author, used in
1498 L{AboutDialog}, this can be shown with L{AboutButton}.
1499 @param description: application description, used in L{AboutDialog}.
1500 @param help: help text, used in L{AboutDialog} and L{HelpDialog}, this
1501 can be shown with L{HelpButton}.
1502 @param version: application version, used in L{AboutDialog}.
1503 @param license: application license, used in L{AboutDialog}.
1504 @param copyright: application copyright, used in L{AboutDialog}.
1505 @param quit_callback: function (or list of functions) that will be
1506 called when application is closed. Function will receive as
1507 parameter the reference to App. If return value is False,
1508 it will abort closing the window.
1509 @param data_changed_callback: function (or list of functions) that will
1510 be called when some widget that holds data have its data
1511 changed. Function will receive as parameters:
1512 - App reference
1513 - Widget reference
1514 - new value
1515 """
1516 _EGObject.__init__( self, id )
1517 self.title = title
1518 self.left = left
1519 self.right = right
1520 self.top = top
1521 self.bottom = bottom
1522 self.center = center
1523 self.preferences = preferences
1524 self.author = _str_tuple( author )
1525 self.description = _str_tuple( description )
1526 self.help = _str_tuple( help )
1527 self.version = _str_tuple( version )
1528 self.license = _str_tuple( license )
1529 self.copyright = _str_tuple( copyright )
1530 self.statusbar = statusbar
1531 self._widgets = {}
1532
1533 self.quit_callback = _callback_tuple( quit_callback )
1534 self.data_changed_callback = _callback_tuple( data_changed_callback )
1535
1536 self.__add_to_app_list__()
1537 self.__setup_gui__()
1538 self.__setup_connections__()
1539 self.load()
1540
1541
1542
1549
1550
1551
1553 w = self.get_widget_by_id( name )
1554 if w is None:
1555 raise ValueError( "Could not find any widget with id=%r" % name )
1556 elif isinstance( w, _EGDataWidget ):
1557 return w.set_value( value )
1558 else:
1559 raise TypeError(
1560 "Could not set value of widget '%s' of type '%s'." % \
1561 ( name, type( w ).__name__ ) )
1562
1563
1564
1572
1573
1574
1576 """Show L{AboutDialog} of this App."""
1577 diag = AboutDialog( app=self,
1578 title=self.title,
1579 author=self.author,
1580 description=self.description,
1581 help=self.help,
1582 version=self.version,
1583 license=self.license,
1584 copyright=self.copyright,
1585 )
1586 diag.run()
1587
1588
1589
1591 """Show L{HelpDialog} of this App."""
1592 diag = HelpDialog( app=self,
1593 title=self.title,
1594 help=self.help,
1595 )
1596 diag.run()
1597
1598
1599
1600 - def file_chooser( self, action, filename=None,
1601 filter=None, multiple=False ):
1602 """Show L{FileChooser} and return selected file(s).
1603
1604 @param action: must be one of ACTION_* as defined in L{FileChooser}.
1605 @param filter: a pattern (ie: '*.png'), mime type or a list.
1606
1607 @see: L{FileChooser}
1608 """
1609 diag = FileChooser( app=self, action=action,
1610 filename=filename, filter=filter,
1611 multiple=multiple )
1612 return diag.run()
1613
1614
1615
1617 """Show L{PreferencesDialog} associated with this App."""
1618 return self._preferences.run()
1619
1620
1621
1623 return self._win
1624
1625
1626
1628 if not self.id:
1629 self.id = self.__get_id__()
1630
1631 if self.id in _apps:
1632 raise ValueError( "App id '%s' already existent!" % self.id )
1633
1634 _apps[ self.id ] = self
1635
1636
1637
1659
1660
1661
1663 self._win = hildon.App()
1664 self._win.set_name( self.id )
1665 self._win.set_title( self.title )
1666
1667 settings = self._win.get_settings()
1668 settings.set_property( "gtk-button-images", False )
1669
1670 self._mv = hildon.AppView( self.title )
1671 self._win.set_appview( self._mv )
1672 self._mv.set_fullscreen_key_allowed( True )
1673
1674 self._top_layout = gtk.VBox( False )
1675 self._mv.add( self._top_layout )
1676
1677 self._hbox = gtk.HBox( False, self.spacing )
1678 self._hbox.set_border_width( self.border_width )
1679 self._top_layout.pack_start( self._hbox, expand=True, fill=True )
1680
1681 self.__setup_menus__()
1682 self.__setup_gui_left__()
1683 self.__setup_gui_right__()
1684 self.__setup_gui_center__()
1685 self.__setup_gui_top__()
1686 self.__setup_gui_bottom__()
1687 self.__setup_gui_preferences__()
1688
1689 self._vbox = gtk.VBox( False, self.spacing )
1690
1691 if self._top.__get_widgets__():
1692 self._vbox.pack_start( self._top, expand=False, fill=True )
1693 if self._center.__get_widgets__() or \
1694 self._bottom.__get_widgets__():
1695 self._vbox.pack_start( gtk.HSeparator(), expand=False,
1696 fill=True )
1697
1698 if self._center.__get_widgets__():
1699 self._vbox.pack_start( self._center, expand=True, fill=True )
1700
1701 if self._bottom.__get_widgets__():
1702 if self._center.__get_widgets__():
1703 self._vbox.pack_start( gtk.HSeparator(), expand=False,
1704 fill=True )
1705 self._vbox.pack_start( self._bottom, expand=False, fill=True )
1706
1707 if self._left.__get_widgets__():
1708 self._hbox.pack_start( self._left, expand=False, fill=True )
1709 if self._center.__get_widgets__() or \
1710 self._top.__get_widgets__() or \
1711 self._bottom.__get_widgets__() or \
1712 self._right.__get_widgets__():
1713 self._hbox.pack_start( gtk.VSeparator(),
1714 expand=False, fill=False )
1715
1716 self._hbox.pack_start( self._vbox, expand=True, fill=True )
1717
1718 if self._right.__get_widgets__():
1719 if self._center.__get_widgets__() or \
1720 self._top.__get_widgets__() or \
1721 self._bottom.__get_widgets__():
1722 self._hbox.pack_start( gtk.VSeparator(),
1723 expand=False, fill=False )
1724 self._hbox.pack_end( self._right, expand=False, fill=True )
1725
1726 if self.statusbar:
1727 self._statusbar = gtk.Statusbar()
1728 self._statusbar_ctx = self._statusbar.get_context_id( self.title )
1729 self._statusbar.set_has_resize_grip( True )
1730 self._top_layout.pack_end( self._statusbar,
1731 expand=False, fill=True )
1732
1733 self._win.show_all()
1734
1735
1736
1738 self._menu = self._mv.get_menu()
1739
1740 mi = gtk.MenuItem( "Help" )
1741 mi.connect( "activate", lambda *x: self.show_help_dialog() )
1742 self._menu.append( mi )
1743
1744 mi = gtk.MenuItem( "About" )
1745 mi.connect( "activate", lambda *x: self.show_about_dialog() )
1746 self._menu.append( mi )
1747
1748 mi = gtk.MenuItem( "Preferences" )
1749 mi.connect( "activate", lambda *x: self.show_preferences_dialog() )
1750 self._menu.append( mi )
1751
1752 mi = gtk.MenuItem( "Close" )
1753 mi.connect( "activate", lambda *x: self.close() )
1754 self._menu.append( mi )
1755
1756 mi = gtk.MenuItem( "Quit" )
1757 mi.connect( "activate", lambda *x: quit() )
1758 self._menu.append( mi )
1759
1760 self._menu.show_all()
1761
1762
1763
1766
1767
1768
1771
1772
1773
1776
1777
1778
1781
1782
1783
1786
1787
1788
1792
1793
1794
1797
1798
1799
1801 """Notify that widget changed it's value.
1802
1803 Probably you will not need to call this directly.
1804 """
1805 self.save()
1806 for c in self.data_changed_callback:
1807 c( self, widget, value )
1808
1809
1810
1812 self.save()
1813
1814 for c in self.quit_callback:
1815 if not c( self ):
1816 return False
1817
1818 del _apps[ self.id ]
1819 if not _apps:
1820 gtk.main_quit()
1821
1822 return True
1823
1824
1825
1827 if self.__do_close__():
1828 return False
1829 else:
1830 return True
1831
1832
1833
1835 return "%s.save_data" % self.id
1836
1837
1838
1840 """Save data from widgets to file.
1841
1842 Probably you will not need to call this directly.
1843 """
1844 d = {}
1845 for id, w in self._widgets.iteritems():
1846 if isinstance( w, _EGDataWidget ) and w.persistent:
1847 d[ id ] = w.get_value()
1848
1849 if d:
1850 f = open( self.__persistence_filename__(), "wb" )
1851 pickle.dump( d, f, pickle.HIGHEST_PROTOCOL )
1852 f.close()
1853
1854
1855
1857 """Load data to widgets from file.
1858
1859 Probably you will not need to call this directly.
1860 """
1861 try:
1862 f = open( self.__persistence_filename__(), "rb" )
1863 except IOError:
1864 return
1865
1866 d = pickle.load( f )
1867 f.close()
1868
1869 for id, v in d.iteritems():
1870 try:
1871 w = self._widgets[ id ]
1872 except KeyError:
1873 w = None
1874 if isinstance( w, _EGDataWidget ) and w.persistent:
1875 w.set_value( v )
1876
1877
1878
1880 """Close application window."""
1881 if self.__do_close__():
1882 self._win.destroy()
1883
1884
1885
1887 """Display a message in status bar and retrieve its identifier for
1888 later removal.
1889
1890 @see: L{remove_status_message}
1891 @note: this only active if statusbar=True
1892 """
1893 if self.statusbar:
1894 return self._statusbar.push( self._statusbar_ctx, message )
1895 else:
1896 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
1897
1898
1899
1901 """Remove a previously displayed message.
1902
1903 @see: L{status_message}
1904 @note: this only active if statusbar=True
1905 """
1906 if self.statusbar:
1907 self._statusbar.remove( self._statusbar_ctx, message_id )
1908 else:
1909 raise ValueError( "App '%s' doesn't use statusbar!" % self.id )
1910
1911
1912
1914 """Register a function to be called after a given timeout/interval.
1915
1916 @param interval: milliseconds between calls.
1917 @param callback: function to call back. This function gets as
1918 argument the app reference and must return C{True} to
1919 keep running, if C{False} is returned, it will not be
1920 called anymore.
1921 @return: id number to be used in L{remove_event_source}
1922 """
1923 - def wrap( *args ):
1925
1926 return gobject.timeout_add( interval, wrap )
1927
1928
1929
1931 """Register a function to be called when system is idle.
1932
1933 System is idle if there is no other event pending.
1934
1935 @param callback: function to call back. This function gets as
1936 argument the app reference and must return C{True} to
1937 keep running, if C{False} is returned, it will not be
1938 called anymore.
1939 @return: id number to be used in L{remove_event_source}
1940 """
1941 - def wrap( *args ):
1943
1944 return gobject.idle_add( wrap )
1945
1946
1947
1948
1949 - def io_watch( self, file, callback,
1950 on_in=False, on_out=False, on_urgent=False, on_error=False,
1951 on_hungup=False ):
1952 """Register a function to be called after an Input/Output event.
1953
1954 @param file: any file object or file descriptor (integer).
1955 @param callback: function to be called back, parameters will be the
1956 application that generated the event, the file that triggered
1957 it and on_in, on_out, on_urgent, on_error or on_hungup,
1958 being True those that triggered the event.
1959 The function must return C{True} to be called back again,
1960 otherwise it is automatically removed.
1961 @param on_in: there is data to read.
1962 @param on_out: data can be written without blocking.
1963 @param on_urgent: there is urgent data to read.
1964 @param on_error: error condition.
1965 @param on_hungup: hung up (the connection has been broken, usually for
1966 pipes and sockets).
1967 @return: id number to be used in L{remove_event_source}
1968 """
1969 - def wrap( source, cb_condition ):
1970 on_in = bool( cb_condition & gobject.IO_IN )
1971 on_out = bool( cb_condition & gobject.IO_OUT )
1972 on_urgent = bool( cb_condition & gobject.IO_PRI )
1973 on_error = bool( cb_condition & gobject.IO_ERR )
1974 on_hungup = bool( cb_condition & gobject.IO_HUP )
1975 return callback( self, source, on_in=on_in,
1976 on_out=on_out, on_urgent=on_urgent,
1977 on_error=on_error, on_hungup=on_hungup )
1978
1979
1980 condition = 0
1981 if on_in:
1982 condition |= gobject.IO_IN
1983 if on_out:
1984 condition |= gobject.IO_OUT
1985 if on_urgent:
1986 condition |= gobject.IO_PRI
1987 if on_error:
1988 condition |= gobject.IO_ERR
1989 if on_hungup:
1990 condition |= gobject.IO_HUP
1991 return gobject.io_add_watch( file, condition, wrap )
1992
1993
1994
1996 """Remove an event generator like those created by L{timeout_add},
1997 L{idle_add} or L{io_watch}.
1998
1999 @param event_id: value returned from L{timeout_add},
2000 L{idle_add} or L{io_watch}.
2001
2002 @return: C{True} if it was removed.
2003 """
2004 return gobject.source_remove( event_id )
2005
2006
2007
2008
2010 """The drawing area.
2011
2012 Eagle's drawing area (Canvas) is provided with a frame and an optional
2013 label, together with scrollbars, to make it fit everywhere.
2014
2015 """
2016 padding = 5
2017 bgcolor= "black"
2018
2019 LEFT = -1
2020 CENTER = 0
2021 RIGHT = 1
2022
2023 FONT_OPTION_BOLD = 1
2024 FONT_OPTION_OBLIQUE = 2
2025 FONT_OPTION_ITALIC = 4
2026
2027 FONT_NAME_NORMAL = "normal"
2028 FONT_NAME_SERIF = "serif"
2029 FONT_NAME_SANS = "sans"
2030 FONT_NAME_MONO = "monospace"
2031
2032 MOUSE_BUTTON_1 = 1
2033 MOUSE_BUTTON_2 = 2
2034 MOUSE_BUTTON_3 = 4
2035 MOUSE_BUTTON_4 = 8
2036 MOUSE_BUTTON_5 = 16
2037
2038 label = _gen_ro_property( "label" )
2039
2040 - def __init__( self, id, width, height, label="", bgcolor=None,
2041 callback=None ):
2042 """Canvas Constructor.
2043
2044 @param id: unique identifier.
2045 @param width: width of the drawing area in pixels, widget can be
2046 larger or smaller because and will use scrollbars if need.
2047 @param height: height of the drawing area in pixels, widget can be
2048 larger or smaller because and will use scrollbars if need.
2049 @param label: label to display in the widget frame around the
2050 drawing area.
2051 @param bgcolor: color to paint background.
2052 @param callback: function (or list of functions) to call when
2053 mouse state changed in the drawing area. Function will get
2054 as parameters:
2055 - App reference
2056 - Canvas reference
2057 - Button state (or'ed MOUSE_BUTTON_*)
2058 - horizontal positon (x)
2059 - vertical positon (y)
2060
2061 @todo: honor the alpha value while drawing colors.
2062 """
2063 _EGWidget.__init__( self, id )
2064 self.label = label
2065 self.width = width
2066 self.height = height
2067
2068 self._pixmap = None
2069 self._callback = _callback_tuple( callback )
2070
2071
2072
2073
2074 self._style = None
2075 self._fg_gc_normal = None
2076 self._bg_gc_normal = None
2077
2078 if bgcolor is not None:
2079 self.bgcolor = self.__color_from__( bgcolor )
2080
2081 self.__setup_gui__( width, height )
2082 self.__setup_connections__()
2083
2084
2085
2087 self._frame = gtk.Frame( self.label )
2088 self._sw = gtk.ScrolledWindow()
2089 self._area = gtk.DrawingArea()
2090
2091 self._sw.set_border_width( self.padding )
2092
2093 self._frame.add( self._sw )
2094 self._frame.set_shadow_type( gtk.SHADOW_OUT )
2095
2096 self._area.set_size_request( width, height )
2097 self._sw.add_with_viewport( self._area )
2098 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
2099 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
2100 self._sw.show_all()
2101
2102 self._widgets = ( self._frame, )
2103
2104
2105
2107 self._style = self._area.get_style()
2108 self._fg_gc_normal = self._style.fg_gc[ gtk.STATE_NORMAL ]
2109 self._bg_gc_normal = self._style.bg_gc[ gtk.STATE_NORMAL ]
2110
2111
2112
2120
2121 self._area.connect( "configure_event", configure_event )
2122
2123
2125 x , y, width, height = event.area
2126 gc = widget.get_style().fg_gc[ gtk.STATE_NORMAL ]
2127 widget.window.draw_drawable( gc, self._pixmap, x, y, x, y,
2128 width, height )
2129 return False
2130
2131 self._area.connect( "expose_event", expose_event )
2132
2133
2147
2148
2149 buttons_map = {
2150 1: self.MOUSE_BUTTON_1,
2151 2: self.MOUSE_BUTTON_2,
2152 3: self.MOUSE_BUTTON_3,
2153 4: self.MOUSE_BUTTON_4,
2154 5: self.MOUSE_BUTTON_5,
2155 }
2156
2168
2169 if self._callback:
2170 self._area.connect( "button_press_event", button_press_event )
2171
2172
2184
2185 if self._callback:
2186 self._area.connect( "button_release_event", button_release_event )
2187
2188
2190 if self._pixmap is None:
2191 return True
2192
2193 if event.is_hint:
2194 x, y, state = event.window.get_pointer()
2195 else:
2196 x = event.x
2197 y = event.y
2198 state = event.state
2199
2200
2201 btns = get_buttons( state )
2202 x = int( x )
2203 y = int( y )
2204
2205 if btns:
2206 for c in self._callback:
2207 c( self.app, self, btns, x, y )
2208
2209 return True
2210
2211 if self._callback:
2212 self._area.connect( "motion_notify_event", motion_notify_event )
2213
2214
2215
2216 self._area.set_events( gtk.gdk.EXPOSURE_MASK |
2217 gtk.gdk.LEAVE_NOTIFY_MASK |
2218 gtk.gdk.BUTTON_PRESS_MASK |
2219 gtk.gdk.BUTTON_RELEASE_MASK |
2220 gtk.gdk.POINTER_MOTION_MASK |
2221 gtk.gdk.POINTER_MOTION_HINT_MASK )
2222
2223
2224
2226 return ( gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND )
2227
2228
2229
2230
2232 """Convert from color to internal representation.
2233
2234 Gets a string, integer or tuple/list arguments and converts into
2235 internal color representation.
2236 """
2237 a = 255
2238
2239 if isinstance( color, str ):
2240 try:
2241 c = gtk.gdk.color_parse( color )
2242 r = int( c.red / 65535.0 * 255 )
2243 g = int( c.green / 65535.0 * 255 )
2244 b = int( c.blue / 65535.0 * 255 )
2245 except ValueError, e:
2246 raise ValueError( "%s. color=%r" % ( e, color ) )
2247 elif isinstance( color, gtk.gdk.Color ):
2248 r = int( color.red / 65535.0 * 255 )
2249 g = int( color.green / 65535.0 * 255 )
2250 b = int( color.blue / 65535.0 * 255 )
2251 elif isinstance( color, int ):
2252 r = ( color >> 16 ) & 0xff
2253 g = ( color >> 8 ) & 0xff
2254 b = ( color & 0xff )
2255 elif isinstance( color, ( tuple, list ) ):
2256 if len( color) == 3:
2257 r, g, b = color
2258 else:
2259 a, r, g, b = color
2260
2261 return a, r, g, b
2262
2263 __color_from__ = staticmethod( __color_from__ )
2264
2265
2267 r = int( color[ 1 ] / 255.0 * 65535 )
2268 g = int( color[ 2 ] / 255.0 * 65535 )
2269 b = int( color[ 3 ] / 255.0 * 65535 )
2270 return gtk.gdk.Color( r, g, b )
2271
2272
2273
2296
2297
2298
2299 - def resize( self, width, height ):
2300 """Resize the drawing area."""
2301 old = self._pixmap
2302 self._pixmap = gtk.gdk.Pixmap( self._area.window, width, height )
2303 if old is None:
2304
2305 self.clear()
2306 else:
2307
2308 w, h = old.get_size()
2309 self._pixmap.draw_drawable( self._fg_gc_normal, old,
2310 0, 0, 0, 0, w, h )
2311
2312
2313
2314 - def draw_image( self, image, x=0, y=0,
2315 width=None, height=None,
2316 src_x=0, src_y=0 ):
2317 """Draw image on canvas.
2318
2319 By default it draws entire image at top canvas corner.
2320
2321 You may restrict which image areas to use with src_x, src_y, width
2322 and height.
2323
2324 You may choose position on canvas with x and y.
2325 """
2326 if not isinstance( image, Image ):
2327 raise TypeError( ( "image must be instance of Image class, "
2328 "but %s found!" ) % ( type( image ).__name__ ) )
2329
2330 p = image.__get_gtk_pixbuf__()
2331
2332 if src_x >= p.get_width():
2333 raise ValueError( "src_x is greater or equal width!" )
2334
2335 if src_y >= p.get_height():
2336 raise ValueError( "src_y is greater or equal height!" )
2337
2338 if width is None or width < 1:
2339 width = p.get_width()
2340
2341 if height is None or height < 1:
2342 height = p.get_height()
2343
2344 if src_x + width > p.get_width():
2345 width = p.get_width() - src_x
2346 if src_y + height > p.get_height():
2347 height = p.get_height() - src_y
2348
2349 self._pixmap.draw_pixbuf( self._fg_gc_normal,
2350 p, src_x, src_y, x, y, width, height )
2351 self._area.queue_draw_area( x, y, width, width )
2352
2353
2354
2355 - def draw_text( self, text, x=0, y=0,
2356 fgcolor=None, bgcolor=None,
2357 font_name=None, font_size=None, font_options=0,
2358 font_family=None,
2359 width=None, wrap_word=False,
2360 alignment=LEFT, justify=True ):
2361 """Draw text on canvas.
2362
2363 By default text is draw with current font and colors at top canvas
2364 corner.
2365
2366 You may limit width providing a value and choose if it should wrap
2367 at words (wrap_word=True) or characters (wrap_word=False).
2368
2369
2370 Colors can be specified with fgcolor an bgcolor. If not provided, the
2371 system foreground color is used and no background color is used.
2372
2373 Font name, family, size and options may be specified using
2374 font_name, font_family, font_size and font_options, respectively.
2375 Try to avoid using system specific font fames, use those provided
2376 by FONT_NAME_*.
2377
2378 Font options is OR'ed values from FONT_OPTIONS_*.
2379
2380 Font name is a string that have all the information, like
2381 "sans bold 12". This is returned by L{Font}.
2382
2383 Text alignment is one of LEFT, RIGHT or CENTER.
2384 """
2385 if fgcolor is not None:
2386 fgcolor = self.__to_gtk_color__( self.__color_from__( fgcolor ) )
2387 if bgcolor is not None:
2388 bgcolor = self.__to_gtk_color__( self.__color_from__( bgcolor ) )
2389
2390 layout = self._area.create_pango_layout( text )
2391 if width is not None:
2392 layout.set_width( width * pango.SCALE )
2393 if wrap_word:
2394 layout.set_wrap( pango.WRAP_WORD )
2395
2396 layout.set_justify( justify )
2397 alignment = { self.LEFT: pango.ALIGN_LEFT,
2398 self.CENTER: pango.ALIGN_CENTER,
2399 self.RIGHT: pango.ALIGN_RIGHT }.get( alignment,
2400 pango.ALIGN_CENTER )
2401 layout.set_alignment( alignment )
2402
2403 if font_name or font_size or font_options or font_family:
2404 if font_name:
2405 fd = pango.FontDescription( font_name )
2406 else:
2407 fd = layout.get_context().get_font_description()
2408
2409 if font_size:
2410 fd.set_size( font_size * pango.SCALE)
2411 if font_options:
2412 if font_options & self.FONT_OPTION_BOLD:
2413 fd.set_weight( pango.WEIGHT_BOLD )
2414 if font_options & self.FONT_OPTION_ITALIC:
2415 fd.set_style( pango.STYLE_ITALIC )
2416 if font_options & self.FONT_OPTION_OBLIQUE:
2417 fd.set_style( pango.STYLE_OBLIQUE )
2418 layout.set_font_description( fd )
2419
2420 self._pixmap.draw_layout( self._fg_gc_normal, x, y, layout,
2421 fgcolor, bgcolor )
2422 w, h = layout.get_pixel_size()
2423 self._area.queue_draw_area( x, y, w, h )
2424
2425
2426
2427
2429 """Draw point."""
2430 gc = self.__configure_gc__( fgcolor=color )
2431 self._pixmap.draw_point( gc, x, y )
2432 self._area.queue_draw_area( x, y, 1, 1 )
2433
2434
2435
2437 """Draw points.
2438
2439 Efficient way to draw more than one point with the same
2440 characteristics.
2441 """
2442 gc = self.__configure_gc__( fgcolor=color )
2443 self._pixmap.draw_points( gc, points )
2444 w, h = self._pixmap.get_size()
2445 self._area.queue_draw_area( 0, 0, w, h )
2446
2447
2448
2449 - def draw_line( self, x0, y0, x1, y1, color=None, size=1 ):
2450 """Draw line."""
2451 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2452 self._pixmap.draw_line( gc, x0, y0, x1, y1 )
2453
2454 size2 = size * 2
2455
2456 w, h = abs( x1 - x0 ) + size2, abs( y1 - y0 ) + size2
2457 x, y = max( min( x0, x1 ) - size, 0 ), max( min( y0, y1 ) - size, 0 )
2458 self._area.queue_draw_area( x, y, w, h )
2459
2460
2461
2463 """Draw line segments.
2464
2465 Efficient way to draw more than one line with the same
2466 characteristics.
2467
2468 Lines are not connected, use L{draw_lines} instead.
2469 """
2470 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2471 self._pixmap.draw_segments( gc, segments )
2472 w, h = self._pixmap.get_size()
2473 self._area.queue_draw_area( 0, 0, w, h )
2474
2475
2476
2477 - def draw_lines( self, points, color=None, size=1 ):
2478 """Draw lines connecting points.
2479
2480 Points are connected using lines, but first and last points
2481 are not connected, use L{draw_polygon} instead.
2482 """
2483 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2484 self._pixmap.draw_lines( gc, points )
2485 w, h = self._pixmap.get_size()
2486 self._area.queue_draw_area( 0, 0, w, h )
2487
2488
2489
2490 - def draw_rectangle( self, x, y, width, height, color=None, size=1,
2491 fillcolor=None, filled=False ):
2492 """Draw rectagle.
2493
2494 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2495
2496 If L{color} is provided, it will draw the rectangle's frame, even
2497 if L{filled} is C{True}.
2498 """
2499 if filled:
2500 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2501 self._pixmap.draw_rectangle( gc, True, x, y, width, height )
2502
2503 if size > 0:
2504 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2505 self._pixmap.draw_rectangle( gc, False, x, y, width, height )
2506 else:
2507 size = 0
2508
2509 half = size / 2
2510 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2511
2512
2513
2514 - def draw_arc( self, x, y, width, height, start_angle, end_angle,
2515 color=None, size=1, fillcolor=None, filled=False ):
2516 """Draw arc on canvas.
2517
2518 Arc will be the part of an ellipse that starts at ( L{x}, L{y} )
2519 and have size of L{width}xL{height}.
2520
2521 L{start_angle} and L{end_angle} are in radians, starts from the
2522 positive x-axis and are counter-clockwise.
2523
2524 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2525
2526 If L{color} is provided, it will draw the arc's frame, even
2527 if L{filled} is C{True}. Frame here is just the curve, not the
2528 straight lines that are limited by L{start_angle} and L{end_angle}.
2529 """
2530
2531 mult = 180.0 / 3.1415926535897931 * 64.0
2532 start_angle = int( mult * start_angle )
2533 end_angle = int( mult * end_angle )
2534
2535 if filled:
2536 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2537 self._pixmap.draw_arc( gc, True, x, y, width, height,
2538 start_angle, end_angle )
2539 if size > 0:
2540 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2541 self._pixmap.draw_arc( gc, False, x, y, width, height,
2542 start_angle, end_angle )
2543 else:
2544 size = 0
2545
2546 half = size / 2
2547 self._area.queue_draw_area( x-half, y-half, width+size, height+size )
2548
2549
2550
2551 - def draw_polygon( self, points, color=None, size=1,
2552 fillcolor=None, filled=False ):
2553 """Draw polygon on canvas.
2554
2555 If L{filled} is C{True}, it will be filled with L{fillcolor}.
2556
2557 If L{color} is provided, it will draw the polygon's frame, even
2558 if L{filled} is C{True}.
2559 """
2560 if filled:
2561 gc = self.__configure_gc__( fgcolor=fillcolor, fill=filled )
2562 self._pixmap.draw_polygon( gc, True, points )
2563
2564 if size > 0:
2565 gc = self.__configure_gc__( fgcolor=color, line_width=size )
2566 self._pixmap.draw_polygon( gc, False, points )
2567 else:
2568 size = 0
2569
2570 w, h = self._pixmap.get_size()
2571 self._area.queue_draw_area( 0, 0, w, h )
2572
2573
2574
2576 """Clear using bgcolor."""
2577 self.fill( self.bgcolor )
2578
2579
2580
2581 - def fill( self, color ):
2585
2586
2587
2590
2591
2592
2594 """Get the L{Image} that represents this drawing area."""
2595 w, h = self._pixmap.get_size()
2596 img = gtk.gdk.Pixbuf( gtk.gdk.COLORSPACE_RGB, True, 8, w, h )
2597 img.get_from_drawable( self._pixmap, self._area.get_colormap(),
2598 0, 0, 0, 0, w, h )
2599 return Image( __int_image__=img )
2600
2601
2602
2604 return "%s( id=%r, width=%r, height=%r, label=%r )" % \
2605 ( self.__class__.__name__, self.id, self.width, self.height,
2606 self.label )
2607
2608 __repr__ = __str__
2609
2610
2611
2612 -class _MultiLineEntry( gtk.ScrolledWindow ):
2613 - def __init__( self ):
2614 gtk.ScrolledWindow.__init__( self )
2615 self.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
2616 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
2617 self.set_shadow_type( gtk.SHADOW_IN )
2618
2619 self.textview = gtk.TextView()
2620 self.add( self.textview )
2621
2622 self.textview.set_editable( True )
2623 self.textview.set_cursor_visible( True )
2624 self.textview.set_left_margin( 2 )
2625 self.textview.set_right_margin( 2 )
2626 self.textview.get_buffer().connect( "changed", self.__emit_changed__ )
2627
2628
2629
2630 - def __emit_changed__( self, *args ):
2631 self.emit( "changed" )
2632
2633
2634
2635 - def set_text( self, value ):
2636 self.textview.get_buffer().set_text( value )
2637
2638
2639
2640 - def get_text( self ):
2641 b = self.textview.get_buffer()
2642 return b.get_text( b.get_start_iter(), b.get_end_iter() )
2643
2644
2645 gobject.signal_new( "changed",
2646 _MultiLineEntry,
2647 gobject.SIGNAL_RUN_LAST,
2648 gobject.TYPE_NONE,
2649 tuple() )
2650
2651
2652 -class Entry( _EGWidLabelEntry ):
2653 """Text entry.
2654
2655 The simplest user input component. Its contents are free-form and not
2656 filtered/masked.
2657 """
2658 value = _gen_ro_property( "value" )
2659 callback = _gen_ro_property( "callback" )
2660 multiline = _gen_ro_property( "multiline" )
2661
2662 - def __init__( self, id, label="", value="", callback=None,
2663 persistent=False, multiline=False ):
2664 """Entry constructor.
2665
2666 @param id: unique identifier.
2667 @param label: what to show on a label on the left side of the widget.
2668 @param value: initial content.
2669 @param callback: function (or list of functions) that will
2670 be called when this widget have its data changed.
2671 Function will receive as parameters:
2672 - App reference
2673 - Widget reference
2674 - new value
2675 @param persistent: if this widget should save its data between
2676 sessions.
2677 @param multiline: if this entry can be multi-line.
2678 """
2679 self.value = value
2680 self.callback = _callback_tuple( callback )
2681 self.multiline = bool( multiline )
2682
2683 _EGWidLabelEntry.__init__( self, id, persistent, label )
2684
2685 self.__setup_gui__()
2686 self.__setup_connections__()
2687
2688
2689
2690 - def __setup_gui__( self ):
2691 if self.multiline:
2692 self._entry = _MultiLineEntry()
2693 else:
2694 self._entry = gtk.Entry()
2695
2696 self._entry.set_name( self.id )
2697 self._entry.set_text( self.value )
2698
2699 _EGWidLabelEntry.__setup_gui__( self )
2700
2701
2702
2704 - def callback( obj ):
2705 v = self.get_value()
2706 self.app.data_changed( self, v )
2707 for c in self.callback:
2708 c( self.app, self, v )
2709
2710 self._entry.connect( "changed", callback )
2711
2712
2713
2714 - def get_value( self ):
2715 return self._entry.get_text()
2716
2717
2718
2719 - def set_value( self, value ):
2720 self._entry.set_text( str( value ) )
2721
2722
2723
2725 """Resize mode.
2726
2727 First tuple of tuple is about horizontal mode for label and entry.
2728 Second tuple of tuple is about vertical mode for label and entry.
2729 """
2730 if self.multiline:
2731 return ( ( gtk.FILL, gtk.FILL | gtk.EXPAND ),
2732 ( gtk.FILL, gtk.FILL | gtk.EXPAND ) )
2733 else:
2734 return ( ( gtk.FILL, gtk.FILL | gtk.EXPAND ),
2735 ( gtk.FILL, gtk.FILL ) )
2736
2737
2738
2739
2741 """Password entry.
2742
2743 Like L{Entry}, but will show '*' instead of typed chars.
2744 """
2745 - def __init__( self, id, label="", value="", callback=None,
2746 persistent=False ):
2747 """Password constructor.
2748
2749 @param id: unique identifier.
2750 @param label: what to show on a label on the left side of the widget.
2751 @param value: initial content.
2752 @param callback: function (or list of functions) that will
2753 be called when this widget have its data changed.
2754 Function will receive as parameters:
2755 - App reference
2756 - Widget reference
2757 - new value
2758 @param persistent: if this widget should save its data between
2759 sessions.
2760 """
2761 Entry.__init__( self, id, label, value, callback, persistent )
2762 self._entry.set_visibility( False )
2763
2764
2765
2766
2767 -class Spin( _EGWidLabelEntry ):
2768 """Spin button entry.
2769
2770 Spin buttons are numeric user input that checks if value is inside
2771 a specified range. It also provides small buttons to help incrementing/
2772 decrementing value.
2773 """
2774 default_min = -1e60
2775 default_max = 1e60
2776
2777 value = _gen_ro_property( "value" )
2778 min = _gen_ro_property( "min" )
2779 max = _gen_ro_property( "max" )
2780 step = _gen_ro_property( "step" )
2781 digits = _gen_ro_property( "digits" )
2782
2783 callback = _gen_ro_property( "callback" )
2784
2785 - def __init__( self, id, label="",
2786 value=None, min=None, max=None, step=None, digits=3,
2787 callback=None, persistent=False ):
2788 """Spin constructor.
2789
2790 @param id: unique identifier.
2791 @param label: what to show on a label on the left side of the widget.
2792 @param value: initial content.
2793 @param min: minimum value. If None, L{default_min} will be used.
2794 @param max: maximum value. If None, L{default_max} will be used.
2795 @param step: step to use to decrement/increment using buttons.
2796 @param digits: how many digits to show.
2797 @param callback: function (or list of functions) that will
2798 be called when this widget have its data changed.
2799 Function will receive as parameters:
2800 - App reference
2801 - Widget reference
2802 - new value
2803 @param persistent: if this widget should save its data between
2804 sessions.
2805 """
2806 self.value = value
2807 self.min = min
2808 self.max = max
2809 self.step = step
2810 self.digits = digits
2811 self.callback = _callback_tuple( callback )
2812
2813 _EGWidLabelEntry.__init__( self, id, persistent, label )
2814
2815 self.__setup_connections__()
2816
2817
2818
2820 k = {}
2821
2822 if self.value is not None:
2823 k[ "value" ] = self.value
2824
2825 if self.min is not None:
2826 k[ "lower" ] = self.min
2827 else:
2828 k[ "lower" ] = self.default_min
2829
2830 if self.max is not None:
2831 k[ "upper" ] = self.max
2832 else:
2833 k[ "upper" ] = self.default_max
2834
2835 if self.step is not None:
2836 k[ "step_incr" ] = self.step
2837 k[ "page_incr" ] = self.step * 2
2838 else:
2839 k[ "step_incr" ] = 1
2840 k[ "page_incr" ] = 2
2841
2842 adj = gtk.Adjustment( **k )
2843 self._entry = gtk.SpinButton( adjustment=adj, digits=self.digits )
2844 self._entry.set_name( self.id )
2845 self._entry.set_numeric( True )
2846 self._entry.set_snap_to_ticks( False )
2847
2848 _EGWidLabelEntry.__setup_gui__( self )
2849
2850
2851
2858
2859 self._entry.connect( "notify::value", callback )
2860
2861
2862
2865
2866
2867
2868
2870 """Integer-only Spin button.
2871
2872 Convenience widget that behaves like L{Spin} with digits set to
2873 zero and also ensure L{set_value} and L{get_value} operates on
2874 integers.
2875 """
2876 default_min = -sys.maxint
2877 default_max = sys.maxint
2878
2879 - def __init__( self, id, label="",
2880 value=None, min=None, max=None, step=None,
2881 callback=None, persistent=False ):
2882 """Integer Spin constructor.
2883
2884 @param id: unique identifier.
2885 @param label: what to show on a label on the left side of the widget.
2886 @param value: initial content.
2887 @param min: minimum value. If None, L{default_min} will be used.
2888 @param max: maximum value. If None, L{default_max} will be used.
2889 @param step: step to use to decrement/increment using buttons.
2890 @param callback: function (or list of functions) that will
2891 be called when this widget have its data changed.
2892 Function will receive as parameters:
2893 - App reference
2894 - Widget reference
2895 - new value
2896 @param persistent: if this widget should save its data between
2897 sessions.
2898 """
2899 if value is not None:
2900 value = int( value )
2901 if min is not None:
2902 min = int( min )
2903 if max is not None:
2904 max = int( max )
2905 if step is not None:
2906 step = int( step )
2907 Spin.__init__( self, id, label, value, min, max, step, 0, callback,
2908 persistent )
2909
2910
2911
2913 if self.min is not None:
2914 vmin = self.min
2915 else:
2916 vmin = self.default_min
2917
2918 if self.max is not None:
2919 vmax = self.max
2920 else:
2921 vmax = self.default_max
2922
2923 if self.step is not None:
2924 sys.stderr.write( "Maemo HildonNumberEditor doesn't support step\n" )
2925
2926 self._entry = hildon.NumberEditor( vmin, vmax )
2927 self._entry.set_name( self.id )
2928 if self.value:
2929 self._entry.set_value( self.value )
2930
2931 _EGWidLabelEntry.__setup_gui__( self )
2932
2933
2934
2941
2942 self._entry.connect( "notify::value", callback )
2943
2944
2945
2948
2949
2950
2953
2954
2955
2956
2958 """Unsigned integer-only Spin button.
2959
2960 Convenience widget that behaves like L{IntSpin} with minimum value
2961 always greater or equal zero.
2962 """
2963 default_min = 0
2964
2965 - def __init__( self, id, label="",
2966 value=None, min=0, max=None, step=None,
2967 callback=None, persistent=False ):
2968 """Unsigned Integer Spin constructor.
2969
2970 @param id: unique identifier.
2971 @param label: what to show on a label on the left side of the widget.
2972 @param value: initial content.
2973 @param min: minimum value, must be greater or equal zero.
2974 @param max: maximum value. If None, L{default_max} will be used.
2975 @param step: step to use to decrement/increment using buttons.
2976 @param callback: function (or list of functions) that will
2977 be called when this widget have its data changed.
2978 Function will receive as parameters:
2979 - App reference
2980 - Widget reference
2981 - new value
2982 @param persistent: if this widget should save its data between
2983 sessions.
2984 """
2985 if min < 0:
2986 raise ValueError( "UIntSpin cannot have min < 0!" )
2987 Spin.__init__( self, id, label, value, min, max, step, 0, callback,
2988 persistent )
2989
2990
2991
2992
2993 -class Color( _EGWidLabelEntry ):
2994 """Button to select colors.
2995
2996 It show current/last selected color and may pop-up a new dialog to
2997 select a new one.
2998 """
2999 color = _gen_ro_property( "color" )
3000 callback = _gen_ro_property( "callback" )
3001
3002 - def __init__( self, id, label="", color=0,
3003 callback=None,
3004 persistent=False ):
3005 """Color selector constructor.
3006
3007 @param id: unique identifier.
3008 @param label: what to show on a label on the left side of the widget.
3009 @param color: initial content. May be a triple with elements within
3010 the range 0-255, an string with color in HTML format or even
3011 a color present in X11's rgb.txt.
3012 @param callback: function (or list of functions) that will
3013 be called when this widget have its data changed.
3014 Function will receive as parameters:
3015 - App reference
3016 - Widget reference
3017 - new value
3018 @param persistent: if this widget should save its data between
3019 sessions.
3020 """
3021 self.color = self.color_from( color )
3022 self.callback = _callback_tuple( callback )
3023 _EGWidLabelEntry.__init__( self, id, persistent, label )
3024
3025 self.__setup_connections__()
3026
3027
3028
3030 a = 255
3031 if isinstance( color, str ):
3032 try:
3033 c = gtk.gdk.color_parse( color )
3034 r = int( c.red / 65535.0 * 255 )
3035 g = int( c.green / 65535.0 * 255 )
3036 b = int( c.blue / 65535.0 * 255 )
3037 except ValueError, e:
3038 raise ValueError( "%s. color=%r" % ( e, color ) )
3039
3040 if isinstance( color, int ):
3041 r = ( color >> 16 ) & 0xff
3042 g = ( color >> 8 ) & 0xff
3043 b = ( color & 0xff )
3044 elif isinstance( color, ( tuple, list ) ):
3045 if len( color ) == 3:
3046 r, g, b = color
3047 else:
3048 a, r, g, b = color
3049
3050 return a, r, g, b
3051
3052 color_from = staticmethod( color_from )
3053
3054
3056 r = int( self.color[ 1 ] / 255.0 * 65535 )
3057 g = int( self.color[ 2 ] / 255.0 * 65535 )
3058 b = int( self.color[ 3 ] / 255.0 * 65535 )
3059
3060 c = gtk.gdk.Color( r, g, b )
3061
3062 self._entry = hildon.ColorButton()
3063 self._entry.set_color( c )
3064 self._entry.set_name( self.id )
3065 _EGWidLabelEntry.__setup_gui__( self )
3066
3067
3068
3075
3076 self._entry.connect( "notify::color", callback )
3077
3078
3079
3081 """Return a tuple with ( alpha, red, green, blue )
3082 each in 0-255 range.
3083 """
3084 c = self._entry.get_color()
3085 r = int( c.red / 65535.0 * 255 )
3086 g = int( c.green / 65535.0 * 255 )
3087 b = int( c.blue / 65535.0 * 255 )
3088
3089 return ( r, g, b )
3090
3091
3092
3094 """
3095 @param value: May be a triple with elements within
3096 the range 0-255, an string with color in HTML format or even
3097 a color present in X11's rgb.txt.
3098 """
3099 a, r, g, b = self.color_from( value )
3100
3101 r = int( r / 255.0 * 65535 )
3102 g = int( g / 255.0 * 65535 )
3103 b = int( b / 255.0 * 65535 )
3104
3105 c = gtk.gdk.Color( r, g, b )
3106 self._entry.set_color( c )
3107
3108
3109
3110
3111 -class Font( _EGWidLabelEntry ):
3112 """Button to select fonts.
3113
3114 It show current/last selected font and may pop-up a new dialog to
3115 select a new one.
3116 """
3117 font = _gen_ro_property( "font" )
3118 callback = _gen_ro_property( "callback" )
3119
3120 - def __init__( self, id, label="", font="sans 12", callback=None,
3121 persistent=False ):
3122 """Font selector constructor.
3123
3124 @param id: unique identifier.
3125 @param label: what to show on a label on the left side of the widget.
3126 @param font: initial content.
3127 @param callback: function (or list of functions) that will
3128 be called when this widget have its data changed.
3129 Function will receive as parameters:
3130 - App reference
3131 - Widget reference
3132 - new value
3133 @param persistent: if this widget should save its data between
3134 sessions.
3135 """
3136 self.font = font
3137 self.callback = _callback_tuple( callback )
3138 _EGWidLabelEntry.__init__( self, id, persistent, label )
3139
3140 self.__setup_connections__()
3141
3142
3143
3149
3150
3151
3158
3159 self._entry.connect( "font-set", callback )
3160
3161
3162
3164 return self._entry.get_font_name()
3165
3166
3167
3169 self._entry.set_font_name( value )
3170
3171
3172
3173
3175 """Selection box (aka Combo box).
3176
3177 Selection or combo box is an element that allow you to select one of
3178 various pre-defined values.
3179 """
3180 options = _gen_ro_property( "options" )
3181 active = _gen_ro_property( "active" )
3182
3183 - def __init__( self, id, label="", options=None, active=None,
3184 callback=None, persistent=False ):
3185 """Selection constructor.
3186
3187 @param id: unique identifier.
3188 @param label: what to show on a label on the left side of the widget.
3189 @param options: list of possible values.
3190 @param active: selected element.
3191 @param callback: function (or list of functions) that will
3192 be called when this widget have its data changed.
3193 Function will receive as parameters:
3194 - App reference
3195 - Widget reference
3196 - new value
3197 @param persistent: if this widget should save its data between
3198 sessions.
3199 """
3200 self.options = options or []
3201 self.active = active
3202 self.callback = _callback_tuple( callback )
3203 _EGWidLabelEntry.__init__( self, id, persistent, label )
3204
3205 self.__setup_connections__()
3206
3207
3208
3210 self._entry = gtk.combo_box_new_text()
3211 self._entry.set_name( self.id )
3212 for i, o in enumerate( self.options ):
3213 self._entry.append_text( str( o ) )
3214 if self.active == o:
3215 self._entry.set_active( i )
3216
3217 _EGWidLabelEntry.__setup_gui__( self )
3218
3219
3220
3227
3228 self._entry.connect( "changed", callback )
3229
3230
3231
3233 return self._entry.get_active_text()
3234
3235
3236
3238 for i, o in enumerate( self._entry.get_model() ):
3239 if o[ 0 ] == value:
3240 self._entry.set_active( i )
3241
3242
3243
3244 - def append( self, value, set_active=False ):
3245 """Append new value to available options.
3246
3247 @param value: string that is not already an option.
3248
3249 @raise: ValueError: if value is already an option.
3250 """
3251 if value not in self.items():
3252 self._entry.append_text( value )
3253 if set_active:
3254 self.set_value( value )
3255 else:
3256 raise ValueError( "value already in selection" )
3257
3258
3259
3261 """Prepend new value to available options.
3262
3263 @param value: string that is not already an option.
3264
3265 @raise: ValueError: if value is already an option.
3266 """
3267 if value not in self.items():
3268 self._entry.prepend_text( value )
3269 if set_active:
3270 self.set_value( value )
3271 else:
3272 raise ValueError( "value already in selection" )
3273
3274
3275
3276 - def insert( self, position, value ):
3277 """Insert new option at position.
3278
3279 @param value: string that is not already an option.
3280
3281 @raise: ValueError: if value is already an option.
3282 """
3283 if value not in self.items():
3284 self._entry.insert_text( position, value )
3285 if set_active:
3286 self.set_value( value )
3287 else:
3288 raise ValueError( "value already in selection" )
3289
3290
3291
3293 """Remove given value from available options.
3294
3295 @param value: string that is an option.
3296
3297 @raise ValueError: if value is not already an option.
3298 """
3299 for i, o in enumerate( self._entry.get_model() ):
3300 if o[ 0 ] == value:
3301 self._entry.remove_text( i )
3302 return
3303
3304 raise ValueError( "value not in selection" )
3305
3306
3307
3309 """Returns every item/option in this selection."""
3310 return [ str( x[ 0 ] ) for x in self._entry.get_model() ]
3311
3312 options = items
3313
3314
3316 return len( self._entry.get_model() )
3317
3318
3319
3322
3323
3324
3328
3329
3330
3334
3335
3336
3337
3339 """Progress bar."""
3340 value = _gen_ro_property( "value" )
3341
3342 - def __init__( self, id, label="", value=0.0 ):
3343 """Progress bar constructor.
3344
3345 0 <= value <= 1.0
3346
3347 @param id: unique identifier.
3348 @param label: what to show on a label on the left side of the widget.
3349 @param value: initial content ( 0.0 <= value <= 1.0 )
3350 """
3351 self.value = value
3352 _EGWidLabelEntry.__init__( self, id, False, label )
3353
3354
3360
3361
3362
3364 return self._entry.get_fraction()
3365
3366
3367
3369 if 1.0 < value <= 100.0:
3370 value /= 100.0
3371 elif not ( 0.0 <= value <= 1.0 ):
3372 raise ValueError( ( "Progress value of \"%s\" must be "
3373 "between 0.0 and 1.0!" ) % self.id )
3374 self._entry.set_fraction( value )
3375 self._entry.set_text( "%d%%" % ( int( value * 100 ), ) )
3376
3377
3378
3380 """Animate progress bar."""
3381 self._entry.pulse()
3382
3383
3384
3385
3387 """Check box.
3388
3389 Check box is an component that have only two values: True (checked) or
3390 False (unchecked).
3391 """
3392 state = _gen_ro_property( "state" )
3393
3394 - def __init__( self, id, label="", state=False, callback=None,
3395 persistent=False ):
3396 """Check box constructor.
3397
3398 @param id: unique identifier.
3399 @param label: what to show on a label on the left side of the widget.
3400 @param state: initial state.
3401 @param callback: function (or list of functions) that will
3402 be called when this widget have its data changed.
3403 Function will receive as parameters:
3404 - App reference
3405 - Widget reference
3406 - new value
3407 @param persistent: if this widget should save its data between
3408 sessions.
3409 """
3410 self.label = label
3411 self.state = state
3412 self.callback = _callback_tuple( callback )
3413
3414 _EGDataWidget.__init__( self, id, persistent )
3415
3416 self.__setup_gui__()
3417 self.__setup_connections__()
3418
3419
3420
3422 self._wid = gtk.CheckButton( self.label )
3423 self._wid.set_name( self.id )
3424 self._wid.set_active( self.state )
3425 self._widgets = ( self._wid, )
3426
3427
3428
3435
3436 self._wid.connect( "toggled", callback )
3437
3438
3439
3441 return self._wid.get_active()
3442
3443
3444
3445
3446 -class Group( _EGWidget ):
3447 """Group of various components.
3448
3449 Group is a component that holds other components, always in a vertical
3450 layout.
3451
3452 Group has a frame and may show a label.
3453 """
3454 children = _gen_ro_property( "children" )
3455
3457 try:
3458 return self.__ro_app
3459 except AttributeError:
3460 return None
3461
3462
3464
3465
3466 try:
3467 v = self.__ro_app
3468 except AttributeError:
3469 v = None
3470 if v is None:
3471 self.__ro_app = value
3472 self.__add_widgets_to_app__()
3473 else:
3474 raise Exception( "Read Only property 'app'." )
3475
3476 app = property( _get_app, _set_app )
3477
3478
3479 - def __init__( self, id, label="", children=None ):
3480 """Group constructor.
3481
3482 @param id: unique identified.
3483 @param label: displayed at top-left.
3484 @param children: a list of eagle widgets that this group contains.
3485 They're presented in vertical layout.
3486 """
3487 _EGWidget.__init__( self, id )
3488 self.label = label
3489 self.children = children or tuple()
3490
3491 self.__setup_gui__()
3492
3493
3494
3496 self._frame = gtk.Frame( self.label )
3497 self._frame.set_name( self.id )
3498 self._contents = _Table( id=( "%s-contents" % self.id ),
3499 children=self.children )
3500 self._frame.add( self._contents )
3501 self._widgets = ( self._frame, )
3502
3503
3504
3508
3509
3510
3511
3512 -class Table( _EGWidget ):
3513 """Data table.
3514
3515 Each column should have only one type, it will be checked.
3516 Can be accessed as a python list:
3517
3518 >>> t = Table( 't', 'table', [ 1, 2, 3 ] )
3519 >>> t[ 0 ]
3520 [ 1 ]
3521 >>> del t[ 1 ]
3522 >>> t[ : ]
3523 [ 1, 3 ]
3524 """
3525 spacing = 3
3526
3527
3528 - class Row( object ):
3529
3531 self.__items = items
3532
3533
3534
3536 return "[" + ", ".join( [ str( x ) for x in self.__items ] ) + "]"
3537
3538 __repr__ = __str__
3539
3540
3542 return len( self.__items )
3543
3544
3545
3548
3549
3550
3552 return self.__items[ index ]
3553
3554
3555
3557 self.__items[ index ] = value
3558
3559
3560
3562 del self.__items[ index ]
3563
3564
3565
3567 return element in self.__items
3568
3569
3570
3572 slice = []
3573
3574 l = len( self.__items )
3575 while start < 0:
3576 start += l
3577 while end < 0:
3578 end += l
3579
3580 start = min( start, l )
3581 end = min( end, l )
3582
3583 for i in xrange( start, end ):
3584 slice.append( self.__items[ i ] )
3585 return slice
3586
3587
3588
3590 l = len( self.__items )
3591 while start < 0:
3592 start += l
3593 while end < 0:
3594 end += l
3595
3596 start = min( start, l )
3597 end = min( end, l )
3598
3599 l2 = len( items )
3600 if end - start > l2:
3601 end = start + l2
3602
3603 j = 0
3604 for i in xrange( start, end ):
3605 self.__items[ i ] = items[ j ]
3606 j += 1
3607
3608
3609
3610
3611
3612 - def __init__( self, id, label, items=None, types=None,
3613 headers=None, show_headers=True, editable=False,
3614 expand_columns_indexes=None,
3615 selection_callback=None, data_changed_callback=None ):
3616 """Table constructor.
3617
3618 @param id: unique identifier.
3619 @param label: what to show on table frame
3620 @param items: a list (single column) or list of lists (multiple
3621 columns)
3622 @param types: a list of types (str, int, long, float, unicode, bool)
3623 for columns, if omitted, will be guessed from items.
3624 @param headers: what to use as table header.
3625 @param show_headers: whenever to show table headers
3626 @param editable: if table is editable. If editable, user can change
3627 values inline or double-clicking, also edit buttons will
3628 show after the table.
3629 @param expand_columns_indexes: list of indexes that can expand size
3630 @param selection_callback: the function (or list of functions) to
3631 call when selection changes. Function will get as parameters:
3632 - App reference
3633 - Table reference
3634 - List of pairs ( index, row_contents )
3635 @param data_changed_callback: the function (or list of functions) to
3636 call when data changes. Function will get as parameters:
3637 - App reference
3638 - Table reference
3639 - Pair ( index, row_contents )
3640
3641 @warning: although this widget contains data, it's not a
3642 _EGDataWidget and thus will not notify application that
3643 data changed, also it cannot persist it's data
3644 automatically, if you wish, do it manually. This behavior
3645 may change in future if Table show to be useful as
3646 _EGDataWidget.
3647 """
3648 _EGWidget.__init__( self, id )
3649 self.editable = editable or False
3650 self.label = str( label or "" )
3651 self.headers = headers or tuple()
3652 self.show_headers = bool( show_headers )
3653
3654 if isinstance( expand_columns_indexes, ( int, long ) ):
3655 expand_columns_indexes = ( expand_columns_indexes, )
3656 elif isinstance( expand_columns_indexes, ( tuple, list ) ):
3657 expand_columns_indexes = tuple( expand_columns_indexes )
3658 elif expand_columns_indexes is None:
3659 expand_columns_indexes = tuple()
3660 else:
3661 raise ValueError( \
3662 "expand_columns_indexes must be a sequence of integers" )
3663 self.expand_columns_indexes = expand_columns_indexes
3664
3665
3666 if not ( types or items ):
3667 raise ValueError( "Must provide items or types!" )
3668 elif not types:
3669 items = items or []
3670 if not isinstance( items[ 0 ], ( list, tuple ) ):
3671
3672 items = [ [ i ] for i in items ]
3673
3674 types = [ type( i ) for i in items[ 0 ] ]
3675 self.types = types
3676 self.items = items
3677
3678 self.selection_callback = _callback_tuple( selection_callback )
3679 self.data_changed_callback = _callback_tuple( data_changed_callback )
3680
3681 self.__setup_gui__()
3682 self.__setup_connections__()
3683
3684
3685
3687 self._frame = gtk.Frame( self.label )
3688 self._frame.set_name( self.id )
3689
3690 self._vbox = gtk.VBox( False, self.spacing )
3691 self._vbox.set_border_width( self.spacing )
3692 self._vbox.set_name( "vbox-%s" % self.id )
3693
3694 self._frame.add( self._vbox )
3695 self._widgets = ( self._frame, )
3696
3697 self.__setup_table__()
3698
3699 if self.editable:
3700 self._hbox = gtk.HBox( False, self.spacing )
3701 self._vbox.pack_start( self._hbox, expand=False, fill=True )
3702
3703 self._btn_add = gtk.Button( stock=gtk.STOCK_ADD )
3704 self._btn_del = gtk.Button( stock=gtk.STOCK_REMOVE )
3705 self._btn_edit = gtk.Button( stock=gtk.STOCK_EDIT )
3706
3707 self._hbox.pack_start( self._btn_add )
3708 self._hbox.pack_start( self._btn_del )
3709 self._hbox.pack_start( self._btn_edit )
3710
3711
3712
3722
3723
3724
3727 index = path[ 0 ]
3728 v = ( index, Table.Row( model[ path ] ) )
3729 for c in self.data_changed_callback:
3730 c( self.app, self, v )
3731
3732
3733
3735 index = path[ 0 ]
3736 v = ( index, None )
3737 for c in self.data_changed_callback:
3738 c( self.app, self, v )
3739
3740
3741 self._model.connect( "row-changed", row_changed )
3742 self._model.connect( "row-deleted", row_deleted )
3743 self._model.connect( "row-inserted", row_changed)
3744
3745
3746
3749 title = "Edit data from table %s" % self.label
3750 buttons = ( gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
3751 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT )
3752 d = gtk.Dialog( title=title,
3753 flags=gtk.DIALOG_MODAL,
3754 buttons=buttons )
3755 d.set_default_response( gtk.RESPONSE_ACCEPT )
3756
3757 l = len( data )
3758 t = gtk.Table( l, 2 )
3759 t.set_border_width( 0 )
3760 w = []
3761 for i, v in enumerate( data ):
3762 title = self._table.get_column( i ).get_title()
3763 label = gtk.Label( title )
3764 label.set_justify( gtk.JUSTIFY_RIGHT )
3765 label.set_alignment( xalign=1.0, yalign=0.5 )
3766
3767 tp = self.types[ i ]
3768 if tp == bool:
3769 entry = gtk.CheckButton()
3770 entry.set_active( data[ i ] )
3771 elif tp in ( int, long ):
3772 entry = gtk.SpinButton( digits=0 )
3773 adj = entry.get_adjustment()
3774 adj.lower = Spin.default_min
3775 adj.upper = Spin.default_max
3776 adj.step_increment = 1
3777 adj.page_increment = 5
3778 entry.set_value( data[ i ] )
3779 elif tp == float:
3780 entry = gtk.SpinButton( digits=6 )
3781 adj = entry.get_adjustment()
3782 adj.lower = Spin.default_min
3783 adj.upper = Spin.default_max
3784 adj.step_increment = 1
3785 adj.page_increment = 5
3786 entry.set_value( data[ i ] )
3787 elif tp in ( str, unicode ):
3788 entry = gtk.Entry()
3789 entry.set_text( data[ i ] )
3790 else:
3791 try:
3792 name = tp.__name__
3793 except:
3794 name = tp
3795 raise ValueError( "Unsuported column (%d) type: %s" %
3796 ( i, name ) )
3797
3798 t.attach( label, 0, 1, i, i + 1,
3799 xoptions=gtk.FILL,
3800 xpadding=self.spacing, ypadding=self.spacing )
3801 t.attach( entry, 1, 2, i, i + 1,
3802 xoptions=gtk.EXPAND|gtk.FILL,
3803 xpadding=self.spacing, ypadding=self.spacing )
3804 w.append( entry )
3805
3806 t.show_all()
3807
3808 sw = gtk.ScrolledWindow()
3809 sw.add_with_viewport( t )
3810 sw.get_child().set_shadow_type( gtk.SHADOW_NONE )
3811 d.vbox.pack_start( sw )
3812
3813
3814 sw.set_policy( hscrollbar_policy=gtk.POLICY_NEVER,
3815 vscrollbar_policy=gtk.POLICY_NEVER )
3816 d.show_all()
3817
3818 sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
3819 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
3820
3821 r = d.run()
3822 d.destroy()
3823 if r in ( gtk.RESPONSE_REJECT, gtk.RESPONSE_DELETE_EVENT ) :
3824 return None
3825 else:
3826 result = []
3827 for i in xrange( len( data ) ):
3828 tp = self.types[ i ]
3829 wid = w[ i ]
3830 if tp == bool:
3831 r = bool( wid.get_active() )
3832 elif tp in ( int, long ):
3833 r = tp( wid.get_value() )
3834 elif tp == float:
3835 r = float( wid.get_value() )
3836 elif tp in ( str, unicode ):
3837 r = tp( wid.get_text() )
3838 else:
3839 try:
3840 name = tp.__name__
3841 except:
3842 name = tp
3843 raise ValueError( \
3844 "Unsuported column (%d) type: %s" %
3845 ( i, name ) )
3846 result.append( r )
3847
3848 return result
3849
3850
3851
3853 entry = []
3854 for i, t in enumerate( self.types ):
3855 if t == bool:
3856 v = False
3857 elif t in ( int, long, float ):
3858 v = 0
3859 elif t in ( str, unicode ):
3860 v = ''
3861 else:
3862 try:
3863 name = t.__name__
3864 except:
3865 name = t
3866 raise ValueError( "Unsuported column (%d) type: %s" %
3867 ( i, name ) )
3868 entry.append( v )
3869 result = edit_dialog( entry )
3870 if result:
3871 self.append( result )
3872
3873
3874
3876 selected = self.selected()
3877 if not selected:
3878 return
3879
3880 for index, data in selected:
3881 print data
3882 result = edit_dialog( data )
3883 if result:
3884 self[ index ] = result
3885
3886
3887
3895
3896
3897 self._btn_add.connect( "clicked", clicked_add )
3898 self._btn_del.connect( "clicked", clicked_del )
3899 self._btn_edit.connect( "clicked", clicked_edit )
3900
3902 data = treeview.get_model()[ path ]
3903 result = edit_dialog( data )
3904 if result:
3905 self[ path[ 0 ] ] = result
3906
3907
3908
3909 self._table.connect( "row-activated", row_activated )
3910
3911
3912
3915 result = self.selected()
3916 for c in self.selection_callback:
3917 c( self.app, self, result )
3918
3919
3920 selection = self._table.get_selection()
3921 selection.connect( "changed", selection_changed )
3922
3923
3924
3926 self.__setup_model__()
3927 self._table = gtk.TreeView( self._model )
3928 self._table.set_property( "allow-checkbox-mode", False )
3929 self._table.set_name( "table-%s" % self.id )
3930 self._table.get_selection().set_mode( gtk.SELECTION_MULTIPLE )
3931
3933 cid, order = self._model.get_sort_column_id()
3934 self._model.set_sort_column_id( cid, order )
3935
3936
3937 - def toggled( cell_render, path, col ):
3938 self._model[ path ][ col ] = not self._model[ path ][ col ]
3939
3940
3941
3942 - def edited( cell_render, path, text, col ):
3943 t = self.types[ col ]
3944 try:
3945 value = t( text )
3946 except ValueError, e:
3947 name = t.__name__
3948 error( "Invalid contents for column of type '%s': %s" %
3949 ( name, text ) )
3950 else:
3951 self._model[ path ][ col ] = value
3952
3953
3954
3955 for i, t in enumerate( self.types ):
3956 if t == bool:
3957 cell_rend = gtk.CellRendererToggle()
3958 props = { "active": i }
3959 if self.editable:
3960 cell_rend.set_property( "activatable", True)
3961 cell_rend.connect( "toggled", toggled, i )
3962
3963 elif t in ( int, long, float, str, unicode ):
3964 cell_rend = gtk.CellRendererText()
3965 if self.editable:
3966 cell_rend.set_property( "editable", True )
3967 cell_rend.connect( "edited", edited, i )
3968
3969 props = { "text": i }
3970 if t in ( int, long, float ):
3971 cell_rend.set_property( "xalign", 1.0 )
3972 else:
3973 try:
3974 name = t.__name__
3975 except:
3976 name = t
3977 raise ValueError( "Unsuported column (%d) type: %s" %
3978 ( i, name ) )
3979
3980 try:
3981 title = self.headers[ i ]
3982 except IndexError:
3983 title = "Col-%d (%s)" % ( i, t.__name__ )
3984
3985 col = gtk.TreeViewColumn( title, cell_rend, **props )
3986 col.set_resizable( True )
3987 col.set_sort_column_id( i )
3988 col.connect( "clicked", column_clicked )
3989 if i in self.expand_columns_indexes:
3990 col.set_expand( True )
3991 else:
3992 col.set_expand( False )
3993 self._table.append_column( col )
3994
3995
3996 self._table.set_headers_visible( self.show_headers )
3997 self._table.set_headers_clickable( True )
3998 self._table.set_reorderable( True )
3999 self._table.set_enable_search( True )
4000
4001
4002 if self.items:
4003 for row in self.items:
4004 self.append( row, select=False, autosize=False )
4005 self.columns_autosize()
4006
4007 self._sw = gtk.ScrolledWindow()
4008 self._sw.set_policy( hscrollbar_policy=gtk.POLICY_AUTOMATIC,
4009 vscrollbar_policy=gtk.POLICY_AUTOMATIC )
4010 self._sw.set_shadow_type( gtk.SHADOW_IN )
4011 self._sw.add( self._table )
4012 self._vbox.pack_start( self._sw )
4013
4014
4015
4017 gtk_types = []
4018 for i, t in enumerate( self.types ):
4019 if t == bool:
4020 gtk_types.append( gobject.TYPE_BOOLEAN )
4021 elif t == int:
4022 gtk_types.append( gobject.TYPE_INT )
4023 elif t == long:
4024 gtk_types.append( gobject.TYPE_LONG )
4025 elif t == float:
4026 gtk_types.append( gobject.TYPE_FLOAT )
4027 elif t in ( str, unicode ):
4028 gtk_types.append( gobject.TYPE_STRING )
4029 else:
4030 try:
4031 name = t.__name__
4032 except:
4033 name = t
4034 raise ValueError( "Unsuported column (%d) type: %s" %
4035 ( i, name ) )
4036 self._model = gtk.ListStore( *gtk_types )
4037
4038 - def sort_fn( model, itr1, itr2, id ):
4039 return cmp( model[ itr1 ][ id ], model[ itr2 ][ id ] )
4040
4041
4042 for i in xrange( len( self.types ) ):
4043 self._model.set_sort_func( i, sort_fn, i )
4044
4045
4046
4048 return ( gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND )
4049
4050
4051
4054
4055
4056
4058 model, paths = self._table.get_selection().get_selected_rows()
4059 if paths:
4060 result = []
4061 for p in paths:
4062 result.append( ( p[ 0 ], Table.Row( model[ p ] ) ) )
4063 return result
4064 else:
4065 return None
4066
4067
4068
4069 - def append( self, row, select=True, autosize=True ):
4070 if not isinstance( row, ( list, tuple ) ):
4071 row = ( row, )
4072
4073 itr = self._model.append( row )
4074
4075 if autosize:
4076 self._table.columns_autosize()
4077 if select:
4078 self._table.set_cursor( self._model[ itr ].path )
4079
4080
4081
4082 - def insert( self, index, row, select=True, autosize=True ):
4083 if not isinstance( row, ( list, tuple ) ):
4084 row = ( row, )
4085
4086 itr = self._model.insert( index, row )
4087 self._table.columns_autosize()
4088
4089 if autosize:
4090 self._table.columns_autosize()
4091 if select:
4092 self._table.set_cursor( self._model[ itr ].path )
4093
4094
4095
4098
4099
4101 return len( self._model )
4102
4103
4104
4106 self.append( other )
4107 return self
4108
4109
4110
4112 if not isinstance( other, ( list, tuple ) ):
4113 other = ( other, )
4114 try:
4115 self._model[ index ] = other
4116 except TypeError, e:
4117 raise IndexError( "index out of range" )
4118
4119
4120
4122 try:
4123 items = self._model[ index ]
4124 except TypeError, e:
4125 raise IndexError( "index out of range" )
4126
4127 return Table.Row( items )
4128
4129
4130
4132 try:
4133 del self._model[ index ]
4134 except TypeError, e:
4135 raise IndexError( "index out of range" )
4136
4137
4138
4140 for r in self._model:
4141 if row in r:
4142 return True
4143 return False
4144
4145
4146
4148 slice = []
4149
4150 l = len( self._model )
4151 while start < 0:
4152 start += l
4153 while end < 0:
4154 end += l
4155
4156 start = min( start, l )
4157 end = min( end, l )
4158
4159 for i in xrange( start, end ):
4160 slice.append( Table.Row( self._model[ i ] ) )
4161 return slice
4162
4163
4164
4166 l = len( self._model )
4167 while start < 0:
4168 start += l
4169 while end < 0:
4170 end += l
4171
4172 del self[ start : end ]
4173
4174
4175 l2 = len( slice )
4176 if end - start > l2:
4177 end = start + l2
4178 for j, i in enumerate( xrange( start, end ) ):
4179 row = list( slice[ j ] )
4180
4181
4182
4183 lr = len( row )
4184 lt = len( self.types )
4185 if lr < lt:
4186 for i in xrange( lr, lt ):
4187 t = self.types[ i ]
4188 row.append( t() )
4189
4190 self.insert( i, row, select=False, autosize=False )
4191
4192
4193
4195 l = len( self._model )
4196 while start < 0:
4197 start += l
4198 while end < 0:
4199 end += l
4200
4201 start = min( start, l )
4202 end = min( end, l )
4203
4204 while end > start:
4205 end -= 1
4206 del self._model[ end ]
4207
4208
4209
4210
4340
4341
4342
4343
4353
4354
4355
4356
4366
4367
4368
4369
4379
4380
4381
4382
4392
4393
4394
4395
4422
4423
4424
4425
4447
4448
4449
4450
4477
4478
4479
4480
4490
4491
4492
4493
4495 """Horizontal separator"""
4502
4503
4504
4505
4507 """Horizontal separator"""
4514
4515
4516
4517
4518 -class Label( _EGDataWidget, AutoGenId ):
4519 """Text label"""
4520 label = _gen_ro_property( "label" )
4521
4522 LEFT = 0.0
4523 RIGHT = 1.0
4524 CENTER = 0.5
4525 TOP = 0.0
4526 MIDDLE = 0.5
4527 BOTTOM = 1.0
4528
4531 """Label constructor.
4532
4533 @param id: may not be provided, it will be generated automatically.
4534 @param label: what this label will show.
4535 @param halignment: horizontal alignment, like L{LEFT}, L{RIGHT} or
4536 L{CENTER}.
4537 @param valignment: vertical alignment, like L{TOP}, L{BOTTOM} or
4538 L{MIDDLE}.
4539 """
4540 _EGDataWidget.__init__( self, id or self.__get_id__(), False )
4541 self.label = label
4542
4543 self._wid = gtk.Label( self.label )
4544 self._wid.set_name( self.id )
4545 self._wid.set_alignment( xalign=halignment, yalign=valignment )
4546 self._widgets = ( self._wid, )
4547
4548
4549
4552
4553
4554
4557
4558
4559
4561 return "%s( id=%r, label=%r )" % \
4562 ( self.__class__.__name__, self.id, self.label )
4563
4564
4565
4566
4576
4577 info = information
4578
4579
4581 """Show warning message to user."""
4582
4583 d = gtk.MessageDialog( type=gtk.MESSAGE_WARNING,
4584 message_format=message,
4585 buttons=gtk.BUTTONS_CLOSE )
4586 d.run()
4587 d.destroy()
4588 return
4589
4590 warn = warning
4591
4592
4594 """Show error message to user."""
4595
4596 d = gtk.MessageDialog( type=gtk.MESSAGE_ERROR,
4597 message_format=message,
4598 buttons=gtk.BUTTONS_CLOSE )
4599 d.run()
4600 d.destroy()
4601 return
4602
4603 err = error
4604
4605
4606 -def yesno( message, yesdefault=False ):
4607 """Show yes/no message to user.
4608
4609 @param yesdefault: if yes should be the default action.
4610 """
4611
4612 d = gtk.MessageDialog( type=gtk.MESSAGE_QUESTION,
4613 message_format=message,
4614 buttons=gtk.BUTTONS_YES_NO )
4615 if yesdefault:
4616 d.set_default_response( gtk.RESPONSE_YES )
4617 else:
4618 d.set_default_response( gtk.RESPONSE_NO )
4619
4620 r = d.run()
4621 d.destroy()
4622
4623 if r == gtk.RESPONSE_YES:
4624 return True
4625 elif r == gtk.RESPONSE_NO:
4626 return False
4627 else:
4628 return yesdefault
4629
4630
4631
4632
4633 -def confirm( message, okdefault=False ):
4634 """Show confirm message to user.
4635
4636 @param okdefault: if ok should be the default action.
4637 """
4638
4639 d = gtk.MessageDialog( type=gtk.MESSAGE_QUESTION,
4640 message_format=message,
4641 buttons=gtk.BUTTONS_OK_CANCEL )
4642 if okdefault:
4643 d.set_default_response( gtk.RESPONSE_OK )
4644 else:
4645 d.set_default_response( gtk.RESPONSE_CANCEL )
4646
4647 r = d.run()
4648 d.destroy()
4649
4650 if r == gtk.RESPONSE_OK:
4651 return True
4652 elif r == gtk.RESPONSE_CANCEL:
4653 return False
4654 else:
4655 return okdefault
4656
4657
4658
4659
4661 """Enter the event loop"""
4662 try:
4663 gtk.main()
4664 except KeyboardInterrupt:
4665 raise SystemExit( "User quit using Control-C" )
4666
4667
4669 """Quit the event loop"""
4670 gtk.main_quit()
4671
4672
4673
4675 """Given an App unique identifier, return the reference to it."""
4676 if app_id is None:
4677 try:
4678 return _apps.values()[ 0 ]
4679 except IndexError, e:
4680 raise ValueError( "No application defined!" )
4681 elif isinstance( app_id, ( str, unicode ) ):
4682 try:
4683 return _apps[ app_id ]
4684 except KeyError, e:
4685 raise ValueError( "Application id \"%s\" doesn't exists!" % \
4686 app_id )
4687 elif isinstance( app_id, App ):
4688 return app_id
4689 else:
4690 raise ValueError( "app_id must be string or App instance!" )
4691
4692
4693
4710
4711
4712
4714 """Convenience function to get widget and call its get_value() method."""
4715 try:
4716 wid = get_widget_by_id( widget_id, app_id )
4717 return wid.get_value()
4718 except ValueError, e:
4719 raise ValueError( e )
4720
4721
4722
4723 -def set_value( widget_id, value, app_id=None ):
4724 """Convenience function to get widget and call its set_value() method."""
4725 try:
4726 wid = get_widget_by_id( widget_id, app_id )
4727 wid.set_value( value )
4728 except ValueError, e:
4729 raise ValueError( e )
4730
4731
4732
4733 -def show( widget_id, app_id=None ):
4734 """Convenience function to get widget and call its show() method."""
4735 try:
4736 wid = get_widget_by_id( widget_id, app_id )
4737 wid.show()
4738 except ValueError, e:
4739 raise ValueError( e )
4740
4741
4742
4743 -def hide( widget_id, app_id=None ):
4744 """Convenience function to get widget and call its hide() method."""
4745 try:
4746 wid = get_widget_by_id( widget_id, app_id )
4747 wid.hide()
4748 except ValueError, e:
4749 raise ValueError( e )
4750
4751
4752
4753 -def set_active( widget_id, active=True, app_id=None ):
4754 """Convenience function to get widget and call its set_active() method."""
4755 try:
4756 wid = get_widget_by_id( widget_id, app_id )
4757 wid.set_active( active )
4758 except ValueError, e:
4759 raise ValueError( e )
4760
4761
4762
4764 """
4765 Convenience function to get widget and call its set_inactive() method.
4766 """
4767 try:
4768 wid = get_widget_by_id( widget_id, app_id )
4769 wid.set_inactive()
4770 except ValueError, e:
4771 raise ValueError( e )
4772
4773
4774
4775 -def close( app_id=None ):
4776 """Convenience function to get app and call its close() method."""
4777 try:
4778 app = get_app_by_id( app_id )
4779 app.close()
4780 except ValueError, e:
4781 raise ValueError( e )
4782
4783