def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default) def DoValidate(self, input_string): if not self.IsNumber(input_string.strip()): return self.InvalidValue(input_string) value = int(input_string.strip()) if value <= 0: return self.InvalidValue(input_string) self.value = str(value) # Dialog data item converted and validated using a SQL table class SQLTableDataItem(LineEditDataItem): def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent): super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent) self.table_name = table_name self.match_column = match_column self.column_name1 = column_name1 self.column_name2 = column_name2 def ValueToIds(self, value): ids = [] query = QSqlQuery(self.glb.db) stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'" ret = query.exec_(stmt) if ret: while query.next(): ids.append(str(query.value(0))) return ids def DoValidate(self, input_string): all_ids = [] for value in [x.strip() for x in input_string.split(",")]: ids = self.ValueToIds(value) if len(ids): all_ids.extend(ids) else: return self.InvalidValue(value) self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")" if self.column_name2: self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )" # Sample time ranges dialog data item converted and validated using 'samples' SQL table class SampleTimeRangesDataItem(LineEditDataItem): def __init__(self, glb, label, placeholder_text, column_name, parent): self.column_name = column_name self.last_id = 0 self.first_time = 0 self.last_time = 2 ** 64 query = QSqlQuery(glb.db) QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1") if query.next(): self.last_id = int(query.value(0)) self.last_time = int(query.value(1)) QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1") if query.next(): self.first_time = int(query.value(0)) if placeholder_text: placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time) super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent) def IdBetween(self, query, lower_id, higher_id, order): QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1") if query.next(): return True, int(query.value(0)) else: return False, 0 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor): query = QSqlQuery(self.glb.db) while True: next_id = int((lower_id + higher_id) / 2) QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) if not query.next(): ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC") if not ok: ok, dbid = self.IdBetween(query, next_id, higher_id, "") if not ok: return str(higher_id) next_id = dbid QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) next_time = int(query.value(0)) if get_floor: if target_time > next_time: lower_id = next_id else: higher_id = next_id if higher_id <= lower_id + 1: return str(higher_id) else: if target_time >= next_time: lower_id = next_id else: higher_id = next_id if higher_id <= lower_id + 1: return str(lower_id) def ConvertRelativeTime(self, val): mult = 1 suffix = val[-2:] if suffix == "ms": mult = 1000000 elif suffix == "us": mult = 1000 elif suffix == "ns": mult = 1 else: return val val = val[:-2].strip() if not self.IsNumber(val): return val val = int(val) * mult if val >= 0: val += self.first_time else: val += self.last_time return str(val) def ConvertTimeRange(self, vrange): if vrange[0] == "": vrange[0] = str(self.first_time) if vrange[1] == "": vrange[1] = str(self.last_time) vrange[0] = self.ConvertRelativeTime(vrange[0]) vrange[1] = self.ConvertRelativeTime(vrange[1]) if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): return False beg_range = max(int(vrange[0]), self.first_time) end_range = min(int(vrange[1]), self.last_time) if beg_range > self.last_time or end_range < self.first_time: return False vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True) vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False) return True def AddTimeRange(self, value, ranges): n = value.count("-") if n == 1: pass elif n == 2: if value.split("-")[1].strip() == "": n = 1 elif n == 3: n = 2 else: return False pos = findnth(value, "-", n) vrange = [value[:pos].strip() ,value[pos+1:].strip()] if self.ConvertTimeRange(vrange): ranges.append(vrange) return True return False def DoValidate(self, input_string): ranges = [] for value in [x.strip() for x in input_string.split(",")]: if not self.AddTimeRange(value, ranges): return self.InvalidValue(value) ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges] self.value = " OR ".join(ranges) # Report Dialog Base class ReportDialogBase(QDialog): def __init__(self, glb, title, items, partial, parent=None): super(ReportDialogBase, self).__init__(parent) self.glb = glb self.report_vars = ReportVars() self.setWindowTitle(title) self.setMinimumWidth(600) self.data_items = [x(glb, self) for x in items] self.partial = partial self.grid = QGridLayout() for row in xrange(len(self.data_items)): self.grid.addWidget(QLabel(self.data_items[row].label), row, 0) self.grid.addWidget(self.data_items[row].widget, row, 1) self.status = QLabel() self.ok_button = QPushButton("Ok", self) self.ok_button.setDefault(True) self.ok_button.released.connect(self.Ok) self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.cancel_button = QPushButton("Cancel", self) self.cancel_button.released.connect(self.reject) self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.hbox = QHBoxLayout() #self.hbox.addStretch() self.hbox.addWidget(self.status) self.hbox.addWidget(self.ok_button) self.hbox.addWidget(self.cancel_button) self.vbox = QVBoxLayout() self.vbox.addLayout(self.grid) self.vbox.addLayout(self.hbox) self.setLayout(self.vbox) def Ok(self): vars = self.report_vars for d in self.data_items: if d.id == "REPORTNAME": vars.name = d.value if not vars.name: self.ShowMessage("Report name is required") return for d in self.data_items: if not d.IsValid(): return for d in self.data_items[1:]: if d.id == "LIMIT": vars.limit = d.value elif len(d.value): if len(vars.where_clause): vars.where_clause += " AND " vars.where_clause += d.value if len(vars.where_clause): if self.partial: vars.where_clause = " AND ( " + vars.where_clause + " ) " else: vars.where_clause = " WHERE " + vars.where_clause + " " self.accept() def ShowMessage(self, msg): self.status.setText("<font color=#FF0000>" + msg) def ClearMessage(self): self.status.setText("") # Selected branch report creation dialog class SelectedBranchDialog(ReportDialogBase): def __init__(self, glb, parent=None): title = "Selected Branches" items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p), lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p), lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p), lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p), lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p), lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p)) super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent) # Event list def GetEventList(db): events = [] query = QSqlQuery(db) QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id") while query.next(): events.append(query.value(0)) return events # Is a table selectable def IsSelectable(db, table, sql = "", columns = "*"): query = QSqlQuery(db) try: QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1") except: return False return True # SQL table data model item class SQLTableItem(): def __init__(self, row, data): self.row = row self.data = data def getData(self, column): return self.data[column] # SQL table data model class SQLTableModel(TableModel): progress = Signal(object) def __init__(self, glb, sql, column_headers, parent=None): super(SQLTableModel, self).__init__(parent) self.glb = glb self.more = True self.populated = 0 self.column_headers = column_headers self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample) self.fetcher.done.connect(self.Update) self.fetcher.Fetch(glb_chunk_sz) def DisplayData(self, item, index): self.FetchIfNeeded(item.row) return item.getData(index.column()) def AddSample(self, data): child = SQLTableItem(self.populated, data) self.child_items.append(child) self.populated += 1 def Update(self, fetched): if not fetched: self.more = False self.progress.emit(0) child_count = self.child_count count = self.populated - child_count if count > 0: parent = QModelIndex() self.beginInsertRows(parent, child_count, child_count + count - 1) self.insertRows(child_count, count, parent) self.child_count += count self.endInsertRows() self.progress.emit(self.child_count) def FetchMoreRecords(self, count): current = self.child_count if self.more: self.fetcher.Fetch(count) else: self.progress.emit(0) return current def HasMoreRecords(self): return self.more def columnCount(self, parent=None): return len(self.column_headers) def columnHeader(self, column): return self.column_headers[column] def SQLTableDataPrep(self, query, count): data = [] for i in xrange(count): data.append(query.value(i)) return data # SQL automatic table data model class SQLAutoTableModel(SQLTableModel): def __init__(self, glb, table_name, parent=None): sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz) if table_name == "comm_threads_view": # For now, comm_threads_view has no id column sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) column_headers = [] query = QSqlQuery(glb.db) if glb.dbref.is_sqlite3: QueryExec(query, "PRAGMA table_info(" + table_name + ")") while query.next(): column_headers.append(query.value(1)) if table_name == "sqlite_master": sql = "SELECT * FROM " + table_name else: if table_name[:19] == "information_schema.": sql = "SELECT * FROM " + table_name select_table_name = table_name[19:] schema = "information_schema" else: select_table_name = table_name schema = "public" QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") while query.next(): column_headers.append(query.value(0)) if pyside_version_1 and sys.version_info[0] == 3: if table_name == "samples_view": self.SQLTableDataPrep = self.samples_view_DataPrep if table_name == "samples": self.SQLTableDataPrep = self.samples_DataPrep super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent) def samples_view_DataPrep(self, query, count): data = [] data.append(query.value(0)) # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string data.append("{:>19}".format(query.value(1))) for i in xrange(2, count): data.append(query.value(i)) return data def samples_DataPrep(self, query, count): data = [] for i in xrange(9): data.append(query.value(i)) # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string data.append("{:>19}".format(query.value(9))) for i in xrange(10, count): data.append(query.value(i)) return data # Base class for custom ResizeColumnsToContents class ResizeColumnsToContentsBase(QObject): def __init__(self, parent=None): super(ResizeColumnsToContentsBase, self).__init__(parent) def ResizeColumnToContents(self, column, n): # Using the view's resizeColumnToContents() here is extrememly slow # so implement a crude alternative font = self.view.font() metrics = QFontMetrics(font) max = 0 for row in xrange(n): val = self.data_model.child_items[row].data[column] len = metrics.width(str(val) + "MM") max = len if len > max else max val = self.data_model.columnHeader(column) len = metrics.width(str(val) + "MM") max = len if len > max else max self.view.setColumnWidth(column, max) def ResizeColumnsToContents(self): n = min(self.data_model.child_count, 100) if n < 1: # No data yet, so connect a signal to notify when there is self.data_model.rowsInserted.connect(self.UpdateColumnWidths) return columns = self.data_model.columnCount() for i in xrange(columns): self.ResizeColumnToContents(i, n) def UpdateColumnWidths(self, *x): # This only needs to be done once, so disconnect the signal now self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths) self.ResizeColumnsToContents() # Convert value to CSV def ToCSValue(val): if '"' in val: val = val.replace('"', '""') if "," in val or '"' in val: val = '"' + val + '"' return val # Key to sort table model indexes by row / column, assuming fewer than 1000 columns glb_max_cols = 1000 def RowColumnKey(a): return a.row() * glb_max_cols + a.column() # Copy selected table cells to clipboard def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False): indexes = sorted(view.selectedIndexes(), key=RowColumnKey) idx_cnt = len(indexes) if not idx_cnt: return if idx_cnt == 1: with_hdr=False min_row = indexes[0].row() max_row = indexes[0].row() min_col = indexes[0].column() max_col = indexes[0].column() for i in indexes: min_row = min(min_row, i.row()) max_row = max(max_row, i.row()) min_col = min(min_col, i.column()) max_col = max(max_col, i.column()) if max_col > glb_max_cols: raise RuntimeError("glb_max_cols is too low") max_width = [0] * (1 + max_col - min_col) for i in indexes: c = i.column() - min_col max_width[c] = max(max_width[c], len(str(i.data()))) text = "" pad = "" sep = "" if with_hdr: model = indexes[0].model() for col in range(min_col, max_col + 1): val = model.headerData(col, Qt.Horizontal) if as_csv: text += sep + ToCSValue(val) sep = "," else: c = col - min_colLines 2001–2500 of 3588. Previous lines Load more
def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default) def DoValidate(self, input_string): if not self.IsNumber(input_string.strip()): return self.InvalidValue(input_string) value = int(input_string.strip()) if value <= 0: return self.InvalidValue(input_string) self.value = str(value) # Dialog data item converted and validated using a SQL table class SQLTableDataItem(LineEditDataItem): def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent): super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent) self.table_name = table_name self.match_column = match_column self.column_name1 = column_name1 self.column_name2 = column_name2 def ValueToIds(self, value): ids = [] query = QSqlQuery(self.glb.db) stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'" ret = query.exec_(stmt) if ret: while query.next(): ids.append(str(query.value(0))) return ids def DoValidate(self, input_string): all_ids = [] for value in [x.strip() for x in input_string.split(",")]: ids = self.ValueToIds(value) if len(ids): all_ids.extend(ids) else: return self.InvalidValue(value) self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")" if self.column_name2: self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )" # Sample time ranges dialog data item converted and validated using 'samples' SQL table class SampleTimeRangesDataItem(LineEditDataItem): def __init__(self, glb, label, placeholder_text, column_name, parent): self.column_name = column_name self.last_id = 0 self.first_time = 0 self.last_time = 2 ** 64 query = QSqlQuery(glb.db) QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1") if query.next(): self.last_id = int(query.value(0)) self.last_time = int(query.value(1)) QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1") if query.next(): self.first_time = int(query.value(0)) if placeholder_text: placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time) super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent) def IdBetween(self, query, lower_id, higher_id, order): QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1") if query.next(): return True, int(query.value(0)) else: return False, 0 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor): query = QSqlQuery(self.glb.db) while True: next_id = int((lower_id + higher_id) / 2) QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) if not query.next(): ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC") if not ok: ok, dbid = self.IdBetween(query, next_id, higher_id, "") if not ok: return str(higher_id) next_id = dbid QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) next_time = int(query.value(0)) if get_floor: if target_time > next_time: lower_id = next_id else: higher_id = next_id if higher_id <= lower_id + 1: return str(higher_id) else: if target_time >= next_time: lower_id = next_id else: higher_id = next_id if higher_id <= lower_id + 1: return str(lower_id) def ConvertRelativeTime(self, val): mult = 1 suffix = val[-2:] if suffix == "ms": mult = 1000000 elif suffix == "us": mult = 1000 elif suffix == "ns": mult = 1 else: return val val = val[:-2].strip() if not self.IsNumber(val): return val val = int(val) * mult if val >= 0: val += self.first_time else: val += self.last_time return str(val) def ConvertTimeRange(self, vrange): if vrange[0] == "": vrange[0] = str(self.first_time) if vrange[1] == "": vrange[1] = str(self.last_time) vrange[0] = self.ConvertRelativeTime(vrange[0]) vrange[1] = self.ConvertRelativeTime(vrange[1]) if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): return False beg_range = max(int(vrange[0]), self.first_time) end_range = min(int(vrange[1]), self.last_time) if beg_range > self.last_time or end_range < self.first_time: return False vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True) vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False) return True def AddTimeRange(self, value, ranges): n = value.count("-") if n == 1: pass elif n == 2: if value.split("-")[1].strip() == "": n = 1 elif n == 3: n = 2 else: return False pos = findnth(value, "-", n) vrange = [value[:pos].strip() ,value[pos+1:].strip()] if self.ConvertTimeRange(vrange): ranges.append(vrange) return True return False def DoValidate(self, input_string): ranges = [] for value in [x.strip() for x in input_string.split(",")]: if not self.AddTimeRange(value, ranges): return self.InvalidValue(value) ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges] self.value = " OR ".join(ranges) # Report Dialog Base class ReportDialogBase(QDialog): def __init__(self, glb, title, items, partial, parent=None): super(ReportDialogBase, self).__init__(parent) self.glb = glb self.report_vars = ReportVars() self.setWindowTitle(title) self.setMinimumWidth(600) self.data_items = [x(glb, self) for x in items] self.partial = partial self.grid = QGridLayout() for row in xrange(len(self.data_items)): self.grid.addWidget(QLabel(self.data_items[row].label), row, 0) self.grid.addWidget(self.data_items[row].widget, row, 1) self.status = QLabel() self.ok_button = QPushButton("Ok", self) self.ok_button.setDefault(True) self.ok_button.released.connect(self.Ok) self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.cancel_button = QPushButton("Cancel", self) self.cancel_button.released.connect(self.reject) self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.hbox = QHBoxLayout() #self.hbox.addStretch() self.hbox.addWidget(self.status) self.hbox.addWidget(self.ok_button) self.hbox.addWidget(self.cancel_button) self.vbox = QVBoxLayout() self.vbox.addLayout(self.grid) self.vbox.addLayout(self.hbox) self.setLayout(self.vbox) def Ok(self): vars = self.report_vars for d in self.data_items: if d.id == "REPORTNAME": vars.name = d.value if not vars.name: self.ShowMessage("Report name is required") return for d in self.data_items: if not d.IsValid(): return for d in self.data_items[1:]: if d.id == "LIMIT": vars.limit = d.value elif len(d.value): if len(vars.where_clause): vars.where_clause += " AND " vars.where_clause += d.value if len(vars.where_clause): if self.partial: vars.where_clause = " AND ( " + vars.where_clause + " ) " else: vars.where_clause = " WHERE " + vars.where_clause + " " self.accept() def ShowMessage(self, msg): self.status.setText("<font color=#FF0000>" + msg) def ClearMessage(self): self.status.setText("") # Selected branch report creation dialog class SelectedBranchDialog(ReportDialogBase): def __init__(self, glb, parent=None): title = "Selected Branches" items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p), lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p), lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p), lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p), lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p), lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p)) super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent) # Event list def GetEventList(db): events = [] query = QSqlQuery(db) QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id") while query.next(): events.append(query.value(0)) return events # Is a table selectable def IsSelectable(db, table, sql = "", columns = "*"): query = QSqlQuery(db) try: QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1") except: return False return True # SQL table data model item class SQLTableItem(): def __init__(self, row, data): self.row = row self.data = data def getData(self, column): return self.data[column] # SQL table data model class SQLTableModel(TableModel): progress = Signal(object) def __init__(self, glb, sql, column_headers, parent=None): super(SQLTableModel, self).__init__(parent) self.glb = glb self.more = True self.populated = 0 self.column_headers = column_headers self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample) self.fetcher.done.connect(self.Update) self.fetcher.Fetch(glb_chunk_sz) def DisplayData(self, item, index): self.FetchIfNeeded(item.row) return item.getData(index.column()) def AddSample(self, data): child = SQLTableItem(self.populated, data) self.child_items.append(child) self.populated += 1 def Update(self, fetched): if not fetched: self.more = False self.progress.emit(0) child_count = self.child_count count = self.populated - child_count if count > 0: parent = QModelIndex() self.beginInsertRows(parent, child_count, child_count + count - 1) self.insertRows(child_count, count, parent) self.child_count += count self.endInsertRows() self.progress.emit(self.child_count) def FetchMoreRecords(self, count): current = self.child_count if self.more: self.fetcher.Fetch(count) else: self.progress.emit(0) return current def HasMoreRecords(self): return self.more def columnCount(self, parent=None): return len(self.column_headers) def columnHeader(self, column): return self.column_headers[column] def SQLTableDataPrep(self, query, count): data = [] for i in xrange(count): data.append(query.value(i)) return data # SQL automatic table data model class SQLAutoTableModel(SQLTableModel): def __init__(self, glb, table_name, parent=None): sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz) if table_name == "comm_threads_view": # For now, comm_threads_view has no id column sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) column_headers = [] query = QSqlQuery(glb.db) if glb.dbref.is_sqlite3: QueryExec(query, "PRAGMA table_info(" + table_name + ")") while query.next(): column_headers.append(query.value(1)) if table_name == "sqlite_master": sql = "SELECT * FROM " + table_name else: if table_name[:19] == "information_schema.": sql = "SELECT * FROM " + table_name select_table_name = table_name[19:] schema = "information_schema" else: select_table_name = table_name schema = "public" QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") while query.next(): column_headers.append(query.value(0)) if pyside_version_1 and sys.version_info[0] == 3: if table_name == "samples_view": self.SQLTableDataPrep = self.samples_view_DataPrep if table_name == "samples": self.SQLTableDataPrep = self.samples_DataPrep super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent) def samples_view_DataPrep(self, query, count): data = [] data.append(query.value(0)) # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string data.append("{:>19}".format(query.value(1))) for i in xrange(2, count): data.append(query.value(i)) return data def samples_DataPrep(self, query, count): data = [] for i in xrange(9): data.append(query.value(i)) # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string data.append("{:>19}".format(query.value(9))) for i in xrange(10, count): data.append(query.value(i)) return data # Base class for custom ResizeColumnsToContents class ResizeColumnsToContentsBase(QObject): def __init__(self, parent=None): super(ResizeColumnsToContentsBase, self).__init__(parent) def ResizeColumnToContents(self, column, n): # Using the view's resizeColumnToContents() here is extrememly slow # so implement a crude alternative font = self.view.font() metrics = QFontMetrics(font) max = 0 for row in xrange(n): val = self.data_model.child_items[row].data[column] len = metrics.width(str(val) + "MM") max = len if len > max else max val = self.data_model.columnHeader(column) len = metrics.width(str(val) + "MM") max = len if len > max else max self.view.setColumnWidth(column, max) def ResizeColumnsToContents(self): n = min(self.data_model.child_count, 100) if n < 1: # No data yet, so connect a signal to notify when there is self.data_model.rowsInserted.connect(self.UpdateColumnWidths) return columns = self.data_model.columnCount() for i in xrange(columns): self.ResizeColumnToContents(i, n) def UpdateColumnWidths(self, *x): # This only needs to be done once, so disconnect the signal now self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths) self.ResizeColumnsToContents() # Convert value to CSV def ToCSValue(val): if '"' in val: val = val.replace('"', '""') if "," in val or '"' in val: val = '"' + val + '"' return val # Key to sort table model indexes by row / column, assuming fewer than 1000 columns glb_max_cols = 1000 def RowColumnKey(a): return a.row() * glb_max_cols + a.column() # Copy selected table cells to clipboard def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False): indexes = sorted(view.selectedIndexes(), key=RowColumnKey) idx_cnt = len(indexes) if not idx_cnt: return if idx_cnt == 1: with_hdr=False min_row = indexes[0].row() max_row = indexes[0].row() min_col = indexes[0].column() max_col = indexes[0].column() for i in indexes: min_row = min(min_row, i.row()) max_row = max(max_row, i.row()) min_col = min(min_col, i.column()) max_col = max(max_col, i.column()) if max_col > glb_max_cols: raise RuntimeError("glb_max_cols is too low") max_width = [0] * (1 + max_col - min_col) for i in indexes: c = i.column() - min_col max_width[c] = max(max_width[c], len(str(i.data()))) text = "" pad = "" sep = "" if with_hdr: model = indexes[0].model() for col in range(min_col, max_col + 1): val = model.headerData(col, Qt.Horizontal) if as_csv: text += sep + ToCSValue(val) sep = "," else: c = col - min_col