Source code for qconcurrency.widgets._sessionwidgets_._sessionlist_

#!/usr/bin/env python
"""
Name :          qconcurrency.widgets._updaterwidgets_._updaterlist_.py
Created :       May 24, 2017
Author :        Will Pittman
Contact :       willjpittman@gmail.com
________________________________________________________________________________
Description :   List that manages creation,updates,deletion in a consistent
                manner.
________________________________________________________________________________
"""
#builtin
from   __future__    import unicode_literals
from   __future__    import absolute_import
from   __future__    import division
from   __future__    import print_function
import uuid
import sys
import os
import copy
#package
from qconcurrency.widgets._sessionwidgets_._basewidgets_ import (
    SessionWidgetBase, SessionWidgetItemBase
)
#external
from Qt import QtWidgets, QtCore, QtGui
#internal


#!NOTE: cannot create item-specific highlight colours.
#!      ex:  lighter variation of 'new', or 'changed'
#!
#!      using transparency in QColor also does not work.
#!      the colour is overlaid on top of the window, not
#!      the QListWidgetItem's background.
#!
#!      In order to have per-listwidgetitem colours,
#!      We would need to implement our own custom delegates,
#!      with customized paintEvent() methods.
#!

[docs]class SessionList( SessionWidgetBase, QtWidgets.QWidget ): changes_exist = QtCore.Signal(bool) #: True/False if unsaved changes exist
[docs] def __init__(self, colours=None): """ Args: colours (dict, optional): An optional dictionary, indicating colours to use for QListWidgetItems based on their status. Any key that is not set in the dictionary uses the stylesheet's default colours for :py:obj:`QListWidgetItems` .. code-block:: python { 'normal': {'fg':QColor(255,255,255), 'bg':QColor(255,255,255)}, 'changed': {'fg':QColor(255,255,255), 'bg':QColor(255,255,255)}, 'new': {'fg':QColor(255,255,255), 'bg':QColor(255,255,255)}, 'editable': {'fg':QColor(255,255,255), 'bg':QColor(255,255,255)}, } """ SessionWidgetBase.__init__(self) QtWidgets.QWidget.__init__(self) # Attributes if not colours: self._colours = {} else: self._colours = colours self._default_palette = copy.copy( self.palette() ) #: the default QlistWidget palette self._editable_widgets = set() #: every widget that is currently editable # Widgets self._list = None # the QListWidget managed by this class self._initui()
def _initui(self): # Create Widgets layout = QtWidgets.QVBoxLayout() self._list = _DeselectableListWidget() # Position Widgets self.setLayout( layout ) layout.addWidget( self._list ) # Widget Attrs layout.setContentsMargins(0,0,0,0) # Connections self._list.itemChanged.connect( self._handle_item_changed ) self._list.itemDoubleClicked.connect( self._handle_itemDoubleClicked ) self._list.itemSelectionChanged.connect( self._handle_itemSelectionChanged ) self._list.itemDelegate().closeEditor.connect( self._handle_itemeditfinished )
[docs] def clear(self): """ Clears the list, and all internal data. """ self._list.clear() self._items = {} self._newitems = set() self._delitems = set() self._changeditems = set() self._last_selected = None
[docs] def add_item(self, val, _id=None, saved_val=None ): """ Adds a new item to the list. Args: val (object): ``(ex: 'itemA' )`` The item you'd like to add to the widget _id (object, optional): If the item exists and has already been assigned an Id provide it here. Or, if the item is new, but you want to assign a specific Id, you may also provide an Id here. saved_val (object, optional): If this argument is assigned a value, the item will tracked as being an item that is already saved to the database (or other long-term-storage). You can test whether or not a widget's current value is different from this saved value using the method :py:meth:`SessionListItem.is_changed` """ if _id in self._items: raise KeyError( 'Another item with the _id(%s) already exists in ``self._items``' ) # Create/Colour/Add Widget # ======================== widget = SessionListItem( val, _id, saved_val ) # if no normal-colour is set, create it based on default if 'default_brush' not in self._colours: self._colours['default_brush'] = { 'fg': widget.foreground(), 'bg': widget.background(), } self._list.addItem( widget ) # set colour self._handle_item_statuschanged( widget, widget.is_new(), widget.is_changed(), widget.is_editable() ) # internal data # ============= if widget.is_new(): self._newitems.add( widget.id() ) elif val != saved_val: self._changeditems.add( widget.id() ) self._items[ widget.id() ] = widget # connections widget.status_changed.connect( self._handle_item_statuschanged ) # emit changes self.changes_exist.emit( self.has_changes() )
[docs] def remove_item(self, _id ): """ Removes a single item from the list. Args: _id (object, optional): The `_id` of the item that you want to delete. """ if _id not in self._items: return # manage internal data widget = self._items[_id] if widget.is_new(): self._newitems.remove(_id) elif widget.is_changed(): self._changeditems.remove(_id) self._delitems.add(_id) else: self._delitems.add(_id) # remove widget from list row = self._list.row(widget) item = self._list.takeItem(row) # remove item from tracked items self._items.pop(_id) # notify any widgets if we have any unsaved changes self.changes_exist.emit( self.has_changes() )
[docs] def setSelectionMode(self, selectionMode): """ Wraps :py:meth:`QListWidget.setSelectionMode` """ return self._list.setSelectionMode( selectionMode )
[docs] def setSelectionModel(self, selectionModel): """ Wraps :py:meth:`QListWidget.setSelectionModel` """ return self._list.setSelectionModel( selectionModel )
[docs] def selectedItems(self): """ Wraps :py:meth:`QtWidgets.QListWidget.selectedItems` , and returns all selected :py:obj:`SessionListItems` . """ return self._list.selectedItems()
[docs] def keyPressEvent(self, event): """ If an item is selected in the list, the last-item in the selection is toggled between editable and normal modes. """ selected_items = self._list.selectedItems() if selected_items: if event.key() == QtCore.Qt.Key_Enter or event.key() == QtCore.Qt.Key_Return: # deselect all items except for the last selected for i in range(len(selected_items) -1): selected_items[i].set_editable(False) selected_items[i].setSelected(False) # toggle editability of last item selected_items[-1].toggle_editable() QtWidgets.QWidget.keyPressEvent(self,event)
def _handle_item_changed(self, item): """ Handles whenever an item's text is changed. """ item.refresh_status() def _handle_item_statuschanged(self, item, is_new, is_changed, is_editable ): """ Runs whenever an item's status changes. Status changes include new, changed, or editable. * widgets colours are modified (if colours were set for the current status) * internal-data is updated, keeping track of new/changed items. """ _id = item.id() # Update Internal Data # ==================== if is_new: # new-items will already be tracked, pass elif is_changed: self._changeditems.add( _id ) else: # if item is not new, but it is still tracked # internally under self._newitems, # (it's saved-data has been updated), # update internal-data so that it looks like a # normal item. if _id in self._newitems: self._newitems.remove( _id ) elif _id in self._changeditems: self._changeditems.remove( _id ) # emit changes_exist if self.has_changes(): self.changes_exist.emit(True) else: self.changes_exist.emit(False) # Set Colours # =========== if is_editable: if 'editable' in self._colours: item.setForeground( QtGui.QBrush(self._colours['editable']['fg'] ) ) item.setBackground( QtGui.QBrush(self._colours['editable']['bg'] ) ) # highlight should match editable colour highlight_palette = copy.copy( self._default_palette ) highlight_palette.setColor( QtGui.QPalette.Highlight, self._colours['editable']['bg'] ) highlight_palette.setColor( QtGui.QPalette.HighlightedText, self._colours['editable']['fg'], ) self.setPalette( highlight_palette ) self._editable_widgets.add( item ) return self.setPalette( self._default_palette ) if is_new: if 'new' in self._colours: item.setForeground( QtGui.QBrush(self._colours['new']['fg']) ) item.setBackground( QtGui.QBrush(self._colours['new']['bg']) ) return if is_changed: if 'changed' in self._colours: item.setForeground( QtGui.QBrush(self._colours['changed']['fg']) ) item.setBackground( QtGui.QBrush(self._colours['changed']['bg']) ) return # normal colours if 'normal' in self._colours: item.setForeground( QtGui.QBrush(self._colours['normal']['fg']) ) item.setBackground( QtGui.QBrush(self._colours['normal']['bg']) ) else: item.setForeground( self._colours['default_brush']['fg'] ) item.setBackground( self._colours['default_brush']['bg'] ) def _handle_itemDoubleClicked(self, item): """ When an item is double-clicked, toggle widget edit/normal mode. """ item.toggle_editable() def _handle_itemSelectionChanged(self): """ Whenever the selection changes in the listwidget, makes all un-selected widgets *not* editable. (this also has the effect of restoring their `normal` colour). """ selected_items = self.selectedItems() # unset editable on all unselected widgets for widget in self._editable_widgets.copy(): if not any([ widget is selwidget for selwidget in selected_items ]): widget.setFlags( widget.flags() ^ QtCore.Qt.ItemIsEditable ) self._editable_widgets.remove( widget ) def _handle_itemeditfinished(self, widget, modelcache ): """ When the user finishes editing an item, refreshes it's status. """ self._editable_widgets = set() for widget in self.selectedItems(): widget.set_editable(False)
class _DeselectableListWidget( QtWidgets.QListWidget ): """ Customized :py:obj:`QListWidget` that deselects all widgets whenever the user clicks empty space in the list. """ def __init__(self,*args,**kwds): QtWidgets.QListWidget.__init__(self,*args,**kwds) def mousePressEvent(self, event): """ If no :py:obj:`QListWidgetItem` is under the cursor, then clear the selection before handling the mousePressEvent. """ # if LMB, and no widget is under cursor, # deselect all widgets. if event.button() is QtCore.Qt.LeftButton: if not self.itemAt( event.pos() ): self.clearSelection() return QtWidgets.QListWidget.mousePressEvent(self,event)
[docs]class SessionListItem( SessionWidgetItemBase, QtCore.QObject, QtWidgets.QListWidgetItem ): def __init__(self, val, _id, saved_val=None ): SessionWidgetItemBase.__init__(self, val, _id, saved_val ) QtCore.QObject.__init__(self) QtWidgets.QListWidgetItem.__init__(self) # load self.setText( str(val) )
[docs] def get_value(self): """ Returns the QListWidget's text. """ return self.text()
[docs] def toggle_editable(self): """ If the widget is currently editable, it's value is stored, otherwise makes editable. The status is refreshed on the widget. """ # toggle widget editability if self.is_editable(): self.set_editable(False) else: self.set_editable(True)
[docs] def set_editable(self, status): """ Sets the widget editable/not editable, and enters edit-mode. """ if status: self.setFlags( self.flags() | QtCore.Qt.ItemIsEditable ) self.listWidget().editItem(self) else: self.setFlags( self.flags() ^ QtCore.Qt.ItemIsEditable )
if __name__ == '__main__': from qconcurrency import QApplication from Qt import QtGui with QApplication(): colours = { 'changed':{ 'fg':QtGui.QColor(30,30,30), 'bg':QtGui.QColor(170,70,50), }, 'new':{ 'fg':QtGui.QColor(30,30,30), 'bg':QtGui.QColor(180,140,50), }, 'editable':{ 'fg':QtGui.QColor(30,30,30), 'bg':QtGui.QColor(60,120,140), }, } slist = SessionList( colours ) slist.show() slist.add_item( 'A' ) slist.add_item( 'B' ) slist.add_item( 'C', _id=3, saved_val='C' ) slist.add_item( 'D1', _id=4, saved_val='D' )