Source code for wbia.guitool.api_item_view

# -*- coding: utf-8 -*-
"""
provides common methods for api_tree_view and api_table_view
"""
from __future__ import absolute_import, division, print_function
from wbia.guitool.__PYQT__ import QtGui  # NOQA
from wbia.guitool.__PYQT__ import QtCore
from wbia.guitool.__PYQT__ import QtWidgets
from wbia.guitool.__PYQT__.QtCore import Qt
import functools
from wbia.guitool import qtype
from wbia.guitool import api_button_delegate
from wbia.guitool import api_thumb_delegate
from wbia.guitool import guitool_main
from wbia.guitool import guitool_misc
from six.moves import range, reduce  # NOQA
import utool
import utool as ut
import operator

# Valid API Models
from wbia.guitool.stripe_proxy_model import StripeProxyModel
from wbia.guitool.filter_proxy_model import FilterProxyModel
from wbia.guitool.api_item_model import APIItemModel

(print, rrr, profile) = utool.inject2(__name__, '[APIItemView]')

VERBOSE_QT = ut.get_argflag(('--verbose-qt', '--verbqt'))
VERBOSE_ITEM_VIEW = ut.get_argflag(('--verbose-item-view'))
VERBOSE = utool.VERBOSE or VERBOSE_QT or VERBOSE_ITEM_VIEW

API_VIEW_BASE = QtWidgets.QAbstractItemView
ABSTRACT_VIEW_INJECT_KEY = ('QtWidgets.QAbstractItemView', 'guitool')
register_view_method = utool.make_class_method_decorator(
    ABSTRACT_VIEW_INJECT_KEY, __name__
)

injectviewinstance = functools.partial(
    utool.inject_instance, classkey=ABSTRACT_VIEW_INJECT_KEY
)


VALID_API_MODELS = (FilterProxyModel, StripeProxyModel, APIItemModel)


[docs]class APIItemView(API_VIEW_BASE): """ Trees and Tables implicitly inherit from this class. Abstractish class. other function in this file will be injected into the concrete implementations of either a table or tree view. The code is only written once but duplicated in each of the psuedo-children. It is done this way to avoid explicit multiple inheritance. """ def __init__(view, parent=None): API_VIEW_BASE.__init__(view, parent)
@register_view_method def _init_api_item_view(view): view.registered_single_keys = [] view.registered_keypress_funcs = [] # --------------- # Data Manipulation # --------------- @register_view_method def _init_itemview_behavior(view): """ Example: >>> # ENABLE_DOCTEST >>> # TODO figure out how to test these >>> from wbia.guitool.api_item_view import * # NOQA >>> from wbia.guitool import api_table_view >>> from wbia.guitool import api_tree_view >>> view = api_table_view.APITableView() >>> view = api_tree_view.APITreeView() References: http://qt-project.org/doc/qt-4.8/qabstractitemview.html """ # http://stackoverflow.com/questions/28680150/qtgui-qtableview-shows-data-in-background-while-a-cell-being-edited-pyqt4 view.setAutoFillBackground(True) view.setWordWrap(True) # Selection behavior # view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) # view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectColumns) view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems) # Selection behavior # view.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) # view.setSelectionMode(QtWidgets.QAbstractItemView.ContiguousSelection) # view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) # view.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) # uniformRowHeights view.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerItem) view.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) # Allow sorting by column view.setSortingEnabled(True) # Edit Triggers # view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) # No Editing # view.setEditTriggers(QtWidgets.QAbstractItemView.SelectedClicked) # QtWidgets.QAbstractItemView.NoEditTriggers | # 0 # QtWidgets.QAbstractItemView.CurrentChanged | # 1 # QtWidgets.QAbstractItemView.DoubleClicked | # 2 # QtWidgets.QAbstractItemView.SelectedClicked | # 4 # QtWidgets.QAbstractItemView.EditKeyPressed | # 8 # QtWidgets.QAbstractItemView.AnyKeyPressed # 16 # view._defaultEditTriggers = QtWidgets.QAbstractItemView.AllEditTriggers bitwise_or = operator.__or__ chosen_triggers = [ # QtWidgets.QAbstractItemView.NoEditTriggers, QtWidgets.QAbstractItemView.CurrentChanged, QtWidgets.QAbstractItemView.DoubleClicked, QtWidgets.QAbstractItemView.SelectedClicked, QtWidgets.QAbstractItemView.EditKeyPressed, QtWidgets.QAbstractItemView.AnyKeyPressed, ] view._defaultEditTriggers = reduce(bitwise_or, chosen_triggers) # view._defaultEditTriggers = QtWidgets.QAbstractItemView.NoEditTriggers view.setEditTriggers(view._defaultEditTriggers) # TODO: Figure out how to not edit when you are selecting # view.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
[docs]@register_view_method def infer_delegates(view, **headers): """ Infers which columns should be given item delegates """ get_thumb_size = headers.get('get_thumb_size', None) col_type_list = headers.get('col_type_list', []) num_cols = view.model().columnCount() num_duplicates = int(num_cols / len(col_type_list)) col_type_list = col_type_list * num_duplicates view.has_thumbs = False for colx, coltype in enumerate(col_type_list): if coltype in qtype.QT_PIXMAP_TYPES: if VERBOSE: print('[view] colx=%r is a PIXMAP' % colx) thumb_delegate = api_thumb_delegate.APIThumbDelegate(view, get_thumb_size) view.setItemDelegateForColumn(colx, thumb_delegate) view.has_thumbs = True # HACK # verticalHeader = view.verticalHeader() # verticalHeader.setDefaultSectionSize(256) elif coltype in qtype.QT_BUTTON_TYPES: if VERBOSE: print('[view] colx=%r is a BUTTON' % colx) button_delegate = api_button_delegate.APIButtonDelegate(view) view.setItemDelegateForColumn(colx, button_delegate) elif isinstance(coltype, QtWidgets.QAbstractItemDelegate): if VERBOSE: print('[view] colx=%r is a CUSTOM DELEGATE' % colx) view.setItemDelegateForColumn(colx, coltype) else: if VERBOSE: print('[view] colx=%r does not have a delgate' % colx) # Effectively unsets any existing delegates default_delegate = QtWidgets.QStyledItemDelegate(view) view.setItemDelegateForColumn(colx, default_delegate)
[docs]@register_view_method def set_column_persistant_editor(view, column): """ Set each row in a column as persistant """ num_rows = view.model.rowCount() print('view.set_persistant: %r rows' % num_rows) for row in range(num_rows): index = view.model.index(row, column) view.view.openPersistentEditor(index)
@register_view_method def _update_headers(view, **headers): """ Mirrors _update_headers in api_item_model """ # Use headers from model #model = view.model #headers = model.headers # Get header info col_sort_index = headers.get('col_sort_index', None) col_sort_reverse = headers.get('col_sort_reverse', False) view.col_hidden_list = headers.get('col_hidden_list', []) view.col_name_list = headers.get('col_name_list', []) # Call updates # FIXME: is this the right thing to do here? view._set_sort(col_sort_index, col_sort_reverse) view.infer_delegates(**headers) if ut.VERBOSE: print('[view] updating headers') col_width_list = headers.get('col_width_list', None) if col_width_list is not None: if isinstance(view, QtWidgets.QTreeView): horizontal_header = view.header() else: horizontal_header = view.horizontalHeader() for index, width in enumerate(col_width_list): # Check for functionally sepcified widths if hasattr(width, '__call__'): width = width() horizontal_header.resizeSection(index, width) # except AttributeError as ex: # ut.embed() # ut.printex(ex, 'tree view?') # view.infer_delegates_from_model(model=model) #view.resizeColumnsToContents() @register_view_method def _set_sort(view, col_sort_index, col_sort_reverse=True): if col_sort_index is not None: order = [Qt.AscendingOrder, Qt.DescendingOrder][col_sort_reverse] view.sortByColumn(col_sort_index, order)
[docs]@register_view_method def hide_cols(view): total_num_cols = view.model().columnCount() num_cols = len(view.col_hidden_list) num_duplicates = int(total_num_cols / num_cols) duplicated_hidden_list = view.col_hidden_list * num_duplicates for col, hidden in enumerate(duplicated_hidden_list): view.setColumnHidden(col, hidden)
# @register_view_method # def clear_selection(view): # #print('[api_item_view] clear_selection()') # selection_model = view.selectionModel() # selection_model.clearSelection()
[docs]@register_view_method def get_row_and_qtindex_from_id(view, _id): """ uses an sqlrowid (from iders) to get a qtindex """ model = view.model() qtindex, row = model.get_row_and_qtindex_from_id(_id) return qtindex, row
[docs]@register_view_method def select_row_from_id(view, _id, scroll=False, collapse=True): """ _id is from the iders function (i.e. an wbia rowid) selects the row in that view if it exists """ with ut.Timer( '[api_item_view] select_row_from_id(id=%r, scroll=%r, collapse=%r)' % (_id, scroll, collapse) ): qtindex, row = view.get_row_and_qtindex_from_id(_id) if row is not None: if isinstance(view, QtWidgets.QTreeView): if collapse: view.collapseAll() select_model = view.selectionModel() select_flag = QtCore.QItemSelectionModel.ClearAndSelect # select_flag = QtCore.QItemSelectionModel.Select # select_flag = QtCore.QItemSelectionModel.NoUpdate with ut.Timer('[api_item_view] selecting name. qtindex=%r' % (qtindex,)): select_model.select(qtindex, select_flag) with ut.Timer('[api_item_view] expanding'): view.setExpanded(qtindex, True) else: # For Table Views view.selectRow(row) # Scroll to selection if scroll: with ut.Timer('scrolling'): view.scrollTo(qtindex) return row return None
[docs]@register_view_method def connect_single_key_to_slot(view, key, func): """ hacky way to simulate slots for generic key press events """ view.registered_single_keys.append((key, func))
[docs]@register_view_method def connect_keypress_to_slot(view, func): """ hacky way to simulate slots for single key press events """ view.registered_keypress_funcs.append(func)
[docs]@register_view_method def selectedRows(view): selected_qtindex_list = view.selectedIndexes() selected_qtindex_list2 = [] seen_ = set([]) for qindex in selected_qtindex_list: row = qindex.row() if row not in seen_: selected_qtindex_list2.append(qindex) seen_.add(row) return selected_qtindex_list2
# --------------- # Qt Overrides # ---------------
[docs]def keyPressEvent(view, event): """ Handles simple key press events. There is probably a better way to do this using real signals / slots, but maybe you need to always overwrite to set the handled flag correctly. CommandLine: xdoctest -m ~/code/guitool/guitool/api_item_view.py keyPressEvent --show Example: >>> # DISABLE_DOCTEST >>> from wbia.guitool.api_item_view import * # NOQA >>> import wbia.guitool as gt >>> app = gt.ensure_qapp()[0] >>> wgt = gt.simple_api_item_widget() >>> view = wgt.view >>> c_pressed = [0] >>> def foo(view, event): >>> key = event.key() >>> print('[foo] Pressed key = %r' % (key,)) >>> if event.key() == Qt.Key_C: >>> print('Pressed C') >>> c_pressed[0] = 1 >>> return True >>> view.connect_keypress_to_slot(foo) >>> view._init_header_behavior() >>> # Try to simulate an event for testing >>> wgt.show() >>> from wbia.guitool.__PYQT__ import QtTest, GUITOOL_PYQT_VERSION >>> QTest = QtTest.QTest >>> if GUITOOL_PYQT_VERSION == 4: >>> QTest.qWaitForWindowShown(wgt) >>> else: >>> QTest.qWaitForWindowActive(wgt) >>> qtindex = view.model().index(1, 2) >>> point = view.visualRect(qtindex).center() >>> #point = wgt.visibleRegion().boundingRect().center() >>> #QTest.mouseClick(view.viewport(), Qt.LeftButton, Qt.NoModifier, point) >>> QTest.mouseClick(wgt, Qt.LeftButton, Qt.NoModifier, point) >>> selected_indices = view.selectedIndexes() >>> print('selected_indices = %r' % (selected_indices,)) >>> # Why does this not work? >>> def check_selection(): >>> selected_indices = view.selectedIndexes() >>> if len(selected_indices) > 0: >>> return selected_indices[0].data() >>> # Hack because I cant figure out how to get a click to simulate >>> # a selection >>> QTest.keyPress(view, Qt.Key_Right) >>> QTest.keyPress(view, Qt.Key_B) >>> ut.assert_eq(check_selection(), 'b') >>> QTest.keyPress(view, Qt.Key_C) >>> ut.assert_eq(check_selection(), 'b') >>> assert c_pressed[0] == 1 >>> # xdoctest: +REQUIRES(--show) >>> ut.quit_if_noshow() >>> gt.qtapp_loop(wgt, frequency=100) """ # TODO: can this be in api_item_view? assert isinstance(event, QtGui.QKeyEvent) if event.matches(QtGui.QKeySequence.Copy): # print('Received Ctrl+C in View') view.copy_selection_to_clipboard() # print ('[view] keyPressEvent: %s' % event.key()) flag = False for func in view.registered_keypress_funcs: flag |= bool(func(view, event)) for key, func in view.registered_single_keys: # print(key) if event.key() == key: flag = True func(view, event) if not flag: view.API_VIEW_BASE.keyPressEvent(view, event)
# @register_view_method
[docs]def itemDelegate(view, qindex): """ QtOverride: Returns item delegate for this index """ # Does this even work? TODO: testme return API_VIEW_BASE.itemDelegate(view, qindex)
[docs]def setModel(view, model): """ QtOverride: Returns item delegate for this index """ assert isinstance(model, VALID_API_MODELS), ( 'APIItemViews only accepts APIItemModels (or one of its proxys),' 'received a %r' % type(model) ) # Learn some things about the model before you fully connect it. if VERBOSE: print('[view] setting model') model._rows_updated.connect(view.on_rows_updated) # view.infer_delegates_from_model(model=model) # TODO: Update headers return view.API_VIEW_BASE.setModel(view, model)
# --------------- # Slots # ---------------
[docs]@register_view_method def copy_selection_to_clipboard(view): """ Copys selected grid to clipboard """ if VERBOSE: print('[guitool] Copying selection to clipboard') copy_str = guitool_misc.get_view_selection_as_str(view) # copy_qstr = QtCore.Q__String(copy_str) copy_qstr = str(copy_str) clipboard = guitool_main.get_qtapp().clipboard() if VERBOSE: print(copy_str) clipboard.setText(copy_qstr) if VERBOSE: print('[guitool] finished copy')
if __name__ == '__main__': r""" CommandLine: python -m wbia.guitool.api_item_view python -m wbia.guitool.api_item_view --allexamples """ import multiprocessing multiprocessing.freeze_support() # for win32 import utool as ut # NOQA ut.doctest_funcs()