what is the best way to show tile map and some other object in graphicsview? - qt

recently i start to learn Qt and now i'm working on GCS project that it must have a map with some tiled imges and and some graphics item like Plan,the path and also on over off all some gauge.
so we have 3 kind of item:
Tiled map in the background so that its change by scrolling .
in the middle there is a picture of airplane that move by gps changes and also its way .
on the all on off these items there 3 or 4 gauge like speed meter, horizontal gauge and altimeter gauge there are must be solid in somewhere of graphicsview and not change when scrolling down/up or left right
The question is what is the best way to implement this ?
here is first look of my project:
in first look gauge are not over map but i want to be ! i want to have bigger map screen with gauges include it !
And here is map updater code :
void mainMap::update()
{
m_scene->clear();
QString TilePathTemp;
QImage *imageTemp = new QImage();
int X_Start=visibleRect().topLeft().x()/256;
int X_Num=qCeil((float)visibleRect().bottomRight().x()/256.0f-(float)visibleRect().topLeft().x()/256.0f);
int Y_Start=visibleRect().topLeft().y()/256;
int Y_Num=qCeil((float)visibleRect().bottomRight().y()/256.0f-(float)visibleRect().topLeft().y()/256.0f);
LastCenterPoint->setX(visibleRect().center().x());
LastCenterPoint->setY(visibleRect().center().y());
X_Start=(X_Start-X_MAP_MARGIN)>0?(X_Start-X_MAP_MARGIN):0;
Y_Start=(Y_Start-Y_MAP_MARGIN)>0?(Y_Start-Y_MAP_MARGIN):0;
X_Num+=X_MAP_MARGIN;
Y_Num+=Y_MAP_MARGIN;
qDebug()<<"XS:"<<X_Start<<" Num:"<<X_Num;
qDebug()<<"YS:"<<Y_Start<<" Num:"<<Y_Num;
for(int x=X_Start;x<=X_Start+X_Num;x++){
for(int y=Y_Start;y<=Y_Start+Y_Num;y++){
if(Setting->value("MapType",gis::Hybrid).toInt()==gis::Hybrid) TilePathTemp=Setting->value("MapPath","/Users/M410/Documents/Map").toString()+"/Hybrid/gh_"+QString::number(x)+"_"+QString::number(y)+"_"+QString::number(ZoomLevel)+".jpeg" ;
else if(Setting->value("MapType",gis::Sattelite).toInt()==gis::Sattelite) TilePathTemp=Setting->value("MapPath","/Users/M410/Documents/Map").toString()+"/Sattelite/gs_"+QString::number(x)+"_"+QString::number(y)+"_"+QString::number(ZoomLevel)+".jpeg" ;
else if(Setting->value("MapType",gis::Street).toInt()==gis::Street) TilePathTemp=Setting->value("MapPath","/Users/M410/Documents/Map").toString()+"/Street/gm_"+QString::number(x)+"_"+QString::number(y)+"_"+QString::number(ZoomLevel)+".jpeg" ;
QFileInfo check_file(TilePathTemp);
// check if file exists and if yes: Is it really a file and no directory?
if (check_file.exists() && check_file.isFile()) {
// qDebug()<<"Exist!";
imageTemp->load(TilePathTemp);
QPixmap srcImage = QPixmap::fromImage(*imageTemp);
//QPixmap srcImage("qrc:/Map/File1.jpeg");
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(srcImage);
item->setPos(QPointF(x*256, y*256));
m_scene->addItem(item);
// centerOn( width() / 2.0f , height() / 2.0f );
} else {
qDebug()<<"NOT Exist!";
}
}
}

Really, you should consider using QML. The advantage of using QML instead of QGraphicsView is you can iterate a lot faster than if you were working directly in C++. The primary downside is generally increased memory usage and incompatibility with QWidgets.
So if you need unique graphics, and very little "standard widget" stuff, you should use QML first and then QGraphicsView ONLY IF requirements dictate it.
Specific to your project though, Qt has a Map type which could be useful: https://doc.qt.io/qt-5/qml-qtlocation-map.html

Related

QTextLayout / QTextLine support grouping of several-characters so it acts as one character for the cursor

Is it possible for QTextLayout to render several characters, but to process/handle it as one character. For example rendering a code point like: [U+202e], and when moving the caret/calculating positions, it is treated as one character.
Edited:
Please check this following issue, were I explain what I'm trying to do. It for the edbee Qt component. It's using QTextLayout for line rendering.
https://github.com/edbee/edbee-lib/issues/127
Possibly it isn't possible with QTextLayout, the documentation is quite limited.
According to Qt docs:
"The class has a rather low level API and unless you intend to implement your own text rendering for some specialized widget, you probably won't need to use it directly." - https://doc.qt.io/qt-5/qtextlayout.html#details
You should probably use a QLineEdit or a QTextEdit (each has a method called setReadOnly(bool)).
Before answering the question, I will point out that the CursorMode enum (https://doc.qt.io/qt-5/qtextlayout.html#CursorMode-enum) seems very promising for this problem, but to me, the documentation isn't clear on how to use it or set it.
Now to answer your question in regards to QLineEdit or QTextEdit, it's a bit complicated, but it's the same for QLineEdit and QTextEdit, so lets look at QTextEdit.
Firstly, mouse clicks: QTextEdit has a signal called cursorPositionChanged(), which will be helpful here. You'll want to connect that to a custom slot, which can make use of the function moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor) (https://doc.qt.io/qt-5/qtextedit.html#moveCursor). Notice that there are very helpful enumeration values for you here in QTextCursor::MoveOperation regarding word hopping (https://doc.qt.io/qt-5/qtextcursor.html#MoveOperation-enum). How do we put all of this together? Well, probably the right way to do it is to determine the width of the chars to the left of the cursor's position and the width of the chars to the right of the cursor's position when the cursorPositionChanged() signal is emitted and go to the side of the word that has less width. However, I'm not sure how to do that. At this point I'd settle with checking the number of chars to the left and right and going to the side with less.
Secondly, keyboard presses: This goes a bit out of my knowledge, but almost everything drawable and iteractable inherits from QWidget. Take a look at https://doc.qt.io/qt-5/qwidget.html#keyPressEvent and it's possible that overriding that in your own implementation of QTextEdit is necessary to get the left arrow and right arrow keypresses to jump words (once you get that part it's pretty easy, just use the same function as last section for moving the cursor, or in the case of QLineEdit, cursorWordForward()/cursorWordBackward()).
All this being said, I've so far been assuming that you're not deleting anything or selecting anything. Selection can be a real pain depending on if you allow multiple selections, but the functions are all there in the documentation to implement those things.
Example of mouse click impl:
myclass.hpp
#include <QTextEdit>
#include <QTextCursor>
#include <QObject>
#include <QString>
int distance_to_word_beginning_or_end(const QString &str, int index, bool beginning);
class MyClass {
MyClass();
~MyClass();
private:
QTextEdit *text_edit;
public slots:
void text_edit_changed_cursor_location();
};
myclass.cpp
#include "myclass.hpp"
int distance_to_word_beginning_or_end(const QString &str, int index, bool beginning)
{
// return the distance from the beginning or end of the word from the index given
int inc_or_dec = (beginning) ? -1 : 1;
int distance = 0;
while (index >= 0 && index < str.length())
{
if (str.at(index) == ' ' || str.at(index) == '\n' || str.at(index) == '\t')
{
return distance;
}
distance++;
index += inc_or_dec;
}
return --distance;
}
MyClass::MyClass()
{
text_edit = new QTextEdit();
QObject::connect(text_edit, &QTextEdit::cursorPositionChanged, this, &MyClass::text_edit_changed_cursor_location);
}
MyClass::~MyClass()
{
delete text_edit;
}
void MyClass::text_edit_changed_cursor_location()
{
QString text_edit_string = text_edit->text();
QTextCursor text_edit_cursor = text_edit->textCursor();
auto current_position = text_edit_cursor.position();
QTextCursor new_text_cursor;
int distance_to_beginning = distance_to_word_beginning_or_end(text_edit_string, current_position, true);
int distance_to_end = distance_to_word_beginning_or_end(text_edit_string, current_position, false);
auto movement_type;
if (distance_to_beginning > distance_to_end)
{
new_text_cursor.setPosition(current_position + distance_to_end);
} else {
new_text_cursor.setPosition(current_position - distance_to_beginning);
}
text_edit->setTextCursor(new_text_cursor);
}

How test Qml/Quick UI on different DPI / screen resolution on the Windows?

Does someone know solution ( Qt methods / external applications / etc ) to test Qml / Quick UI for different DPI scaling and screen resolution on the Windows?
I am writing android qml application with custom ui elements, but when on my own android phone it looks normally, on the other android phones with other DPI it become very different.
I think to use Android emulators or even some virtual displays (like pyvirtualdisplay) but all this solutions are slow and hard to use.
I am writing android app with also C++ code so its not only Qml files for rendering.
So maybe someone already decided this issue when dev on Qt for android before?
Ideally it will be some Qt method for QWidget (QQuickView) to render widget content with specific scaling (to simulate DPI).
I have had "good enough" success by writing some helper code based on the ratio math described in Calculating Scaling Ratio (in the Qt docs).
The main drawback is that this requires you to form a habit around wrapping any raw literal sizing numbers in a helper function.
The starting point is code like the following, which only needs to execute once at the start of the application:
#include <QGuiApplication>
#include <QScreen>
#include <QDebug>
...
auto screen = QGuiApplication::primaryScreen();
const QRect currScreen = screen->geometry();
const qreal dpi = screen->logicalDotsPerInch();
const double refDpi = 96;
const double refHeight = 2160;
const double refWidth = 3840;
// These computations are taken nearly verbatim from:
// https://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio
// (archival) http://web.archive.org/web/20210511233243/https://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio
m_extentsRatio = qMin( currScreen.height() / refHeight, currScreen.width() / refWidth );
m_fontsRatio = qMin( currScreen.height() * refDpi / ( dpi * refHeight ), currScreen.width() * refDpi / ( dpi * refWidth ) );
qDebug() << "m_extentsRatio" << m_extentsRatio;
qDebug() << "m_fontsRatio" << m_fontsRatio;
To possibly state the obvious, these variables capture information about the host you are testing on (such as your laptop):
auto screen = QGuiApplication::primaryScreen();
const QRect currScreen = screen->geometry();
const qreal dpi = screen->logicalDotsPerInch();
In contrast:
refDpi, refHeight, and refWidth represent the {DPI,height,width} of the screen you wish to "emulate" (such as some exotic cell phone model).
If you wanted to make this whole approach more readily "pluggable", you could pass in the refDpi, refHeight, and refWidth as command-line arguments to your application, so that you could relaunch to emulate various target models without having to recompile.
The remaining pieces of the solution are to: (1) write a Q_INVOKABLE helper function that your QML code can use, and (2) consistently prefer to wrap raw literal integer sizes in that helper function.
Concretely:
Q_INVOKABLE double asIfOnSomePhone( double input, bool isFontSize )
{
if( isFontSize )
{
return input * m_fontsRatio;
}
else
{
return input * m_extentsRatio;
}
}
And in the QML:
Layout.minimumHeight: localScreen.asIfOnSomePhone(157,
false /*isFontSize*/)
Layout.maximumHeight: Layout.minimumHeight
Layout.minimumWidth: localScreen.asIfOnSomePhone(625,
false /*isFontSize*/)
Layout.maximumWidth: Layout.minimumWidth
...
property font basicFont
basicFont.bold: false
basicFont.underline: false
basicFont.pointSize: localScreen.asIfOnSomePhone(14, true /*isFontSize*/)
basicFont.family: "Bitstream Vera Sans"
This has allowed me to comfortably iterate on a GUI design that is intended to ultimately run on a display with a much different resolution than the one I use in my day-to-day work. Once I began applying this tactic, I was able to transfer my design to the final platform with minimal surprises.
Another habit that I have developed that eases some of the struggles of writing a responsive UI in QML is to express many measurements as proportions of the total display height and/or width. I use helper functions named percentOfAppHeightCappedMinAndMax and percentOfAppWidthCappedMinAndMax. You can find a full working sample on GitHub.

Initializing QVector of QImages

I am fairly new to Qt. It is the first framework I have worked with. I am writing a blackjack game using Qt. It seems to me that I should store the images of each card in a container class such as QVector. The container type would be of QImage. So I would have a declaration such as QVector<QImage> cards; Perhaps this is not the best way about approaching this problem so any alternative suggestion is of course welcomed. However, regardless, I would like to know if it is possible to initialize the container during the declaration. I have not been able to solve this so my solution is the following:
// Deck.h
class Deck
{
public:
Deck();
void shuffle(); // Creates new deck and shuffles it.
QImage &popCard(); // Removes first card off deck.
private:
void emptyDeck(); // Empty the deck so new cards can be added
QVector<QImage> cards;
QQueue<QImage> deck;
};
// Deck.cpp
Deck::Deck()
{
cards.push_back(QImage(":/PlayingCards/Clubs 1.png"));
cards.push_back(QImage(":/PlayingCards/Clubs 2.png"));
cards.push_back(QImage(":/PlayingCards/Clubs 3.png"));
cards.push_back(QImage(":/PlayingCards/Clubs 4.png"));
// continue process for entire deck of cards...
}
This seems to be painfully tedious especially if I consider adding a different style of playing cards later on, or if I give the user an option to change the style of the cards at run time. What would be an efficient design to this?
I would like to know if it is possible to initialize the container during the declaration
Yes you can since C++11:
QList<int> list{1, 2, 3, 4, 5};
Well about your question one of the way can be:
Create in resources all types of your images style calling like template, for example: "Name n.png", where n - number from 1 to 54 (cnt of cards with Jokers);
Create some QList<QImage> (I think it'll be better then QVector);
Create some QMap for searching correct template easy;
Create some enum class for template map;
Write a function that change images of your cards by selected enum.
However it is very light codding. I think there is more usefull ways and there is a lot of other more beauty ways to do this game and logic. But as part of your question here some code (can be not very right, cause write as is):
// Somewhere in global
enum class CardsTemplate: {
Clubs,
SomeTemp1,
SomeTemp2,
...
SomeTempN
}
.H file:
private:
QList<QImage> _images;
QMap<CardsTemplate, QString> _imagesMap {
{CardsTemplate::Clubs, QString("Clubs")},
{CardsTemplate::SomeTemp1, QString("SomeTemp1")},
{CardsTemplate::SomeTemp2, QString("SomeTemp2")},
...
{CardsTemplate::SomeTempN, QString("SomeTempN")}
}
public:
Deck(CardsTemplate temp);
void setNewTemplate(CardsTemplate temp);
.CPP file:
Deck::Deck(CardsTemplate temp){
for(int i = 1; i <= 54; i++)
_images << QImage(QString(":/Playing cards/%1 %2.png")
.arg(_imagesMap.value(temp)).arg(i));
}
void Deck::setNewTemplate(CardsTemplate temp) {
for(int i = 1; i <= _images.size(); i++)
_images[i] = QImage(QString(":/Playing cards/%1 %2.png")
.arg(_imagesMap.value(temp)).arg(i));
}

How to trace the missing pixels when using drawLine

We know that for drawing on an image in qt, qpainter is used. Recently, I used drawLine() function to draw whatever an user is scribbling. This was done by passing the lastPoint and currentPoint from the mouseMoveEvent to a custom function which implements drawLine(). I have passed the arguments for that custom function as given below:
void myPaint::mouseMoveEvent(QMouseEvent *event) {
qDebug() << event->pos();
if ((event->buttons() & Qt::LeftButton) && scribbling) {
pixelList.append(event->pos());
drawLineTo(event->pos());
lastPoint = event->pos();
}
}
Now with the help of qDebug() I noticed that some pixels are missed while drawing but the drawing is precise. I looked up the source of qt-painting where I saw that drawLine() was calling drawLines() which was making use of qpainterPath to have a shape drawn on the image.
My question is that, is there anyway to track these "missed" pixels or any approach to find all the pixels which have been drawn?
Thanks!
void myPaint::drawLineTo(const QPoint &endPoint) {
QPainter painter(image); //image is initialized in the constructor of myPaint
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(Qt::blue, myPenWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
painter.drawLine(lastPoint, endPoint);
modified = true;
lastPoint = endPoint; //at the mousePressEvent, the event->pos() will be stored as
// lastPoint
update();
}
For a start, don't draw in a mouseEvent(). Actually handling a mouseevent should be done as quick as possible. Also, it is not a good idea to look at the Qt source, it can be confusing. Rather assume that what Qt gives you work, and first try to answer "What I am doing wrong?". As I said drawing in a mouse event is definitely wrong.
Your description is really subjective, maybe an image of your output is better. Are you trying to emulate a pen (like in windows paint)? In this case do the mouse button has to be down ? is that the purpose of your variable scribbling?
There is more. following the documentation, QMouseEvent::buttons() always return a combination of all buttons for mouse move event. Which make sense : the mouse movements are independent of the buttons. It means
if ((event->buttons() & Qt::LeftButton)
will always be true.
Let's assume you want to draw the path of your mouse when the left button is pressed. Then you use something like :
void myPaint::mousePressEvent(QMouseEvent *event){
scribbling = true;
pixelList.clear();
}
void myPaint::mouseReleaseEvent(QMouseEvent *event){
scribbling = false;
}
void myPaint::mouseMoveEvent(QMouseEvent *event) {
if ( scribbling) {
pixelList.append(event->pos());
}
}
void myPaint::paintEvent(){
QPainter painter(this)
//some painting here
if ( scribbling) {
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(Qt::blue, myPenWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
// here draw your path
// for example if your path can be made of lines,
// or you just put the points if they are close from each other
}
//other painting here
}
If after all of this you don't have a good rendering, try using float precision (slower), ie QMouseEvent::posF() instead of QMouseEvent::pos().
EDIT :
"I want to know whether there is any way to calculate all the sub-pixels between any two pixels that we send as arguments to drawLine"
Yes there is. I don't know why you need to do such thing but is really simple. A line can be characterized with the equation
y = ax + b
Both of the endpoints of the line p0 = (x0, y0) and p1 = (x1, y1) satisfy this equation so you can easily find a and b. Now all you need to do is increment from x0 to x1 by the amount of
pixels you want (say 1), and to compute the corresponding y value, each time saving point(x,y).
So will go over all of the points saved in pixelList and repeat this process for any two consecutive points.

OpenNI + OpenCV + Qt

I'm trying to make an app using Kinect (OpenNI), processing the image (OpenCV) with a GUI.
I tested de OpenNI+OpenCV and OpenCV+Qt
Normally when we use OpenCV+Qt we can make a QWidget to show the content of the camera (VideoCapture) .. Capture a frame and update this querying for new frames to device.
With OpenNI and OpenCV i see examples using a for cycle to pull data from Kinect Sensors (image, depth) , but i don't know how to make this pulling routing mora straightforward. I mean, similar to the OpenCV frame querying.
The idea is embed in a QWidget the images captured from Kinect. The QWidget will have (for now) 2 buttons "Start Kinect" and "Quit" ..and below the Painting section to show the data captured.
Any thoughs?
You can try the QTimer class to query the kinect at fixed time intervals. In my application I use the code below.
void UpperBodyGestures::refreshUsingTimer()
{
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(MainEventFunction()));
timer->start(30);
}
void UpperBodyGestures::on_pushButton_Kinect_clicked()
{
InitKinect();
ui.pushButton_Kinect->setEnabled(false);
}
// modify the main function to call refreshUsingTimer function
UpperBodyGestures w;
w.show();
w.refreshUsingTimer();
return a.exec();
Then to query the frame you can use the label widget. I'm posting an example code below:
// Query the depth data from Openni
const XnDepthPixel* pDepth = depthMD.Data();
// Convert it to opencv for manipulation etc
cv::Mat DepthBuf(480,640,CV_16UC1,(unsigned char*)g_Depth);
// Normalize Depth image to 0-255 range (cant remember max range number so assuming it as 10k)
DepthBuf = DepthBuf / 10000 *255;
DepthBuf.convertTo(DepthBuf,CV_8UC1);
// Convert opencv image to a Qimage object
QImage qimage((const unsigned char*)DepthBuf.data, DepthBuf.size().width, DepthBuf.size().height, DepthBuf.step, QImage::Format_RGB888);
// Display the Qimage in the defined mylabel object
ui.myLabel->setPixmap(pixmap.fromImage(qimage,0).scaled(QSize(300,300), Qt::KeepAspectRatio, Qt::FastTransformation));

Resources