1# This file is part of pybootchartgui. 2 3# pybootchartgui is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7 8# pybootchartgui is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12 13# You should have received a copy of the GNU General Public License 14# along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>. 15 16import gi 17gi.require_version('Gtk', '3.0') 18from gi.repository import Gtk as gtk 19from gi.repository import Gtk 20from gi.repository import Gdk 21from gi.repository import GObject as gobject 22from gi.repository import GObject 23 24from . import draw 25from .draw import RenderOptions 26 27class PyBootchartWidget(gtk.DrawingArea, gtk.Scrollable): 28 __gsignals__ = { 29 'clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, Gdk.Event)), 30 'position-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT, gobject.TYPE_INT)), 31 'set-scroll-adjustments' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gtk.Adjustment, gtk.Adjustment)) 32 } 33 34 hadjustment = GObject.property(type=Gtk.Adjustment, 35 default=Gtk.Adjustment(), 36 flags=GObject.PARAM_READWRITE) 37 hscroll_policy = GObject.property(type=Gtk.ScrollablePolicy, 38 default=Gtk.ScrollablePolicy.MINIMUM, 39 flags=GObject.PARAM_READWRITE) 40 vadjustment = GObject.property(type=Gtk.Adjustment, 41 default=Gtk.Adjustment(), 42 flags=GObject.PARAM_READWRITE) 43 vscroll_policy = GObject.property(type=Gtk.ScrollablePolicy, 44 default=Gtk.ScrollablePolicy.MINIMUM, 45 flags=GObject.PARAM_READWRITE) 46 47 def __init__(self, trace, options, xscale): 48 gtk.DrawingArea.__init__(self) 49 50 self.trace = trace 51 self.options = options 52 53 self.set_can_focus(True) 54 55 self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK) 56 self.connect("button-press-event", self.on_area_button_press) 57 self.connect("button-release-event", self.on_area_button_release) 58 self.add_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK) 59 self.connect("motion-notify-event", self.on_area_motion_notify) 60 self.connect("scroll-event", self.on_area_scroll_event) 61 self.connect('key-press-event', self.on_key_press_event) 62 63 self.connect("size-allocate", self.on_allocation_size_changed) 64 self.connect("position-changed", self.on_position_changed) 65 66 self.connect("draw", self.on_draw) 67 68 self.zoom_ratio = 1.0 69 self.xscale = xscale 70 self.x, self.y = 0.0, 0.0 71 72 self.chart_width, self.chart_height = draw.extents(self.options, self.xscale, self.trace) 73 self.our_width, self.our_height = self.chart_width, self.chart_height 74 75 self.hadj = gtk.Adjustment(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) 76 self.vadj = gtk.Adjustment(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) 77 self.vadj.connect('value-changed', self.on_adjustments_changed) 78 self.hadj.connect('value-changed', self.on_adjustments_changed) 79 80 def bound_vals(self): 81 self.x = max(0, self.x) 82 self.y = max(0, self.y) 83 self.x = min(self.chart_width - self.our_width, self.x) 84 self.y = min(self.chart_height - self.our_height, self.y) 85 86 def on_draw(self, darea, cr): 87 # set a clip region 88 #cr.rectangle( 89 # self.x, self.y, 90 # self.chart_width, self.chart_height 91 #) 92 #cr.clip() 93 cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) 94 cr.paint() 95 cr.scale(self.zoom_ratio, self.zoom_ratio) 96 cr.translate(-self.x, -self.y) 97 draw.render(cr, self.options, self.xscale, self.trace) 98 99 def position_changed(self): 100 self.emit("position-changed", self.x, self.y) 101 102 ZOOM_INCREMENT = 1.25 103 104 def zoom_image (self, zoom_ratio): 105 self.zoom_ratio = zoom_ratio 106 self._set_scroll_adjustments() 107 self.queue_draw() 108 109 def zoom_to_rect (self, rect): 110 zoom_ratio = float(rect.width)/float(self.chart_width) 111 self.zoom_image(zoom_ratio) 112 self.x = 0 113 self.position_changed() 114 115 def set_xscale(self, xscale): 116 old_mid_x = self.x + self.hadj.page_size / 2 117 self.xscale = xscale 118 self.chart_width, self.chart_height = draw.extents(self.options, self.xscale, self.trace) 119 new_x = old_mid_x 120 self.zoom_image (self.zoom_ratio) 121 122 def on_expand(self, action): 123 self.set_xscale (int(self.xscale * 1.5 + 0.5)) 124 125 def on_contract(self, action): 126 self.set_xscale (max(int(self.xscale / 1.5), 1)) 127 128 def on_zoom_in(self, action): 129 self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT) 130 131 def on_zoom_out(self, action): 132 self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT) 133 134 def on_zoom_fit(self, action): 135 self.zoom_to_rect(self.get_allocation()) 136 137 def on_zoom_100(self, action): 138 self.zoom_image(1.0) 139 self.set_xscale(1.0) 140 141 def show_toggled(self, button): 142 self.options.app_options.show_all = button.get_property ('active') 143 self.chart_width, self.chart_height = draw.extents(self.options, self.xscale, self.trace) 144 self._set_scroll_adjustments() 145 self.queue_draw() 146 147 POS_INCREMENT = 100 148 149 def on_key_press_event(self, widget, event): 150 if event.keyval == Gdk.keyval_from_name("Left"): 151 self.x -= self.POS_INCREMENT/self.zoom_ratio 152 elif event.keyval == Gdk.keyval_from_name("Right"): 153 self.x += self.POS_INCREMENT/self.zoom_ratio 154 elif event.keyval == Gdk.keyval_from_name("Up"): 155 self.y -= self.POS_INCREMENT/self.zoom_ratio 156 elif event.keyval == Gdk.keyval_from_name("Down"): 157 self.y += self.POS_INCREMENT/self.zoom_ratio 158 else: 159 return False 160 self.bound_vals() 161 self.queue_draw() 162 self.position_changed() 163 return True 164 165 def on_area_button_press(self, area, event): 166 if event.button == 2 or event.button == 1: 167 window = self.get_window() 168 window.set_cursor(Gdk.Cursor(Gdk.CursorType.FLEUR)) 169 self.prevmousex = event.x 170 self.prevmousey = event.y 171 if event.type not in (Gdk.EventType.BUTTON_PRESS, Gdk.EventType.BUTTON_RELEASE): 172 return False 173 return False 174 175 def on_area_button_release(self, area, event): 176 if event.button == 2 or event.button == 1: 177 window = self.get_window() 178 window.set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW)) 179 self.prevmousex = None 180 self.prevmousey = None 181 return True 182 return False 183 184 def on_area_scroll_event(self, area, event): 185 if event.state & Gdk.CONTROL_MASK: 186 if event.direction == Gdk.SCROLL_UP: 187 self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT) 188 return True 189 if event.direction == Gdk.SCROLL_DOWN: 190 self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT) 191 return True 192 return False 193 194 def on_area_motion_notify(self, area, event): 195 state = event.state 196 if state & Gdk.ModifierType.BUTTON2_MASK or state & Gdk.ModifierType.BUTTON1_MASK: 197 x, y = int(event.x), int(event.y) 198 # pan the image 199 self.x += (self.prevmousex - x)/self.zoom_ratio 200 self.y += (self.prevmousey - y)/self.zoom_ratio 201 self.bound_vals() 202 self.queue_draw() 203 self.prevmousex = x 204 self.prevmousey = y 205 self.position_changed() 206 return True 207 208 def on_allocation_size_changed(self, widget, allocation): 209 self.hadj.page_size = allocation.width 210 self.hadj.page_increment = allocation.width * 0.9 211 self.vadj.page_size = allocation.height 212 self.vadj.page_increment = allocation.height * 0.9 213 self.our_width = allocation.width 214 if self.chart_width < self.our_width: 215 self.our_width = self.chart_width 216 self.our_height = allocation.height 217 if self.chart_height < self.our_height: 218 self.our_height = self.chart_height 219 self._set_scroll_adjustments() 220 221 def _set_adj_upper(self, adj, upper): 222 223 if adj.get_upper() != upper: 224 adj.set_upper(upper) 225 226 def _set_scroll_adjustments(self): 227 self._set_adj_upper (self.hadj, self.zoom_ratio * (self.chart_width - self.our_width)) 228 self._set_adj_upper (self.vadj, self.zoom_ratio * (self.chart_height - self.our_height)) 229 230 def on_adjustments_changed(self, adj): 231 self.x = self.hadj.get_value() / self.zoom_ratio 232 self.y = self.vadj.get_value() / self.zoom_ratio 233 self.queue_draw() 234 235 def on_position_changed(self, widget, x, y): 236 self.hadj.set_value(x * self.zoom_ratio) 237 #self.hadj.value_changed() 238 self.vadj.set_value(y * self.zoom_ratio) 239 240class PyBootchartShell(gtk.VBox): 241 ui = ''' 242 <ui> 243 <toolbar name="ToolBar"> 244 <toolitem action="Expand"/> 245 <toolitem action="Contract"/> 246 <separator/> 247 <toolitem action="ZoomIn"/> 248 <toolitem action="ZoomOut"/> 249 <toolitem action="ZoomFit"/> 250 <toolitem action="Zoom100"/> 251 </toolbar> 252 </ui> 253 ''' 254 def __init__(self, window, trace, options, xscale): 255 gtk.VBox.__init__(self) 256 257 self.widget2 = PyBootchartWidget(trace, options, xscale) 258 259 # Create a UIManager instance 260 uimanager = self.uimanager = gtk.UIManager() 261 262 # Add the accelerator group to the toplevel window 263 accelgroup = uimanager.get_accel_group() 264 window.add_accel_group(accelgroup) 265 266 # Create an ActionGroup 267 actiongroup = gtk.ActionGroup('Actions') 268 self.actiongroup = actiongroup 269 270 # Create actions 271 actiongroup.add_actions(( 272 ('Expand', gtk.STOCK_ADD, None, None, None, self.widget2.on_expand), 273 ('Contract', gtk.STOCK_REMOVE, None, None, None, self.widget2.on_contract), 274 ('ZoomIn', gtk.STOCK_ZOOM_IN, None, None, None, self.widget2.on_zoom_in), 275 ('ZoomOut', gtk.STOCK_ZOOM_OUT, None, None, None, self.widget2.on_zoom_out), 276 ('ZoomFit', gtk.STOCK_ZOOM_FIT, 'Fit Width', None, None, self.widget2.on_zoom_fit), 277 ('Zoom100', gtk.STOCK_ZOOM_100, None, None, None, self.widget2.on_zoom_100), 278 )) 279 280 # Add the actiongroup to the uimanager 281 uimanager.insert_action_group(actiongroup, 0) 282 283 # Add a UI description 284 uimanager.add_ui_from_string(self.ui) 285 286 # Scrolled window 287 scrolled = gtk.ScrolledWindow(self.widget2.hadj, self.widget2.vadj) 288 scrolled.add(self.widget2) 289 290 #scrolled.set_hadjustment() 291 #scrolled.set_vadjustment(self.widget2.vadj) 292 scrolled.set_policy(gtk.PolicyType.ALWAYS, gtk.PolicyType.ALWAYS) 293 294 # toolbar / h-box 295 hbox = gtk.HBox(False, 8) 296 297 # Create a Toolbar 298 toolbar = uimanager.get_widget('/ToolBar') 299 hbox.pack_start(toolbar, True, True, 0) 300 301 if not options.kernel_only: 302 # Misc. options 303 button = gtk.CheckButton("Show more") 304 button.connect ('toggled', self.widget2.show_toggled) 305 button.set_active(options.app_options.show_all) 306 hbox.pack_start (button, False, True, 0) 307 308 self.pack_start(hbox, False, True, 0) 309 self.pack_start(scrolled, True, True, 0) 310 self.show_all() 311 312 def grab_focus(self, window): 313 window.set_focus(self.widget2) 314 315 316class PyBootchartWindow(gtk.Window): 317 318 def __init__(self, trace, app_options): 319 gtk.Window.__init__(self) 320 321 window = self 322 window.set_title("Bootchart %s" % trace.filename) 323 window.set_default_size(750, 550) 324 325 tab_page = gtk.Notebook() 326 tab_page.show() 327 window.add(tab_page) 328 329 full_opts = RenderOptions(app_options) 330 full_tree = PyBootchartShell(window, trace, full_opts, 1.0) 331 tab_page.append_page (full_tree, gtk.Label("Full tree")) 332 333 if trace.kernel is not None and len (trace.kernel) > 2: 334 kernel_opts = RenderOptions(app_options) 335 kernel_opts.cumulative = False 336 kernel_opts.charts = False 337 kernel_opts.kernel_only = True 338 kernel_tree = PyBootchartShell(window, trace, kernel_opts, 5.0) 339 tab_page.append_page (kernel_tree, gtk.Label("Kernel boot")) 340 341 full_tree.grab_focus(self) 342 self.show() 343 344 345def show(trace, options): 346 win = PyBootchartWindow(trace, options) 347 win.connect('destroy', gtk.main_quit) 348 gtk.main() 349