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, Qt.DisplayRole) if as_csv: text += sep + ToCSValue(val) sep = "," else: c = col - min_col max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" last_row = min_row for i in indexes: if i.row() > last_row: last_row = i.row() text += "\n" pad = "" sep = "" if as_csv: text += sep + ToCSValue(str(i.data())) sep = "," else: width = max_width[i.column() - min_col] if i.data(Qt.TextAlignmentRole) & Qt.AlignRight: val = str(i.data()).rjust(width) else: val = str(i.data()) text += pad + sep + val pad = " " * (width - len(val)) sep = " " QApplication.clipboard().setText(text) def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False): indexes = view.selectedIndexes() if not len(indexes): return selection = view.selectionModel() first = None for i in indexes: above = view.indexAbove(i) if not selection.isSelected(above): first = i break if first is None: raise RuntimeError("CopyTreeCellsToClipboard internal error") model = first.model() row_cnt = 0 col_cnt = model.columnCount(first) max_width = [0] * col_cnt indent_sz = 2 indent_str = " " * indent_sz expanded_mark_sz = 2 if sys.version_info[0] == 3: expanded_mark = "\u25BC " not_expanded_mark = "\u25B6 " else: expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8") not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8") leaf_mark = " " if not as_csv: pos = first while True: row_cnt += 1 row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) if c: n = len(str(i.data())) else: n = len(str(i.data()).strip()) n += (i.internalPointer().level - 1) * indent_sz n += expanded_mark_sz max_width[c] = max(max_width[c], n) pos = view.indexBelow(pos) if not selection.isSelected(pos): break text = "" pad = "" sep = "" if with_hdr: for c in range(col_cnt): val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip() if as_csv: text += sep + ToCSValue(val) sep = "," else: max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" pos = first while True: row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) val = str(i.data()) if not c: if model.hasChildren(i):Lines 3501–4000 of 5030. Previous lines Load more
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, Qt.DisplayRole) if as_csv: text += sep + ToCSValue(val) sep = "," else: c = col - min_col max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" last_row = min_row for i in indexes: if i.row() > last_row: last_row = i.row() text += "\n" pad = "" sep = "" if as_csv: text += sep + ToCSValue(str(i.data())) sep = "," else: width = max_width[i.column() - min_col] if i.data(Qt.TextAlignmentRole) & Qt.AlignRight: val = str(i.data()).rjust(width) else: val = str(i.data()) text += pad + sep + val pad = " " * (width - len(val)) sep = " " QApplication.clipboard().setText(text) def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False): indexes = view.selectedIndexes() if not len(indexes): return selection = view.selectionModel() first = None for i in indexes: above = view.indexAbove(i) if not selection.isSelected(above): first = i break if first is None: raise RuntimeError("CopyTreeCellsToClipboard internal error") model = first.model() row_cnt = 0 col_cnt = model.columnCount(first) max_width = [0] * col_cnt indent_sz = 2 indent_str = " " * indent_sz expanded_mark_sz = 2 if sys.version_info[0] == 3: expanded_mark = "\u25BC " not_expanded_mark = "\u25B6 " else: expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8") not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8") leaf_mark = " " if not as_csv: pos = first while True: row_cnt += 1 row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) if c: n = len(str(i.data())) else: n = len(str(i.data()).strip()) n += (i.internalPointer().level - 1) * indent_sz n += expanded_mark_sz max_width[c] = max(max_width[c], n) pos = view.indexBelow(pos) if not selection.isSelected(pos): break text = "" pad = "" sep = "" if with_hdr: for c in range(col_cnt): val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip() if as_csv: text += sep + ToCSValue(val) sep = "," else: max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" pos = first while True: row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) val = str(i.data()) if not c: if model.hasChildren(i):