History

perf scripts python: exported-sql-viewer.py: Add support for multiple sub-windows

Use Qt MDI (multiple document interface) to support multiple sub-windows. Put the data model in a cache so that each sub-window can share the same data. This allows mutiple views of the call-graph at the same time and paves the way to add more reports. Committer testing: Starts with a "File Reports Windows" main menu, from the "Reports" I can get what was available up to now, the "Context-Sensitivi Call Graph" option.

Adrian Hunter committed 8 years ago

Commit 1beb5c71 parent 031c2a01 file · +173 −9Browse files

Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all

Unified · Split

tools/perf/scripts/python/exported-sql-viewer.py+173−9
@@ -47,6 +47,8 @@ · 46 unmodified lines
4747 # functions that it calls
4848
4949 import sys
50+import weakref
51+import threading
5052 from PySide.QtCore import *
5153 from PySide.QtGui import *
5254 from PySide.QtSql import *
@@ -138,6 +140,23 @@ class TreeModel(QAbstractItemModel): · 85 unmodified lines
138140 item = index.internalPointer()
139141 return self.DisplayData(item, index)
140142
143+# Model cache
144+
145+model_cache = weakref.WeakValueDictionary()
146+model_cache_lock = threading.Lock()
147+
148+def LookupCreateModel(model_name, create_fn):
149+ model_cache_lock.acquire()
150+ try:
151+ model = model_cache[model_name]
152+ except:
153+ model = None
154+ if model is None:
155+ model = create_fn()
156+ model_cache[model_name] = model
157+ model_cache_lock.release()
158+ return model
159+
141160 # Context-sensitive call graph data model item base
142161
143162 class CallGraphLevelItemBase(object):
@@ -289,6 +308,144 @@ class CallGraphModel(TreeModel): · 145 unmodified lines
289308 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
290309 return alignment[column]
291310
311+# Context-sensitive call graph window
312+
313+class CallGraphWindow(QMdiSubWindow):
314+
315+ def __init__(self, glb, parent=None):
316+ super(CallGraphWindow, self).__init__(parent)
317+
318+ self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
319+
320+ self.view = QTreeView()
321+ self.view.setModel(self.model)
322+
323+ for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
324+ self.view.setColumnWidth(c, w)
325+
326+ self.setWidget(self.view)
327+
328+ AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
329+
330+# Action Definition
331+
332+def CreateAction(label, tip, callback, parent=None, shortcut=None):
333+ action = QAction(label, parent)
334+ if shortcut != None:
335+ action.setShortcuts(shortcut)
336+ action.setStatusTip(tip)
337+ action.triggered.connect(callback)
338+ return action
339+
340+# Typical application actions
341+
342+def CreateExitAction(app, parent=None):
343+ return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
344+
345+# Typical MDI actions
346+
347+def CreateCloseActiveWindowAction(mdi_area):
348+ return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
349+
350+def CreateCloseAllWindowsAction(mdi_area):
351+ return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
352+
353+def CreateTileWindowsAction(mdi_area):
354+ return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
355+
356+def CreateCascadeWindowsAction(mdi_area):
357+ return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
358+
359+def CreateNextWindowAction(mdi_area):
360+ return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
361+
362+def CreatePreviousWindowAction(mdi_area):
363+ return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
364+
365+# Typical MDI window menu
366+
367+class WindowMenu():
368+
369+ def __init__(self, mdi_area, menu):
370+ self.mdi_area = mdi_area
371+ self.window_menu = menu.addMenu("&Windows")
372+ self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
373+ self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
374+ self.tile_windows = CreateTileWindowsAction(mdi_area)
375+ self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
376+ self.next_window = CreateNextWindowAction(mdi_area)
377+ self.previous_window = CreatePreviousWindowAction(mdi_area)
378+ self.window_menu.aboutToShow.connect(self.Update)
379+
380+ def Update(self):
381+ self.window_menu.clear()
382+ sub_window_count = len(self.mdi_area.subWindowList())
383+ have_sub_windows = sub_window_count != 0
384+ self.close_active_window.setEnabled(have_sub_windows)
385+ self.close_all_windows.setEnabled(have_sub_windows)
386+ self.tile_windows.setEnabled(have_sub_windows)
387+ self.cascade_windows.setEnabled(have_sub_windows)
388+ self.next_window.setEnabled(have_sub_windows)
389+ self.previous_window.setEnabled(have_sub_windows)
390+ self.window_menu.addAction(self.close_active_window)
391+ self.window_menu.addAction(self.close_all_windows)
392+ self.window_menu.addSeparator()
393+ self.window_menu.addAction(self.tile_windows)
394+ self.window_menu.addAction(self.cascade_windows)
395+ self.window_menu.addSeparator()
396+ self.window_menu.addAction(self.next_window)
397+ self.window_menu.addAction(self.previous_window)
398+ if sub_window_count == 0:
399+ return
400+ self.window_menu.addSeparator()
401+ nr = 1
402+ for sub_window in self.mdi_area.subWindowList():
403+ label = str(nr) + " " + sub_window.name
404+ if nr < 10:
405+ label = "&" + label
406+ action = self.window_menu.addAction(label)
407+ action.setCheckable(True)
408+ action.setChecked(sub_window == self.mdi_area.activeSubWindow())
409+ action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
410+ self.window_menu.addAction(action)
411+ nr += 1
412+
413+ def setActiveSubWindow(self, nr):
414+ self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
415+
416+# Unique name for sub-windows
417+
418+def NumberedWindowName(name, nr):
419+ if nr > 1:
420+ name += " <" + str(nr) + ">"
421+ return name
422+
423+def UniqueSubWindowName(mdi_area, name):
424+ nr = 1
425+ while True:
426+ unique_name = NumberedWindowName(name, nr)
427+ ok = True
428+ for sub_window in mdi_area.subWindowList():
429+ if sub_window.name == unique_name:
430+ ok = False
431+ break
432+ if ok:
433+ return unique_name
434+ nr += 1
435+
436+# Add a sub-window
437+
438+def AddSubWindow(mdi_area, sub_window, name):
439+ unique_name = UniqueSubWindowName(mdi_area, name)
440+ sub_window.setMinimumSize(200, 100)
441+ sub_window.resize(800, 600)
442+ sub_window.setWindowTitle(unique_name)
443+ sub_window.setAttribute(Qt.WA_DeleteOnClose)
444+ sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
445+ sub_window.name = unique_name
446+ mdi_area.addSubWindow(sub_window)
447+ sub_window.show()
448+
292449 # Main window
293450
294451 class MainWindow(QMainWindow):
@@ -298,21 +455,28 @@ class MainWindow(QMainWindow): · 3 unmodified lines
298455
299456 self.glb = glb
300457
301− self.setWindowTitle("Call Graph: " + glb.dbname)
302− self.move(100, 100)
303− self.resize(800, 600)
458+ self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
304459 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
305460 self.setMinimumSize(200, 100)
306461
307− self.model = CallGraphModel(glb)
462+ self.mdi_area = QMdiArea()
463+ self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
464+ self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
308465
309− self.view = QTreeView()
310− self.view.setModel(self.model)
466+ self.setCentralWidget(self.mdi_area)
311467
312− for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
313− self.view.setColumnWidth(c, w)
468+ menu = self.menuBar()
469+
470+ file_menu = menu.addMenu("&File")
471+ file_menu.addAction(CreateExitAction(glb.app, self))
472+
473+ reports_menu = menu.addMenu("&Reports")
474+ reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
475+
476+ self.window_menu = WindowMenu(self.mdi_area, menu)
314477
315− self.setCentralWidget(self.view)
478+ def NewCallGraph(self):
479+ CallGraphWindow(self.glb, self)
316480
317481 # Global data
318482