user1244932
user1244932

Reputation: 8142

Qt::Popup breaks QScroller kinetic scroll?

First of all my problem is reproducible only on device with touch screen, with PC/mouse all works fine.

The problem that if I use QTableView + QScroller as standalone window all works fine - I move finger from bottom to top content scrolls down, from top to bottom scrolls up.

But if I put QTableView inside QWidget with Qt::Popup attribute, then scrolling changes direction! I move finger from bottom to top and it scrolls up, from top to bottom scrolls down.

Here is my code:

#include <QAbstractTableModel>
#include <QScroller>
#include <QTouchDevice>
#include <QVBoxLayout>
#include <QtDebug>
#include <QtWidgets/QApplication>
#include <QtWidgets/QTableView>

class MyModel : public QAbstractTableModel {
public:
  MyModel(QObject *parent) : QAbstractTableModel(parent) {}

  int rowCount(const QModelIndex &parent = QModelIndex()) const override {
    return 100;
  }
  int columnCount(const QModelIndex &parent = QModelIndex()) const override {
    return 3;
  }
  QVariant data(const QModelIndex &index,
                int role = Qt::DisplayRole) const override {
    if (role == Qt::DisplayRole) {
      return QString("Row%1, Column%2")
          .arg(index.row() + 1)
          .arg(index.column() + 1);
    }
    return QVariant();
  }
  QVariant headerData(int section, Qt::Orientation orientation,
                      int role) const override {
    if (role == Qt::DisplayRole) {
      if (orientation == Qt::Horizontal) {
        switch (section) {
        case 0:
          return QString("first");
        case 1:
          return QString("second");
        case 2:
          return QString("third");
        }
      }
    }
    return QVariant();
  }
};

bool is_touch_screen_avaible() {
  const auto devs = QTouchDevice::devices();
  for (const auto &dev : devs) {
    if (dev->type() == QTouchDevice::TouchScreen) {
      return true;
    }
  }
  return false;
}

void configure_scoller_for_item_view(QAbstractItemView *view) {
  QScroller *scroller = QScroller::scroller(view);
  view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
  view->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
  QScrollerProperties properties =
      QScroller::scroller(scroller)->scrollerProperties();
  QVariant overshootPolicy =
      QVariant::fromValue<QScrollerProperties::OvershootPolicy>(
          QScrollerProperties::OvershootAlwaysOff);
  properties.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy,
                             overshootPolicy);
  scroller->setScrollerProperties(properties);
  properties.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy,
                             overshootPolicy);
  scroller->setScrollerProperties(properties);
  if (is_touch_screen_avaible())
    scroller->grabGesture(view, QScroller::TouchGesture);
  else
    scroller->grabGesture(view, QScroller::LeftMouseButtonGesture);
}

#define POPUP

int main(int argc, char *argv[]) {
  QApplication a(argc, argv);
#ifdef POPUP
  QWidget *mainWin = new QWidget;
  mainWin->setWindowFlags(Qt::Popup);
  auto lay = new QVBoxLayout(mainWin);
  mainWin->setLayout(lay);
  auto tableView = new QTableView(mainWin);
  lay->addWidget(tableView);
#else
  auto tableView = new QTableView;
#endif
  MyModel myModel(nullptr);
  tableView->setModel(&myModel);
  tableView->setSelectionMode(QAbstractItemView::NoSelection);
  tableView->setFocusPolicy(Qt::NoFocus);
  configure_scoller_for_item_view(tableView);

#ifdef POPUP
  mainWin->resize(500, 500);
  mainWin->show();
#else
  tableView->resize(500, 500);
  tableView->show();
#endif

  return a.exec();
}

Upvotes: 3

Views: 781

Answers (1)

cbuchart
cbuchart

Reputation: 11575

Qt doesn't fully implement gestures in scrollable areas as explained in their own documentation:

Qt does not really reflect the system behavior wrt gestures on scrollable views (widget classes inheriting QAbstractItemView, QML classes) well.

[...]

In widgets, the pan recognizer is currently hard-coded to use 2 touch points. For touchscreens, it should be changed to one. But that can't be done as long as single-finger-panning is reserved for selecting text.

When using a touch screen, the selection in widgets is driven by mouse events synthesized from touch by the system (Windows) or Qt itself (other platforms). The same touch events drive QGestureManager.

On the other hand, there is a known (and old) undefined behavior with QTouchEvents and pop-up widgets:

The behavior of QTouchEvents is undefined when opening a pop-up or grabbing the mouse while there are more than one active touch points.

Probably the combination of both issues is the origin of your problem.

As a possible workaround (although not completely what you want), you can enable the two-fingers scroll with QWidget::grabGesture(Qt::PanGesture) as an alternative. Also, as mentioned by @mohammad-kanan in a comment, you may try with Qt::FramelessWindowHint | Qt::Tool instead of Qt::Popup.

Upvotes: 2

Related Questions