Multitple icons/pixmap in same cell

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Multitple icons/pixmap in same cell

donoban
Hi,

I am trying to adapt a medium sized pyqt appication to model/view
architecture (https://github.com/QubesOS/qubes-manager/pull/195)

I have a problem when trying to imitate the 'State' column behavior. As
you could see here:
https://www.linuxjournal.com/sites/default/files/styles/850x500/public/nodeimage/story/12011f2.png

This column can have multiple icons which show the current state of the
VM. In the internal code it uses a custom widget with a layout and adds
new widgets with a pixmap and tooltip.

Does anybody know how to imitate this with model/view design? Could I
split the 'State' column in multiple columns which share the same header
cell?

Regards.
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: Multitple icons/pixmap in same cell

David Boddie
On Fri Sep 6 15:43:52 BST 2019, donoban wrote:

> I am trying to adapt a medium sized pyqt appication to model/view
> architecture (https://github.com/QubesOS/qubes-manager/pull/195)
>
> I have a problem when trying to imitate the 'State' column behavior. As
> you could see here:
> https://www.linuxjournal.com/sites/default/files/styles/850x500/public/nodei
> mage/story/12011f2.png
>
> This column can have multiple icons which show the current state of the
> VM. In the internal code it uses a custom widget with a layout and adds
> new widgets with a pixmap and tooltip.

You can still use widgets in QTableView if you want to:

https://doc.qt.io/qt-5/qtableview.html#visual-appearance

> Does anybody know how to imitate this with model/view design? Could I
> split the 'State' column in multiple columns which share the same header
> cell?

While it is possible to make cells span multiple columns, I don't think it's
possible to make headers do that, though I haven't looked at this in a long
time.

You might instead use a single pixmap for the column and draw individual
indicators at different places in the pixmap.

David
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: Multitple icons/pixmap in same cell

Maurizio Berti
In reply to this post by donoban
Using multiple columns with a "shared" header cell is possible, but not easy.

You'll need to create a subclass of QHeaderView for the horizontal header and possibly set a custom proxy model (QIdentityProxyModel) overriding its setModel, but that's not only. I think setSelectionModel requires some care, and you'll have to implement a lot of things if you need correct resizing of columns.
I would sincerely discourage this approach, at least if you're looking for a good implementation in a similar scenario: since you're obviously grouping all icons in the same item, the obvious solution is an item delegate.

Looking at your image reference it seems that the icon on the sixth item is right aligned, but it's unclear if that's because extra icons have some reserved space or it's just a matter of alignment.

I've decided for a more "standard" approach which simply puts all icons left aligned, but since you've cited tooltips I also added support for those.
The idea is that you use a role for each icon you want to show, given that a "status" icon is always visible, and any other icon is shown whenever exists and its role is set to True.
I use QStyle and QStyleItemView to draw the base item rectangle, then find the rectangle where an icon would be shown and use that data to paint all icons.

from random import choice
from PyQt5 import QtCore, QtGui, QtWidgets

StateRole = QtCore.Qt.UserRole + 1
FolderRole = StateRole + 1
DriveRole = FolderRole + 1

class IconDelegate(QtWidgets.QStyledItemDelegate):
    lastIndex = None
    def __init__(self):
        super(IconDelegate, self).__init__()
        self.stateIcons = (QtGui.QIcon.fromTheme('offline'), 
            QtGui.QIcon.fromTheme('online'))
        self.extraIconData = [
            (FolderRole, 'Folder', QtGui.QIcon.fromTheme('folder')), 
            (DriveRole, 'Drive', QtGui.QIcon.fromTheme('drive-harddisk'))
        ]

    def sizeHint(self, option, index):
        hint = super(IconDelegate, self).sizeHint(option, index)
        option = QtWidgets.QStyleOptionViewItem(option)
        option.features |= option.HasDecoration
        widget = option.widget
        style = widget.style()
        iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, 
            option, widget)
        margin = iconRect.left() - option.rect.left()
        width = iconRect.width() * (len(self.extraIconData) + 1)
        width += margin * (len(self.extraIconData) + 2)
        hint.setWidth(width)
        return hint

    def paint(self, qp, option, index):
        # create a new QStyleOption (*never* use the one given in arguments)
        option = QtWidgets.QStyleOptionViewItem(option)

        widget = option.widget
        style = widget.style()

        # paint the base item (borders, gradients, selection colors, etc)
        style.drawControl(style.CE_ItemViewItem, option, qp, widget)

        # "lie" about the decoration, to get a valid icon rectangle (even if we
        # don't have any "real" icon set for the item)
        option.features |= option.HasDecoration
        iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, 
            option, widget)
        iconSize = iconRect.size()
        margin = iconRect.left() - option.rect.left()

        qp.save()
        # ensure that we do not draw outside the item rectangle (and add some
        # fancy margin on the right
        qp.setClipRect(option.rect.adjusted(0, 0, -margin, 0))

        # draw the main state icon, assuming all items have one
        qp.drawPixmap(iconRect, 
            self.stateIcons[index.data(StateRole)].pixmap(iconSize))

        # cycle through roles, draw an icon if the index has that role set to True
        left = delta = margin + iconRect.width()
        for role, name, icon in self.extraIconData:
            if index.data(role):
                qp.drawPixmap(iconRect.translated(left, 0), 
                    icon.pixmap(iconSize))
                left += delta
        qp.restore()

    def helpEvent(self, event, view, option, index):
        if event.type() != QtCore.QEvent.ToolTip:
            return super(IconDelegate, self).helpEvent(event, view, option, index)
        option = QtWidgets.QStyleOptionViewItem(option)
        widget = option.widget
        style = widget.style()
        option.features |= option.HasDecoration

        iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, 
            option, widget)
        iconRect.setTop(option.rect.y())
        iconRect.setHeight(option.rect.height())

        # similar to what we do in the paint() method
        if event.pos() in iconRect:
            # (*) clear any existing tooltip; a single space is better , as
            # sometimes it's not enough to use an empty string
            if index != self.lastIndex:
                QtWidgets.QToolTip.showText(QtCore.QPoint(), ' ')
            QtWidgets.QToolTip.showText(event.globalPos(), 
                'State: {}'.format(('Off', 'On')[index.data(StateRole)]), view)
        else:
            margin = iconRect.left() - option.rect.left()
            left = delta = margin + iconRect.width()
            for role, name, icon in self.extraIconData:
                if index.data(role):
                    # if the role is set to True check if the mouse is within
                    # the icon rectangle
                    if event.pos() in iconRect.translated(left, 0):
                        # see above (*)
                        if index != self.lastIndex:
                            QtWidgets.QToolTip.showText(QtCore.QPoint(), ' ')
                        QtWidgets.QToolTip.showText(event.globalPos(), name, view)
                        break
                    # shift the left *only* if the role is True, otherwise we
                    # can assume that that icon doesn't exist at all
                    left += delta
            else:
                # nothing interesting, hide the existing tooltip, if any
                QtWidgets.QToolTip.hideText()
        self.lastIndex = index
        return True


class Window(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)

        table = QtWidgets.QTableView()
        layout.addWidget(table)

        model = QtGui.QStandardItemModel()
        model.setHorizontalHeaderLabels(['Name', 'State'])
        for row in range(5):
            mainItem = QtGui.QStandardItem('Item {}'.format(row + 1))
            stateItem = QtGui.QStandardItem()
            stateItem.setData(choice([0, 1]), StateRole)
            stateItem.setData(choice([0, 1]), FolderRole)
            stateItem.setData(choice([0, 1]), DriveRole)
            model.appendRow([mainItem, stateItem])

        table.setModel(model)
        table.setItemDelegateForColumn(1, IconDelegate())
        table.resizeColumnsToContents()

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())


Il giorno ven 6 set 2019 alle ore 16:45 donoban <[hidden email]> ha scritto:
Hi,

I am trying to adapt a medium sized pyqt appication to model/view
architecture (https://github.com/QubesOS/qubes-manager/pull/195)

I have a problem when trying to imitate the 'State' column behavior. As
you could see here:
https://www.linuxjournal.com/sites/default/files/styles/850x500/public/nodeimage/story/12011f2.png

This column can have multiple icons which show the current state of the
VM. In the internal code it uses a custom widget with a layout and adds
new widgets with a pixmap and tooltip.

Does anybody know how to imitate this with model/view design? Could I
split the 'State' column in multiple columns which share the same header
cell?

Regards.
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt


--
È difficile avere una convinzione precisa quando si parla delle ragioni del cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net

_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: Multitple icons/pixmap in same cell

donoban
In reply to this post by David Boddie
On 9/7/19 1:48 AM, David Boddie wrote:

> On Fri Sep 6 15:43:52 BST 2019, donoban wrote:
>
>> I am trying to adapt a medium sized pyqt appication to model/view
>> architecture (https://github.com/QubesOS/qubes-manager/pull/195)
>>
>> I have a problem when trying to imitate the 'State' column behavior. As
>> you could see here:
>> https://www.linuxjournal.com/sites/default/files/styles/850x500/public/nodei
>> mage/story/12011f2.png
>>
>> This column can have multiple icons which show the current state of the
>> VM. In the internal code it uses a custom widget with a layout and adds
>> new widgets with a pixmap and tooltip.
>
> You can still use widgets in QTableView if you want to:
>
> https://doc.qt.io/qt-5/qtableview.html#visual-appearance
>

Thanks for the info. I will investigate the other alternative first
since it seems more model/view approach.
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: Multitple icons/pixmap in same cell

donoban
In reply to this post by Maurizio Berti
On 9/7/19 2:55 AM, Maurizio Berti wrote:

> Using multiple columns with a "shared" header cell is possible, but not
> easy.
> ...
> I've decided for a more "standard" approach which simply puts all icons
> left aligned, but since you've cited tooltips I also added support for
> those.
> The idea is that you use a role for each icon you want to show, given
> that a "status" icon is always visible, and any other icon is shown
> whenever exists and its role is set to True.
> I use QStyle and QStyleItemView to draw the base item rectangle, then
> find the rectangle where an icon would be shown and use that data to
> paint all icons.
>

Thanks. I will try to implement this way, the left alignment should be fine.
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt