1

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
MarKS
  • 494
  • 6
  • 22
  • what is the meaning of *I want the green line to run through the widget*?,, You could explain me better, I did not understand anything, that you want to be done with that line. – eyllanesc Jul 09 '18 at 14:48
  • I want it to be my seek bar or QSlider. Like when i play a video stream it should run through the widget making the tablewidget scroll as well. Moreover, i should be able to move it using mouse to go back and forth in the video. – MarKS Jul 09 '18 at 14:52
  • okay, now I understand, why the height of that line is similar to the Qtablewidget? I think you should have another rectangle above that indicates the advance. – eyllanesc Jul 09 '18 at 14:55
  • Ok. yah i can add something to the head of the line to make it look like a seek bar. – MarKS Jul 09 '18 at 14:58
  • I would not make this part of a QGraphicsScene. Instead I would subclass a QTableView and put it in a layout next to your Preview. The subclass (TimelineView) would handle the I/O and painting of the Playhead. Moving the Playhead should emit a signal (playheadChanged), and the Preview area should connect to this signal and respond by changing it's current frame. This design will simplify much of the programming of the Playhead, imo. – walkingTarget Jul 09 '18 at 15:54
  • @MarKS try with my answer :) – eyllanesc Jul 09 '18 at 16:10

1 Answers1

0

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

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • This is beautifully made! I know my question is rather ill framed but i need the whole thing(head and the bar) to move horizontally inside the widget making the widget to scroll horizontally along with the bar showing the position of the frame indicated by the number shown in the header. – MarKS Jul 09 '18 at 16:27
  • @MarKS plop, please edit your question and add this information in your question. – eyllanesc Jul 09 '18 at 16:31
  • Edited the question. – MarKS Jul 09 '18 at 16:38
  • This is amazing stuff! Exactly what i needed. Thank you @eyllanesc. Hope it will help others as well. – MarKS Jul 09 '18 at 17:11