def index(model, row, column, parent=QtCore.QModelIndex()): """ Qt Override Returns: the index of the item in the model specified by the given row, column and parent index. When reimplementing this function in a subclass, call createIndex() to generate model indexes that other components can use to refer to items in your model. NOTE: Object must be specified to sort delegates. """ # model.lazy_checks() if not parent.isValid(): # This is a top level == 0 index # logger.info('[model.index] ROOT: row=%r, col=%r' % (row, column)) if row >= model.root_node.get_num_children(): return QtCore.QModelIndex() # import traceback # traceback.print_stack() node = model.root_node[row] if model.col_level_list[column] != node.get_level(): return QtCore.QModelIndex() qtindex = model.createIndex(row, column, object=node) return qtindex else: # This is a child level > 0 index parent_node = parent.internalPointer() node = parent_node[row] if ut.USE_ASSERT: assert isinstance(parent_node, _atn.TreeNode), type(parent_node) assert isinstance(node, _atn.TreeNode), type(node) return model.createIndex(row, column, object=node)
def parent(model, qindex): """ A common convention used in models that expose tree data structures is that only items in the first column have children. For that case, when reimplementing this function in a subclass the column of the returned QModelIndex would be 0. When reimplementing this function in a subclass, be careful to avoid calling QModelIndex member functions, such as QModelIndex.parent(), since indexes belonging to your model will simply call your implementation, leading to infinite recursion. FIXME: seems to segfault in here https://riverbankcomputing.com/pipermail/pyqt/2016-February/036977.html https://gist.github.com/estan/c051d1f798c4c46caa7d Returns: the parent of the model item with the given index. If the item has no parent, an invalid QModelIndex is returned. """ # model.lazy_checks() if qindex.isValid(): try: node = qindex.internalPointer() # <HACK> # A segfault happens in isinstance when updating rows? if not isinstance(node, _atn.TreeNode): logger.info( 'WARNING: tried to access parent of %r type object' % type(node)) return QtCore.QModelIndex() # assert node.__dict__, "node.__dict__=%r" % node.__dict__ # </HACK> parent_node = node.get_parent() parent_id = parent_node.get_id() if parent_id == -1 or parent_id is None: return QtCore.QModelIndex() row = parent_node.get_row() col = model.col_level_list.index(parent_node.get_level()) return model.createIndex(row, col, parent_node) except Exception as ex: import utool with utool.embed_on_exception_context: qindex_rc = (qindex.row(), qindex.column()) # NOQA ut.printex( ex, 'failed to do parenty things', keys=['qindex_rc', 'model.name'], tb=True, ) import utool utool.embed() raise return QtCore.QModelIndex()
def index(self, row, col, parent=QtCore.QModelIndex()): """Returns the index of the item in the model specified by the given row, column and parent index.""" if parent.isValid() and parent.column() != 0: return QtCore.QModelIndex() parentPref = self.index2Pref(parent) childPref = parentPref.qt_get_child(row) if childPref: return self.createIndex(row, col, childPref) else: return QtCore.QModelIndex()
def parent(self, index=None): """Returns the parent of the model item with the given index. If the item has no parent, an invalid QModelIndex is returned.""" if index is None: # Overload with QtCore.QObject.parent() return QtCore.QObject.parent(self) if not index.isValid(): return QtCore.QModelIndex() nodePref = self.index2Pref(index) parentPref = nodePref.qt_get_parent() if parentPref == self.rootPref: return QtCore.QModelIndex() return self.createIndex(parentPref.qt_parents_index_of_me(), 0, parentPref)
def createEditor(self, parent, option, index): combo = QtWidgets.QComboBox(parent) combo.addItems(['option1', 'option2', 'option3']) # FIXME: Change to newstyle signal slot if GUITOOL_PYQT_VERSION == 5: self.connect(combo.currentIndexChanged, self.currentIndexChanged) else: # I believe this particular option is broken in pyqt4 self.connect( combo, QtCore.SIGNAL('currentIndexChanged(int)'), self, QtCore.SLOT('currentIndexChanged()'), ) return combo
def index2Pref(self, index=QtCore.QModelIndex()): """ Internal helper method """ if index.isValid(): item = index.internalPointer() if item: return item return self.rootPref
def fetchMore(model, parent=QtCore.QModelIndex()): """ Fetches any available data for the items with the parent specified by the parent index. Reimplement this if you are populating your model incrementally. The default implementation does nothing. """ if parent is None: return if parent.isValid(): # Check if we are at a leaf node node = parent.internalPointer() if node.get_num_children() == 0: return remainder = model.num_rows_total - model.num_rows_loaded if model.batch_size is None: num_fetching = remainder else: num_fetching = min(model.batch_size, remainder) if VERBOSE_MODEL: logger.info('Fetching %r more %s' % (num_fetching, model.name)) idx1 = model.num_rows_total idx2 = model.num_rows_total + num_fetching - 1 # model.beginInsertRows(QtCore.QModelIndex(), idx1, idx2) model.beginInsertRows(parent, idx1, idx2) model.num_rows_loaded += num_fetching # logger.info('model.num_rows_total = %r' % (model.num_rows_total,)) # logger.info('model.num_rows_loaded = %r' % (model.num_rows_loaded,)) model.endInsertRows() if VERBOSE_MODEL: logger.info('Fetched %r/%r rows' % (model.num_rows_loaded, model.num_rows_total))
def canFetchMore(model, parent=QtCore.QModelIndex()): """ Returns true if there is more data available for parent; otherwise returns false. The default implementation always returns false. If canFetchMore() returns true, the fetchMore() function should be called. This is the behavior of QAbstractItemView, for example. References: http://doc.qt.io/qt-5/qtwidgets-itemviews-fetchmore-example.html # Extend this to work well with QTreeViews http://blog.tjwakeham.com/lazy-loading-pyqt-data-models/ http://stackoverflow.com/questions/38506808/pyqt4-force-view-to-fetchmore-from """ if parent is None: return if parent.isValid(): # Check if we are at a leaf node node = parent.internalPointer() if node.get_num_children() == 0: return # if node.get_level() == len(model.col_level_list): # return # logger.info('model.num_rows_total = %r' % (model.num_rows_total,)) # logger.info('model.num_rows_loaded = %r' % (model.num_rows_loaded,)) if model.num_rows_total is not None: if model.num_rows_loaded < model.num_rows_total: if VERBOSE_MODEL: logger.info('canFetchMore %s? -- Yes' % (model.name, )) return True if VERBOSE_MODEL: logger.info('canFetchMore %s? -- No' % (model.name, )) return False
def data(self, qtindex, role=Qt.DisplayRole): """ Returns the data stored under the given role for the item referred to by the qtindex. """ if not qtindex.isValid(): return QVariantHack() # Specify CheckState Role: flags = self.flags(qtindex) # if role == Qt.CheckStateRole and (flags & Qt.ItemIsUserCheckable or flags & Qt.ItemIsTristate): if role == Qt.CheckStateRole and flags & Qt.ItemIsUserCheckable: data = self.index2Pref(qtindex).qt_get_data(qtindex.column()) data_to_state = { True: Qt.Checked, 'True': Qt.Checked, None: Qt.PartiallyChecked, 'None': Qt.PartiallyChecked, False: Qt.Unchecked, 'False': Qt.Unchecked, } state = data_to_state[data] return state # elif role == QtCore.Qt.SizeHintRole: # #return QtCore.QSize(40, 30) # return QVariantHack() if role != Qt.DisplayRole and role != Qt.EditRole: return QVariantHack() nodePref = self.index2Pref(qtindex) data = nodePref.qt_get_data(qtindex.column()) if isinstance(data, float): var = QtCore.QLocale().toString(float(data), format='g', precision=6) else: var = data return str(var)
def data(model, index, role=Qt.DisplayRole): """ Returns the data to display """ if not index.isValid(): return None flags = model.flags(index) if role == Qt.TextAlignmentRole: return model.get_column_alignment(index.column()) if role == Qt.BackgroundRole and (flags & Qt.ItemIsEditable or flags & Qt.ItemIsUserCheckable): return QtCore.QVariant(QtGui.QColor(250, 240, 240)) if role == Qt.DisplayRole or role == Qt.CheckStateRole: data = model.get_data(index) var = qtype.cast_into_qt(data, role, flags) return var else: return QtCore.QVariant()
def proxy_to_source(self, row, col, parent=QtCore.QModelIndex()): source_model = self.sourceModel() source_cols = source_model.columnCount(parent=parent) r, c, p = row, col, parent r2 = int(math.floor(c / source_cols)) + (r * self._nd) c2 = c % source_cols p2 = p return r2, c2, p2
def source_to_proxy(self, row, col, parent=QtCore.QModelIndex()): source_model = self.sourceModel() source_cols = source_model.columnCount(parent=parent) r, c, p = row, col, parent r2 = int(math.floor(r / self._nd)) c2 = ((r % self._nd) * source_cols) + c p2 = p return r2, c2, p2
class GUILoggingSender(QtCore.QObject): write_ = QtCore.pyqtSignal(str) def __init__(self, write_slot): QtCore.QObject.__init__(self) self.write_.connect(write_slot) def write_gui(self, msg): self.write_.emit(str(msg))
def sizeHint(dgt, option, qtindex): view = dgt.parent() offset = view.verticalOffset() + option.rect.y() try: thumb_path = dgt.get_thumb_path_if_exists(view, offset, qtindex) if thumb_path is not None: # Read the precomputed thumbnail width, height = read_thumb_size(thumb_path) return QtCore.QSize(width, height) else: # print("[APIThumbDelegate] Name not found") return QtCore.QSize() except Exception as ex: print('Error in APIThumbDelegate') ut.printex(ex, 'Error in APIThumbDelegate', tb=True, iswarning=True) return QtCore.QSize()
def _init_table_behavior(view): """ Table behavior SeeAlso: api_item_view._init_itemview_behavior """ # Allow sorting by column view.setCornerButtonEnabled(False) view.setShowGrid(True) view.setIconSize(QtCore.QSize(64, 64))
def mapFromSource(self, sourceIndex): """ returns index into proxy model """ if sourceIndex is None: return None if sourceIndex.isValid(): r2, c2, p2 = self.source_to_proxy(sourceIndex.row(), sourceIndex.column(), sourceIndex.parent()) proxyIndex = self.index(r2, c2, p2) else: proxyIndex = QtCore.QModelIndex() return proxyIndex
def mapToSource(self, proxyIndex): """ returns index into original model """ if proxyIndex is None: return None if proxyIndex.isValid(): r2, c2, p2 = self.proxy_to_source(proxyIndex.row(), proxyIndex.column()) sourceIndex = self.sourceModel().index( r2, c2, parent=p2 ) # self.sourceModel().root_node[r2] else: sourceIndex = QtCore.QModelIndex() return sourceIndex
def ping_python_interpreter(frequency=420): # 4200): """ Create a QTimer which lets the python catch ctrl+c """ if not QUIET and VERBOSE: print('[guitool] pinging python interpreter for ctrl+c freq=%r' % frequency) timer = QtCore.QTimer() def ping_func(): # print('lub dub') return None timer.ping_func = ping_func timer.timeout.connect(timer.ping_func) timer.start(frequency) return timer
def rowCount(model, parent=QtCore.QModelIndex()): """ Qt Override """ # model.lazy_checks() if not parent.isValid(): # Root row count if len(model.level_index_list) == 0: return 0 return model.num_rows_loaded # nRows = len(model.level_index_list) # # logger.info('* nRows=%r' % nRows) # return nRows else: node = parent.internalPointer() nRows = node.get_num_children() # logger.info('+ nRows=%r' % nRows) return nRows
def paint(dgt, painter, option, qtindex): """ TODO: prevent recursive paint """ view = dgt.parent() offset = view.verticalOffset() + option.rect.y() # Check if still in viewport if view_would_not_be_visible(view, offset): return None try: thumb_path = dgt.get_thumb_path_if_exists(view, offset, qtindex) if thumb_path is not None: # Check if still in viewport if view_would_not_be_visible(view, offset): return None # Read the precomputed thumbnail if thumb_path in dgt.thumb_cache: qimg = dgt.thumb_cache[thumb_path] else: qimg = read_thumb_as_qimg(thumb_path) dgt.thumb_cache[thumb_path] = qimg width, height = qimg.width(), qimg.height() # Adjust the cell size to fit the image dgt.adjust_thumb_cell_size(qtindex, width, height) # Check if still in viewport if view_would_not_be_visible(view, offset): return None # Paint image on an item in some view painter.save() painter.setClipRect(option.rect) painter.translate(option.rect.x(), option.rect.y()) painter.drawImage(QtCore.QRectF(0, 0, width, height), qimg) painter.restore() except Exception as ex: print('Error in APIThumbDelegate') ut.printex(ex, 'Error in APIThumbDelegate', tb=True) painter.save() painter.restore()
def _get_adjacent_qtindex(model, qtindex=QtCore.QModelIndex(), offset=1): # check qtindex if qtindex is None or not qtindex.isValid(): return None node = qtindex.internalPointer() # check node try: if ut.USE_ASSERT: assert isinstance(node, _atn.TreeNode), type(node) except AssertionError as ex: ut.printex(ex, key_list=['node'], pad_stdout=True) raise # get node parent try: node_parent = node.get_parent() except Exception as ex: ut.printex(ex, key_list=['node'], reraise=False, pad_stdout=True) raise # parent_node check if node_parent is None: logger.info('[model._get_adjacent_qtindex] node_parent is None!') return None # Offset to find the next qtindex next_index = node_parent.child_index(node) + offset nChildren = node_parent.get_num_children() # check next index validitiy if next_index >= 0 and next_index < nChildren: next_node = node_parent.get_child(next_index) next_level = next_node.get_level() col = model.col_level_list.index(next_level) row = next_node.get_row() # Create qtindex for the adjacent note parent_qtindex = model.parent(qtindex) next_qtindex = model.index(row, col, parent_qtindex) return next_qtindex else: # There is no adjacent node return None
def _get_row_id(model, qtindex=QtCore.QModelIndex()): """ returns the id (specified by iders i.e. an wbia rowid) from qtindex """ if qtindex is not None and qtindex.isValid(): node = qtindex.internalPointer() if ut.USE_ASSERT: try: assert isinstance(node, _atn.TreeNode), 'type(node)=%r, node=%r' % ( type(node), node, ) except AssertionError as ex: ut.printex( ex, 'error in _get_row_id', keys=['model', 'qtindex', 'node'] ) raise try: id_ = node.get_id() except AttributeError as ex: ut.printex(ex, key_list=['node', 'model', 'qtindex']) raise return id_
def sizeHint(qres_wgt): # should eventually improve this to use the widths of the header columns return QtCore.QSize(1100, 500)
def index(model, row, column, parent=QtCore.QModelIndex()): """ Qt Override """ return model.createIndex(row, column)
def source_to_proxy(self, row, col, parent=QtCore.QModelIndex()): r2, c2, p2 = row, col, parent return r2, c2, p2
def index(self, row, col, parent=QtCore.QModelIndex()): if (row, col) != (-1, -1): proxyIndex = self.createIndex(row, col, parent) else: proxyIndex = QtCore.QModelIndex() return proxyIndex
def rowCount(self, parent=QtCore.QModelIndex()): sourceParent = self.mapToSource(parent) source_rows = self.sourceModel().rowCount(parent=sourceParent) rows = math.ceil(source_rows / self._nd) # print('StripeProxyModel.rowCount(): %r %r' % (source_rows, rows)) return int(rows)
def columnCount(self, parent=QtCore.QModelIndex()): source_cols = self.sourceModel().columnCount(parent=parent) cols = self._nd * source_cols # print('StripeProxyModel.columnCount(): %r %r' % (source_cols, cols)) return int(cols)
class APIButtonDelegate(DELEGATE_BASE): button_clicked = QtCore.pyqtSignal(QtCore.QModelIndex) def __init__(dgt, parent=None): assert parent is not None, 'parent must be a view' DELEGATE_BASE.__init__(dgt, parent) # FIXME: I don't like this state in the delegate, as it renders all # buttons dgt._pressed = None dgt.button_clicked.connect(dgt.on_button_click) def get_index_butkw(dgt, qtindex): """ The model data for a button should be a (text, callback) tuple. OR it could be a function which accepts an qtindex and returns a button """ data = qtindex.model().data(qtindex, QtCore.Qt.DisplayRole) # Get info if isinstance(data, tuple): buttontup = data elif utool.is_funclike(data): func = data buttontup = func(qtindex) else: raise AssertionError('bad type') text, callback = buttontup[0:2] butkw = { # 'parent': dgt.parent(), 'text': text, 'clicked': callback, } if len(buttontup) > 2: butkw['bgcolor'] = buttontup[2] butkw['fgcolor'] = (0, 0, 0) return butkw def paint(dgt, painter, option, qtindex): painter.save() butkw = dgt.get_index_butkw(qtindex) # FIXME: I don't want to create a widget each time just # so I can access the button's style button = guitool_components.newButton(**butkw) pressed = dgt.is_qtindex_pressed(qtindex) view = dgt.parent() paint_button(painter, option, button=button, pressed=pressed, view=view, **butkw) painter.restore() def is_qtindex_pressed(dgt, qtindex): return dgt._pressed is not None and dgt._pressed == ( qtindex.row(), qtindex.column(), ) @QtCore.pyqtSlot(QtCore.QModelIndex) def on_button_click(dgt, qtindex): if utool.VERBOSE: print('pressed button') butkw = dgt.get_index_butkw(qtindex) callback = butkw['clicked'] callback() def editorEvent(dgt, event, model, option, qtindex): # http://stackoverflow.com/questions/14585575/button-delegate-issue # print('editor event') event_type = event.type() if event_type == QtCore.QEvent.MouseButtonPress: # store the position that is clicked dgt._pressed = (qtindex.row(), qtindex.column()) if utool.VERBOSE: print('store') return True elif event_type == QtCore.QEvent.MouseButtonRelease: if dgt.is_qtindex_pressed(qtindex): print('emit') dgt.button_clicked.emit(qtindex) pass elif dgt._pressed is not None: # different place. # force a repaint on the pressed cell by emitting a dataChanged # Note: This is probably not the best idea # but I've yet to find a better solution. print('repaint') oldIndex = qtindex.model().index(*dgt._pressed) dgt._pressed = None qtindex.model().dataChanged.emit(oldIndex, oldIndex) pass dgt._pressed = None # print('mouse release') return True elif event_type == QtCore.QEvent.Leave: print('leave') elif event_type == QtCore.QEvent.MouseButtonDblClick: print('doubleclick') else: print('event_type = %r' % event_type) return DELEGATE_BASE.editorEvent(dgt, event, model, option, qtindex)
def headerData(model, section, orientation, role=Qt.DisplayRole): """ Qt Override """ if role == Qt.DisplayRole and orientation == Qt.Horizontal: return model.get_niceheader(section) else: return QtCore.QVariant()