|
@@ -47,6 +47,8 @@
|
|
# functions that it calls
|
|
# functions that it calls
|
|
|
|
|
|
import sys
|
|
import sys
|
|
|
|
+import weakref
|
|
|
|
+import threading
|
|
from PySide.QtCore import *
|
|
from PySide.QtCore import *
|
|
from PySide.QtGui import *
|
|
from PySide.QtGui import *
|
|
from PySide.QtSql import *
|
|
from PySide.QtSql import *
|
|
@@ -138,6 +140,23 @@ class TreeModel(QAbstractItemModel):
|
|
item = index.internalPointer()
|
|
item = index.internalPointer()
|
|
return self.DisplayData(item, index)
|
|
return self.DisplayData(item, index)
|
|
|
|
|
|
|
|
+# Model cache
|
|
|
|
+
|
|
|
|
+model_cache = weakref.WeakValueDictionary()
|
|
|
|
+model_cache_lock = threading.Lock()
|
|
|
|
+
|
|
|
|
+def LookupCreateModel(model_name, create_fn):
|
|
|
|
+ model_cache_lock.acquire()
|
|
|
|
+ try:
|
|
|
|
+ model = model_cache[model_name]
|
|
|
|
+ except:
|
|
|
|
+ model = None
|
|
|
|
+ if model is None:
|
|
|
|
+ model = create_fn()
|
|
|
|
+ model_cache[model_name] = model
|
|
|
|
+ model_cache_lock.release()
|
|
|
|
+ return model
|
|
|
|
+
|
|
# Context-sensitive call graph data model item base
|
|
# Context-sensitive call graph data model item base
|
|
|
|
|
|
class CallGraphLevelItemBase(object):
|
|
class CallGraphLevelItemBase(object):
|
|
@@ -289,6 +308,144 @@ class CallGraphModel(TreeModel):
|
|
alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
|
|
alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
|
|
return alignment[column]
|
|
return alignment[column]
|
|
|
|
|
|
|
|
+# Context-sensitive call graph window
|
|
|
|
+
|
|
|
|
+class CallGraphWindow(QMdiSubWindow):
|
|
|
|
+
|
|
|
|
+ def __init__(self, glb, parent=None):
|
|
|
|
+ super(CallGraphWindow, self).__init__(parent)
|
|
|
|
+
|
|
|
|
+ self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
|
|
|
|
+
|
|
|
|
+ self.view = QTreeView()
|
|
|
|
+ self.view.setModel(self.model)
|
|
|
|
+
|
|
|
|
+ for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
|
|
|
|
+ self.view.setColumnWidth(c, w)
|
|
|
|
+
|
|
|
|
+ self.setWidget(self.view)
|
|
|
|
+
|
|
|
|
+ AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
|
|
|
|
+
|
|
|
|
+# Action Definition
|
|
|
|
+
|
|
|
|
+def CreateAction(label, tip, callback, parent=None, shortcut=None):
|
|
|
|
+ action = QAction(label, parent)
|
|
|
|
+ if shortcut != None:
|
|
|
|
+ action.setShortcuts(shortcut)
|
|
|
|
+ action.setStatusTip(tip)
|
|
|
|
+ action.triggered.connect(callback)
|
|
|
|
+ return action
|
|
|
|
+
|
|
|
|
+# Typical application actions
|
|
|
|
+
|
|
|
|
+def CreateExitAction(app, parent=None):
|
|
|
|
+ return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
|
|
|
|
+
|
|
|
|
+# Typical MDI actions
|
|
|
|
+
|
|
|
|
+def CreateCloseActiveWindowAction(mdi_area):
|
|
|
|
+ return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
|
|
|
|
+
|
|
|
|
+def CreateCloseAllWindowsAction(mdi_area):
|
|
|
|
+ return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
|
|
|
|
+
|
|
|
|
+def CreateTileWindowsAction(mdi_area):
|
|
|
|
+ return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
|
|
|
|
+
|
|
|
|
+def CreateCascadeWindowsAction(mdi_area):
|
|
|
|
+ return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
|
|
|
|
+
|
|
|
|
+def CreateNextWindowAction(mdi_area):
|
|
|
|
+ return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
|
|
|
|
+
|
|
|
|
+def CreatePreviousWindowAction(mdi_area):
|
|
|
|
+ return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
|
|
|
|
+
|
|
|
|
+# Typical MDI window menu
|
|
|
|
+
|
|
|
|
+class WindowMenu():
|
|
|
|
+
|
|
|
|
+ def __init__(self, mdi_area, menu):
|
|
|
|
+ self.mdi_area = mdi_area
|
|
|
|
+ self.window_menu = menu.addMenu("&Windows")
|
|
|
|
+ self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
|
|
|
|
+ self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
|
|
|
|
+ self.tile_windows = CreateTileWindowsAction(mdi_area)
|
|
|
|
+ self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
|
|
|
|
+ self.next_window = CreateNextWindowAction(mdi_area)
|
|
|
|
+ self.previous_window = CreatePreviousWindowAction(mdi_area)
|
|
|
|
+ self.window_menu.aboutToShow.connect(self.Update)
|
|
|
|
+
|
|
|
|
+ def Update(self):
|
|
|
|
+ self.window_menu.clear()
|
|
|
|
+ sub_window_count = len(self.mdi_area.subWindowList())
|
|
|
|
+ have_sub_windows = sub_window_count != 0
|
|
|
|
+ self.close_active_window.setEnabled(have_sub_windows)
|
|
|
|
+ self.close_all_windows.setEnabled(have_sub_windows)
|
|
|
|
+ self.tile_windows.setEnabled(have_sub_windows)
|
|
|
|
+ self.cascade_windows.setEnabled(have_sub_windows)
|
|
|
|
+ self.next_window.setEnabled(have_sub_windows)
|
|
|
|
+ self.previous_window.setEnabled(have_sub_windows)
|
|
|
|
+ self.window_menu.addAction(self.close_active_window)
|
|
|
|
+ self.window_menu.addAction(self.close_all_windows)
|
|
|
|
+ self.window_menu.addSeparator()
|
|
|
|
+ self.window_menu.addAction(self.tile_windows)
|
|
|
|
+ self.window_menu.addAction(self.cascade_windows)
|
|
|
|
+ self.window_menu.addSeparator()
|
|
|
|
+ self.window_menu.addAction(self.next_window)
|
|
|
|
+ self.window_menu.addAction(self.previous_window)
|
|
|
|
+ if sub_window_count == 0:
|
|
|
|
+ return
|
|
|
|
+ self.window_menu.addSeparator()
|
|
|
|
+ nr = 1
|
|
|
|
+ for sub_window in self.mdi_area.subWindowList():
|
|
|
|
+ label = str(nr) + " " + sub_window.name
|
|
|
|
+ if nr < 10:
|
|
|
|
+ label = "&" + label
|
|
|
|
+ action = self.window_menu.addAction(label)
|
|
|
|
+ action.setCheckable(True)
|
|
|
|
+ action.setChecked(sub_window == self.mdi_area.activeSubWindow())
|
|
|
|
+ action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
|
|
|
|
+ self.window_menu.addAction(action)
|
|
|
|
+ nr += 1
|
|
|
|
+
|
|
|
|
+ def setActiveSubWindow(self, nr):
|
|
|
|
+ self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
|
|
|
|
+
|
|
|
|
+# Unique name for sub-windows
|
|
|
|
+
|
|
|
|
+def NumberedWindowName(name, nr):
|
|
|
|
+ if nr > 1:
|
|
|
|
+ name += " <" + str(nr) + ">"
|
|
|
|
+ return name
|
|
|
|
+
|
|
|
|
+def UniqueSubWindowName(mdi_area, name):
|
|
|
|
+ nr = 1
|
|
|
|
+ while True:
|
|
|
|
+ unique_name = NumberedWindowName(name, nr)
|
|
|
|
+ ok = True
|
|
|
|
+ for sub_window in mdi_area.subWindowList():
|
|
|
|
+ if sub_window.name == unique_name:
|
|
|
|
+ ok = False
|
|
|
|
+ break
|
|
|
|
+ if ok:
|
|
|
|
+ return unique_name
|
|
|
|
+ nr += 1
|
|
|
|
+
|
|
|
|
+# Add a sub-window
|
|
|
|
+
|
|
|
|
+def AddSubWindow(mdi_area, sub_window, name):
|
|
|
|
+ unique_name = UniqueSubWindowName(mdi_area, name)
|
|
|
|
+ sub_window.setMinimumSize(200, 100)
|
|
|
|
+ sub_window.resize(800, 600)
|
|
|
|
+ sub_window.setWindowTitle(unique_name)
|
|
|
|
+ sub_window.setAttribute(Qt.WA_DeleteOnClose)
|
|
|
|
+ sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
|
|
|
|
+ sub_window.name = unique_name
|
|
|
|
+ mdi_area.addSubWindow(sub_window)
|
|
|
|
+ sub_window.show()
|
|
|
|
+
|
|
# Main window
|
|
# Main window
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
class MainWindow(QMainWindow):
|
|
@@ -298,21 +455,28 @@ class MainWindow(QMainWindow):
|
|
|
|
|
|
self.glb = glb
|
|
self.glb = glb
|
|
|
|
|
|
- self.setWindowTitle("Call Graph: " + glb.dbname)
|
|
|
|
- self.move(100, 100)
|
|
|
|
- self.resize(800, 600)
|
|
|
|
|
|
+ self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
|
|
self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
|
|
self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
|
|
self.setMinimumSize(200, 100)
|
|
self.setMinimumSize(200, 100)
|
|
|
|
|
|
- self.model = CallGraphModel(glb)
|
|
|
|
|
|
+ self.mdi_area = QMdiArea()
|
|
|
|
+ self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
|
|
+ self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
|
|
|
|
- self.view = QTreeView()
|
|
|
|
- self.view.setModel(self.model)
|
|
|
|
|
|
+ self.setCentralWidget(self.mdi_area)
|
|
|
|
|
|
- for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
|
|
|
|
- self.view.setColumnWidth(c, w)
|
|
|
|
|
|
+ menu = self.menuBar()
|
|
|
|
+
|
|
|
|
+ file_menu = menu.addMenu("&File")
|
|
|
|
+ file_menu.addAction(CreateExitAction(glb.app, self))
|
|
|
|
+
|
|
|
|
+ reports_menu = menu.addMenu("&Reports")
|
|
|
|
+ reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
|
|
|
|
+
|
|
|
|
+ self.window_menu = WindowMenu(self.mdi_area, menu)
|
|
|
|
|
|
- self.setCentralWidget(self.view)
|
|
|
|
|
|
+ def NewCallGraph(self):
|
|
|
|
+ CallGraphWindow(self.glb, self)
|
|
|
|
|
|
# Global data
|
|
# Global data
|
|
|
|
|