i am trying to use gestures on ios with qt like this:
#ifndef SWIPESTACKWIDGET_H
#define SWIPESTACKWIDGET_H
#include <QStackedWidget>
#include <QSwipeGesture>
class SwipeStackWidget : public QStackedWidget
{
Q_OBJECT
public:
explicit SwipeStackWidget(QWidget *parent = 0);
bool event(QEvent *event);
bool gestureEvent(QGestureEvent *event);
void swipeTriggered(QSwipeGesture *gesture);
signals:
public slots:
};
#endif // SWIPESTACKWIDGET_H
and
#include "swipestackwidget.h"
#include <QDebug>
SwipeStackWidget::SwipeStackWidget(QWidget *parent) :
QStackedWidget(parent)
{
setAttribute(Qt::WA_AcceptTouchEvents);
grabGesture(Qt::TapGesture);
grabGesture(Qt::TapAndHoldGesture);
grabGesture(Qt::PanGesture);
grabGesture(Qt::PinchGesture);
grabGesture(Qt::SwipeGesture);
}
bool SwipeStackWidget::event(QEvent *event)
{
if (event->type() == QEvent::Gesture)
return gestureEvent(static_cast<QGestureEvent*>(event));
return QWidget::event(event);
}
bool SwipeStackWidget::gestureEvent(QGestureEvent *event)
{
qDebug() << "gestureEvent():" << event->gestures().size();
if (QGesture *swipe = event->gesture(Qt::SwipeGesture))
swipeTriggered(static_cast<QSwipeGesture *>(swipe));
if (QGesture *pan = event->gesture(Qt::PanGesture))
qDebug() << "Pan";
if (QGesture *pinch = event->gesture(Qt::PinchGesture))
qDebug() << "Pinch";
if (QGesture *pinch = event->gesture(Qt::TapGesture))
qDebug() << "Tap";
if (QGesture *pinch = event->gesture(Qt::TapAndHoldGesture))
qDebug() << "Tapandhold";
return true;
}
void SwipeStackWidget::swipeTriggered(QSwipeGesture *gesture)
{
qDebug() << "swipeTriggered()";
if (gesture->state() == Qt::GestureFinished) {
if (gesture->horizontalDirection() == QSwipeGesture::Left) {
qDebug() << "swipeTriggered(): swipe to previous";
setCurrentIndex( std::max( 0, currentIndex()-1) );
} else if (gesture->horizontalDirection() == QSwipeGesture::Right) {
qDebug() << "swipeTriggered(): swipe to next";
setCurrentIndex( std::min( count()-1, currentIndex()+1) );
}
update();
}
}
I can compile the code and execute it on the iphone. I do recieve tab gestures and tabAndHold reliably. Pan and Pimch do occur sometimes. Swipe is a big problem:
It only appears with 3 fingers
It appears only when swiping to bottom or to the right
It appears only sometimes
Swiping to the bottom is sometime recognized as next, sometimes as left
Does anyone have experience with QGestures on ios an can help me?
My test class is directly used in the main window and i use the grabGestures command in the main window as well but i do not handle the gestures there.
I can confirm that it requires three fingers. I tested with PyQt, Qt5.3.1, on an iPad. (Also, pan requires two fingers.)
I suspect that Qt designed it that way, so it is the same across platforms (choosing the finger count that is common on Ubuntu, since Canonical may have contributed much of the code?)
In the iOS SDK, some of the base gesture classes are configurable for number of fingers and directions. Read more. Thats only relevant since it means Qt could easily have configured the native gestures Qt subscribes to, if they are subscribing to native gestures. You could look at Qt's code (for the iOS platform abstraction QPA and in their other gesture related code) to verify that they designed 3 fingers for a swipe (that it is not a bug.)
But the 3-finger swipe physical gesture in the upward direction on the iOS platforms should mean "hide the app and show the system tray"? (That's my experience, I can't quote the iOS HIG, and don't know what the App Store requires.) If so, then an app should subscribe to Qt's swipe gesture and follow the guidelines.
But you could also implement a recognizer in Qt for a one-finger swipe?
Related
Context:
We are developing an IDE for VR/AR effects editing. Users would like to have MACRO recording. Something similar to Selenim/AutoIT in spirit, but specific for this app with deep dataModel integration. Users are trained JS developers. Software GUI is Qt/QML (mostly qml)
Current Blocker
I need a way to uniquely identify a specific QQuickItem by its objectName:
across multiple restart of the app.
across platform (OS/Cpu/...)
name should be human readable enough for use in JS Macro
Should serialise enough information from QQuickItem/QEvent to post a valid event
It is acceptable to use CI static analysis to ensure all item have a valid unique name and detect name changes.
The problem here is some button, for instance the number buttons have exactly the same fqn/path/name unless I use and ugly hack, renaming it by the text of its sub-component:
Component.onCompleted: {
objectName = "button." + button.text
}
property bool keepObjectName: true
Here a fully qualified name based on metaObject()->className() is not enough, as it wont be unique. qml ids are not usable as per documentation.
Question: Is there a way in Qt to obtain such unique identifier?
Related Question:
Qt GUI event recording and playback
Abandoned Road:
print trace of signals/slots called
Reason: Most events in IDE do not use connect() the standard Qt way. Too Low level.
Draft Technical Solution / POC (mostly for reference)
https://doc.qt.io/qt-5/qtdoc-demos-calqlatr-example.html
Using the Qt QML calculator example here. Added this to main.cpp:
#include "Filter.h"
#include <unordered_map>
std::unordered_map<QString,QQuickItem*> map;
void install(QQuickItem* root, KeyPressEater* keyPressEater, const QString& fqn){
if(!root->property("keepObjectName").isValid() || !root->property("keepObjectName").value<bool>()){
qDebug("setObjectName: %s", root->metaObject()->className());
qDebug("Object original name:%s", root->objectName().toStdString().c_str());
root->setObjectName(root->metaObject()->className());
}
else{
qDebug("unchanged Object Name: %s", root->objectName().toStdString().c_str());
}
auto fqnRoot = fqn.isEmpty() ? root->objectName() : fqn + "." + root->objectName();
qDebug("Object fqn: %s", fqnRoot.toStdString().c_str());
root->installEventFilter(keyPressEater);
keyPressEater->insert(root,fqnRoot);
for(auto* child: root->childItems()){
QString fqn = fqnRoot + "." + child->metaObject()->className();
install(child, keyPressEater,fqnRoot);
}
qDebug("\n\n\n");
}
Filter.h looks like this:
#ifndef FILTER_H
#define FILTER_H
#include <QQuickItem>
#include <QtCore>
#include <unordered_map>
#include <QQuickItem>
class KeyPressEater : public QObject
{
Q_OBJECT
public:
void insert(QQuickItem* item,const QString& str){
if(map2.find(str) != map2.end())
{
qDebug("Duplicate object name:%s", str.toStdString().c_str());
}
else{
qDebug("Object name:%s", str.toStdString().c_str());
map.insert({item,str});
map2.insert({str,item});
}
}
void play(){
playing = true;
for(auto p: records){
//p.second->event(p.first);
QCoreApplication::postEvent(p.second, p.first);
QCoreApplication::sendPostedEvents();
}
playing = false;
}
protected:
bool eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
//QMouseEvent *keyEvent = static_cast<QMouseEvent *>(event);
qDebug("click");
QQuickItem *item = static_cast<QQuickItem *>(obj);
qDebug("%s",map[item].toStdString().c_str());
qDebug("%s",item->metaObject()->className());
if(!playing){
records.push_back({event->clone(),item});
}
return false;
} else {
// standard event processing
if(event->type() == QEvent::KeyPress){
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == 32){
play();
}
}
return QObject::eventFilter(obj, event);
}
}
std::unordered_map<QQuickItem*,QString> map;
std::unordered_map<QString,QQuickItem*> map2;
std::atomic<bool> playing = false;
std::vector<std::pair<QEvent*,QObject*>> records;
};
#endif // FILTER_H
How to use:
Copy the above the main.cpp and new filter.h file in the calculator app example. Call install in main function:
install(view.rootObject(), &keyPressEater,"");
I am working on a Qt project consisting in a QMainWindow and multiple Qt and non-Qt classes. Many of them use QStrings with tr() that are translated with Qt Linguist. The language change (QTranslator load & install/QTranslator load & remove) is triggered by QActions in the app's menu.
I have read the official Qt documentation concerning dynamic translation, and it basically suggests the following overload:
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
titleLabel->setText(tr("Document Title"));
... // all my tr() QStrings here
okPushButton->setText(tr("&OK"));
} else
QWidget::changeEvent(event);
}
The problem I am facing is that the QStrings to translate are many (58 in QMainWindow alone), and several are filled at runtime as well, through user interaction; e. g. myFunction(a,b) below is called through a QPushButton:
void MainWindow::myFunction(MyClassA a, MyClassB b)
{
...
if(b.myCondition() == 0)
{
...
// below is the problem
myLabel->setText(myLabel->text() + QString("\n" + a->getName() + tr(" gagne ") + exp + tr(" points d'expérience")));
}
else
{
myLabel->setText(QString(tr("something else")));
}
...
}
So I hardly see how I can include this type of QString in the changeEvent() method above. What about the classes outside of MainWindow, which also have QStrings to translate but are not QWidget (so no changeEvent overload possible) ?
I have read that there's another way to use this method, with a UI form:
void MainWindow::changeEvent(QEvent* event)
{
if (event->type() == QEvent::LanguageChange)
{
ui.retranslateUi(this);
}
...
}
But this involves that I am using a UI form in my project, which I am not doing (all widgets are created in code).
I tried to export my MainWindow in a UI form, but when I try to include the generated header into the project, I get the following error:
ui_fenetreprincipale.h:32: error: qmainwindowlayout.h: No such file or directory
Thank you by advance for any suggestion to select the best way to translate my application.
Organize your code so that all the setting of translatable strings is done in one method in each class.
eg give every class that has translatable strings a setTrs() method that actually sets the strings.
class A
{
void setTrs()
{
okPushButton->setText(tr("&OK"));
}
}
//--------------
class B
{
int _trCond;
void myFunction(MyClassA a, MyClassB b)
{
_trCond = b.myCondition();
setTrs();
}
void setTrs()
{
if(_trCond == 0)
myLabel->setText(myLabel->text() + QString("\n" + a->getName() + tr(" gagne ") + exp + tr(" points d'expérience")));
else
myLabel->setText(QString(tr("something else")));
}
Then whenever the language for the application changes (eg connect to a menu entry selection, or MainWindow::event() or however the required language can change), you have to manually call the setTrs method of each of these objects
eg
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange)
{
setTrs();
objA.setTrs();
objB.setTrs();
}
}
Even more elegant would be to store the objects in a QList and just iterate through it calling the setTrs method on each element in turn
I had the same problem, menus not translating, fixed completely by creating and installing the QTranslator...
QScopedPointer<QApplication> app(new QApplication(argc, argv));
QTranslator myappTranslator;
myappTranslator.load(QString("Languages/de"))
app->installTranslator(&myappTranslator);
...before creating and showing the main window...
MainWindow *mainWin;
mainWin = new MainWindow(&splash);
mainWin->show();
I've spent better half of today trying to resolve seemingly trivial QListWidget behavior customization: when used presses mouse left button and moves mouse cursor, the content of ListWidget is scrolled and selection is moved to another item that happens to appear under mouse cursor. I am alright with scrolling, but I want to avoid selecting all consequtive items because this causes timely operation in my program. Finally I would like to keep list content scrolling on mouse press and move, but select items only by clicking directly on them.
Drag-n-drop is disabled for this list (which is default behavior) and it should be; I've tried to disable it explicitly: no changes whatsoever.
I have read all available docs on Qt related classes like QListWidget, QListWidgetItem, QListView, you name it! Tried to make sense of source code for these widgets; dug up StackOverflow and Google... but sadly no result :(
Here is all relevant code for my QListWidget: single selection, nothing fancy:
QListWidget* categoryListWidget;
...
categoryListWidget = new QListWidget();
categoryListWidget->move(offsetX, offsetY);
categoryListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
categoryListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
categoryListWidget->setFocusPolicy(Qt::NoFocus);
categoryListWidget->setStyleSheet(listQSS);
...
categoryListWidget->clear();
new QListWidgetItem(tr("1 - Sample Category 1"), categoryListWidget);
new QListWidgetItem(tr("2 - Sample Category 2"), categoryListWidget);
new QListWidgetItem(tr("3 - Sample Category 3 with a very long name"), categoryListWidget);
new QListWidgetItem(tr("4 - Sample Category 4"), categoryListWidget);
C++/Qt 5.5 if that's somehow relevant, both Win and Mac platforms share similar behavior.
For the sake of whoever stumbles upon the same question, here is my solution: subclass QListWidget and make child class ignore mouseMove events when leftButton is pressed.
Header:
class QtListWidget: public QListWidget
{ // QListWidget clone that suppresses item selection on mouse click+drag
private:
bool mousePressed;
public:
QtListWidget():QListWidget(), mousePressed(false) {}
protected:
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
};
Source:
//////////////////////////////////////////////////////////////////////////
void QtListWidget::mousePressEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mousePressEvent";
if(event->button() == Qt::LeftButton)
mousePressed = true;
QListWidget::mousePressEvent(event);
}
void QtListWidget::mouseMoveEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mouseMoveEvent";
if(!mousePressed) // disable click+drag
QListWidget::mouseMoveEvent(event);
}
void QtListWidget::mouseReleaseEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mouseReleaseEvent";
if(event->button() == Qt::LeftButton)
mousePressed = false;
QListWidget::mouseReleaseEvent(event);
}
//////////////////////////////////////////////////////////////////////////
Usage is trivial, for as many List Widgets as you need:
QtListWidget* categoryListWidget;
// all original code above would still work as expected
...
Want it done right? then do it yourself! :)
Your solution killed scrolling for me. I am using QListView. Here's another way:
In the constructor of the QListView's parent:
ui->listView->setSelectionMode(QAbstractItemView::NoSelection);
connect(ui->listView, SIGNAL(clicked(QModelIndex)), this, SLOT(on_listview_clicked(QModelIndex)));
In the connected slot:
on_listview_clicked(const QModelIndex &index)
{
if (index.isValid())
{
ui->listView->selectionModel->select(index, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
}
}
So, it only selects on a click.
I'm in the process of moving my code from QtWebKit to QtWebEngine. In general, the transition has been fairly smooth, however, I'm stuck on one particular issue. I use a QWebEngineView to display a Google Maps page. Some of the markers placed have have infowindows that pop up "Click Here for More Information" which opens the link in an external browser.
Using QtWebKit, this was fairly easy through the setLinkDelegation policy. However, it seems a little more complex here. I've tried to follow the example but somehow I need to redefine QWebEnginePage within QWebEngineView. Below is what I've come up with so far. Any idea how I can actually connect this all up?
Thanks
#ifndef MYQWEBENGINEVIEW_H
#define MYQWEBENGINEVIEW_H
#include <QWebEngineView>
#include <QDesktopServices>
class MyQWebEnginePage : public QWebEnginePage
{
Q_OBJECT
public:
MyQWebEnginePage(QObject* parent = 0) : QWebEnginePage(parent){}
bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
qDebug() << "acceptNavigationRequest("<<url << "," << type << "," << isMainFrame<<")";
if (type == QWebEnginePage::NavigationTypeLinkClicked)
{
QDesktopServices::openUrl(url);
return false;
}
return true;
}
};
class MyQWebEngineView : public QWebEngineView
{
Q_OBJECT
public:
MyQWebEngineView(QWidget* parent = 0);
MyQWebEnginePage* page() const;
};
#endif // MYQWEBENGINEVIEW_H
You don't need the second part. Try this:
QWebEngineView *view = new QWebEngineView();
MyQWebEnginePage *page = new MyQWebEnginePage();
view->setPage(page);
Is there a way to configure a Qt application to not detect the touch screen to keep the behavior of a normal screen?
It depends. If your operating system differentiates between touch events and mouse presses, you could create a touchEvent filter to ignore the events like so:
#include <QObject>
#include <QTouchEvent> // to get rid of "error: invalid use of incomplete type 'class QEvent'"
class QTouchEventFilter: public QObject
{
Q_OBJECT
public:
QTouchEventFilter(QObject *parent = 0) : QObject(parent)
{
}
protected:
bool eventFilter(QObject * p_obj, QEvent * p_event)
{
if (p_event->type() == QEvent::TouchBegin ||
p_event->type() == QEvent::TouchUpdate ||
p_event->type() == QEvent::TouchEnd ||
p_event->type() == QEvent::TouchCancel)
{
p_event->ignore();
return true;
}
return false;
}
};
Then install it on the widget you want to ignore the touch events:
myWidget->installEventFilter(new QTouchEventFilter);
However, my instinct is that on most OS's the 'touches' you're talking about are going to come in as mouse press events, and you won't be able to filter them out unless you are willing to give up all mouse input for that widget. If you are willing, use the same concept but replace the QEvent's with the once associated with the mouse.