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