Editing QTreeWidgetItem while retaining file extension - c++

I have a QListWidget that contains filenames. Right-clicking on an item brings up a menu which contains a Rename... item. Clicking on Rename... lets the user edit the filename. So far so good.
But in the Qt Creator Projects window, right-clicking on (say) foo.cpp and selecting Rename... presents the user with a field in which foo is selected, and .cpp isn't. So the default action is to rename the foo part without changing the .cpp extension.
This is exactly what I need, but I don't see a way to achieve it using Qt's public API. What do I have to do? Custom delegates, I suppose; but this is surely a common requirement, so I wondered if anybody had some ready-to-use C++ code that I could snarf?

A possible solution is to create a QObject that intercepts the editor's show event and then change the selection:
#include <QtWidgets>
class Helper: public QObject{
public:
Helper(QLineEdit *le): QObject(le), m_le(le){
m_le->installEventFilter(this);
}
bool eventFilter(QObject *watched, QEvent *event){
if(watched == m_le && event->type() == QEvent::Show){
QString filename = m_le->text();
QFileInfo fi(filename);
QString base = fi.baseName();
m_le->setSelection(0, base.length());
}
return QObject::eventFilter(watched, event);
}
private:
QLineEdit* m_le;
};
class StyledItemDelegate: public QStyledItemDelegate{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex &index) const{
QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index);
if(QLineEdit *le = qobject_cast<QLineEdit *>(editor)){
new Helper(le);
}
return editor;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeWidget w;
w.setItemDelegate(new StyledItemDelegate);
for(const QString & filename: {"foo.txt", "foo.tar.gz", "foo.cpp"}){
auto item = new QTreeWidgetItem({filename});
item->setFlags(item->flags() | Qt::ItemIsEditable);
w.addTopLevelItem(item);
}
w.show();
return a.exec();
}

Related

QTableWidgetItem text() property during editing

I have the following problem with the QTableWidget or QTableWidgetItem:
I would like to analyze the text in the cell during its editing/typing,
for example as a reaction on KeyReleaseEvent.
However the QTableWidgetItem::text() property is changed only AFTER the
cell editing is finished (focus has left the cell).
How can I overcome such behavior? Of course, it is possible to analyze the
button keys in the KeyReleaseEvent, but with the text() property it would be much easier...
One possible solution is to establish a custom QLineEdit as editor through the delegate:
#include <QtWidgets>
class LineEdit: public QLineEdit{
public:
using QLineEdit::QLineEdit;
protected:
void keyReleaseEvent(QKeyEvent *event) {
QLineEdit::keyPressEvent(event);
qDebug() << text();
}
};
class StyledItemDelegate: public QStyledItemDelegate{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const{
LineEdit *editor = new LineEdit(parent);
return editor;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableWidget w(10, 10);
w.setItemDelegate(new StyledItemDelegate(&w));
w.show();
return a.exec();
}

How to know when down-arrow of Combo box is clicked?

I have a ComboBox and set it to be edited.
QComboBox *myCombo = new QComboBox(this);
myCombo->setEditable(true);
myCombo->setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
myCombo->setCursor( QCursor( Qt::PointingHandCursor ) );
So now when i click onto the editing field, nothing happen. But what I need is, when I click onto the bulb (which is the down-arrow), something (like a table or a dialog....) should be appeared. How can I recognize this click event in this case? I looked at the list of signals for combo box but could not find any signal for that.
By overwriting the mousePressEvent() method you must use hitTestComplexControl() method to know that QStyle::SubControl has been pressed by issuing a signal if it is QStyle::SC_ComboBoxArrow.
#include <QtWidgets>
class ComboBox: public QComboBox
{
Q_OBJECT
public:
using QComboBox::QComboBox;
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event) override{
QComboBox::mousePressEvent(event);
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, event->pos(), this);
if(sc == QStyle::SC_ComboBoxArrow)
emit clicked();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ComboBox w;
w.setEditable(true);
w.setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
QObject::connect(&w, &ComboBox::clicked, [](){
qDebug()<<"clicked";
});
w.show();
return a.exec();
}
#include "main.moc"
Although showPopup() is a possible option this can be called directly without the down-arrow being pressed, for example by calling it directly: myCombo->showPopup() so it is not the correct option.
A possible solution is to subclass QComboBox and reimplement showPopup() virtual method:
.h:
#ifndef COMBOBOXDROPDOWN_H
#define COMBOBOXDROPDOWN_H
#include <QComboBox>
#include <QDebug>
class ComboBoxDropDown : public QComboBox
{
public:
ComboBoxDropDown(QWidget *parent = nullptr);
void showPopup() override;
};
#endif // COMBOBOXDROPDOWN_H
.cpp:
#include "comboboxdropdown.h"
ComboBoxDropDown::ComboBoxDropDown(QWidget *parent)
: QComboBox (parent)
{
}
void ComboBoxDropDown::showPopup()
{
//QComboBox::showPopup();
qDebug() << "Do something";
}

Drag and drop file into QTreeWidget inside of QDialog

I've been searching online for days and I cant find anything to help out with my specific problem. I'm trying to set up this dialog to accept files to be dropped into the QTreeWidget, named filesTreeWidget, but everything I've been searching online doesn't seem to make a difference. I'm pretty new to QT and C++ as well, so I'm sure that doesn't help.
Thanks for any help
Header
class FileIQ : public QDialog
{
Q_OBJECT
protected:
void dropEvent(QDropEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
}
Cpp
FileIQ::FileIQ(QWidget *parent, DR::EnginePtr engine)
: QDialog(parent)
, ui(new Ui::FileIQ)
, engine_(engine)
{
ui->filesTreeWidget->setAcceptDrops(true);
ui->filesTreeWidget->setDropIndicatorShown(true);
setAcceptDrops(true);
}
void FileIQ::dropEvent(QDropEvent *event)
{
foreach(const QUrl &url, event->mimeData()->urls()) {
QString filename = url.toLocalFile();
qDebug() << "Dropped file:" << filename;
QTreeWidgetItem *item = new QTreeWidgetItem(ui->filesTreeWidget);
item->setText(0, filename);
}
}
void FileIQ::dragEnterEvent(QDragEnterEvent *event)
{
event->accept();
}
void FileIQ::dragMoveEvent(QDragMoveEvent * event)
{
event->accept();
}
void FileIQ::dragLeaveEvent(QDragLeaveEvent * event)
{
event->accept();
}
First, the right thing is to implement drag and drop within QTreeWidget, not inside QDialog. To do this we must create a class that inherits from QTreeWidget and we must implement the following protected methods:
bool QTreeWidget::dropMimeData(QTreeWidgetItem *parent, int index,
const QMimeData *data, Qt::DropAction action)
Handles the data supplied by a drag and drop operation that ended with
the given action in the index in the given parent item.
The default implementation returns true if the drop was successfully
handled by decoding the mime data and inserting it into the model;
otherwise it returns false.
QStringList QTreeWidget::mimeTypes() const
Returns a list of MIME types that can be used to describe a list of
treewidget items.
Qt::DropActions QTreeWidget::supportedDropActions() const
Returns the drop actions supported by this view.
From the above we implemented this class:
#ifndef TREEWIDGET_H
#define TREEWIDGET_H
#include <QDropEvent>
#include <QTreeWidget>
#include <QMimeData>
#include <QFileInfo>
class FilesTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
FilesTreeWidget(QWidget *parent= Q_NULLPTR):
QTreeWidget(parent)
{
setAcceptDrops(true);
setDropIndicatorShown(true);
setColumnCount(2);
}
protected:
bool dropMimeData(QTreeWidgetItem *parent, int /*index*/, const QMimeData *data, Qt::DropAction /*action*/)
{
for(const QUrl url: data->urls()) {
const QFileInfo info( url.toLocalFile());
if(info.isFile()){
QTreeWidgetItem *item;
if (parent){
item = new QTreeWidgetItem(parent);
parent->setExpanded(true);
}
else
item = new QTreeWidgetItem(this);
item->setText(0, info.fileName());
item->setText(1, info.filePath());
}
}
return true;
}
QStringList mimeTypes () const
{
return QStringList()<<"text/uri-list";
}
Qt::DropActions supportedDropActions () const
{
return Qt::CopyAction;
}
};
#endif // TREEWIDGET_H
The complete example can be found in the following link. If you already have a QTreeWidget assigned by Qt Designer the simplest solution is to promote the Qt Designer QTreeWidget to use the new class.
Output:

QUiLoader: requirements for loading .ui file with custom widgets?

I've created a UI using Qt5-Designer which I load at runtime by calling
QUiLoader().load(qfile_object, this);
Works like charm but now I've promoted some QLabel elements to a widget class MyQLabel with is derived from QLabel.
When I now try to load the UI I get a warning for each promoted widget:
"QFormBuilder was unable to create a custom widget of the class 'MyQLabel'; defaulting to base class 'QLabel'."
The class looks like this:
class MyQLabel : public QLabel {
Q_OBJECT
public:
MyQLabel(QWidget *parent = nullptr) : QLabel(parent) {}
};
It's been auto-moc'ed and linked against my executable.
I have the feeling that somehow I have to tell QUiLoader about my class before trying to use it but I don't know how..
Do I have to create a plugin for this? Is there a way to reproduce, what to QUiLoader does to examine it?
You need to create your own derived version of QUiLoader, and provide an implementation of the factory method QUiLoader::createWidget that can create your widgets.
You could factor this out into a plugin that gets loaded by the QUiLoader. It would have to implement a QDesignerCustomWidgetInterface instance. See the Custom Widget Plugin Example for a complete example of a plugin.
// https://github.com/KubaO/stackoverflown/tree/master/questions/uiloader-custom-37775472
#include <QtUiTools>
#include <QtWidgets>
const char uiData[] =
"<ui version=\"4.0\"><class>Widget</class><widget class=\"MyWidget\" name=\"Widget\">\n"
"<property name=\"windowTitle\" ><string>Widget</string></property>\n"
"</widget><pixmapfunction></pixmapfunction><resources/><connections/>\n"
"</ui>";
class MyWidget : public QLabel
{
Q_OBJECT
bool m_closed = false;
public:
MyWidget(QWidget* parent = 0) : QLabel("This is MyWidget", parent) {}
bool isClosed() const { return m_closed; }
void closeEvent(QCloseEvent *) Q_DECL_OVERRIDE { m_closed = true; }
};
class MyUiLoader : public QUiLoader {
public:
MyUiLoader(QObject * parent = 0) : QUiLoader(parent) {}
QWidget * createWidget(const QString & className, QWidget * parent = 0,
const QString & name = QString()) {
if (className == "MyWidget") {
MyWidget * w = new MyWidget(parent);
w->setObjectName(name);
return w;
}
return QUiLoader::createWidget(className, parent, name);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QBuffer buf;
buf.setData(uiData, sizeof(uiData));
MyUiLoader uiLoader;
auto uiMain = qobject_cast<MyWidget*>(uiLoader.load(&buf));
uiMain->show();
return app.exec();
}
#include "main.moc"

How to capture the QDockWidget close button click event

I have a QStackedWidget within a QDockWidget - depending on which page is shown I wish to show/hide the close button on the QDockWidget. I can do this by using QDockWidget::setFeatures().
However the issue I'm facing is how to capture the signal of the close button so that I can change the dock features / set the stacked widget page index.
I have attempted to use an event filter:
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter( QObject* aParent );
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
EventFilter::EventFilter( QObject* aParent )
: QObject( aParent )
{
}
bool EventFilter::eventFilter( QObject *obj, QEvent *event )
{
if ( event->type() == QEvent::Close )
{
return true;
}
return QObject::eventFilter( obj, event );
}
And installed it as so:
EventFilter* filter = new EventFilter( this );
u->dockWidget_6->installEventFilter( filter );
In the constructor of my QMainWindow - the eventFilter() method did not get called.
So next I attempted to use the QDockWidget::visibilityChanged changed signal since it sounded like this might be what I wanted:
connect( u->dockWidget_6, SIGNAL(visibilityChanged(bool)), SLOT(dockWindowClosed(bool)) );
This slot did get called - but not when the close button was clicked.
Finally I attempted to use a promoted QDockWidget to capture the QWidget::closeEvent().
class DockWidgetWithCloseSignal : public QDockWidget
{
Q_OBJECT
public:
explicit DockWidgetWithCloseSignal(const QString &title, QWidget *parent = 0, Qt::WindowFlags flags = 0)
: QDockWidget( title, parent, flags )
{
}
explicit DockWidgetWithCloseSignal(QWidget *parent = 0, Qt::WindowFlags flags = 0)
: QDockWidget( parent, flags )
{
}
protected:
void closeEvent(QCloseEvent *event)
{
event->ignore();
}
};
I saw the constructor was called which means this new widget was indeed being used, but again no joy since the closeEvent() was never called when the close button was clicked.
Turns out that everything apart from the visibilityChanged signal works!
I added a signal to the overridden closeEvent() method which I could then connect to any slot I wanted.
The actual issue was that within the stacked widget I had another QDockWidget on another page, hence I was adding all of these things to the wrong QDockWidget! (And of course promoted the wrong QDockWidget too doh!).
Hopefully this question can serve as a reference to anyone else that needs to figure out how to do this - rather than why it isn't working.
Create a new CloseDockWidget based on DockWidget.
Override the closeEvent() method, but emit an additional closed() signal from there.
widgets/qclosedockwidget.cpp:
#include "qclosedockwidget.h"
namespace Widgets
{
QCloseDockWidget::QCloseDockWidget(const QString &title, QWidget *parent)
: QDockWidget(title, parent)
{
// constructor
}
void QCloseDockWidget::closeEvent(QCloseEvent *event)
{
emit closed(); // <------ signal
QDockWidget::closeEvent(event);
}
} // namespace Widgets
widgets/qclosedockwidget.h:
#ifndef QCLOSEDOCKWIDGET_H
#define QCLOSEDOCKWIDGET_H
#include <QDockWidget>
namespace Widgets
{
class QCloseDockWidget : public QDockWidget
{
Q_OBJECT
public:
QCloseDockWidget(const QString &title = "", QWidget *parent = nullptr);
protected:
void closeEvent(QCloseEvent *event);
signals:
void closed();
};
} // namespace Widgets
#endif // QCLOSEDOCKWIDGET_H
Now you are able to instantiate and connect to the new signal:
auto *dockWidget = new Widgets::QCloseDockWidget("MyDockWidget", this);
connect(dockWidget, &Widgets::QCloseDockWidget::closed, this, &MainWindow::dockWidgetCloseClicked);