2 # -*- coding: utf-8 -*-
5 # License: CC-BY-NC-SA-3.0
6 # http://creativecommons.org/licenses/by-nc-sa/3.0/de/
7 # http://creativecommons.org/licenses/by-nc-sa/3.0/de/deed.en
8 # Johann Klähn <kljohann@gmail.com>
14 from optparse
import OptionParser
16 def float_to_percent(flt
):
17 return (("%.2f" % flt
).replace('.', '').lstrip("0") or "0")+"%"
20 class Scroller(object):
21 # defaults that will be overridden by instances
24 def __init__(self
, fraction
=0.5, title
=None, factor
=20):
26 self
._window
= gtk
.Window()
27 self
._window
.set_title('Knubbler')
29 # Cancel when closed by window-manager
30 self
._window
.connect('delete-event', lambda *a
, **k
: self
.exit(1))
32 # Put ProgressBar in EventBox to capture Scroll & Mouse-Click
33 eventbox
= gtk
.EventBox()
34 self
._progress
= gtk
.ProgressBar()
35 self
._progress
.set_property('show-text', True)
37 # Add widgets to window
38 eventbox
.add(self
._progress
)
41 # Display text above progressbar
42 label
= gtk
.Label(title
)
43 label
.set_alignment(0.5, 0.5)
44 label
.set_justify(gtk
.JUSTIFY_CENTER
)
45 label
.set_use_markup(True)
46 label
.set_line_wrap(True)
49 vbox
.pack_start(label
, expand
=False, padding
=5)
50 vbox
.pack_end(eventbox
)
52 self
._window
.add(vbox
)
54 self
._window
.add(eventbox
)
57 # Atach event-handlers:
58 self
._progress
.connect("realize",
59 lambda w
: w
.window
.set_cursor(gtk
.gdk
.Cursor(gtk
.gdk
.CIRCLE
)))
60 eventbox
.connect("button-press-event", self
.on_button_press_event
)
61 eventbox
.connect("motion-notify-event", self
.on_motion_notify_event
)
62 eventbox
.connect('scroll-event', self
.on_scroll_event
)
63 self
._window
.connect('key-press-event', self
.on_key_press_event
)
65 # Set value of progressbar, initialize everything else
66 # (most of this is done by properties/set_x, see below)
67 self
._factor
= float(factor
)
68 self
.fraction
= fraction
69 self
.orientation
= gtk
.PROGRESS_BOTTOM_TO_TOP
71 def window(self
, width
=150, height
=250):
72 self
._window
.unfullscreen()
73 self
._window
.set_size_request(width
, height
)
74 self
._window
.set_position(gtk
.WIN_POS_CENTER
)
77 self
._window
.fullscreen()
80 def set_orientation(self
, orientation
):
81 assert orientation
in [gtk
.PROGRESS_LEFT_TO_RIGHT
,
82 gtk
.PROGRESS_BOTTOM_TO_TOP
], "Only LTR/BTT"
83 self
._progress
.set_orientation(orientation
)
84 self
._orientation
= orientation
86 def get_orientation(self
):
87 return self
._orientation
89 orientation
= property(lambda s
: s
.get_orientation(),
90 lambda s
, v
: s
.set_orientation(v
))
92 def set_fraction(self
, value
):
93 value
= max(0, min(1, value
))
94 # round to steps of 1/factor
95 self
._fraction
= round(value
* self
._factor
)/self
._factor
96 self
._progress
.set_fraction(self
._fraction
)
98 def get_fraction(self
):
101 fraction
= property(lambda s
: s
.get_fraction(),
102 lambda s
, v
: s
.set_fraction(v
))
105 self
.fraction
+= 1.0/self
._factor
108 self
.fraction
-= 1.0/self
._factor
111 def exit(self
, code
=0):
112 self
.exitstatus
= code
117 self
._window
.show_all()
119 return self
.exitstatus
or 0
122 def on_scroll_event(self
, widget
, event
):
123 # Modify progessbar according to scrolling
124 if self
.orientation
== gtk
.PROGRESS_LEFT_TO_RIGHT
:
125 # Horizontal scrolling
126 if event
.direction
== gtk
.gdk
.SCROLL_RIGHT
:
128 elif event
.direction
== gtk
.gdk
.SCROLL_LEFT
:
130 elif self
.orientation
== gtk
.PROGRESS_BOTTOM_TO_TOP
:
132 if event
.direction
== gtk
.gdk
.SCROLL_UP
:
134 elif event
.direction
== gtk
.gdk
.SCROLL_DOWN
:
137 def on_button_press_event(self
, widget
, event
):
138 if event
.button
== 3:
139 # Set distinct value by right-clicking
140 if self
.orientation
== gtk
.PROGRESS_LEFT_TO_RIGHT
:
141 total
= self
._progress
.allocation
.width
142 self
.fraction
= max(0, min(1,
143 event
.get_coords()[0] / float(total
)))
144 elif self
.orientation
== gtk
.PROGRESS_BOTTOM_TO_TOP
:
145 total
= self
._progress
.allocation
.height
146 self
.fraction
= 1 - max(0, min(1,
147 event
.get_coords()[1] / float(total
)))
148 elif event
.button
== 1:
149 # accept current value on mouseclick
152 def on_motion_notify_event(self
, widget
, event
):
153 if event
.get_state() == gtk
.gdk
.BUTTON3_MASK
:
154 # Right click-and-hold to modify value
155 if self
.orientation
== gtk
.PROGRESS_LEFT_TO_RIGHT
:
156 total
= self
._progress
.allocation
.width
157 self
.fraction
= max(0, min(1,
158 event
.x
/ float(total
)))
159 elif self
.orientation
== gtk
.PROGRESS_BOTTOM_TO_TOP
:
160 total
= self
._progress
.allocation
.height
161 self
.fraction
= 1 - max(0, min(1,
162 event
.y
/ float(total
)))
164 def on_key_press_event(self
, widget
, event
):
165 #Return or Space is pressed -> Accept current value
166 if event
.keyval
in [gtk
.keysyms
.Return
, gtk
.keysyms
.space
]:
168 #Escape is pressed -> Cancel
169 elif event
.keyval
== gtk
.keysyms
.Escape
:
171 #Number key is pressed -> set value of progressbar
172 elif event
.keyval
>= 48 and event
.keyval
<= 57:
173 self
.fraction
= float(event
.keyval
- 48)/10.0
177 parser
= OptionParser()
178 parser
.add_option('-i', '--stdin', dest
='stdin',
179 action
="store_true", help='Use first percentage found on stdin')
180 parser
.add_option('-p', '--percent', dest
='percent',
181 action
="store_true", help='Output XX% instead of 0.XX')
182 parser
.add_option('-w', '--window', dest
='window', metavar
="DIMENSIONS",
183 help='Display window with given dimensions. '
184 +'DIMENSIONS has the format 150x250 (WIDTHxHEIGHT)')
185 parser
.add_option('-t', '--title', dest
='title', metavar
="TITLE",
186 help='Display a short help at the top of the window.'
187 +'You can use Pango-Markup.')
188 parser
.add_option('-f', '--factor', dest
='factor', metavar
="FACTOR",
189 help='Sets the accuracy of the progressbar to steps of 100/FACTOR percent.')
190 parser
.add_option('-l', '--left-to-right', dest
='ltr',
191 action
="store_true", help='Display progressbar from left to right')
192 parser
.usage
= "%prog [options] [-i|FRACTION]"
194 parser
.set_defaults(title
=None, ltr
=False, factor
=20)
196 (options
, args
) = parser
.parse_args(args
)
199 re_percent
= re
.compile("(?:\D|^)(0|100|[1-9][0-9]?)\%(?:\D|$)").search
200 #re_fractal = re.compile("\D0.[0-9]+\D").search
202 # determine fraction to start with
205 # Look for percentage on stdin.
206 text
= sys
.stdin
.read()
207 match
= re_percent(text
)
209 frac
= max(0, min(1, float(match
.group(1))/100.0))
211 # Was a fraction passed as first argument? (0,xx or 0.xx)
213 frac
= max(0, min(1, float(args
[1].replace(',', '.'))))
214 except (IndexError, ValueError):
217 scr
= Scroller(fraction
=frac
, title
=options
.title
, factor
=options
.factor
)
219 scr
.orientation
= gtk
.PROGRESS_LEFT_TO_RIGHT
222 # read dimensions (WIDTHxHEIGHT)
223 dimensions
= [int(x
) for x
in options
.window
.lower().split("x")]
224 scr
.window(*dimensions
)
225 except (IndexError, ValueError):
226 # Use standard values
233 # only print value on success/accept
234 if scr
.exitstatus
== 0:
236 print float_to_percent(scr
.fraction
)
238 print "%.2f" % scr
.fraction
239 return scr
.exitstatus
241 if __name__
== '__main__':
242 sys
.exit(main(sys
.argv
))