c++qtqt5qtablewidgetqgraphicsrectitem

Adding horizontal slider to QTableWidget


I am trying to design something like a timeline view for my video player. I decided to use QTableWidget as the timeline since it suits my purpose. My widget looks like this:

enter image description here

I want the green line to run through the widget when i click on play. Here is my MVCE example:

//View.cpp

View::View(QWidget* parent) :  QGraphicsView(parent)
{
    QGraphicsScene* scene = new QGraphicsScene(this);

    TableWidget* wgt = new TableWidget;

    scene->addWidget(wgt);

    QGraphicsLineItem* item = new QGraphicsLineItem(30, 12, 30, wgt->height() - 9);
    item->setPen(QPen(QBrush(Qt::green), 3));
    item->setFlags(QGraphicsItem::ItemIsMovable);
    scene->addItem(item);

    setScene(scene);
}

Here is TableWidget

TableWidget::TableWidget(QWidget* parent) : QTableWidget(parent)
{
    setColumnCount(10);
    setRowCount(10);

    //Hides the numbers on the left side of the table
    verticalHeader()->hide();

    //Prevents top header from highlighting on selection
    horizontalHeader()->setHighlightSections(false);

    //Makes the cells un-editable
    setEditTriggers(QAbstractItemView::NoEditTriggers);

    setSelectionMode(QAbstractItemView::MultiSelection);
}

Problem:

Moving the line item reflects changes to the scene it has been added to i.e. when i drag the line using mouse, the line moves in the scene but not inside the TableWidget.

What do i want

I want the green bar to act like a horizontal slider. It should go through the TableWidget horizontally making the widget scroll along with it showing the current position of the frame indicated by the numbers shown on the header.

Something like as shown below (notice the Red line):

The Red line

I know this might not be the best way to implement a timeline but i would appreciate any other ideas to implement.


Solution

  • A possible solution is to overwrite the itemChange method to restrict movement as shown below:

    #include <QApplication>
    #include <QGraphicsRectItem>
    #include <QGraphicsView>
    #include <QTableWidget>
    #include <QHeaderView>
    #include <QGraphicsProxyWidget>
    
    class SeekBarItem: public QGraphicsRectItem{
    public:
        SeekBarItem(QRectF rect, QGraphicsItem *parent=nullptr)
            : QGraphicsRectItem(rect, parent)
        {
            setFlag(QGraphicsItem::ItemIsMovable, true);
            setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
            setBrush(Qt::red);
        }
    protected:
        QVariant itemChange(GraphicsItemChange change, const QVariant &value){
            if(change == QGraphicsItem::ItemPositionChange){
                QPointF p = value.toPointF();
    
                qreal max = parentItem()->boundingRect().bottom()- boundingRect().bottom();
                qreal min = parentItem()->boundingRect().top()-boundingRect().top();
    
                if(p.y() > max) p.setY(max);
                else if (p.y() < min) p.setY(min);
                p.setX(pos().x());
                return p;
            }
            return QGraphicsRectItem::itemChange(change, value);
        }
    };
    
    class TableWidget: public QTableWidget
    {
    public:
        TableWidget(QWidget* parent=nullptr) : QTableWidget(10, 10, parent)
        {
            verticalHeader()->hide();
            horizontalHeader()->setHighlightSections(false);
            setEditTriggers(QAbstractItemView::NoEditTriggers);
            setSelectionMode(QAbstractItemView::MultiSelection);
        }
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QGraphicsView view;
    
        QGraphicsScene *scene = new QGraphicsScene;
        view.setScene(scene);
    
        QGraphicsProxyWidget *proxy = scene->addWidget(new TableWidget);
    
        QGraphicsRectItem *it = new QGraphicsRectItem(QRectF(0, 0, 10, proxy->boundingRect().height()), proxy);
        it->setBrush(Qt::green);
    
        SeekBarItem *seekBarItem = new SeekBarItem(QRectF(-5, 0, 20, 50));
        seekBarItem->setParentItem(it);
    
        view.resize(640, 480);
        view.show();
    
        return a.exec();
    }
    

    enter image description here

    enter image description here

    enter image description here


    Update:

    #include <QApplication>
    #include <QGraphicsRectItem>
    #include <QGraphicsView>
    #include <QTableWidget>
    #include <QHeaderView>
    #include <QGraphicsProxyWidget>
    #include <QScrollBar>
    
    class TableWidget: public QTableWidget
    {
    public:
        TableWidget(QWidget* parent=nullptr) : QTableWidget(10, 10, parent)
        {
            verticalHeader()->hide();
            horizontalHeader()->setHighlightSections(false);
            setEditTriggers(QAbstractItemView::NoEditTriggers);
            setSelectionMode(QAbstractItemView::MultiSelection);
            setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
        }
    };
    
    class SeekBarItem: public QGraphicsRectItem{
    public:
        SeekBarItem(int width, QAbstractItemView *view, QGraphicsScene *scene)
            : QGraphicsRectItem(nullptr),
              proxy(new QGraphicsProxyWidget()),
              m_view(view)
        {
            proxy->setWidget(m_view);
            scene->addItem(proxy);
            setParentItem(proxy);
            setFlag(QGraphicsItem::ItemIsMovable, true);
            setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
            setBrush(Qt::red);
            setRect(0, 0, width, m_view->height());
            scrollbar = m_view->horizontalScrollBar();
        }
    protected:
        QVariant itemChange(GraphicsItemChange change, const QVariant &value){
            if(change == QGraphicsItem::ItemPositionChange){
                QPointF p = value.toPointF();
    
                qreal max = parentItem()->boundingRect().right()- boundingRect().right();
                qreal min = parentItem()->boundingRect().left()-boundingRect().left();
    
                if(p.x() > max) p.setX(max);
                else if (p.x() < min) p.setX(min);
                p.setY(pos().y());
    
                float percentage = (p.x()-min)*1.0/(max-min);
                int value = scrollbar->minimum() + percentage*(scrollbar->maximum() - scrollbar->minimum());
                scrollbar->setValue(value);
                return p;
            }
            return QGraphicsRectItem::itemChange(change, value);
        }
    private:
        QGraphicsProxyWidget *proxy;
        QAbstractItemView *m_view;
        QScrollBar *scrollbar;
    };
    
    
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QGraphicsView view;
    
        QGraphicsScene *scene = new QGraphicsScene;
        view.setScene(scene);
    
       TableWidget *table =  new TableWidget;
    
        SeekBarItem *seekBarItem = new SeekBarItem(15, table, scene);
        view.resize(640, 480);
        view.show();
    
        return a.exec();
    }
    

    enter image description here

    enter image description here

    enter image description here