Related
I am trying to write a library with a custom Qt icon-text label type object in it. However the icon never displays although the widget is shown (as tested by replacing the pixmap with plain text).
My CMakeLists.txt:
...
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/resources.cpp
COMMAND rcc -no-compress ${CMAKE_CURRENT_SOURCE_DIR}/src/qt/configmgr.qrc -name configmgr -o ${CMAKE_CURRENT_BINARY_DIR}/resources.cpp
DEPENDS src/qt/configmgr.qrc src/qt/info.png
)
...
add_library(ConfigMgr STATIC
...
src/qt/section_header.cpp
${CMAKE_CURRENT_BINARY_DIR}/resources.cpp
)
...
My configmgr.qrc:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>info.png</file>
</qresource>
</RCC>
This produces a "resources.cpp" That looks like this:
static const unsigned char qt_resource_data[] = {
0x0,0x0,0x1,0x76,
0x89,
0x50,0x4e,0x47,0xd,0xa,0x1a,0xa,0x0,0x0,0x0,0xd,0x49,0x48,0x44,0x52,0x0,
...
0x44,0xae,0x42,0x60,0x82,
};
static const unsigned char qt_resource_name[] = {
// info.png
0x0,0x8,
0x4,0xd2,0x59,0x47,
0x0,0x69,
0x0,0x6e,0x0,0x66,0x0,0x6f,0x0,0x2e,0x0,0x70,0x0,0x6e,0x0,0x67,
};
static const unsigned char qt_resource_struct[] = {
// :
0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
// :/info.png
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0,
0x0,0x0,0x1,0x78,0x7d,0xe0,0x40,0x12,
};
...
Build output includes resources.cpp.o and that should be getting linked into the library.
My class header:
class SectionHeader : public QWidget
{
Q_OBJECT
public:
SectionHeader(const std::string &text, const std::string &info);
virtual ~SectionHeader();
private:
std::string m_info;
QPixmap m_icon;
};
My class constructor:
SectionHeader::SectionHeader(const std::string &text, const std::string &info)
: m_info{info},
m_icon(":/info.png")
{
QHBoxLayout *layout = new QHBoxLayout;
QLabel *label;
if (!m_info.empty()) {
label = new QLabel;
label->setPixmap(m_icon);
layout->addWidget(label);
}
label = new QLabel(text.c_str());
label->setAlignment(Qt::AlignCenter);
label->setStyleSheet("font-weight: bold; font-family: Calibre; font-size: 10pt");
layout->addWidget(label);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
}
I have also tried calling load() on a QPixmap instance and it returns false.
Everything I have read, and similar code I have written before which is very similar to this, tells me that this should work. Why doesn't it?
The problem here is that I am trying to write a library.
I solved the problem by explicitly initialising the resources.
Find the generated CPP file for the resources. In my case it was resources.cpp.
Identify the name of the initialisation and cleanup functions. In my case they are:
int qInitResources_configmgr();
int qCleanupResources_configmgr();
Somewhere suitable, such as in the CPP file for your library's main class, locally declare these two functions.
Call the two functions in the appropriate place. E.g. main class constructor and destructor respectively.
So far, I understand that we have two threads in QML, our main application thread, and our "scene graph" thread : http://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html
I've implemented my own vtkQmlItem with the help of this link : http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html
and I've noticed that my vtkscene is only rendered when the afterrendering signal is emitted by the qml flow.
So far, everything is ok and works perfectly, I can see my vtk scene and can even interract with it.
But I would like to also programmatically render my vtk scene as well, since I want to do an animation by moving the camera around a vtk object.
Calling renderer->render() directly shows a lot of vtk error, and does not seem to be the good way to do this.
Calling this->window()->update() seems to put the event in the eventLoop, when I want it to be handled instantly. The only way I've managed to make it work instantly is by using QApplication::processEvents(), which is a hack I don't like and would love another solution.
So the pseudocode of the working solution that I don't like is the following :
for (int i = 0; i < 50; i++)
{
ChangeCameraPosition(i); // Change the position and orientation of the vtk camera
this->window()->update();
QApplication::processEvents(); // The hack I don't like
QThread::msleep(500);
}
For people looking for a solution for this using Qt QuickControls 2 and VTK 8, you can find a working example in this repository https://github.com/nicanor-romero/QtVtk with building instructions in the README.
the problem is actually a bit complicated and if nothing changed in the past few months, there is still no support for QtQuick in VTK, which means no simple few lines solution. You can find support classes for QtWidgets in VTK/GUISupport/QtOpenGL/ and use them as a template to derive support for qml. But mainly I recommend checking this thread for a discussion about this topic.
The key point is that QtQuick holds the openGL context for the qml window you are trying to render into in a dedicated thread and it won't let anything else get that context. So in order to render into it from VTK, you have to do it within that thread. This means:
1) Create your own vtkRenderWindow that overrides the Render() method such that it is ensured it happens in the qml's render thread.
2) Make that render window render into a framebuffer object provided by the qtquick (instance of QQuickFramebufferObject).
3) Interconnect vtk's rendering signals with the qt's rendering methods -> e.g. when the vtk render window calls makeCurrent, the qt's rendering thread "wakes up".
Here is my implementation based on Taylor Braun-Jones' template linked above. It might not be perfect, but it works for me (I have removed some parts specific to my app so it might not compile straight away, but it should put you on a path to some working solution):
qmlVtk.h:
#include <vtkEventQtSlotConnect.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkRenderer.h>
#include <QtQuick/QQuickFramebufferObject>
// Use the OpenGL API abstraction from Qt instead of from VTK because vtkgl.h
// and other Qt OpenGL-related headers do not play nice when included in the
// same compilation unit
#include <QOpenGLFunctions>
#include <qqmlapplicationengine.h>
class QVTKFramebufferObjectRenderer;
class QVTKInteractorAdapter;
class vtkInternalOpenGLRenderWindow;
class QVTKFramebufferObjectRenderer;
class QVTKFrameBufferObjectItem : public QQuickFramebufferObject
{
Q_OBJECT
public:
QVTKFrameBufferObjectItem(QQuickItem *parent = 0);
~QVTKFrameBufferObjectItem();
Renderer *createRenderer() const;
vtkSmartPointer<vtkInternalOpenGLRenderWindow> GetRenderWindow() const;
protected:
// Called once before the FBO is created for the first time. This method is
// called from render thread while the GUI thread is blocked.
virtual void init();
vtkSmartPointer<vtkInternalOpenGLRenderWindow> m_win;
QVTKInteractorAdapter* m_irenAdapter;
vtkSmartPointer<vtkEventQtSlotConnect> mConnect;
friend class QVTKFramebufferObjectRenderer;
// Convert the position of the event from openGL coordinate to native coordinate
QMouseEvent openGLToNative(QMouseEvent const& event);
virtual void mouseMoveEvent(QMouseEvent * event);
virtual void mousePressEvent(QMouseEvent * event);
virtual void mouseReleaseEvent(QMouseEvent * event);
virtual void mouseDoubleClickEvent(QMouseEvent * event);
virtual void wheelEvent(QWheelEvent *event);
virtual void keyPressEvent(QKeyEvent* event);
virtual void keyReleaseEvent(QKeyEvent* event);
virtual void focusInEvent(QFocusEvent * event);
virtual void focusOutEvent(QFocusEvent * event);
protected Q_SLOTS:
// slot to make this vtk render window current
virtual void MakeCurrent();
// slot called when vtk wants to know if the context is current
virtual void IsCurrent(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data);
// slot called when vtk wants to start the render
virtual void Start();
// slot called when vtk wants to end the render
virtual void End();
// slot called when vtk wants to know if a window is direct
virtual void IsDirect(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data);
// slot called when vtk wants to know if a window supports OpenGL
virtual void SupportsOpenGL(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data);
};
/// <summary>
/// An extension of vktGenericOpenGLRenderWindow to work with Qt.
/// Serves to write VTK-generated render calls to a framebuffer provided and maintained by Qt. It is meant to be used within Qt render loop, i.e. using Qt's render thread.
/// </summary>
/// <seealso cref="vtkGenericOpenGLRenderWindow" />
/// <seealso cref="QOpenGLFunctions" />
class vtkInternalOpenGLRenderWindow : public vtkGenericOpenGLRenderWindow, protected QOpenGLFunctions
{
public:
static vtkInternalOpenGLRenderWindow* New();
vtkTypeMacro(vtkInternalOpenGLRenderWindow, vtkGenericOpenGLRenderWindow)
virtual void OpenGLInitState();
// Override to use deferred rendering - Tell the QSG that we need to
// be rendered which will then, at the appropriate time, call
// InternalRender to do the actual OpenGL rendering.
virtual void Render();
// Do the actual OpenGL rendering
void InternalRender();
// Provides a convenient way to set the protected FBO ivars from an existing
// FBO that was created and owned by Qt's FBO abstraction class
// QOpenGLFramebufferObject
void SetFramebufferObject(QOpenGLFramebufferObject *fbo);
QVTKFramebufferObjectRenderer *QtParentRenderer;
protected:
vtkInternalOpenGLRenderWindow();
~vtkInternalOpenGLRenderWindow()
{
// Prevent superclass destructors from destroying the framebuffer object.
// QOpenGLFramebufferObject owns the FBO and manages it's lifecyle.
this->OffScreenRendering = 0;
}
};
qmlVtk.cpp:
#include "QVTKFramebufferObjectItem.h"
#include <QQuickFramebufferObject>
#include <QQuickWindow>
#include <QOpenGLFramebufferObject>
#include <QVTKInteractorAdapter.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkObjectFactory.h>
#include <vtkSmartPointer.h>
#include <vtkCamera.h>
#include <vtkProperty.h>
#include <qglfunctions.h>
class QVTKFramebufferObjectRenderer : public QQuickFramebufferObject::Renderer
{
friend class vtkInternalOpenGLRenderWindow;
public:
QVTKFramebufferObjectRenderer(vtkSmartPointer<vtkInternalOpenGLRenderWindow> rw) :
m_framebufferObject(0)
{
m_vtkRenderWindow = rw;
m_vtkRenderWindow->QtParentRenderer = this;
}
~QVTKFramebufferObjectRenderer()
{
m_vtkRenderWindow->QtParentRenderer = 0;
glFrontFace(GL_CCW); // restore default settings
}
virtual void synchronize(QQuickFramebufferObject * item)
{
// the first synchronize call - right before the the framebufferObject
// is created for the first time
if (!m_framebufferObject)
{
QVTKFrameBufferObjectItem *vtkItem = static_cast<QVTKFrameBufferObjectItem*>(item);
vtkItem->init();
}
}
virtual void render()
{
m_vtkRenderWindow->InternalRender(); // vtkXOpenGLRenderWindow renders the scene to the FBO
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size)
{
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::Depth);
m_framebufferObject = new QOpenGLFramebufferObject(size, format);
m_vtkRenderWindow->SetFramebufferObject(m_framebufferObject);
return m_framebufferObject;
}
vtkSmartPointer<vtkInternalOpenGLRenderWindow> m_vtkRenderWindow;
QOpenGLFramebufferObject *m_framebufferObject;
};
vtkStandardNewMacro(vtkInternalOpenGLRenderWindow);
vtkInternalOpenGLRenderWindow::vtkInternalOpenGLRenderWindow() :
QtParentRenderer(0)
{
vtkOpenGLRenderWindow::OpenGLInitContext();
}
void vtkInternalOpenGLRenderWindow::OpenGLInitState()
{
this->MakeCurrent();
vtkOpenGLRenderWindow::OpenGLInitState();
// Before any of the gl* functions in QOpenGLFunctions are called for a
// given OpenGL context, an initialization must be run within that context
initializeOpenGLFunctions();
glFrontFace(GL_CW); // to compensate for the switched Y axis
}
void vtkInternalOpenGLRenderWindow::InternalRender()
{
vtkOpenGLRenderWindow::Render();
}
//
// vtkInternalOpenGLRenderWindow Definitions
//
void vtkInternalOpenGLRenderWindow::Render()
{
this->QtParentRenderer->update();
}
void vtkInternalOpenGLRenderWindow::SetFramebufferObject(QOpenGLFramebufferObject *fbo)
{
// QOpenGLFramebufferObject documentation states that "The color render
// buffer or texture will have the specified internal format, and will
// be bound to the GL_COLOR_ATTACHMENT0 attachment in the framebuffer
// object"
this->BackLeftBuffer = this->FrontLeftBuffer = this->BackBuffer = this->FrontBuffer =
static_cast<unsigned int>(GL_COLOR_ATTACHMENT0);
// Save GL objects by static casting to standard C types. GL* types
// are not allowed in VTK header files.
QSize fboSize = fbo->size();
this->Size[0] = fboSize.width();
this->Size[1] = fboSize.height();
this->NumberOfFrameBuffers = 1;
this->FrameBufferObject = static_cast<unsigned int>(fbo->handle());
this->DepthRenderBufferObject = 0; // static_cast<unsigned int>(depthRenderBufferObject);
this->TextureObjects[0] = static_cast<unsigned int>(fbo->texture());
this->OffScreenRendering = 1;
this->OffScreenUseFrameBuffer = 1;
this->Modified();
}
void QVTKFrameBufferObjectItem::Start()
{
m_win->OpenGLInitState();
}
void QVTKFrameBufferObjectItem::End()
{
}
void QVTKFrameBufferObjectItem::MakeCurrent()
{
this->window()->openglContext()->makeCurrent(this->window());
}
void QVTKFrameBufferObjectItem::IsCurrent(vtkObject*, unsigned long, void*, void* call_data)
{
bool* ptr = reinterpret_cast<bool*>(call_data);
*ptr = this->window()->openglContext();
}
void QVTKFrameBufferObjectItem::IsDirect(vtkObject*, unsigned long, void*, void* call_data)
{
int* ptr = reinterpret_cast<int*>(call_data);
*ptr = QGLFormat::fromSurfaceFormat(this->window()->openglContext()->format()).directRendering();
}
void QVTKFrameBufferObjectItem::SupportsOpenGL(vtkObject*, unsigned long, void*, void* call_data)
{
int* ptr = reinterpret_cast<int*>(call_data);
*ptr = QGLFormat::hasOpenGL();
}
QVTKFrameBufferObjectItem::QVTKFrameBufferObjectItem(QQuickItem *parent) : QQuickFramebufferObject(parent)
{
setAcceptedMouseButtons(Qt::AllButtons);
m_irenAdapter = new QVTKInteractorAdapter(this);
m_win = vtkSmartPointer<vtkInternalOpenGLRenderWindow>::New();
// make a connection between the vtk signals and qt slots so that an initialized and madeCurrent opengl context is given to the vtk
// we probably need only the Start(), MakeCurrent() and End() one, but just to be sure...
mConnect = vtkSmartPointer<vtkEventQtSlotConnect>::New();
mConnect->Connect(m_win, vtkCommand::WindowMakeCurrentEvent, this, SLOT(MakeCurrent()));
mConnect->Connect(m_win, vtkCommand::WindowIsCurrentEvent, this, SLOT(IsCurrent(vtkObject*, unsigned long, void*, void*)));
mConnect->Connect(m_win, vtkCommand::StartEvent, this, SLOT(Start()));
mConnect->Connect(m_win, vtkCommand::EndEvent, this, SLOT(End()));
mConnect->Connect(m_win, vtkCommand::WindowIsDirectEvent, this, SLOT(IsDirect(vtkObject*, unsigned long, void*, void*)));
mConnect->Connect(m_win, vtkCommand::WindowSupportsOpenGLEvent, this, SLOT(SupportsOpenGL(vtkObject*, unsigned long, void*, void*)));
}
QVTKFrameBufferObjectItem::~QVTKFrameBufferObjectItem()
{
mConnect->Disconnect(); // disconnect all slots
if (m_irenAdapter)
delete m_irenAdapter;
}
QQuickFramebufferObject::Renderer *QVTKFrameBufferObjectItem::createRenderer() const
{
return new QVTKFramebufferObjectRenderer(m_win);
}
vtkSmartPointer<vtkInternalOpenGLRenderWindow> QVTKFrameBufferObjectItem::GetRenderWindow() const
{
return m_win;
}
void QVTKFrameBufferObjectItem::init()
{
}
// theoretically not needed now - the Y is being flipped in render and devicePixelRatio will almost always be = 1 on a PC anyway...but lets keep it to be sure
QMouseEvent QVTKFrameBufferObjectItem::openGLToNative(QMouseEvent const& event)
{
QPointF localPos(event.localPos());
localPos.setX(localPos.x() * window()->devicePixelRatio());
localPos.setY(localPos.y() * window()->devicePixelRatio());
QMouseEvent nativeEvent(event.type(), localPos, event.button(), event.buttons(), event.modifiers());
return nativeEvent;
}
void QVTKFrameBufferObjectItem::mouseMoveEvent(QMouseEvent * event)
{
m_win->GetInteractor()->SetSize(this->width(), this->height());
QMouseEvent nativeEvent = openGLToNative(*event);
m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::mousePressEvent(QMouseEvent * event)
{
m_win->GetInteractor()->SetSize(this->width(), this->height());
QMouseEvent nativeEvent = openGLToNative(*event);
m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::mouseReleaseEvent(QMouseEvent * event)
{
m_win->GetInteractor()->SetSize(this->width(), this->height());
QMouseEvent nativeEvent = openGLToNative(*event);
m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::wheelEvent(QWheelEvent *event)
{
m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::keyPressEvent(QKeyEvent* event)
{
m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::keyReleaseEvent(QKeyEvent* event)
{
m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::focusInEvent(QFocusEvent * event)
{
m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::focusOutEvent(QFocusEvent * event)
{
m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}
To use it, define an instance of the framebuffer in your qml form and stretch it across the window you want to render into, e.g. like this (assuming you registered the QVTKFrameBufferObjectItem as a QVTKFrameBuffer in qml e.g. like this qmlRegisterType<QVTKFrameBufferObjectItem>("VtkQuick", 1, 0, "QVTKFrameBuffer");):
import VtkQuick 1.0
QVTKFrameBuffer
{
id: renderBuffer
anchors.fill : parent
Component.onCompleted :
{
myCppDisplay.framebuffer = renderBuffer // tell the c++ side of your app that this is the framebuffer into which it should render
}
}
You then use the vtkRenderWindow you get by myCppDisplay.framebuffer.GetRenderWindow() the same way you would use any other vtkRenderWindow if you were rendering into a vtk-managed window, i.e. you can assign vtkRenderer to it, assign actors to that renderer, call theWindow.Render() as you wish and it will all be rendered into the qml component to which you assigned the framebuffer.
Two notes: 1) the vtk and qt use different coordinate system, you need to flip the y-coordinate...I am doing it by assigning a scale transformation to the camera, but there is plenty of other ways to do it:
vtkSmartPointer<vtkTransform> scale = vtkSmartPointer<vtkTransform>::New();
scale->Scale(1, -1, 1);
renderer->GetActiveCamera()->SetUserTransform(scale);
2) things get quite tricky once you start using multiple threads - you have to make sure that you are not trying to render in two different threads, because they would compete for that one QtQuick's rendering thread. This does not mean only not calling renderWindow.Render() in parallel - that is easy to avoid - but you have to realize that that qt thread is used also for rendering the GUI, so you might get into trouble this way (updating GUI while doing VTK rendering).
I tried implementing this example and have successfully compiled and ran it as well but I am facing some challenges with respect to the code.
I am not able to figure out where these lines are defined as they are giving me runtime errors and are not getting recognized as well.
(Defined in qmlVtk.cpp -> SetFramebufferObject() function)
this->NumberOfFrameBuffers = 1;
this->FrameBufferObject = static_cast<unsigned int>(fbo->handle());
this->DepthRenderBufferObject = 0;
this->TextureObjects[0] = static_cast<unsigned int>(fbo->texture());
this->OffScreenRendering = 1;
this->OffScreenUseFrameBuffer = 1;
Also, can anyone guide me as how to integrate QML with OpenGLWidget.
I am trying to use the QCloseEvent to close all my dialogs and quit the application when the main window closes. I have read the documentation and looked at many examples and this is what I've come up with so far:
In my .h file:
protected:
void mainwindow::closeEvent(QCloseEvent * );
In my mainwindow.cpp file:
class QCloseEvent;
void mainwindow::closeEvent(QCloseEvent *event)
{
event->accept();
if (event->isAccepted())
{
QApplication::quit();
}
}
when I run this code I get the following errors:
mainwindow::closeEvent' : local function definitions are illegal
mainwindow.cpp(13): this line contains a '{' which has not yet been matched
There is a problem with function declaration in your .h file :
void mainwindow::closeEvent(QCloseEvent * );
It should be :
void closeEvent(QCloseEvent * );
Also include QCloseEvent instead of class QCloseEvent;.
I am creating a Qt application and I added dynamic translation (I followed the example at http://www.qtcentre.org/wiki/index.php?title=Dynamic_translation_in_Qt4_applications) with a QCombobox which lists different languages. It works well but the problem is that I don't see how to translate dynamically the text in the dialog windows (for example YES and NO buttons).
In the main.cpp, before executing the app, I have :
QTranslator qtTranslator;
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
a.installTranslator(&qtTranslator);
which translate the dialog Windows in the user system language but I would like to do it dynamically like the rest of my app.
Here are the code of the example :
application.h :
#ifndef APPLICATION_H
#include <QApplication>
#include <QHash>
#include <QStringList>
class QDir;
class QTranslator;
typedef QHash<QString, QTranslator*> Translators;
class Application : public QApplication
{
Q_OBJECT
public:
explicit Application(int& argc, char* argv[]);
~Application();
static void loadTranslations(const QString& dir);
static void loadTranslations(const QDir& dir);
static const QStringList availableLanguages();
public slots:
static void setLanguage(const QString& locale);
private:
static QTranslator* current;
static Translators translators;
//static QTranslator* qtTranslator;//test to translate dialog windows
};
#endif // APPLICATION_H
application.cpp :
#include <QDir>
#include <QFileInfo>
#include <QTranslator>
#include <QLibraryInfo>
#include "application.h"
QTranslator* Application::current = 0;
//QTranslator* Application::qtTranslator = 0;//test to translate dialog windows
Translators Application::translators;
Application::Application(int& argc, char* argv[])
: QApplication(argc, argv)
{
}
Application::~Application()
{
}
void Application::loadTranslations(const QString& dir)
{
loadTranslations(QDir(dir));
QString locale = QLocale::system().name().section('_', 0, 0);
QString language=locale+ "_" + locale;
if(!QFile::exists(":Localization/Localization/"+language+".qm"))//if system language is not available, load english version
setLanguage("en_en");
else
setLanguage(language);
}
void Application::loadTranslations(const QDir& dir)
{
// <language>_<country>.qm
QString filter = "*_*.qm";
QDir::Filters filters = QDir::Files | QDir::Readable;
QDir::SortFlags sort = QDir::Name;
QFileInfoList entries = dir.entryInfoList(QStringList() << filter, filters, sort);
foreach (QFileInfo file, entries)
{
// pick country and language out of the file name
QStringList parts = file.baseName().split("_");
QString language = parts.at(parts.count() - 2);
QString country = parts.at(parts.count() - 1);
// construct and load translator
QTranslator* translator = new QTranslator(instance());
if (translator->load(file.absoluteFilePath()))
{
QString locale = language + "_" + country;
translators.insert(locale, translator);
}
}
}
const QStringList Application::availableLanguages()
{
// the content won't get copied thanks to implicit sharing and constness
return QStringList(translators.keys());
}
void Application::setLanguage(const QString& locale)
{
//test to translate dialog windows
/*
QTranslator qtTranslator;
QString qTLocale=locale.mid(0,2);
qtTranslator->load("qt_"+ qTLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath));
installTranslator(qtTranslator);
//*/
// remove previous
if (current)
{
removeTranslator(current);
}
// install new
current = translators.value(locale, 0);
if (current)
{
installTranslator(current);
}
}
I added the lines commented with "//test to translate dialog Windows" to try the dynamic translation of the dialog Windows but it doesn't work (no error at compilation but the application isn't launched with error message "the program stopped suddenly", I am on Qt Creator). Thanks!
So I finally got this to work after having the same problems. There are two things which were wrong in my case:
Name of the qt translation file:
QTranslator qtTranslator;
qtTranslator.load("qt_de"); // worked in older qt versions
qtTranslator.load("qtbase_de"); // works for qt5.2
a.installTranslator(&qtTranslator);
Have the correct parent for the QMessageBox. This is obvious after you think about it but pretty easy to miss.
QMessageBox::information(someChildOfMainWindow, ...);
For the latter, if you happen to be in a class which is a QObject but not a QWidget you can also use the following code to access your MainWindow from anywhere:
QMainWindow* mw = 0;
foreach(QWidget* widget, QApplication::topLevelWidgets()) {
if(widget->objectName() == "<your-main-window-class-name-here>") {
mw = qobject_cast<QMainWindow>(widget);
}
}
Ok Sébastian Lange, so finally I created the box and didn't use the static ones (
QMessageBox::question(..) for example)
QMessageBox quitMessageBox;
quitMessageBox.setWindowTitle(tr("Quit"));
quitMessageBox.setWindowIcon(QIcon("myIcon.jpg"));
quitMessageBox.setIcon(QMessageBox::Question);
quitMessageBox.setText(tr("Quit the application?"));
quitMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
quitMessageBox.setDefaultButton(QMessageBox::No);
quitMessageBox.button(QMessageBox::Yes)->setText(tr("Yes"));
quitMessageBox.button(QMessageBox::No)->setText(tr("No"));
And then
quitMessageBox.exec();
Like that it's ok. Thanks again!
When providing buttons for the dialog use
tr("Yes")
as for default dialogs, the created .ts-language file (to be edited via QtLinguist) should have default translations included.
The tr() marks the given argument to be translated. This concludes to if you do not know what will be written on a given label, you cannot translate it...
I am trying to tweak the ui of a QComboBox in a way that the user can remove items from the drop down list (without first selecting them).
The background is that I am using the QComboBox to indicate which data file is open right now. I am also using it as a cache for recently opened files. I would like the user to be able to remove entries he does not want to have listed anymore. This could be either by just hitting the delete key, or a context menu, or whatever is straightforward to implement. I do not want to rely on selecting the item first. A similar behavior can be found in Firefox, where old cached suggestions for an entry filed can be deleted.
I was considering subclassing the list view used by QComboBox, however, I did not find enough documentation to get me started.
I would be grateful for any hints and suggestions. I am using PyQt, but have no problems with C++ samples.
I solved this problem using code from the installEventFilter documentation.
//must be in a header, otherwise moc gets confused with missing vtable
class DeleteHighlightedItemWhenShiftDelPressedEventFilter : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
bool DeleteHighlightedItemWhenShiftDelPressedEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key::Key_Delete && keyEvent->modifiers() == Qt::ShiftModifier)
{
auto combobox = dynamic_cast<QComboBox *>(obj);
if (combobox){
combobox->removeItem(combobox->currentIndex());
return true;
}
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
myQComboBox->installEventFilter(new DeleteHighlightedItemWhenShiftDelPressedEventFilter);
comboBox->removeItem(int index) // removes item at index
You can use a specialized class that automates processes, therefore it saves time in the end.
For example, there's a class named KrHistoryComboBox (which inherits from the KHistoryComboBox class) that is used in the program named Krusader.
Although this time, for this answer: the following code is a version that inherits directly from a QComboBox (though a QComboBox can not do as many things as a KHistoryComboBox), and one example of its use:
File main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
File mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
File mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "krhistorcombobox.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Creates a new editable comboBox, and populates it with data
KrHistorComboBox *combox;
combox = new KrHistorComboBox(this);
combox->setEditable(true);
QStringList elementsToAdd = {"one", "two", "three", "four", "five", "six"};
combox->insertItems(0, elementsToAdd);
}
MainWindow::~MainWindow()
{
delete ui;
}
File krhistorcombobox.h
/*****************************************************************************
* Copyright (C) 2018-2019 Shie Erlich <krusader#users.sourceforge.net> *
* Copyright (C) 2018-2019 Rafi Yanai <krusader#users.sourceforge.net> *
* Copyright (C) 2018-2019 Krusader Krew [https://krusader.org] *
* *
* This file is part of Krusader [https://krusader.org]. *
* *
* Krusader is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 2 of the License, or *
* (at your option) any later version. *
* *
* Krusader is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
*****************************************************************************/
#ifndef KRHISTORCOMBOBOX_H
#define KRHISTORCOMBOBOX_H
// QtWidgets
#include <QComboBox>
/**
* A specialized version of a QComboBox, e.g. it deletes the current
* item when the user presses Shift+Del
*/
class KrHistorComboBox : public QComboBox
{
Q_OBJECT
public:
explicit KrHistorComboBox(QWidget *parent = nullptr);
};
#endif // KRHISTORCOMBOBOX_H
File krhistorcombobox.cpp
/*****************************************************************************
* Copyright (C) 2018-2019 Shie Erlich <krusader#users.sourceforge.net> *
* Copyright (C) 2018-2019 Rafi Yanai <krusader#users.sourceforge.net> *
* Copyright (C) 2018-2019 Krusader Krew [https://krusader.org] *
* *
* This file is part of Krusader [https://krusader.org]. *
* *
* Krusader is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 2 of the License, or *
* (at your option) any later version. *
* *
* Krusader is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
*****************************************************************************/
#include "krhistorcombobox.h"
// QtCore
#include <QEvent>
// QtGui
#include <QKeyEvent>
// QtWidgets
#include <QAbstractItemView>
/**
* A KrHistorComboBox event filter that e.g. deletes the current item when Shift+Del is pressed
* There was more information in https://doc.qt.io/qt-5/qobject.html#installEventFilter,
* https://forum.qt.io/post/160618 and
* https://stackoverflow.com/questions/17820947/remove-items-from-qcombobox-from-ui/52459337#52459337
*/
class KHBoxEventFilter : public QObject
{
Q_OBJECT
public:
explicit KHBoxEventFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
bool KHBoxEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
auto keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->modifiers() == Qt::ShiftModifier && keyEvent->key() == Qt::Key::Key_Delete) {
auto comboBox = qobject_cast<QComboBox *>(obj);
if (comboBox != nullptr) {
// Delete the current item
comboBox->removeItem(comboBox->currentIndex());
return true;
}
}
}
// Perform the usual event processing
return QObject::eventFilter(obj, event);
}
/**
* An event filter for the popup list of a KrHistorComboBox, e.g. it deletes the current
* item when the user presses Shift+Del
*/
class KHBoxListEventFilter : public QObject
{
Q_OBJECT
public:
explicit KHBoxListEventFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
bool KHBoxListEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
auto keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->modifiers() == Qt::ShiftModifier && keyEvent->key() == Qt::Key::Key_Delete) {
auto itemView = qobject_cast<QAbstractItemView *>(obj);
if (itemView->model() != nullptr) {
// Delete the current item from the popup list
itemView->model()->removeRow(itemView->currentIndex().row());
return true;
}
}
}
// Perform the usual event processing
return QObject::eventFilter(obj, event);
}
#include "krhistorcombobox.moc" // required for class definitions with Q_OBJECT macro in implementation files
KrHistorComboBox::KrHistorComboBox(QWidget *parent): QComboBox(parent)
{
installEventFilter(new KHBoxEventFilter(this));
QAbstractItemView *itemView = view();
if (itemView != nullptr)
itemView->installEventFilter(new KHBoxListEventFilter(this));
}
File krexample.pro
#-------------------------------------------------
#
# Project created by QtCreator 2018-09-22T18:33:23
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = untitled
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
krhistorcombobox.cpp \
mainwindow.cpp
HEADERS += \
krhistorcombobox.h \
mainwindow.h
FORMS += \
mainwindow.ui
File mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle" >
<string>MainWindow</string>
</property>
<widget class="QMenuBar" name="menuBar" />
<widget class="QToolBar" name="mainToolBar" />
<widget class="QWidget" name="centralWidget" />
<widget class="QStatusBar" name="statusBar" />
</widget>
<layoutDefault spacing="6" margin="11" />
<pixmapfunction></pixmapfunction>
<resources/>
<connections/>
</ui>
This is a screenshot of the example program being executed, before pressing Shift+Del (which would remove the option named "two"):
Note: Some source code in the present answer is based on https://doc.qt.io/qt-5/qobject.html#installEventFilter, https://forum.qt.io/post/160618 and the good work by the user named "nwp" in https://stackoverflow.com/a/26976984 (although that answer does not include code to delete an element of the popup list if the popup list is being seen, and it has a "memory leak" (an object is constructed but not destroyed) therefore if a developer adds a destructor like e.g. ~DeleteHighlightedItemWhenShiftDelPressedEventFilter() { QTextStream(stdout) << "DESTRUCTED" << endl; } the developer later sees that the code of the destructor is never executed, and so there are memory leaks; currently I haven't got stackoverflow points in order to add a comment in https://stackoverflow.com/a/26976984).
Sorry for being that late to this thread, but I'd like to contribute some other methods I found, just in case that someone else is looking for it like me. The methods have been tested with Qt 5.6. I cannot guarantee that they will work in other versions.
One possibility is to listen to the "pressed()" signal of the QCombobox' view(). That way we could use the right mouse button to remove items from the list. I was surprised to see that the view() is always available, never NULL, and that items can be deleted while it is displayed, so the following works quite well:
class MyCombobox : public QComboBox
{
Q_OBJECT
public: MyCombobox(QWidget *pParent = NULL);
protected slots: void itemMouseDown(const QModelIndex &pIndex);
};
MyCombobox::MyCombobox(QWidget *pParent)
{
connect( QComboBox::view(), SIGNAL(pressed(const QModelIndex &)),
this, SLOT(itemMouseDown(const QModelIndex &)) );
}
void MyCombobox::itemMouseDown(const QModelIndex &pIndex)
{
if( QApplication::mouseButtons() == Qt::RightButton )
{
QComboBox::model()->removeRow(pIndex.row());
}
}
A second option is to install an event filter, but also into the view. That way we can use the delete key or anything else to remove items. It might be a good idea to test for NULL pointers and invalid row indices, but I omitted that for clarity.
class MyCombobox : public QComboBox
{
Q_OBJECT
public: MyCombobox(QWidget *pParent = NULL);
protected: bool eventFilter(QObject *pWatched, QEvent *pEvent);
};
MyCombobox::MyCombobox(QWidget *pParent)
{
QComboBox::view()->installEventFilter(this);
}
bool MyCombobox::eventFilter(QObject *pWatched, QEvent *pEvent)
{
if( pEvent->type() == QEvent::KeyPress )
{
QKeyEvent *tKeyEvent = static_cast<QKeyEvent*>(pEvent);
if( tKeyEvent->key() == Qt::Key_Delete )
{
QComboBox::model()->removeRow(QComboBox::view()->currentIndex().row());
return true;
}
}
return QObject::eventFilter(pWatched, pEvent);
}
That's it.
You can delete the active selected value of a QComboBox by:
ui->comboBox->removeItem(ui->comboBox->currentIndex());
If you could live with selecting the entry first,
and could accept a "Remove" button besides the combobox,
you could extend QComboBox with a suitable slot.
class IQComboBox : public QComboBox
{
Q_OBJECT
public :
IQComboBox(QWidget *parent = nullptr) : QComboBox(parent) {}
public slots :
void remove_current_item(void) { removeItem(currentIndex()); }
};
Then connect the button's "released" signal with the new slot.
IQComboBox combo;
QPushButton remove_button(tr("Remove"));
remove_button.setToolTip(tr("Remove the current item from the list."));
connect(&remove_button, SIGNAL(released()),
&combo, SLOT(remove_current_item()));