1031c2a00SAdrian Hunter#!/usr/bin/python2 2031c2a00SAdrian Hunter# SPDX-License-Identifier: GPL-2.0 3031c2a00SAdrian Hunter# exported-sql-viewer.py: view data from sql database 4031c2a00SAdrian Hunter# Copyright (c) 2014-2018, Intel Corporation. 5031c2a00SAdrian Hunter 6031c2a00SAdrian Hunter# To use this script you will need to have exported data using either the 7031c2a00SAdrian Hunter# export-to-sqlite.py or the export-to-postgresql.py script. Refer to those 8031c2a00SAdrian Hunter# scripts for details. 9031c2a00SAdrian Hunter# 10031c2a00SAdrian Hunter# Following on from the example in the export scripts, a 11031c2a00SAdrian Hunter# call-graph can be displayed for the pt_example database like this: 12031c2a00SAdrian Hunter# 13031c2a00SAdrian Hunter# python tools/perf/scripts/python/exported-sql-viewer.py pt_example 14031c2a00SAdrian Hunter# 15031c2a00SAdrian Hunter# Note that for PostgreSQL, this script supports connecting to remote databases 16031c2a00SAdrian Hunter# by setting hostname, port, username, password, and dbname e.g. 17031c2a00SAdrian Hunter# 18031c2a00SAdrian Hunter# python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example" 19031c2a00SAdrian Hunter# 20031c2a00SAdrian Hunter# The result is a GUI window with a tree representing a context-sensitive 21031c2a00SAdrian Hunter# call-graph. Expanding a couple of levels of the tree and adjusting column 22031c2a00SAdrian Hunter# widths to suit will display something like: 23031c2a00SAdrian Hunter# 24031c2a00SAdrian Hunter# Call Graph: pt_example 25031c2a00SAdrian Hunter# Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%) 26031c2a00SAdrian Hunter# v- ls 27031c2a00SAdrian Hunter# v- 2638:2638 28031c2a00SAdrian Hunter# v- _start ld-2.19.so 1 10074071 100.0 211135 100.0 29031c2a00SAdrian Hunter# |- unknown unknown 1 13198 0.1 1 0.0 30031c2a00SAdrian Hunter# >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3 31031c2a00SAdrian Hunter# >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3 32031c2a00SAdrian Hunter# v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4 33031c2a00SAdrian Hunter# >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1 34031c2a00SAdrian Hunter# >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0 35031c2a00SAdrian Hunter# >- __libc_csu_init ls 1 10354 0.1 10 0.0 36031c2a00SAdrian Hunter# |- _setjmp libc-2.19.so 1 0 0.0 4 0.0 37031c2a00SAdrian Hunter# v- main ls 1 8182043 99.6 180254 99.9 38031c2a00SAdrian Hunter# 39031c2a00SAdrian Hunter# Points to note: 40031c2a00SAdrian Hunter# The top level is a command name (comm) 41031c2a00SAdrian Hunter# The next level is a thread (pid:tid) 42031c2a00SAdrian Hunter# Subsequent levels are functions 43031c2a00SAdrian Hunter# 'Count' is the number of calls 44031c2a00SAdrian Hunter# 'Time' is the elapsed time until the function returns 45031c2a00SAdrian Hunter# Percentages are relative to the level above 46031c2a00SAdrian Hunter# 'Branch Count' is the total number of branches for that function and all 47031c2a00SAdrian Hunter# functions that it calls 48031c2a00SAdrian Hunter 49031c2a00SAdrian Hunterimport sys 501beb5c7bSAdrian Hunterimport weakref 511beb5c7bSAdrian Hunterimport threading 52ebd70c7dSAdrian Hunterimport string 53*8392b74bSAdrian Hunterimport cPickle 54*8392b74bSAdrian Hunterimport re 55*8392b74bSAdrian Hunterimport os 56031c2a00SAdrian Hunterfrom PySide.QtCore import * 57031c2a00SAdrian Hunterfrom PySide.QtGui import * 58031c2a00SAdrian Hunterfrom PySide.QtSql import * 59031c2a00SAdrian Hunterfrom decimal import * 60*8392b74bSAdrian Hunterfrom ctypes import * 61*8392b74bSAdrian Hunterfrom multiprocessing import Process, Array, Value, Event 62031c2a00SAdrian Hunter 63031c2a00SAdrian Hunter# Data formatting helpers 64031c2a00SAdrian Hunter 65031c2a00SAdrian Hunterdef dsoname(name): 66031c2a00SAdrian Hunter if name == "[kernel.kallsyms]": 67031c2a00SAdrian Hunter return "[kernel]" 68031c2a00SAdrian Hunter return name 69031c2a00SAdrian Hunter 70031c2a00SAdrian Hunter# Percent to one decimal place 71031c2a00SAdrian Hunter 72031c2a00SAdrian Hunterdef PercentToOneDP(n, d): 73031c2a00SAdrian Hunter if not d: 74031c2a00SAdrian Hunter return "0.0" 75031c2a00SAdrian Hunter x = (n * Decimal(100)) / d 76031c2a00SAdrian Hunter return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP)) 77031c2a00SAdrian Hunter 78031c2a00SAdrian Hunter# Helper for queries that must not fail 79031c2a00SAdrian Hunter 80031c2a00SAdrian Hunterdef QueryExec(query, stmt): 81031c2a00SAdrian Hunter ret = query.exec_(stmt) 82031c2a00SAdrian Hunter if not ret: 83031c2a00SAdrian Hunter raise Exception("Query failed: " + query.lastError().text()) 84031c2a00SAdrian Hunter 85ebd70c7dSAdrian Hunter# Background thread 86ebd70c7dSAdrian Hunter 87ebd70c7dSAdrian Hunterclass Thread(QThread): 88ebd70c7dSAdrian Hunter 89ebd70c7dSAdrian Hunter done = Signal(object) 90ebd70c7dSAdrian Hunter 91ebd70c7dSAdrian Hunter def __init__(self, task, param=None, parent=None): 92ebd70c7dSAdrian Hunter super(Thread, self).__init__(parent) 93ebd70c7dSAdrian Hunter self.task = task 94ebd70c7dSAdrian Hunter self.param = param 95ebd70c7dSAdrian Hunter 96ebd70c7dSAdrian Hunter def run(self): 97ebd70c7dSAdrian Hunter while True: 98ebd70c7dSAdrian Hunter if self.param is None: 99ebd70c7dSAdrian Hunter done, result = self.task() 100ebd70c7dSAdrian Hunter else: 101ebd70c7dSAdrian Hunter done, result = self.task(self.param) 102ebd70c7dSAdrian Hunter self.done.emit(result) 103ebd70c7dSAdrian Hunter if done: 104ebd70c7dSAdrian Hunter break 105ebd70c7dSAdrian Hunter 106031c2a00SAdrian Hunter# Tree data model 107031c2a00SAdrian Hunter 108031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel): 109031c2a00SAdrian Hunter 110031c2a00SAdrian Hunter def __init__(self, root, parent=None): 111031c2a00SAdrian Hunter super(TreeModel, self).__init__(parent) 112031c2a00SAdrian Hunter self.root = root 113031c2a00SAdrian Hunter self.last_row_read = 0 114031c2a00SAdrian Hunter 115031c2a00SAdrian Hunter def Item(self, parent): 116031c2a00SAdrian Hunter if parent.isValid(): 117031c2a00SAdrian Hunter return parent.internalPointer() 118031c2a00SAdrian Hunter else: 119031c2a00SAdrian Hunter return self.root 120031c2a00SAdrian Hunter 121031c2a00SAdrian Hunter def rowCount(self, parent): 122031c2a00SAdrian Hunter result = self.Item(parent).childCount() 123031c2a00SAdrian Hunter if result < 0: 124031c2a00SAdrian Hunter result = 0 125031c2a00SAdrian Hunter self.dataChanged.emit(parent, parent) 126031c2a00SAdrian Hunter return result 127031c2a00SAdrian Hunter 128031c2a00SAdrian Hunter def hasChildren(self, parent): 129031c2a00SAdrian Hunter return self.Item(parent).hasChildren() 130031c2a00SAdrian Hunter 131031c2a00SAdrian Hunter def headerData(self, section, orientation, role): 132031c2a00SAdrian Hunter if role == Qt.TextAlignmentRole: 133031c2a00SAdrian Hunter return self.columnAlignment(section) 134031c2a00SAdrian Hunter if role != Qt.DisplayRole: 135031c2a00SAdrian Hunter return None 136031c2a00SAdrian Hunter if orientation != Qt.Horizontal: 137031c2a00SAdrian Hunter return None 138031c2a00SAdrian Hunter return self.columnHeader(section) 139031c2a00SAdrian Hunter 140031c2a00SAdrian Hunter def parent(self, child): 141031c2a00SAdrian Hunter child_item = child.internalPointer() 142031c2a00SAdrian Hunter if child_item is self.root: 143031c2a00SAdrian Hunter return QModelIndex() 144031c2a00SAdrian Hunter parent_item = child_item.getParentItem() 145031c2a00SAdrian Hunter return self.createIndex(parent_item.getRow(), 0, parent_item) 146031c2a00SAdrian Hunter 147031c2a00SAdrian Hunter def index(self, row, column, parent): 148031c2a00SAdrian Hunter child_item = self.Item(parent).getChildItem(row) 149031c2a00SAdrian Hunter return self.createIndex(row, column, child_item) 150031c2a00SAdrian Hunter 151031c2a00SAdrian Hunter def DisplayData(self, item, index): 152031c2a00SAdrian Hunter return item.getData(index.column()) 153031c2a00SAdrian Hunter 154*8392b74bSAdrian Hunter def FetchIfNeeded(self, row): 155*8392b74bSAdrian Hunter if row > self.last_row_read: 156*8392b74bSAdrian Hunter self.last_row_read = row 157*8392b74bSAdrian Hunter if row + 10 >= self.root.child_count: 158*8392b74bSAdrian Hunter self.fetcher.Fetch(glb_chunk_sz) 159*8392b74bSAdrian Hunter 160*8392b74bSAdrian Hunter def columnAlignment(self, column): 161*8392b74bSAdrian Hunter return Qt.AlignLeft 162*8392b74bSAdrian Hunter 163*8392b74bSAdrian Hunter def columnFont(self, column): 164*8392b74bSAdrian Hunter return None 165*8392b74bSAdrian Hunter 166*8392b74bSAdrian Hunter def data(self, index, role): 167*8392b74bSAdrian Hunter if role == Qt.TextAlignmentRole: 168*8392b74bSAdrian Hunter return self.columnAlignment(index.column()) 169*8392b74bSAdrian Hunter if role == Qt.FontRole: 170*8392b74bSAdrian Hunter return self.columnFont(index.column()) 171*8392b74bSAdrian Hunter if role != Qt.DisplayRole: 172*8392b74bSAdrian Hunter return None 173*8392b74bSAdrian Hunter item = index.internalPointer() 174*8392b74bSAdrian Hunter return self.DisplayData(item, index) 175*8392b74bSAdrian Hunter 176*8392b74bSAdrian Hunter# Table data model 177*8392b74bSAdrian Hunter 178*8392b74bSAdrian Hunterclass TableModel(QAbstractTableModel): 179*8392b74bSAdrian Hunter 180*8392b74bSAdrian Hunter def __init__(self, parent=None): 181*8392b74bSAdrian Hunter super(TableModel, self).__init__(parent) 182*8392b74bSAdrian Hunter self.child_count = 0 183*8392b74bSAdrian Hunter self.child_items = [] 184*8392b74bSAdrian Hunter self.last_row_read = 0 185*8392b74bSAdrian Hunter 186*8392b74bSAdrian Hunter def Item(self, parent): 187*8392b74bSAdrian Hunter if parent.isValid(): 188*8392b74bSAdrian Hunter return parent.internalPointer() 189*8392b74bSAdrian Hunter else: 190*8392b74bSAdrian Hunter return self 191*8392b74bSAdrian Hunter 192*8392b74bSAdrian Hunter def rowCount(self, parent): 193*8392b74bSAdrian Hunter return self.child_count 194*8392b74bSAdrian Hunter 195*8392b74bSAdrian Hunter def headerData(self, section, orientation, role): 196*8392b74bSAdrian Hunter if role == Qt.TextAlignmentRole: 197*8392b74bSAdrian Hunter return self.columnAlignment(section) 198*8392b74bSAdrian Hunter if role != Qt.DisplayRole: 199*8392b74bSAdrian Hunter return None 200*8392b74bSAdrian Hunter if orientation != Qt.Horizontal: 201*8392b74bSAdrian Hunter return None 202*8392b74bSAdrian Hunter return self.columnHeader(section) 203*8392b74bSAdrian Hunter 204*8392b74bSAdrian Hunter def index(self, row, column, parent): 205*8392b74bSAdrian Hunter return self.createIndex(row, column, self.child_items[row]) 206*8392b74bSAdrian Hunter 207*8392b74bSAdrian Hunter def DisplayData(self, item, index): 208*8392b74bSAdrian Hunter return item.getData(index.column()) 209*8392b74bSAdrian Hunter 210*8392b74bSAdrian Hunter def FetchIfNeeded(self, row): 211*8392b74bSAdrian Hunter if row > self.last_row_read: 212*8392b74bSAdrian Hunter self.last_row_read = row 213*8392b74bSAdrian Hunter if row + 10 >= self.child_count: 214*8392b74bSAdrian Hunter self.fetcher.Fetch(glb_chunk_sz) 215*8392b74bSAdrian Hunter 216031c2a00SAdrian Hunter def columnAlignment(self, column): 217031c2a00SAdrian Hunter return Qt.AlignLeft 218031c2a00SAdrian Hunter 219031c2a00SAdrian Hunter def columnFont(self, column): 220031c2a00SAdrian Hunter return None 221031c2a00SAdrian Hunter 222031c2a00SAdrian Hunter def data(self, index, role): 223031c2a00SAdrian Hunter if role == Qt.TextAlignmentRole: 224031c2a00SAdrian Hunter return self.columnAlignment(index.column()) 225031c2a00SAdrian Hunter if role == Qt.FontRole: 226031c2a00SAdrian Hunter return self.columnFont(index.column()) 227031c2a00SAdrian Hunter if role != Qt.DisplayRole: 228031c2a00SAdrian Hunter return None 229031c2a00SAdrian Hunter item = index.internalPointer() 230031c2a00SAdrian Hunter return self.DisplayData(item, index) 231031c2a00SAdrian Hunter 2321beb5c7bSAdrian Hunter# Model cache 2331beb5c7bSAdrian Hunter 2341beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary() 2351beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock() 2361beb5c7bSAdrian Hunter 2371beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn): 2381beb5c7bSAdrian Hunter model_cache_lock.acquire() 2391beb5c7bSAdrian Hunter try: 2401beb5c7bSAdrian Hunter model = model_cache[model_name] 2411beb5c7bSAdrian Hunter except: 2421beb5c7bSAdrian Hunter model = None 2431beb5c7bSAdrian Hunter if model is None: 2441beb5c7bSAdrian Hunter model = create_fn() 2451beb5c7bSAdrian Hunter model_cache[model_name] = model 2461beb5c7bSAdrian Hunter model_cache_lock.release() 2471beb5c7bSAdrian Hunter return model 2481beb5c7bSAdrian Hunter 249ebd70c7dSAdrian Hunter# Find bar 250ebd70c7dSAdrian Hunter 251ebd70c7dSAdrian Hunterclass FindBar(): 252ebd70c7dSAdrian Hunter 253ebd70c7dSAdrian Hunter def __init__(self, parent, finder, is_reg_expr=False): 254ebd70c7dSAdrian Hunter self.finder = finder 255ebd70c7dSAdrian Hunter self.context = [] 256ebd70c7dSAdrian Hunter self.last_value = None 257ebd70c7dSAdrian Hunter self.last_pattern = None 258ebd70c7dSAdrian Hunter 259ebd70c7dSAdrian Hunter label = QLabel("Find:") 260ebd70c7dSAdrian Hunter label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 261ebd70c7dSAdrian Hunter 262ebd70c7dSAdrian Hunter self.textbox = QComboBox() 263ebd70c7dSAdrian Hunter self.textbox.setEditable(True) 264ebd70c7dSAdrian Hunter self.textbox.currentIndexChanged.connect(self.ValueChanged) 265ebd70c7dSAdrian Hunter 266ebd70c7dSAdrian Hunter self.progress = QProgressBar() 267ebd70c7dSAdrian Hunter self.progress.setRange(0, 0) 268ebd70c7dSAdrian Hunter self.progress.hide() 269ebd70c7dSAdrian Hunter 270ebd70c7dSAdrian Hunter if is_reg_expr: 271ebd70c7dSAdrian Hunter self.pattern = QCheckBox("Regular Expression") 272ebd70c7dSAdrian Hunter else: 273ebd70c7dSAdrian Hunter self.pattern = QCheckBox("Pattern") 274ebd70c7dSAdrian Hunter self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 275ebd70c7dSAdrian Hunter 276ebd70c7dSAdrian Hunter self.next_button = QToolButton() 277ebd70c7dSAdrian Hunter self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown)) 278ebd70c7dSAdrian Hunter self.next_button.released.connect(lambda: self.NextPrev(1)) 279ebd70c7dSAdrian Hunter 280ebd70c7dSAdrian Hunter self.prev_button = QToolButton() 281ebd70c7dSAdrian Hunter self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp)) 282ebd70c7dSAdrian Hunter self.prev_button.released.connect(lambda: self.NextPrev(-1)) 283ebd70c7dSAdrian Hunter 284ebd70c7dSAdrian Hunter self.close_button = QToolButton() 285ebd70c7dSAdrian Hunter self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton)) 286ebd70c7dSAdrian Hunter self.close_button.released.connect(self.Deactivate) 287ebd70c7dSAdrian Hunter 288ebd70c7dSAdrian Hunter self.hbox = QHBoxLayout() 289ebd70c7dSAdrian Hunter self.hbox.setContentsMargins(0, 0, 0, 0) 290ebd70c7dSAdrian Hunter 291ebd70c7dSAdrian Hunter self.hbox.addWidget(label) 292ebd70c7dSAdrian Hunter self.hbox.addWidget(self.textbox) 293ebd70c7dSAdrian Hunter self.hbox.addWidget(self.progress) 294ebd70c7dSAdrian Hunter self.hbox.addWidget(self.pattern) 295ebd70c7dSAdrian Hunter self.hbox.addWidget(self.next_button) 296ebd70c7dSAdrian Hunter self.hbox.addWidget(self.prev_button) 297ebd70c7dSAdrian Hunter self.hbox.addWidget(self.close_button) 298ebd70c7dSAdrian Hunter 299ebd70c7dSAdrian Hunter self.bar = QWidget() 300ebd70c7dSAdrian Hunter self.bar.setLayout(self.hbox); 301ebd70c7dSAdrian Hunter self.bar.hide() 302ebd70c7dSAdrian Hunter 303ebd70c7dSAdrian Hunter def Widget(self): 304ebd70c7dSAdrian Hunter return self.bar 305ebd70c7dSAdrian Hunter 306ebd70c7dSAdrian Hunter def Activate(self): 307ebd70c7dSAdrian Hunter self.bar.show() 308ebd70c7dSAdrian Hunter self.textbox.setFocus() 309ebd70c7dSAdrian Hunter 310ebd70c7dSAdrian Hunter def Deactivate(self): 311ebd70c7dSAdrian Hunter self.bar.hide() 312ebd70c7dSAdrian Hunter 313ebd70c7dSAdrian Hunter def Busy(self): 314ebd70c7dSAdrian Hunter self.textbox.setEnabled(False) 315ebd70c7dSAdrian Hunter self.pattern.hide() 316ebd70c7dSAdrian Hunter self.next_button.hide() 317ebd70c7dSAdrian Hunter self.prev_button.hide() 318ebd70c7dSAdrian Hunter self.progress.show() 319ebd70c7dSAdrian Hunter 320ebd70c7dSAdrian Hunter def Idle(self): 321ebd70c7dSAdrian Hunter self.textbox.setEnabled(True) 322ebd70c7dSAdrian Hunter self.progress.hide() 323ebd70c7dSAdrian Hunter self.pattern.show() 324ebd70c7dSAdrian Hunter self.next_button.show() 325ebd70c7dSAdrian Hunter self.prev_button.show() 326ebd70c7dSAdrian Hunter 327ebd70c7dSAdrian Hunter def Find(self, direction): 328ebd70c7dSAdrian Hunter value = self.textbox.currentText() 329ebd70c7dSAdrian Hunter pattern = self.pattern.isChecked() 330ebd70c7dSAdrian Hunter self.last_value = value 331ebd70c7dSAdrian Hunter self.last_pattern = pattern 332ebd70c7dSAdrian Hunter self.finder.Find(value, direction, pattern, self.context) 333ebd70c7dSAdrian Hunter 334ebd70c7dSAdrian Hunter def ValueChanged(self): 335ebd70c7dSAdrian Hunter value = self.textbox.currentText() 336ebd70c7dSAdrian Hunter pattern = self.pattern.isChecked() 337ebd70c7dSAdrian Hunter index = self.textbox.currentIndex() 338ebd70c7dSAdrian Hunter data = self.textbox.itemData(index) 339ebd70c7dSAdrian Hunter # Store the pattern in the combo box to keep it with the text value 340ebd70c7dSAdrian Hunter if data == None: 341ebd70c7dSAdrian Hunter self.textbox.setItemData(index, pattern) 342ebd70c7dSAdrian Hunter else: 343ebd70c7dSAdrian Hunter self.pattern.setChecked(data) 344ebd70c7dSAdrian Hunter self.Find(0) 345ebd70c7dSAdrian Hunter 346ebd70c7dSAdrian Hunter def NextPrev(self, direction): 347ebd70c7dSAdrian Hunter value = self.textbox.currentText() 348ebd70c7dSAdrian Hunter pattern = self.pattern.isChecked() 349ebd70c7dSAdrian Hunter if value != self.last_value: 350ebd70c7dSAdrian Hunter index = self.textbox.findText(value) 351ebd70c7dSAdrian Hunter # Allow for a button press before the value has been added to the combo box 352ebd70c7dSAdrian Hunter if index < 0: 353ebd70c7dSAdrian Hunter index = self.textbox.count() 354ebd70c7dSAdrian Hunter self.textbox.addItem(value, pattern) 355ebd70c7dSAdrian Hunter self.textbox.setCurrentIndex(index) 356ebd70c7dSAdrian Hunter return 357ebd70c7dSAdrian Hunter else: 358ebd70c7dSAdrian Hunter self.textbox.setItemData(index, pattern) 359ebd70c7dSAdrian Hunter elif pattern != self.last_pattern: 360ebd70c7dSAdrian Hunter # Keep the pattern recorded in the combo box up to date 361ebd70c7dSAdrian Hunter index = self.textbox.currentIndex() 362ebd70c7dSAdrian Hunter self.textbox.setItemData(index, pattern) 363ebd70c7dSAdrian Hunter self.Find(direction) 364ebd70c7dSAdrian Hunter 365ebd70c7dSAdrian Hunter def NotFound(self): 366ebd70c7dSAdrian Hunter QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found") 367ebd70c7dSAdrian Hunter 368031c2a00SAdrian Hunter# Context-sensitive call graph data model item base 369031c2a00SAdrian Hunter 370031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object): 371031c2a00SAdrian Hunter 372031c2a00SAdrian Hunter def __init__(self, glb, row, parent_item): 373031c2a00SAdrian Hunter self.glb = glb 374031c2a00SAdrian Hunter self.row = row 375031c2a00SAdrian Hunter self.parent_item = parent_item 376031c2a00SAdrian Hunter self.query_done = False; 377031c2a00SAdrian Hunter self.child_count = 0 378031c2a00SAdrian Hunter self.child_items = [] 379031c2a00SAdrian Hunter 380031c2a00SAdrian Hunter def getChildItem(self, row): 381031c2a00SAdrian Hunter return self.child_items[row] 382031c2a00SAdrian Hunter 383031c2a00SAdrian Hunter def getParentItem(self): 384031c2a00SAdrian Hunter return self.parent_item 385031c2a00SAdrian Hunter 386031c2a00SAdrian Hunter def getRow(self): 387031c2a00SAdrian Hunter return self.row 388031c2a00SAdrian Hunter 389031c2a00SAdrian Hunter def childCount(self): 390031c2a00SAdrian Hunter if not self.query_done: 391031c2a00SAdrian Hunter self.Select() 392031c2a00SAdrian Hunter if not self.child_count: 393031c2a00SAdrian Hunter return -1 394031c2a00SAdrian Hunter return self.child_count 395031c2a00SAdrian Hunter 396031c2a00SAdrian Hunter def hasChildren(self): 397031c2a00SAdrian Hunter if not self.query_done: 398031c2a00SAdrian Hunter return True 399031c2a00SAdrian Hunter return self.child_count > 0 400031c2a00SAdrian Hunter 401031c2a00SAdrian Hunter def getData(self, column): 402031c2a00SAdrian Hunter return self.data[column] 403031c2a00SAdrian Hunter 404031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base 405031c2a00SAdrian Hunter 406031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase): 407031c2a00SAdrian Hunter 408031c2a00SAdrian Hunter def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item): 409031c2a00SAdrian Hunter super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item) 410031c2a00SAdrian Hunter self.comm_id = comm_id 411031c2a00SAdrian Hunter self.thread_id = thread_id 412031c2a00SAdrian Hunter self.call_path_id = call_path_id 413031c2a00SAdrian Hunter self.branch_count = branch_count 414031c2a00SAdrian Hunter self.time = time 415031c2a00SAdrian Hunter 416031c2a00SAdrian Hunter def Select(self): 417031c2a00SAdrian Hunter self.query_done = True; 418031c2a00SAdrian Hunter query = QSqlQuery(self.glb.db) 419031c2a00SAdrian Hunter QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)" 420031c2a00SAdrian Hunter " FROM calls" 421031c2a00SAdrian Hunter " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" 422031c2a00SAdrian Hunter " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" 423031c2a00SAdrian Hunter " INNER JOIN dsos ON symbols.dso_id = dsos.id" 424031c2a00SAdrian Hunter " WHERE parent_call_path_id = " + str(self.call_path_id) + 425031c2a00SAdrian Hunter " AND comm_id = " + str(self.comm_id) + 426031c2a00SAdrian Hunter " AND thread_id = " + str(self.thread_id) + 427031c2a00SAdrian Hunter " GROUP BY call_path_id, name, short_name" 428031c2a00SAdrian Hunter " ORDER BY call_path_id") 429031c2a00SAdrian Hunter while query.next(): 430031c2a00SAdrian Hunter child_item = CallGraphLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self) 431031c2a00SAdrian Hunter self.child_items.append(child_item) 432031c2a00SAdrian Hunter self.child_count += 1 433031c2a00SAdrian Hunter 434031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item 435031c2a00SAdrian Hunter 436031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase): 437031c2a00SAdrian Hunter 438031c2a00SAdrian Hunter def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item): 439031c2a00SAdrian Hunter super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item) 440031c2a00SAdrian Hunter dso = dsoname(dso) 441031c2a00SAdrian Hunter self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ] 442031c2a00SAdrian Hunter self.dbid = call_path_id 443031c2a00SAdrian Hunter 444031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item 445031c2a00SAdrian Hunter 446031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase): 447031c2a00SAdrian Hunter 448031c2a00SAdrian Hunter def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item): 449031c2a00SAdrian Hunter super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item) 450031c2a00SAdrian Hunter self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""] 451031c2a00SAdrian Hunter self.dbid = thread_id 452031c2a00SAdrian Hunter 453031c2a00SAdrian Hunter def Select(self): 454031c2a00SAdrian Hunter super(CallGraphLevelTwoItem, self).Select() 455031c2a00SAdrian Hunter for child_item in self.child_items: 456031c2a00SAdrian Hunter self.time += child_item.time 457031c2a00SAdrian Hunter self.branch_count += child_item.branch_count 458031c2a00SAdrian Hunter for child_item in self.child_items: 459031c2a00SAdrian Hunter child_item.data[4] = PercentToOneDP(child_item.time, self.time) 460031c2a00SAdrian Hunter child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count) 461031c2a00SAdrian Hunter 462031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item 463031c2a00SAdrian Hunter 464031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase): 465031c2a00SAdrian Hunter 466031c2a00SAdrian Hunter def __init__(self, glb, row, comm_id, comm, parent_item): 467031c2a00SAdrian Hunter super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item) 468031c2a00SAdrian Hunter self.data = [comm, "", "", "", "", "", ""] 469031c2a00SAdrian Hunter self.dbid = comm_id 470031c2a00SAdrian Hunter 471031c2a00SAdrian Hunter def Select(self): 472031c2a00SAdrian Hunter self.query_done = True; 473031c2a00SAdrian Hunter query = QSqlQuery(self.glb.db) 474031c2a00SAdrian Hunter QueryExec(query, "SELECT thread_id, pid, tid" 475031c2a00SAdrian Hunter " FROM comm_threads" 476031c2a00SAdrian Hunter " INNER JOIN threads ON thread_id = threads.id" 477031c2a00SAdrian Hunter " WHERE comm_id = " + str(self.dbid)) 478031c2a00SAdrian Hunter while query.next(): 479031c2a00SAdrian Hunter child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self) 480031c2a00SAdrian Hunter self.child_items.append(child_item) 481031c2a00SAdrian Hunter self.child_count += 1 482031c2a00SAdrian Hunter 483031c2a00SAdrian Hunter# Context-sensitive call graph data model root item 484031c2a00SAdrian Hunter 485031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase): 486031c2a00SAdrian Hunter 487031c2a00SAdrian Hunter def __init__(self, glb): 488031c2a00SAdrian Hunter super(CallGraphRootItem, self).__init__(glb, 0, None) 489031c2a00SAdrian Hunter self.dbid = 0 490031c2a00SAdrian Hunter self.query_done = True; 491031c2a00SAdrian Hunter query = QSqlQuery(glb.db) 492031c2a00SAdrian Hunter QueryExec(query, "SELECT id, comm FROM comms") 493031c2a00SAdrian Hunter while query.next(): 494031c2a00SAdrian Hunter if not query.value(0): 495031c2a00SAdrian Hunter continue 496031c2a00SAdrian Hunter child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self) 497031c2a00SAdrian Hunter self.child_items.append(child_item) 498031c2a00SAdrian Hunter self.child_count += 1 499031c2a00SAdrian Hunter 500031c2a00SAdrian Hunter# Context-sensitive call graph data model 501031c2a00SAdrian Hunter 502031c2a00SAdrian Hunterclass CallGraphModel(TreeModel): 503031c2a00SAdrian Hunter 504031c2a00SAdrian Hunter def __init__(self, glb, parent=None): 505031c2a00SAdrian Hunter super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent) 506031c2a00SAdrian Hunter self.glb = glb 507031c2a00SAdrian Hunter 508031c2a00SAdrian Hunter def columnCount(self, parent=None): 509031c2a00SAdrian Hunter return 7 510031c2a00SAdrian Hunter 511031c2a00SAdrian Hunter def columnHeader(self, column): 512031c2a00SAdrian Hunter headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] 513031c2a00SAdrian Hunter return headers[column] 514031c2a00SAdrian Hunter 515031c2a00SAdrian Hunter def columnAlignment(self, column): 516031c2a00SAdrian Hunter alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] 517031c2a00SAdrian Hunter return alignment[column] 518031c2a00SAdrian Hunter 519ebd70c7dSAdrian Hunter def FindSelect(self, value, pattern, query): 520ebd70c7dSAdrian Hunter if pattern: 521ebd70c7dSAdrian Hunter # postgresql and sqlite pattern patching differences: 522ebd70c7dSAdrian Hunter # postgresql LIKE is case sensitive but sqlite LIKE is not 523ebd70c7dSAdrian Hunter # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not 524ebd70c7dSAdrian Hunter # postgresql supports ILIKE which is case insensitive 525ebd70c7dSAdrian Hunter # sqlite supports GLOB (text only) which uses * and ? and is case sensitive 526ebd70c7dSAdrian Hunter if not self.glb.dbref.is_sqlite3: 527ebd70c7dSAdrian Hunter # Escape % and _ 528ebd70c7dSAdrian Hunter s = value.replace("%", "\%") 529ebd70c7dSAdrian Hunter s = s.replace("_", "\_") 530ebd70c7dSAdrian Hunter # Translate * and ? into SQL LIKE pattern characters % and _ 531ebd70c7dSAdrian Hunter trans = string.maketrans("*?", "%_") 532ebd70c7dSAdrian Hunter match = " LIKE '" + str(s).translate(trans) + "'" 533ebd70c7dSAdrian Hunter else: 534ebd70c7dSAdrian Hunter match = " GLOB '" + str(value) + "'" 535ebd70c7dSAdrian Hunter else: 536ebd70c7dSAdrian Hunter match = " = '" + str(value) + "'" 537ebd70c7dSAdrian Hunter QueryExec(query, "SELECT call_path_id, comm_id, thread_id" 538ebd70c7dSAdrian Hunter " FROM calls" 539ebd70c7dSAdrian Hunter " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" 540ebd70c7dSAdrian Hunter " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" 541ebd70c7dSAdrian Hunter " WHERE symbols.name" + match + 542ebd70c7dSAdrian Hunter " GROUP BY comm_id, thread_id, call_path_id" 543ebd70c7dSAdrian Hunter " ORDER BY comm_id, thread_id, call_path_id") 544ebd70c7dSAdrian Hunter 545ebd70c7dSAdrian Hunter def FindPath(self, query): 546ebd70c7dSAdrian Hunter # Turn the query result into a list of ids that the tree view can walk 547ebd70c7dSAdrian Hunter # to open the tree at the right place. 548ebd70c7dSAdrian Hunter ids = [] 549ebd70c7dSAdrian Hunter parent_id = query.value(0) 550ebd70c7dSAdrian Hunter while parent_id: 551ebd70c7dSAdrian Hunter ids.insert(0, parent_id) 552ebd70c7dSAdrian Hunter q2 = QSqlQuery(self.glb.db) 553ebd70c7dSAdrian Hunter QueryExec(q2, "SELECT parent_id" 554ebd70c7dSAdrian Hunter " FROM call_paths" 555ebd70c7dSAdrian Hunter " WHERE id = " + str(parent_id)) 556ebd70c7dSAdrian Hunter if not q2.next(): 557ebd70c7dSAdrian Hunter break 558ebd70c7dSAdrian Hunter parent_id = q2.value(0) 559ebd70c7dSAdrian Hunter # The call path root is not used 560ebd70c7dSAdrian Hunter if ids[0] == 1: 561ebd70c7dSAdrian Hunter del ids[0] 562ebd70c7dSAdrian Hunter ids.insert(0, query.value(2)) 563ebd70c7dSAdrian Hunter ids.insert(0, query.value(1)) 564ebd70c7dSAdrian Hunter return ids 565ebd70c7dSAdrian Hunter 566ebd70c7dSAdrian Hunter def Found(self, query, found): 567ebd70c7dSAdrian Hunter if found: 568ebd70c7dSAdrian Hunter return self.FindPath(query) 569ebd70c7dSAdrian Hunter return [] 570ebd70c7dSAdrian Hunter 571ebd70c7dSAdrian Hunter def FindValue(self, value, pattern, query, last_value, last_pattern): 572ebd70c7dSAdrian Hunter if last_value == value and pattern == last_pattern: 573ebd70c7dSAdrian Hunter found = query.first() 574ebd70c7dSAdrian Hunter else: 575ebd70c7dSAdrian Hunter self.FindSelect(value, pattern, query) 576ebd70c7dSAdrian Hunter found = query.next() 577ebd70c7dSAdrian Hunter return self.Found(query, found) 578ebd70c7dSAdrian Hunter 579ebd70c7dSAdrian Hunter def FindNext(self, query): 580ebd70c7dSAdrian Hunter found = query.next() 581ebd70c7dSAdrian Hunter if not found: 582ebd70c7dSAdrian Hunter found = query.first() 583ebd70c7dSAdrian Hunter return self.Found(query, found) 584ebd70c7dSAdrian Hunter 585ebd70c7dSAdrian Hunter def FindPrev(self, query): 586ebd70c7dSAdrian Hunter found = query.previous() 587ebd70c7dSAdrian Hunter if not found: 588ebd70c7dSAdrian Hunter found = query.last() 589ebd70c7dSAdrian Hunter return self.Found(query, found) 590ebd70c7dSAdrian Hunter 591ebd70c7dSAdrian Hunter def FindThread(self, c): 592ebd70c7dSAdrian Hunter if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern: 593ebd70c7dSAdrian Hunter ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern) 594ebd70c7dSAdrian Hunter elif c.direction > 0: 595ebd70c7dSAdrian Hunter ids = self.FindNext(c.query) 596ebd70c7dSAdrian Hunter else: 597ebd70c7dSAdrian Hunter ids = self.FindPrev(c.query) 598ebd70c7dSAdrian Hunter return (True, ids) 599ebd70c7dSAdrian Hunter 600ebd70c7dSAdrian Hunter def Find(self, value, direction, pattern, context, callback): 601ebd70c7dSAdrian Hunter class Context(): 602ebd70c7dSAdrian Hunter def __init__(self, *x): 603ebd70c7dSAdrian Hunter self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x 604ebd70c7dSAdrian Hunter def Update(self, *x): 605ebd70c7dSAdrian Hunter self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern) 606ebd70c7dSAdrian Hunter if len(context): 607ebd70c7dSAdrian Hunter context[0].Update(value, direction, pattern) 608ebd70c7dSAdrian Hunter else: 609ebd70c7dSAdrian Hunter context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None)) 610ebd70c7dSAdrian Hunter # Use a thread so the UI is not blocked during the SELECT 611ebd70c7dSAdrian Hunter thread = Thread(self.FindThread, context[0]) 612ebd70c7dSAdrian Hunter thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection) 613ebd70c7dSAdrian Hunter thread.start() 614ebd70c7dSAdrian Hunter 615ebd70c7dSAdrian Hunter def FindDone(self, thread, callback, ids): 616ebd70c7dSAdrian Hunter callback(ids) 617ebd70c7dSAdrian Hunter 618ebd70c7dSAdrian Hunter# Vertical widget layout 619ebd70c7dSAdrian Hunter 620ebd70c7dSAdrian Hunterclass VBox(): 621ebd70c7dSAdrian Hunter 622ebd70c7dSAdrian Hunter def __init__(self, w1, w2, w3=None): 623ebd70c7dSAdrian Hunter self.vbox = QWidget() 624ebd70c7dSAdrian Hunter self.vbox.setLayout(QVBoxLayout()); 625ebd70c7dSAdrian Hunter 626ebd70c7dSAdrian Hunter self.vbox.layout().setContentsMargins(0, 0, 0, 0) 627ebd70c7dSAdrian Hunter 628ebd70c7dSAdrian Hunter self.vbox.layout().addWidget(w1) 629ebd70c7dSAdrian Hunter self.vbox.layout().addWidget(w2) 630ebd70c7dSAdrian Hunter if w3: 631ebd70c7dSAdrian Hunter self.vbox.layout().addWidget(w3) 632ebd70c7dSAdrian Hunter 633ebd70c7dSAdrian Hunter def Widget(self): 634ebd70c7dSAdrian Hunter return self.vbox 635ebd70c7dSAdrian Hunter 6361beb5c7bSAdrian Hunter# Context-sensitive call graph window 6371beb5c7bSAdrian Hunter 6381beb5c7bSAdrian Hunterclass CallGraphWindow(QMdiSubWindow): 6391beb5c7bSAdrian Hunter 6401beb5c7bSAdrian Hunter def __init__(self, glb, parent=None): 6411beb5c7bSAdrian Hunter super(CallGraphWindow, self).__init__(parent) 6421beb5c7bSAdrian Hunter 6431beb5c7bSAdrian Hunter self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x)) 6441beb5c7bSAdrian Hunter 6451beb5c7bSAdrian Hunter self.view = QTreeView() 6461beb5c7bSAdrian Hunter self.view.setModel(self.model) 6471beb5c7bSAdrian Hunter 6481beb5c7bSAdrian Hunter for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): 6491beb5c7bSAdrian Hunter self.view.setColumnWidth(c, w) 6501beb5c7bSAdrian Hunter 651ebd70c7dSAdrian Hunter self.find_bar = FindBar(self, self) 652ebd70c7dSAdrian Hunter 653ebd70c7dSAdrian Hunter self.vbox = VBox(self.view, self.find_bar.Widget()) 654ebd70c7dSAdrian Hunter 655ebd70c7dSAdrian Hunter self.setWidget(self.vbox.Widget()) 6561beb5c7bSAdrian Hunter 6571beb5c7bSAdrian Hunter AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") 6581beb5c7bSAdrian Hunter 659ebd70c7dSAdrian Hunter def DisplayFound(self, ids): 660ebd70c7dSAdrian Hunter if not len(ids): 661ebd70c7dSAdrian Hunter return False 662ebd70c7dSAdrian Hunter parent = QModelIndex() 663ebd70c7dSAdrian Hunter for dbid in ids: 664ebd70c7dSAdrian Hunter found = False 665ebd70c7dSAdrian Hunter n = self.model.rowCount(parent) 666ebd70c7dSAdrian Hunter for row in xrange(n): 667ebd70c7dSAdrian Hunter child = self.model.index(row, 0, parent) 668ebd70c7dSAdrian Hunter if child.internalPointer().dbid == dbid: 669ebd70c7dSAdrian Hunter found = True 670ebd70c7dSAdrian Hunter self.view.setCurrentIndex(child) 671ebd70c7dSAdrian Hunter parent = child 672ebd70c7dSAdrian Hunter break 673ebd70c7dSAdrian Hunter if not found: 674ebd70c7dSAdrian Hunter break 675ebd70c7dSAdrian Hunter return found 676ebd70c7dSAdrian Hunter 677ebd70c7dSAdrian Hunter def Find(self, value, direction, pattern, context): 678ebd70c7dSAdrian Hunter self.view.setFocus() 679ebd70c7dSAdrian Hunter self.find_bar.Busy() 680ebd70c7dSAdrian Hunter self.model.Find(value, direction, pattern, context, self.FindDone) 681ebd70c7dSAdrian Hunter 682ebd70c7dSAdrian Hunter def FindDone(self, ids): 683ebd70c7dSAdrian Hunter found = True 684ebd70c7dSAdrian Hunter if not self.DisplayFound(ids): 685ebd70c7dSAdrian Hunter found = False 686ebd70c7dSAdrian Hunter self.find_bar.Idle() 687ebd70c7dSAdrian Hunter if not found: 688ebd70c7dSAdrian Hunter self.find_bar.NotFound() 689ebd70c7dSAdrian Hunter 690*8392b74bSAdrian Hunter# Child data item finder 691*8392b74bSAdrian Hunter 692*8392b74bSAdrian Hunterclass ChildDataItemFinder(): 693*8392b74bSAdrian Hunter 694*8392b74bSAdrian Hunter def __init__(self, root): 695*8392b74bSAdrian Hunter self.root = root 696*8392b74bSAdrian Hunter self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5 697*8392b74bSAdrian Hunter self.rows = [] 698*8392b74bSAdrian Hunter self.pos = 0 699*8392b74bSAdrian Hunter 700*8392b74bSAdrian Hunter def FindSelect(self): 701*8392b74bSAdrian Hunter self.rows = [] 702*8392b74bSAdrian Hunter if self.pattern: 703*8392b74bSAdrian Hunter pattern = re.compile(self.value) 704*8392b74bSAdrian Hunter for child in self.root.child_items: 705*8392b74bSAdrian Hunter for column_data in child.data: 706*8392b74bSAdrian Hunter if re.search(pattern, str(column_data)) is not None: 707*8392b74bSAdrian Hunter self.rows.append(child.row) 708*8392b74bSAdrian Hunter break 709*8392b74bSAdrian Hunter else: 710*8392b74bSAdrian Hunter for child in self.root.child_items: 711*8392b74bSAdrian Hunter for column_data in child.data: 712*8392b74bSAdrian Hunter if self.value in str(column_data): 713*8392b74bSAdrian Hunter self.rows.append(child.row) 714*8392b74bSAdrian Hunter break 715*8392b74bSAdrian Hunter 716*8392b74bSAdrian Hunter def FindValue(self): 717*8392b74bSAdrian Hunter self.pos = 0 718*8392b74bSAdrian Hunter if self.last_value != self.value or self.pattern != self.last_pattern: 719*8392b74bSAdrian Hunter self.FindSelect() 720*8392b74bSAdrian Hunter if not len(self.rows): 721*8392b74bSAdrian Hunter return -1 722*8392b74bSAdrian Hunter return self.rows[self.pos] 723*8392b74bSAdrian Hunter 724*8392b74bSAdrian Hunter def FindThread(self): 725*8392b74bSAdrian Hunter if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern: 726*8392b74bSAdrian Hunter row = self.FindValue() 727*8392b74bSAdrian Hunter elif len(self.rows): 728*8392b74bSAdrian Hunter if self.direction > 0: 729*8392b74bSAdrian Hunter self.pos += 1 730*8392b74bSAdrian Hunter if self.pos >= len(self.rows): 731*8392b74bSAdrian Hunter self.pos = 0 732*8392b74bSAdrian Hunter else: 733*8392b74bSAdrian Hunter self.pos -= 1 734*8392b74bSAdrian Hunter if self.pos < 0: 735*8392b74bSAdrian Hunter self.pos = len(self.rows) - 1 736*8392b74bSAdrian Hunter row = self.rows[self.pos] 737*8392b74bSAdrian Hunter else: 738*8392b74bSAdrian Hunter row = -1 739*8392b74bSAdrian Hunter return (True, row) 740*8392b74bSAdrian Hunter 741*8392b74bSAdrian Hunter def Find(self, value, direction, pattern, context, callback): 742*8392b74bSAdrian Hunter self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern) 743*8392b74bSAdrian Hunter # Use a thread so the UI is not blocked 744*8392b74bSAdrian Hunter thread = Thread(self.FindThread) 745*8392b74bSAdrian Hunter thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection) 746*8392b74bSAdrian Hunter thread.start() 747*8392b74bSAdrian Hunter 748*8392b74bSAdrian Hunter def FindDone(self, thread, callback, row): 749*8392b74bSAdrian Hunter callback(row) 750*8392b74bSAdrian Hunter 751*8392b74bSAdrian Hunter# Number of database records to fetch in one go 752*8392b74bSAdrian Hunter 753*8392b74bSAdrian Hunterglb_chunk_sz = 10000 754*8392b74bSAdrian Hunter 755*8392b74bSAdrian Hunter# size of pickled integer big enough for record size 756*8392b74bSAdrian Hunter 757*8392b74bSAdrian Hunterglb_nsz = 8 758*8392b74bSAdrian Hunter 759*8392b74bSAdrian Hunter# Background process for SQL data fetcher 760*8392b74bSAdrian Hunter 761*8392b74bSAdrian Hunterclass SQLFetcherProcess(): 762*8392b74bSAdrian Hunter 763*8392b74bSAdrian Hunter def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep): 764*8392b74bSAdrian Hunter # Need a unique connection name 765*8392b74bSAdrian Hunter conn_name = "SQLFetcher" + str(os.getpid()) 766*8392b74bSAdrian Hunter self.db, dbname = dbref.Open(conn_name) 767*8392b74bSAdrian Hunter self.sql = sql 768*8392b74bSAdrian Hunter self.buffer = buffer 769*8392b74bSAdrian Hunter self.head = head 770*8392b74bSAdrian Hunter self.tail = tail 771*8392b74bSAdrian Hunter self.fetch_count = fetch_count 772*8392b74bSAdrian Hunter self.fetching_done = fetching_done 773*8392b74bSAdrian Hunter self.process_target = process_target 774*8392b74bSAdrian Hunter self.wait_event = wait_event 775*8392b74bSAdrian Hunter self.fetched_event = fetched_event 776*8392b74bSAdrian Hunter self.prep = prep 777*8392b74bSAdrian Hunter self.query = QSqlQuery(self.db) 778*8392b74bSAdrian Hunter self.query_limit = 0 if "$$last_id$$" in sql else 2 779*8392b74bSAdrian Hunter self.last_id = -1 780*8392b74bSAdrian Hunter self.fetched = 0 781*8392b74bSAdrian Hunter self.more = True 782*8392b74bSAdrian Hunter self.local_head = self.head.value 783*8392b74bSAdrian Hunter self.local_tail = self.tail.value 784*8392b74bSAdrian Hunter 785*8392b74bSAdrian Hunter def Select(self): 786*8392b74bSAdrian Hunter if self.query_limit: 787*8392b74bSAdrian Hunter if self.query_limit == 1: 788*8392b74bSAdrian Hunter return 789*8392b74bSAdrian Hunter self.query_limit -= 1 790*8392b74bSAdrian Hunter stmt = self.sql.replace("$$last_id$$", str(self.last_id)) 791*8392b74bSAdrian Hunter QueryExec(self.query, stmt) 792*8392b74bSAdrian Hunter 793*8392b74bSAdrian Hunter def Next(self): 794*8392b74bSAdrian Hunter if not self.query.next(): 795*8392b74bSAdrian Hunter self.Select() 796*8392b74bSAdrian Hunter if not self.query.next(): 797*8392b74bSAdrian Hunter return None 798*8392b74bSAdrian Hunter self.last_id = self.query.value(0) 799*8392b74bSAdrian Hunter return self.prep(self.query) 800*8392b74bSAdrian Hunter 801*8392b74bSAdrian Hunter def WaitForTarget(self): 802*8392b74bSAdrian Hunter while True: 803*8392b74bSAdrian Hunter self.wait_event.clear() 804*8392b74bSAdrian Hunter target = self.process_target.value 805*8392b74bSAdrian Hunter if target > self.fetched or target < 0: 806*8392b74bSAdrian Hunter break 807*8392b74bSAdrian Hunter self.wait_event.wait() 808*8392b74bSAdrian Hunter return target 809*8392b74bSAdrian Hunter 810*8392b74bSAdrian Hunter def HasSpace(self, sz): 811*8392b74bSAdrian Hunter if self.local_tail <= self.local_head: 812*8392b74bSAdrian Hunter space = len(self.buffer) - self.local_head 813*8392b74bSAdrian Hunter if space > sz: 814*8392b74bSAdrian Hunter return True 815*8392b74bSAdrian Hunter if space >= glb_nsz: 816*8392b74bSAdrian Hunter # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer 817*8392b74bSAdrian Hunter nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL) 818*8392b74bSAdrian Hunter self.buffer[self.local_head : self.local_head + len(nd)] = nd 819*8392b74bSAdrian Hunter self.local_head = 0 820*8392b74bSAdrian Hunter if self.local_tail - self.local_head > sz: 821*8392b74bSAdrian Hunter return True 822*8392b74bSAdrian Hunter return False 823*8392b74bSAdrian Hunter 824*8392b74bSAdrian Hunter def WaitForSpace(self, sz): 825*8392b74bSAdrian Hunter if self.HasSpace(sz): 826*8392b74bSAdrian Hunter return 827*8392b74bSAdrian Hunter while True: 828*8392b74bSAdrian Hunter self.wait_event.clear() 829*8392b74bSAdrian Hunter self.local_tail = self.tail.value 830*8392b74bSAdrian Hunter if self.HasSpace(sz): 831*8392b74bSAdrian Hunter return 832*8392b74bSAdrian Hunter self.wait_event.wait() 833*8392b74bSAdrian Hunter 834*8392b74bSAdrian Hunter def AddToBuffer(self, obj): 835*8392b74bSAdrian Hunter d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL) 836*8392b74bSAdrian Hunter n = len(d) 837*8392b74bSAdrian Hunter nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL) 838*8392b74bSAdrian Hunter sz = n + glb_nsz 839*8392b74bSAdrian Hunter self.WaitForSpace(sz) 840*8392b74bSAdrian Hunter pos = self.local_head 841*8392b74bSAdrian Hunter self.buffer[pos : pos + len(nd)] = nd 842*8392b74bSAdrian Hunter self.buffer[pos + glb_nsz : pos + sz] = d 843*8392b74bSAdrian Hunter self.local_head += sz 844*8392b74bSAdrian Hunter 845*8392b74bSAdrian Hunter def FetchBatch(self, batch_size): 846*8392b74bSAdrian Hunter fetched = 0 847*8392b74bSAdrian Hunter while batch_size > fetched: 848*8392b74bSAdrian Hunter obj = self.Next() 849*8392b74bSAdrian Hunter if obj is None: 850*8392b74bSAdrian Hunter self.more = False 851*8392b74bSAdrian Hunter break 852*8392b74bSAdrian Hunter self.AddToBuffer(obj) 853*8392b74bSAdrian Hunter fetched += 1 854*8392b74bSAdrian Hunter if fetched: 855*8392b74bSAdrian Hunter self.fetched += fetched 856*8392b74bSAdrian Hunter with self.fetch_count.get_lock(): 857*8392b74bSAdrian Hunter self.fetch_count.value += fetched 858*8392b74bSAdrian Hunter self.head.value = self.local_head 859*8392b74bSAdrian Hunter self.fetched_event.set() 860*8392b74bSAdrian Hunter 861*8392b74bSAdrian Hunter def Run(self): 862*8392b74bSAdrian Hunter while self.more: 863*8392b74bSAdrian Hunter target = self.WaitForTarget() 864*8392b74bSAdrian Hunter if target < 0: 865*8392b74bSAdrian Hunter break 866*8392b74bSAdrian Hunter batch_size = min(glb_chunk_sz, target - self.fetched) 867*8392b74bSAdrian Hunter self.FetchBatch(batch_size) 868*8392b74bSAdrian Hunter self.fetching_done.value = True 869*8392b74bSAdrian Hunter self.fetched_event.set() 870*8392b74bSAdrian Hunter 871*8392b74bSAdrian Hunterdef SQLFetcherFn(*x): 872*8392b74bSAdrian Hunter process = SQLFetcherProcess(*x) 873*8392b74bSAdrian Hunter process.Run() 874*8392b74bSAdrian Hunter 875*8392b74bSAdrian Hunter# SQL data fetcher 876*8392b74bSAdrian Hunter 877*8392b74bSAdrian Hunterclass SQLFetcher(QObject): 878*8392b74bSAdrian Hunter 879*8392b74bSAdrian Hunter done = Signal(object) 880*8392b74bSAdrian Hunter 881*8392b74bSAdrian Hunter def __init__(self, glb, sql, prep, process_data, parent=None): 882*8392b74bSAdrian Hunter super(SQLFetcher, self).__init__(parent) 883*8392b74bSAdrian Hunter self.process_data = process_data 884*8392b74bSAdrian Hunter self.more = True 885*8392b74bSAdrian Hunter self.target = 0 886*8392b74bSAdrian Hunter self.last_target = 0 887*8392b74bSAdrian Hunter self.fetched = 0 888*8392b74bSAdrian Hunter self.buffer_size = 16 * 1024 * 1024 889*8392b74bSAdrian Hunter self.buffer = Array(c_char, self.buffer_size, lock=False) 890*8392b74bSAdrian Hunter self.head = Value(c_longlong) 891*8392b74bSAdrian Hunter self.tail = Value(c_longlong) 892*8392b74bSAdrian Hunter self.local_tail = 0 893*8392b74bSAdrian Hunter self.fetch_count = Value(c_longlong) 894*8392b74bSAdrian Hunter self.fetching_done = Value(c_bool) 895*8392b74bSAdrian Hunter self.last_count = 0 896*8392b74bSAdrian Hunter self.process_target = Value(c_longlong) 897*8392b74bSAdrian Hunter self.wait_event = Event() 898*8392b74bSAdrian Hunter self.fetched_event = Event() 899*8392b74bSAdrian Hunter glb.AddInstanceToShutdownOnExit(self) 900*8392b74bSAdrian Hunter self.process = Process(target=SQLFetcherFn, args=(glb.dbref, sql, self.buffer, self.head, self.tail, self.fetch_count, self.fetching_done, self.process_target, self.wait_event, self.fetched_event, prep)) 901*8392b74bSAdrian Hunter self.process.start() 902*8392b74bSAdrian Hunter self.thread = Thread(self.Thread) 903*8392b74bSAdrian Hunter self.thread.done.connect(self.ProcessData, Qt.QueuedConnection) 904*8392b74bSAdrian Hunter self.thread.start() 905*8392b74bSAdrian Hunter 906*8392b74bSAdrian Hunter def Shutdown(self): 907*8392b74bSAdrian Hunter # Tell the thread and process to exit 908*8392b74bSAdrian Hunter self.process_target.value = -1 909*8392b74bSAdrian Hunter self.wait_event.set() 910*8392b74bSAdrian Hunter self.more = False 911*8392b74bSAdrian Hunter self.fetching_done.value = True 912*8392b74bSAdrian Hunter self.fetched_event.set() 913*8392b74bSAdrian Hunter 914*8392b74bSAdrian Hunter def Thread(self): 915*8392b74bSAdrian Hunter if not self.more: 916*8392b74bSAdrian Hunter return True, 0 917*8392b74bSAdrian Hunter while True: 918*8392b74bSAdrian Hunter self.fetched_event.clear() 919*8392b74bSAdrian Hunter fetch_count = self.fetch_count.value 920*8392b74bSAdrian Hunter if fetch_count != self.last_count: 921*8392b74bSAdrian Hunter break 922*8392b74bSAdrian Hunter if self.fetching_done.value: 923*8392b74bSAdrian Hunter self.more = False 924*8392b74bSAdrian Hunter return True, 0 925*8392b74bSAdrian Hunter self.fetched_event.wait() 926*8392b74bSAdrian Hunter count = fetch_count - self.last_count 927*8392b74bSAdrian Hunter self.last_count = fetch_count 928*8392b74bSAdrian Hunter self.fetched += count 929*8392b74bSAdrian Hunter return False, count 930*8392b74bSAdrian Hunter 931*8392b74bSAdrian Hunter def Fetch(self, nr): 932*8392b74bSAdrian Hunter if not self.more: 933*8392b74bSAdrian Hunter # -1 inidcates there are no more 934*8392b74bSAdrian Hunter return -1 935*8392b74bSAdrian Hunter result = self.fetched 936*8392b74bSAdrian Hunter extra = result + nr - self.target 937*8392b74bSAdrian Hunter if extra > 0: 938*8392b74bSAdrian Hunter self.target += extra 939*8392b74bSAdrian Hunter # process_target < 0 indicates shutting down 940*8392b74bSAdrian Hunter if self.process_target.value >= 0: 941*8392b74bSAdrian Hunter self.process_target.value = self.target 942*8392b74bSAdrian Hunter self.wait_event.set() 943*8392b74bSAdrian Hunter return result 944*8392b74bSAdrian Hunter 945*8392b74bSAdrian Hunter def RemoveFromBuffer(self): 946*8392b74bSAdrian Hunter pos = self.local_tail 947*8392b74bSAdrian Hunter if len(self.buffer) - pos < glb_nsz: 948*8392b74bSAdrian Hunter pos = 0 949*8392b74bSAdrian Hunter n = cPickle.loads(self.buffer[pos : pos + glb_nsz]) 950*8392b74bSAdrian Hunter if n == 0: 951*8392b74bSAdrian Hunter pos = 0 952*8392b74bSAdrian Hunter n = cPickle.loads(self.buffer[0 : glb_nsz]) 953*8392b74bSAdrian Hunter pos += glb_nsz 954*8392b74bSAdrian Hunter obj = cPickle.loads(self.buffer[pos : pos + n]) 955*8392b74bSAdrian Hunter self.local_tail = pos + n 956*8392b74bSAdrian Hunter return obj 957*8392b74bSAdrian Hunter 958*8392b74bSAdrian Hunter def ProcessData(self, count): 959*8392b74bSAdrian Hunter for i in xrange(count): 960*8392b74bSAdrian Hunter obj = self.RemoveFromBuffer() 961*8392b74bSAdrian Hunter self.process_data(obj) 962*8392b74bSAdrian Hunter self.tail.value = self.local_tail 963*8392b74bSAdrian Hunter self.wait_event.set() 964*8392b74bSAdrian Hunter self.done.emit(count) 965*8392b74bSAdrian Hunter 966*8392b74bSAdrian Hunter# Fetch more records bar 967*8392b74bSAdrian Hunter 968*8392b74bSAdrian Hunterclass FetchMoreRecordsBar(): 969*8392b74bSAdrian Hunter 970*8392b74bSAdrian Hunter def __init__(self, model, parent): 971*8392b74bSAdrian Hunter self.model = model 972*8392b74bSAdrian Hunter 973*8392b74bSAdrian Hunter self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:") 974*8392b74bSAdrian Hunter self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 975*8392b74bSAdrian Hunter 976*8392b74bSAdrian Hunter self.fetch_count = QSpinBox() 977*8392b74bSAdrian Hunter self.fetch_count.setRange(1, 1000000) 978*8392b74bSAdrian Hunter self.fetch_count.setValue(10) 979*8392b74bSAdrian Hunter self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 980*8392b74bSAdrian Hunter 981*8392b74bSAdrian Hunter self.fetch = QPushButton("Go!") 982*8392b74bSAdrian Hunter self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 983*8392b74bSAdrian Hunter self.fetch.released.connect(self.FetchMoreRecords) 984*8392b74bSAdrian Hunter 985*8392b74bSAdrian Hunter self.progress = QProgressBar() 986*8392b74bSAdrian Hunter self.progress.setRange(0, 100) 987*8392b74bSAdrian Hunter self.progress.hide() 988*8392b74bSAdrian Hunter 989*8392b74bSAdrian Hunter self.done_label = QLabel("All records fetched") 990*8392b74bSAdrian Hunter self.done_label.hide() 991*8392b74bSAdrian Hunter 992*8392b74bSAdrian Hunter self.spacer = QLabel("") 993*8392b74bSAdrian Hunter 994*8392b74bSAdrian Hunter self.close_button = QToolButton() 995*8392b74bSAdrian Hunter self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton)) 996*8392b74bSAdrian Hunter self.close_button.released.connect(self.Deactivate) 997*8392b74bSAdrian Hunter 998*8392b74bSAdrian Hunter self.hbox = QHBoxLayout() 999*8392b74bSAdrian Hunter self.hbox.setContentsMargins(0, 0, 0, 0) 1000*8392b74bSAdrian Hunter 1001*8392b74bSAdrian Hunter self.hbox.addWidget(self.label) 1002*8392b74bSAdrian Hunter self.hbox.addWidget(self.fetch_count) 1003*8392b74bSAdrian Hunter self.hbox.addWidget(self.fetch) 1004*8392b74bSAdrian Hunter self.hbox.addWidget(self.spacer) 1005*8392b74bSAdrian Hunter self.hbox.addWidget(self.progress) 1006*8392b74bSAdrian Hunter self.hbox.addWidget(self.done_label) 1007*8392b74bSAdrian Hunter self.hbox.addWidget(self.close_button) 1008*8392b74bSAdrian Hunter 1009*8392b74bSAdrian Hunter self.bar = QWidget() 1010*8392b74bSAdrian Hunter self.bar.setLayout(self.hbox); 1011*8392b74bSAdrian Hunter self.bar.show() 1012*8392b74bSAdrian Hunter 1013*8392b74bSAdrian Hunter self.in_progress = False 1014*8392b74bSAdrian Hunter self.model.progress.connect(self.Progress) 1015*8392b74bSAdrian Hunter 1016*8392b74bSAdrian Hunter self.done = False 1017*8392b74bSAdrian Hunter 1018*8392b74bSAdrian Hunter if not model.HasMoreRecords(): 1019*8392b74bSAdrian Hunter self.Done() 1020*8392b74bSAdrian Hunter 1021*8392b74bSAdrian Hunter def Widget(self): 1022*8392b74bSAdrian Hunter return self.bar 1023*8392b74bSAdrian Hunter 1024*8392b74bSAdrian Hunter def Activate(self): 1025*8392b74bSAdrian Hunter self.bar.show() 1026*8392b74bSAdrian Hunter self.fetch.setFocus() 1027*8392b74bSAdrian Hunter 1028*8392b74bSAdrian Hunter def Deactivate(self): 1029*8392b74bSAdrian Hunter self.bar.hide() 1030*8392b74bSAdrian Hunter 1031*8392b74bSAdrian Hunter def Enable(self, enable): 1032*8392b74bSAdrian Hunter self.fetch.setEnabled(enable) 1033*8392b74bSAdrian Hunter self.fetch_count.setEnabled(enable) 1034*8392b74bSAdrian Hunter 1035*8392b74bSAdrian Hunter def Busy(self): 1036*8392b74bSAdrian Hunter self.Enable(False) 1037*8392b74bSAdrian Hunter self.fetch.hide() 1038*8392b74bSAdrian Hunter self.spacer.hide() 1039*8392b74bSAdrian Hunter self.progress.show() 1040*8392b74bSAdrian Hunter 1041*8392b74bSAdrian Hunter def Idle(self): 1042*8392b74bSAdrian Hunter self.in_progress = False 1043*8392b74bSAdrian Hunter self.Enable(True) 1044*8392b74bSAdrian Hunter self.progress.hide() 1045*8392b74bSAdrian Hunter self.fetch.show() 1046*8392b74bSAdrian Hunter self.spacer.show() 1047*8392b74bSAdrian Hunter 1048*8392b74bSAdrian Hunter def Target(self): 1049*8392b74bSAdrian Hunter return self.fetch_count.value() * glb_chunk_sz 1050*8392b74bSAdrian Hunter 1051*8392b74bSAdrian Hunter def Done(self): 1052*8392b74bSAdrian Hunter self.done = True 1053*8392b74bSAdrian Hunter self.Idle() 1054*8392b74bSAdrian Hunter self.label.hide() 1055*8392b74bSAdrian Hunter self.fetch_count.hide() 1056*8392b74bSAdrian Hunter self.fetch.hide() 1057*8392b74bSAdrian Hunter self.spacer.hide() 1058*8392b74bSAdrian Hunter self.done_label.show() 1059*8392b74bSAdrian Hunter 1060*8392b74bSAdrian Hunter def Progress(self, count): 1061*8392b74bSAdrian Hunter if self.in_progress: 1062*8392b74bSAdrian Hunter if count: 1063*8392b74bSAdrian Hunter percent = ((count - self.start) * 100) / self.Target() 1064*8392b74bSAdrian Hunter if percent >= 100: 1065*8392b74bSAdrian Hunter self.Idle() 1066*8392b74bSAdrian Hunter else: 1067*8392b74bSAdrian Hunter self.progress.setValue(percent) 1068*8392b74bSAdrian Hunter if not count: 1069*8392b74bSAdrian Hunter # Count value of zero means no more records 1070*8392b74bSAdrian Hunter self.Done() 1071*8392b74bSAdrian Hunter 1072*8392b74bSAdrian Hunter def FetchMoreRecords(self): 1073*8392b74bSAdrian Hunter if self.done: 1074*8392b74bSAdrian Hunter return 1075*8392b74bSAdrian Hunter self.progress.setValue(0) 1076*8392b74bSAdrian Hunter self.Busy() 1077*8392b74bSAdrian Hunter self.in_progress = True 1078*8392b74bSAdrian Hunter self.start = self.model.FetchMoreRecords(self.Target()) 1079*8392b74bSAdrian Hunter 1080*8392b74bSAdrian Hunter# SQL data preparation 1081*8392b74bSAdrian Hunter 1082*8392b74bSAdrian Hunterdef SQLTableDataPrep(query, count): 1083*8392b74bSAdrian Hunter data = [] 1084*8392b74bSAdrian Hunter for i in xrange(count): 1085*8392b74bSAdrian Hunter data.append(query.value(i)) 1086*8392b74bSAdrian Hunter return data 1087*8392b74bSAdrian Hunter 1088*8392b74bSAdrian Hunter# SQL table data model item 1089*8392b74bSAdrian Hunter 1090*8392b74bSAdrian Hunterclass SQLTableItem(): 1091*8392b74bSAdrian Hunter 1092*8392b74bSAdrian Hunter def __init__(self, row, data): 1093*8392b74bSAdrian Hunter self.row = row 1094*8392b74bSAdrian Hunter self.data = data 1095*8392b74bSAdrian Hunter 1096*8392b74bSAdrian Hunter def getData(self, column): 1097*8392b74bSAdrian Hunter return self.data[column] 1098*8392b74bSAdrian Hunter 1099*8392b74bSAdrian Hunter# SQL table data model 1100*8392b74bSAdrian Hunter 1101*8392b74bSAdrian Hunterclass SQLTableModel(TableModel): 1102*8392b74bSAdrian Hunter 1103*8392b74bSAdrian Hunter progress = Signal(object) 1104*8392b74bSAdrian Hunter 1105*8392b74bSAdrian Hunter def __init__(self, glb, sql, column_count, parent=None): 1106*8392b74bSAdrian Hunter super(SQLTableModel, self).__init__(parent) 1107*8392b74bSAdrian Hunter self.glb = glb 1108*8392b74bSAdrian Hunter self.more = True 1109*8392b74bSAdrian Hunter self.populated = 0 1110*8392b74bSAdrian Hunter self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample) 1111*8392b74bSAdrian Hunter self.fetcher.done.connect(self.Update) 1112*8392b74bSAdrian Hunter self.fetcher.Fetch(glb_chunk_sz) 1113*8392b74bSAdrian Hunter 1114*8392b74bSAdrian Hunter def DisplayData(self, item, index): 1115*8392b74bSAdrian Hunter self.FetchIfNeeded(item.row) 1116*8392b74bSAdrian Hunter return item.getData(index.column()) 1117*8392b74bSAdrian Hunter 1118*8392b74bSAdrian Hunter def AddSample(self, data): 1119*8392b74bSAdrian Hunter child = SQLTableItem(self.populated, data) 1120*8392b74bSAdrian Hunter self.child_items.append(child) 1121*8392b74bSAdrian Hunter self.populated += 1 1122*8392b74bSAdrian Hunter 1123*8392b74bSAdrian Hunter def Update(self, fetched): 1124*8392b74bSAdrian Hunter if not fetched: 1125*8392b74bSAdrian Hunter self.more = False 1126*8392b74bSAdrian Hunter self.progress.emit(0) 1127*8392b74bSAdrian Hunter child_count = self.child_count 1128*8392b74bSAdrian Hunter count = self.populated - child_count 1129*8392b74bSAdrian Hunter if count > 0: 1130*8392b74bSAdrian Hunter parent = QModelIndex() 1131*8392b74bSAdrian Hunter self.beginInsertRows(parent, child_count, child_count + count - 1) 1132*8392b74bSAdrian Hunter self.insertRows(child_count, count, parent) 1133*8392b74bSAdrian Hunter self.child_count += count 1134*8392b74bSAdrian Hunter self.endInsertRows() 1135*8392b74bSAdrian Hunter self.progress.emit(self.child_count) 1136*8392b74bSAdrian Hunter 1137*8392b74bSAdrian Hunter def FetchMoreRecords(self, count): 1138*8392b74bSAdrian Hunter current = self.child_count 1139*8392b74bSAdrian Hunter if self.more: 1140*8392b74bSAdrian Hunter self.fetcher.Fetch(count) 1141*8392b74bSAdrian Hunter else: 1142*8392b74bSAdrian Hunter self.progress.emit(0) 1143*8392b74bSAdrian Hunter return current 1144*8392b74bSAdrian Hunter 1145*8392b74bSAdrian Hunter def HasMoreRecords(self): 1146*8392b74bSAdrian Hunter return self.more 1147*8392b74bSAdrian Hunter 1148*8392b74bSAdrian Hunter# SQL automatic table data model 1149*8392b74bSAdrian Hunter 1150*8392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel): 1151*8392b74bSAdrian Hunter 1152*8392b74bSAdrian Hunter def __init__(self, glb, table_name, parent=None): 1153*8392b74bSAdrian Hunter sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz) 1154*8392b74bSAdrian Hunter if table_name == "comm_threads_view": 1155*8392b74bSAdrian Hunter # For now, comm_threads_view has no id column 1156*8392b74bSAdrian Hunter sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) 1157*8392b74bSAdrian Hunter self.column_headers = [] 1158*8392b74bSAdrian Hunter query = QSqlQuery(glb.db) 1159*8392b74bSAdrian Hunter if glb.dbref.is_sqlite3: 1160*8392b74bSAdrian Hunter QueryExec(query, "PRAGMA table_info(" + table_name + ")") 1161*8392b74bSAdrian Hunter while query.next(): 1162*8392b74bSAdrian Hunter self.column_headers.append(query.value(1)) 1163*8392b74bSAdrian Hunter if table_name == "sqlite_master": 1164*8392b74bSAdrian Hunter sql = "SELECT * FROM " + table_name 1165*8392b74bSAdrian Hunter else: 1166*8392b74bSAdrian Hunter if table_name[:19] == "information_schema.": 1167*8392b74bSAdrian Hunter sql = "SELECT * FROM " + table_name 1168*8392b74bSAdrian Hunter select_table_name = table_name[19:] 1169*8392b74bSAdrian Hunter schema = "information_schema" 1170*8392b74bSAdrian Hunter else: 1171*8392b74bSAdrian Hunter select_table_name = table_name 1172*8392b74bSAdrian Hunter schema = "public" 1173*8392b74bSAdrian Hunter QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") 1174*8392b74bSAdrian Hunter while query.next(): 1175*8392b74bSAdrian Hunter self.column_headers.append(query.value(0)) 1176*8392b74bSAdrian Hunter super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent) 1177*8392b74bSAdrian Hunter 1178*8392b74bSAdrian Hunter def columnCount(self, parent=None): 1179*8392b74bSAdrian Hunter return len(self.column_headers) 1180*8392b74bSAdrian Hunter 1181*8392b74bSAdrian Hunter def columnHeader(self, column): 1182*8392b74bSAdrian Hunter return self.column_headers[column] 1183*8392b74bSAdrian Hunter 1184*8392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents 1185*8392b74bSAdrian Hunter 1186*8392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject): 1187*8392b74bSAdrian Hunter 1188*8392b74bSAdrian Hunter def __init__(self, parent=None): 1189*8392b74bSAdrian Hunter super(ResizeColumnsToContentsBase, self).__init__(parent) 1190*8392b74bSAdrian Hunter 1191*8392b74bSAdrian Hunter def ResizeColumnToContents(self, column, n): 1192*8392b74bSAdrian Hunter # Using the view's resizeColumnToContents() here is extrememly slow 1193*8392b74bSAdrian Hunter # so implement a crude alternative 1194*8392b74bSAdrian Hunter font = self.view.font() 1195*8392b74bSAdrian Hunter metrics = QFontMetrics(font) 1196*8392b74bSAdrian Hunter max = 0 1197*8392b74bSAdrian Hunter for row in xrange(n): 1198*8392b74bSAdrian Hunter val = self.data_model.child_items[row].data[column] 1199*8392b74bSAdrian Hunter len = metrics.width(str(val) + "MM") 1200*8392b74bSAdrian Hunter max = len if len > max else max 1201*8392b74bSAdrian Hunter val = self.data_model.columnHeader(column) 1202*8392b74bSAdrian Hunter len = metrics.width(str(val) + "MM") 1203*8392b74bSAdrian Hunter max = len if len > max else max 1204*8392b74bSAdrian Hunter self.view.setColumnWidth(column, max) 1205*8392b74bSAdrian Hunter 1206*8392b74bSAdrian Hunter def ResizeColumnsToContents(self): 1207*8392b74bSAdrian Hunter n = min(self.data_model.child_count, 100) 1208*8392b74bSAdrian Hunter if n < 1: 1209*8392b74bSAdrian Hunter # No data yet, so connect a signal to notify when there is 1210*8392b74bSAdrian Hunter self.data_model.rowsInserted.connect(self.UpdateColumnWidths) 1211*8392b74bSAdrian Hunter return 1212*8392b74bSAdrian Hunter columns = self.data_model.columnCount() 1213*8392b74bSAdrian Hunter for i in xrange(columns): 1214*8392b74bSAdrian Hunter self.ResizeColumnToContents(i, n) 1215*8392b74bSAdrian Hunter 1216*8392b74bSAdrian Hunter def UpdateColumnWidths(self, *x): 1217*8392b74bSAdrian Hunter # This only needs to be done once, so disconnect the signal now 1218*8392b74bSAdrian Hunter self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths) 1219*8392b74bSAdrian Hunter self.ResizeColumnsToContents() 1220*8392b74bSAdrian Hunter 1221*8392b74bSAdrian Hunter# Table window 1222*8392b74bSAdrian Hunter 1223*8392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase): 1224*8392b74bSAdrian Hunter 1225*8392b74bSAdrian Hunter def __init__(self, glb, table_name, parent=None): 1226*8392b74bSAdrian Hunter super(TableWindow, self).__init__(parent) 1227*8392b74bSAdrian Hunter 1228*8392b74bSAdrian Hunter self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name)) 1229*8392b74bSAdrian Hunter 1230*8392b74bSAdrian Hunter self.model = QSortFilterProxyModel() 1231*8392b74bSAdrian Hunter self.model.setSourceModel(self.data_model) 1232*8392b74bSAdrian Hunter 1233*8392b74bSAdrian Hunter self.view = QTableView() 1234*8392b74bSAdrian Hunter self.view.setModel(self.model) 1235*8392b74bSAdrian Hunter self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) 1236*8392b74bSAdrian Hunter self.view.verticalHeader().setVisible(False) 1237*8392b74bSAdrian Hunter self.view.sortByColumn(-1, Qt.AscendingOrder) 1238*8392b74bSAdrian Hunter self.view.setSortingEnabled(True) 1239*8392b74bSAdrian Hunter 1240*8392b74bSAdrian Hunter self.ResizeColumnsToContents() 1241*8392b74bSAdrian Hunter 1242*8392b74bSAdrian Hunter self.find_bar = FindBar(self, self, True) 1243*8392b74bSAdrian Hunter 1244*8392b74bSAdrian Hunter self.finder = ChildDataItemFinder(self.data_model) 1245*8392b74bSAdrian Hunter 1246*8392b74bSAdrian Hunter self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) 1247*8392b74bSAdrian Hunter 1248*8392b74bSAdrian Hunter self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) 1249*8392b74bSAdrian Hunter 1250*8392b74bSAdrian Hunter self.setWidget(self.vbox.Widget()) 1251*8392b74bSAdrian Hunter 1252*8392b74bSAdrian Hunter AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table") 1253*8392b74bSAdrian Hunter 1254*8392b74bSAdrian Hunter def Find(self, value, direction, pattern, context): 1255*8392b74bSAdrian Hunter self.view.setFocus() 1256*8392b74bSAdrian Hunter self.find_bar.Busy() 1257*8392b74bSAdrian Hunter self.finder.Find(value, direction, pattern, context, self.FindDone) 1258*8392b74bSAdrian Hunter 1259*8392b74bSAdrian Hunter def FindDone(self, row): 1260*8392b74bSAdrian Hunter self.find_bar.Idle() 1261*8392b74bSAdrian Hunter if row >= 0: 1262*8392b74bSAdrian Hunter self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) 1263*8392b74bSAdrian Hunter else: 1264*8392b74bSAdrian Hunter self.find_bar.NotFound() 1265*8392b74bSAdrian Hunter 1266*8392b74bSAdrian Hunter# Table list 1267*8392b74bSAdrian Hunter 1268*8392b74bSAdrian Hunterdef GetTableList(glb): 1269*8392b74bSAdrian Hunter tables = [] 1270*8392b74bSAdrian Hunter query = QSqlQuery(glb.db) 1271*8392b74bSAdrian Hunter if glb.dbref.is_sqlite3: 1272*8392b74bSAdrian Hunter QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name") 1273*8392b74bSAdrian Hunter else: 1274*8392b74bSAdrian Hunter QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name") 1275*8392b74bSAdrian Hunter while query.next(): 1276*8392b74bSAdrian Hunter tables.append(query.value(0)) 1277*8392b74bSAdrian Hunter if glb.dbref.is_sqlite3: 1278*8392b74bSAdrian Hunter tables.append("sqlite_master") 1279*8392b74bSAdrian Hunter else: 1280*8392b74bSAdrian Hunter tables.append("information_schema.tables") 1281*8392b74bSAdrian Hunter tables.append("information_schema.views") 1282*8392b74bSAdrian Hunter tables.append("information_schema.columns") 1283*8392b74bSAdrian Hunter return tables 1284*8392b74bSAdrian Hunter 12851beb5c7bSAdrian Hunter# Action Definition 12861beb5c7bSAdrian Hunter 12871beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None): 12881beb5c7bSAdrian Hunter action = QAction(label, parent) 12891beb5c7bSAdrian Hunter if shortcut != None: 12901beb5c7bSAdrian Hunter action.setShortcuts(shortcut) 12911beb5c7bSAdrian Hunter action.setStatusTip(tip) 12921beb5c7bSAdrian Hunter action.triggered.connect(callback) 12931beb5c7bSAdrian Hunter return action 12941beb5c7bSAdrian Hunter 12951beb5c7bSAdrian Hunter# Typical application actions 12961beb5c7bSAdrian Hunter 12971beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None): 12981beb5c7bSAdrian Hunter return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit) 12991beb5c7bSAdrian Hunter 13001beb5c7bSAdrian Hunter# Typical MDI actions 13011beb5c7bSAdrian Hunter 13021beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area): 13031beb5c7bSAdrian Hunter return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area) 13041beb5c7bSAdrian Hunter 13051beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area): 13061beb5c7bSAdrian Hunter return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area) 13071beb5c7bSAdrian Hunter 13081beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area): 13091beb5c7bSAdrian Hunter return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area) 13101beb5c7bSAdrian Hunter 13111beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area): 13121beb5c7bSAdrian Hunter return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area) 13131beb5c7bSAdrian Hunter 13141beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area): 13151beb5c7bSAdrian Hunter return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild) 13161beb5c7bSAdrian Hunter 13171beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area): 13181beb5c7bSAdrian Hunter return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild) 13191beb5c7bSAdrian Hunter 13201beb5c7bSAdrian Hunter# Typical MDI window menu 13211beb5c7bSAdrian Hunter 13221beb5c7bSAdrian Hunterclass WindowMenu(): 13231beb5c7bSAdrian Hunter 13241beb5c7bSAdrian Hunter def __init__(self, mdi_area, menu): 13251beb5c7bSAdrian Hunter self.mdi_area = mdi_area 13261beb5c7bSAdrian Hunter self.window_menu = menu.addMenu("&Windows") 13271beb5c7bSAdrian Hunter self.close_active_window = CreateCloseActiveWindowAction(mdi_area) 13281beb5c7bSAdrian Hunter self.close_all_windows = CreateCloseAllWindowsAction(mdi_area) 13291beb5c7bSAdrian Hunter self.tile_windows = CreateTileWindowsAction(mdi_area) 13301beb5c7bSAdrian Hunter self.cascade_windows = CreateCascadeWindowsAction(mdi_area) 13311beb5c7bSAdrian Hunter self.next_window = CreateNextWindowAction(mdi_area) 13321beb5c7bSAdrian Hunter self.previous_window = CreatePreviousWindowAction(mdi_area) 13331beb5c7bSAdrian Hunter self.window_menu.aboutToShow.connect(self.Update) 13341beb5c7bSAdrian Hunter 13351beb5c7bSAdrian Hunter def Update(self): 13361beb5c7bSAdrian Hunter self.window_menu.clear() 13371beb5c7bSAdrian Hunter sub_window_count = len(self.mdi_area.subWindowList()) 13381beb5c7bSAdrian Hunter have_sub_windows = sub_window_count != 0 13391beb5c7bSAdrian Hunter self.close_active_window.setEnabled(have_sub_windows) 13401beb5c7bSAdrian Hunter self.close_all_windows.setEnabled(have_sub_windows) 13411beb5c7bSAdrian Hunter self.tile_windows.setEnabled(have_sub_windows) 13421beb5c7bSAdrian Hunter self.cascade_windows.setEnabled(have_sub_windows) 13431beb5c7bSAdrian Hunter self.next_window.setEnabled(have_sub_windows) 13441beb5c7bSAdrian Hunter self.previous_window.setEnabled(have_sub_windows) 13451beb5c7bSAdrian Hunter self.window_menu.addAction(self.close_active_window) 13461beb5c7bSAdrian Hunter self.window_menu.addAction(self.close_all_windows) 13471beb5c7bSAdrian Hunter self.window_menu.addSeparator() 13481beb5c7bSAdrian Hunter self.window_menu.addAction(self.tile_windows) 13491beb5c7bSAdrian Hunter self.window_menu.addAction(self.cascade_windows) 13501beb5c7bSAdrian Hunter self.window_menu.addSeparator() 13511beb5c7bSAdrian Hunter self.window_menu.addAction(self.next_window) 13521beb5c7bSAdrian Hunter self.window_menu.addAction(self.previous_window) 13531beb5c7bSAdrian Hunter if sub_window_count == 0: 13541beb5c7bSAdrian Hunter return 13551beb5c7bSAdrian Hunter self.window_menu.addSeparator() 13561beb5c7bSAdrian Hunter nr = 1 13571beb5c7bSAdrian Hunter for sub_window in self.mdi_area.subWindowList(): 13581beb5c7bSAdrian Hunter label = str(nr) + " " + sub_window.name 13591beb5c7bSAdrian Hunter if nr < 10: 13601beb5c7bSAdrian Hunter label = "&" + label 13611beb5c7bSAdrian Hunter action = self.window_menu.addAction(label) 13621beb5c7bSAdrian Hunter action.setCheckable(True) 13631beb5c7bSAdrian Hunter action.setChecked(sub_window == self.mdi_area.activeSubWindow()) 13641beb5c7bSAdrian Hunter action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x)) 13651beb5c7bSAdrian Hunter self.window_menu.addAction(action) 13661beb5c7bSAdrian Hunter nr += 1 13671beb5c7bSAdrian Hunter 13681beb5c7bSAdrian Hunter def setActiveSubWindow(self, nr): 13691beb5c7bSAdrian Hunter self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) 13701beb5c7bSAdrian Hunter 137182f68e28SAdrian Hunter# Font resize 137282f68e28SAdrian Hunter 137382f68e28SAdrian Hunterdef ResizeFont(widget, diff): 137482f68e28SAdrian Hunter font = widget.font() 137582f68e28SAdrian Hunter sz = font.pointSize() 137682f68e28SAdrian Hunter font.setPointSize(sz + diff) 137782f68e28SAdrian Hunter widget.setFont(font) 137882f68e28SAdrian Hunter 137982f68e28SAdrian Hunterdef ShrinkFont(widget): 138082f68e28SAdrian Hunter ResizeFont(widget, -1) 138182f68e28SAdrian Hunter 138282f68e28SAdrian Hunterdef EnlargeFont(widget): 138382f68e28SAdrian Hunter ResizeFont(widget, 1) 138482f68e28SAdrian Hunter 13851beb5c7bSAdrian Hunter# Unique name for sub-windows 13861beb5c7bSAdrian Hunter 13871beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr): 13881beb5c7bSAdrian Hunter if nr > 1: 13891beb5c7bSAdrian Hunter name += " <" + str(nr) + ">" 13901beb5c7bSAdrian Hunter return name 13911beb5c7bSAdrian Hunter 13921beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name): 13931beb5c7bSAdrian Hunter nr = 1 13941beb5c7bSAdrian Hunter while True: 13951beb5c7bSAdrian Hunter unique_name = NumberedWindowName(name, nr) 13961beb5c7bSAdrian Hunter ok = True 13971beb5c7bSAdrian Hunter for sub_window in mdi_area.subWindowList(): 13981beb5c7bSAdrian Hunter if sub_window.name == unique_name: 13991beb5c7bSAdrian Hunter ok = False 14001beb5c7bSAdrian Hunter break 14011beb5c7bSAdrian Hunter if ok: 14021beb5c7bSAdrian Hunter return unique_name 14031beb5c7bSAdrian Hunter nr += 1 14041beb5c7bSAdrian Hunter 14051beb5c7bSAdrian Hunter# Add a sub-window 14061beb5c7bSAdrian Hunter 14071beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name): 14081beb5c7bSAdrian Hunter unique_name = UniqueSubWindowName(mdi_area, name) 14091beb5c7bSAdrian Hunter sub_window.setMinimumSize(200, 100) 14101beb5c7bSAdrian Hunter sub_window.resize(800, 600) 14111beb5c7bSAdrian Hunter sub_window.setWindowTitle(unique_name) 14121beb5c7bSAdrian Hunter sub_window.setAttribute(Qt.WA_DeleteOnClose) 14131beb5c7bSAdrian Hunter sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon)) 14141beb5c7bSAdrian Hunter sub_window.name = unique_name 14151beb5c7bSAdrian Hunter mdi_area.addSubWindow(sub_window) 14161beb5c7bSAdrian Hunter sub_window.show() 14171beb5c7bSAdrian Hunter 1418031c2a00SAdrian Hunter# Main window 1419031c2a00SAdrian Hunter 1420031c2a00SAdrian Hunterclass MainWindow(QMainWindow): 1421031c2a00SAdrian Hunter 1422031c2a00SAdrian Hunter def __init__(self, glb, parent=None): 1423031c2a00SAdrian Hunter super(MainWindow, self).__init__(parent) 1424031c2a00SAdrian Hunter 1425031c2a00SAdrian Hunter self.glb = glb 1426031c2a00SAdrian Hunter 14271beb5c7bSAdrian Hunter self.setWindowTitle("Exported SQL Viewer: " + glb.dbname) 1428031c2a00SAdrian Hunter self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) 1429031c2a00SAdrian Hunter self.setMinimumSize(200, 100) 1430031c2a00SAdrian Hunter 14311beb5c7bSAdrian Hunter self.mdi_area = QMdiArea() 14321beb5c7bSAdrian Hunter self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) 14331beb5c7bSAdrian Hunter self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) 1434031c2a00SAdrian Hunter 14351beb5c7bSAdrian Hunter self.setCentralWidget(self.mdi_area) 1436031c2a00SAdrian Hunter 14371beb5c7bSAdrian Hunter menu = self.menuBar() 1438031c2a00SAdrian Hunter 14391beb5c7bSAdrian Hunter file_menu = menu.addMenu("&File") 14401beb5c7bSAdrian Hunter file_menu.addAction(CreateExitAction(glb.app, self)) 14411beb5c7bSAdrian Hunter 1442ebd70c7dSAdrian Hunter edit_menu = menu.addMenu("&Edit") 1443ebd70c7dSAdrian Hunter edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find)) 1444*8392b74bSAdrian Hunter edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)])) 144582f68e28SAdrian Hunter edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")])) 144682f68e28SAdrian Hunter edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) 1447ebd70c7dSAdrian Hunter 14481beb5c7bSAdrian Hunter reports_menu = menu.addMenu("&Reports") 14491beb5c7bSAdrian Hunter reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) 14501beb5c7bSAdrian Hunter 1451*8392b74bSAdrian Hunter self.TableMenu(GetTableList(glb), menu) 1452*8392b74bSAdrian Hunter 14531beb5c7bSAdrian Hunter self.window_menu = WindowMenu(self.mdi_area, menu) 14541beb5c7bSAdrian Hunter 1455ebd70c7dSAdrian Hunter def Find(self): 1456ebd70c7dSAdrian Hunter win = self.mdi_area.activeSubWindow() 1457ebd70c7dSAdrian Hunter if win: 1458ebd70c7dSAdrian Hunter try: 1459ebd70c7dSAdrian Hunter win.find_bar.Activate() 1460ebd70c7dSAdrian Hunter except: 1461ebd70c7dSAdrian Hunter pass 1462ebd70c7dSAdrian Hunter 1463*8392b74bSAdrian Hunter def FetchMoreRecords(self): 1464*8392b74bSAdrian Hunter win = self.mdi_area.activeSubWindow() 1465*8392b74bSAdrian Hunter if win: 1466*8392b74bSAdrian Hunter try: 1467*8392b74bSAdrian Hunter win.fetch_bar.Activate() 1468*8392b74bSAdrian Hunter except: 1469*8392b74bSAdrian Hunter pass 1470*8392b74bSAdrian Hunter 147182f68e28SAdrian Hunter def ShrinkFont(self): 147282f68e28SAdrian Hunter win = self.mdi_area.activeSubWindow() 147382f68e28SAdrian Hunter ShrinkFont(win.view) 147482f68e28SAdrian Hunter 147582f68e28SAdrian Hunter def EnlargeFont(self): 147682f68e28SAdrian Hunter win = self.mdi_area.activeSubWindow() 147782f68e28SAdrian Hunter EnlargeFont(win.view) 147882f68e28SAdrian Hunter 1479*8392b74bSAdrian Hunter def TableMenu(self, tables, menu): 1480*8392b74bSAdrian Hunter table_menu = menu.addMenu("&Tables") 1481*8392b74bSAdrian Hunter for table in tables: 1482*8392b74bSAdrian Hunter table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self)) 1483*8392b74bSAdrian Hunter 14841beb5c7bSAdrian Hunter def NewCallGraph(self): 14851beb5c7bSAdrian Hunter CallGraphWindow(self.glb, self) 1486031c2a00SAdrian Hunter 1487*8392b74bSAdrian Hunter def NewTableView(self, table_name): 1488*8392b74bSAdrian Hunter TableWindow(self.glb, table_name, self) 1489*8392b74bSAdrian Hunter 1490031c2a00SAdrian Hunter# Global data 1491031c2a00SAdrian Hunter 1492031c2a00SAdrian Hunterclass Glb(): 1493031c2a00SAdrian Hunter 1494031c2a00SAdrian Hunter def __init__(self, dbref, db, dbname): 1495031c2a00SAdrian Hunter self.dbref = dbref 1496031c2a00SAdrian Hunter self.db = db 1497031c2a00SAdrian Hunter self.dbname = dbname 1498031c2a00SAdrian Hunter self.app = None 1499031c2a00SAdrian Hunter self.mainwindow = None 1500*8392b74bSAdrian Hunter self.instances_to_shutdown_on_exit = weakref.WeakSet() 1501*8392b74bSAdrian Hunter 1502*8392b74bSAdrian Hunter def AddInstanceToShutdownOnExit(self, instance): 1503*8392b74bSAdrian Hunter self.instances_to_shutdown_on_exit.add(instance) 1504*8392b74bSAdrian Hunter 1505*8392b74bSAdrian Hunter # Shutdown any background processes or threads 1506*8392b74bSAdrian Hunter def ShutdownInstances(self): 1507*8392b74bSAdrian Hunter for x in self.instances_to_shutdown_on_exit: 1508*8392b74bSAdrian Hunter try: 1509*8392b74bSAdrian Hunter x.Shutdown() 1510*8392b74bSAdrian Hunter except: 1511*8392b74bSAdrian Hunter pass 1512031c2a00SAdrian Hunter 1513031c2a00SAdrian Hunter# Database reference 1514031c2a00SAdrian Hunter 1515031c2a00SAdrian Hunterclass DBRef(): 1516031c2a00SAdrian Hunter 1517031c2a00SAdrian Hunter def __init__(self, is_sqlite3, dbname): 1518031c2a00SAdrian Hunter self.is_sqlite3 = is_sqlite3 1519031c2a00SAdrian Hunter self.dbname = dbname 1520031c2a00SAdrian Hunter 1521031c2a00SAdrian Hunter def Open(self, connection_name): 1522031c2a00SAdrian Hunter dbname = self.dbname 1523031c2a00SAdrian Hunter if self.is_sqlite3: 1524031c2a00SAdrian Hunter db = QSqlDatabase.addDatabase("QSQLITE", connection_name) 1525031c2a00SAdrian Hunter else: 1526031c2a00SAdrian Hunter db = QSqlDatabase.addDatabase("QPSQL", connection_name) 1527031c2a00SAdrian Hunter opts = dbname.split() 1528031c2a00SAdrian Hunter for opt in opts: 1529031c2a00SAdrian Hunter if "=" in opt: 1530031c2a00SAdrian Hunter opt = opt.split("=") 1531031c2a00SAdrian Hunter if opt[0] == "hostname": 1532031c2a00SAdrian Hunter db.setHostName(opt[1]) 1533031c2a00SAdrian Hunter elif opt[0] == "port": 1534031c2a00SAdrian Hunter db.setPort(int(opt[1])) 1535031c2a00SAdrian Hunter elif opt[0] == "username": 1536031c2a00SAdrian Hunter db.setUserName(opt[1]) 1537031c2a00SAdrian Hunter elif opt[0] == "password": 1538031c2a00SAdrian Hunter db.setPassword(opt[1]) 1539031c2a00SAdrian Hunter elif opt[0] == "dbname": 1540031c2a00SAdrian Hunter dbname = opt[1] 1541031c2a00SAdrian Hunter else: 1542031c2a00SAdrian Hunter dbname = opt 1543031c2a00SAdrian Hunter 1544031c2a00SAdrian Hunter db.setDatabaseName(dbname) 1545031c2a00SAdrian Hunter if not db.open(): 1546031c2a00SAdrian Hunter raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text()) 1547031c2a00SAdrian Hunter return db, dbname 1548031c2a00SAdrian Hunter 1549031c2a00SAdrian Hunter# Main 1550031c2a00SAdrian Hunter 1551031c2a00SAdrian Hunterdef Main(): 1552031c2a00SAdrian Hunter if (len(sys.argv) < 2): 1553031c2a00SAdrian Hunter print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>" 1554031c2a00SAdrian Hunter raise Exception("Too few arguments") 1555031c2a00SAdrian Hunter 1556031c2a00SAdrian Hunter dbname = sys.argv[1] 1557031c2a00SAdrian Hunter 1558031c2a00SAdrian Hunter is_sqlite3 = False 1559031c2a00SAdrian Hunter try: 1560031c2a00SAdrian Hunter f = open(dbname) 1561031c2a00SAdrian Hunter if f.read(15) == "SQLite format 3": 1562031c2a00SAdrian Hunter is_sqlite3 = True 1563031c2a00SAdrian Hunter f.close() 1564031c2a00SAdrian Hunter except: 1565031c2a00SAdrian Hunter pass 1566031c2a00SAdrian Hunter 1567031c2a00SAdrian Hunter dbref = DBRef(is_sqlite3, dbname) 1568031c2a00SAdrian Hunter db, dbname = dbref.Open("main") 1569031c2a00SAdrian Hunter glb = Glb(dbref, db, dbname) 1570031c2a00SAdrian Hunter app = QApplication(sys.argv) 1571031c2a00SAdrian Hunter glb.app = app 1572031c2a00SAdrian Hunter mainwindow = MainWindow(glb) 1573031c2a00SAdrian Hunter glb.mainwindow = mainwindow 1574031c2a00SAdrian Hunter mainwindow.show() 1575031c2a00SAdrian Hunter err = app.exec_() 1576*8392b74bSAdrian Hunter glb.ShutdownInstances() 1577031c2a00SAdrian Hunter db.close() 1578031c2a00SAdrian Hunter sys.exit(err) 1579031c2a00SAdrian Hunter 1580031c2a00SAdrian Hunterif __name__ == "__main__": 1581031c2a00SAdrian Hunter Main() 1582