How to disable the default copy behavior in QTreeView? - c++

I have a QTreeView with a QStandardItemModel and I would like to be able to prevent the user from copying the text of the items.
#include <QMainWindow>
#include <QStandardItemModel>
#include <QTreeView>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr) :
QMainWindow(parent)
{
auto *treeView = new QTreeView(this);
auto *model = new QStandardItemModel(this);
for (int n = 0; n < 5; n++)
model->appendRow(createItem(QString::number(n)));
treeView->setModel(model);
treeView->setContextMenuPolicy(Qt::NoContextMenu);
setCentralWidget(treeView);
}
private:
QStandardItem *createItem(const QString &name)
{
auto *item = new QStandardItem(name);
item->setFlags(Qt::ItemIsEnabled);
return item;
}
};
I have already made the items not editable and disabled the context menu. However, it is still possible for the user to click on an item and copy the text by pressing Ctrl+C. I can use Qt::NoItemFlags, but I want the items to be enabled.
How to accomplish that?

To disable the default copy behavior of QTreeView reimplement QTreeView::keyPressEvent in a subclass, e.g. TreeView, like that:
void TreeView::keyPressEvent(QKeyEvent *event)
{
if (!(event == QKeySequence::Copy))
QTreeView::keyPressEvent(event);
}
Then in your code instead of QTreeView:
auto *treeView = new QTreeView(this);
instantiate TreeView:
auto *treeView = new TreeView(this);

Alternatively, you can use installEventFilter to trap the keystroke events with having to subclass.

Related

How to show selected text different from list text in QComboBox?

I have a QComboBox with a popup list (a QAbstractItemView) showing different items (QStandardItems). Now, I want the item in the list to show a different text than if the item is selected.
Background:
I am creating a word-processor like style chooser where one can choose, say, "1.1 Heading 2" from the list, indicating the numbering and the style name, but when an item is chosen the combobox should only show the style name, say "Heading 2".
I thought the following question was exactly about what I was asking for but apparently an answer was chosen that does not work (apparently even according to the person asking the question): Can a QComboBox display a different value than whats in it's list?
Solution
Since QComboBox uses a list view to display the values, probably the "Qt'iest" way to achieve the desired effect, is to use a custom delegate and modify the text within its paint method, using a hash map (QHash) to get the corresponding string.
Example
Here is a simple example I have prepared for you to demonstrate how the proposed solution could be implemented:
Delegate.h this is where the magic is happening
#include <QStyledItemDelegate>
#include <QApplication>
class Delegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit Delegate(QObject *parent = nullptr) :
QStyledItemDelegate(parent) {}
void setHash(const QHash<int, QString> &hash) {
m_hash = hash;
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
if (!index.isValid())
return;
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
opt.text = m_hash.value(index.row());
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
}
private:
QHash<int, QString> m_hash;
};
MainWindow.h only for demo purposes
#include <QWidget>
#include <QBoxLayout>
#include <QComboBox>
#include <QStandardItemModel>
#include "Delegate.h"
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr)
: QWidget(parent)
{
auto *l = new QVBoxLayout(this);
auto *cmbBox = new QComboBox(this);
auto *model = new QStandardItemModel(this);
auto *delegate = new Delegate(this);
QHash<int, QString> hash;
for (int n = 0; n < 5; n++) {
// For demo purposes I am using "it#" and "item #"
// Feel free to set those strings to whatever you need
model->appendRow(new QStandardItem(tr("it%1").arg(QString::number(n))));
hash.insert(n, tr("item %1").arg(QString::number(n)));
}
delegate->setHash(hash);
cmbBox->setModel(model);
cmbBox->setItemDelegate(delegate);
l->addWidget(cmbBox);
resize(600, 480);
}
};
Result
The example produces the following result:
The easiest way to do is to set the ComboBox as editable and then when the current item changes, you change the text to whatever you want. Example:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStringList a = {"Red", "Green", "Blue"};
aModel.setStringList(a);
ui->comboBox->setModel(&aModel);
ui->comboBox->setEditable(true);
}
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
if (arg1 == "Green") {
ui->comboBox->setCurrentText("Green on");
} else if (arg1 == "Red") {
ui->comboBox->setCurrentText("Red on");
}
}
ui->comboBox->setCurrentText("Green on"); will only change the text when the item is selected, when you reopen the combobox, the text will be reverted back to original. This is somewhat similar to my answer here.
Another way to do this would be to inherit the QComboBox class and then reimplement the mousePressEvent to change the model whenever the mouse is pressed, and switch it back after releasing. This will probably be more difficult to get right or may not even work as I have not tried it myself

Qt signals and slots passing data

I'm pretty new to c++ and qt. I'm not sure if i use the right terminology describe what I want to achieve. But here it goes.
My application spawns and removes widgets in a gridlayout when the user pushes buttons. Managed to do this successfully. However when the user uses the spawned widgets I want the widgets to interact with each other.
QList<QLineEdit*> m_ptrLEPathList;
QList<QPushButton*> m_ptrPBList;
qint8 m_noFields;
void MainWindow::on_pbIncFields_clicked()
{
//create widgets and place on a new row in a gridLayout
QLineEdit *lineEditPath = new QLineEdit(this);
QPushButton *pushButton = new QPushButton(this);
//storing pointers in lists to be able to delete them later.
m_ptrLEPathList.append(lineEditPath);
m_ptrPBList.append(pushButton);
ui->gridLayout->addWidget(m_ptrLEPathList.last(),m_noFields,0);
ui->gridLayout->addWidget(m_ptrPBList.last(),m_noFields,1);
connect(m_ptrPBList.last(), SIGNAL(clicked(bool), this, SLOT(on_addPath()));
m_noFields++;
}
void MainWindow::on_pbDecFields()
{
//delete last spawned widgets
}
void MainWindow::on_addPath()
{
QFileDialog getPath();
getPath.exec();
//somehow set the text of the line edit spawned on the same row as the pushbutton
}
So my slot is executed when I push any spawned button but I have no idea how to store the data from the file dialog in the related lineEdit.
Is the basic idea of what I'm trying to do ok or is there any other solution to achieve the fuctionality I'm looking for?
In on_addPath slot you can use QObject::sender method to get the clicked button, and, assuming m_ptrLEPathList and m_ptrPBList lists are equal, you can easily get the corresponding QLineEdit:
void MainWindow::on_addPath()
{
QFileDialog dialog;
if (!dialog.exec())
{
return;
}
QStringList fileNames = dialog.selectedFiles();
if (fileNames.isEmpty())
{
return;
}
QPushButton *btn = qobject_cast<QPushButton*>(sender());
if (!btn)
{
return;
}
Q_ASSERT(m_ptrPBList.size() == m_ptrLEPathList.size());
int index = m_ptrPBList.indexOf(btn);
if (index == -1)
{
return;
}
QLineEdit *edit = m_ptrLEPathList.at(index);
edit->setText(fileNames.first());
}
You are including 'on_addPath' function out of the scope of the 'MainWindow' class, so when the slot is called you have not access to member elements in the class.
Try to include the slot function into the class and check if you have direct access to the member elements. Also, the 'lineEditPath' element must be a member object, so it must be included into the class definition.
Something like this:
void MainWindow::on_addPath()
{
QFileDialog getPath();
getPath.exec();
QStringList fileNames = dialog.selectedFiles();
if (fileNames.isEmpty())
{
return;
}
m_lineEditPath->setText(fileNames.first());
}
First off, void on_addPath() must be void MainWindow::on_addPath()
As for linking the data from QFileDialog it is simple. Try this:
void MainWindow::on_addPath() {
/* Get the push button you clicked */
QPushButon *btn = qobject_cast<QPushButton*>( sender() );
/* Make sure both the lists have the same size */
Q_ASSERT(m_ptrPBList.size() == m_ptrLEPathList.size());
/* If the sender is a button in your list */
if ( m_ptrPBList.contains( btn ) ) {
/* Get the index of your push button */
int idx = m_ptrPBList.indexOf( btn );
/* Get the corresponding line edit */
QLineEdit *le = m_ptrLEPathList.at( idx );
/* Get your path */
QString path = QFileDialog::getOpenFileName( this, "Caption", "Default Location" );
/* If you path is not empty, store it */
if ( not path.isEmpty() )
le->setText( path );
}
}
Add a map to private section
QMap<QPushButton*, QLineEdit*> map;
Then
QLineEdit *lineEditPath = new QLineEdit(this);
QPushButton *pushButton = new QPushButton(this);
map.insert(pushButton, lineEditPath);
You can use sender() method like follow:
void on_addPath()
{
QFileDialog getPath();
getPath.exec();
QObject* obj = sender();
QPushButton *pb = 0;
if((pb = qobject_cast<QPushButton *>(obj)) != 0) {
QLineEdit* lineEdit = map->value(pb, 0);
if( lineEdit != 0 )
lineEdit->setText( getPath.<some function to get selected file name> );
}
}
I think the cleanest solution would be to contain the QLineEdit and QPushButton in a custom widget class, if it suits your project. That way you could use the file dialog inside this class, and you won't have to store the widgets in lists. It is hard to give you all the information, as you didn't really provide any details what your application is supposed to do. But in any case, the custom widget class would look something like this (you should define all the functions inside a .cpp file):
#ifndef WIDGETCONTAINER_H
#define WIDGETCONTAINER_H
#include <QWidget>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QFileDialog>
class WidgetContainer : public QWidget
{
Q_OBJECT
public:
WidgetContainer(QWidget *parent = 0) : QWidget(parent)
{
setLayout(new QHBoxLayout);
button.setText("BUTTON");
layout()->addWidget(&lineEdit);
layout()->addWidget(&button);
connect(&button, SIGNAL(clicked()), this, SLOT(buttonPressed()));
}
private:
QLineEdit lineEdit;
QPushButton button;
private slots:
void buttonPressed()
{
QString filename = QFileDialog::getSaveFileName();
lineEdit.setText(filename);
}
};
#endif // WIDGETCONTAINER_H

Qt: Context menu / connect with parameters

I have a problem connecting context menu actions in Qt. I know there are plenty of similar questions around, nevertheless I couldn't find a solution yet.
I have a series of plots built using QCustomplot.
What I want to do is to create a context menu when right-clicking on the background of each plot listing all the signal present in the graph. By clicking an entry of this menu, the corresponding signal should be hidden (if currently visible) or made visible (if hidden).
Now, I've defined a class called PlotHandler of which I paste the relevant parts here below:
plotHandler.cpp
#include "plothandler.h"
PlotHandler::PlotHandler(QStringList groupNames, int startIdx, QWidget *parent) :
QWidget(parent), scrolling(false), refreshing(true)
{
pPlot = new QCustomPlot(this);
pPlot->setContextMenuPolicy(Qt::CustomContextMenu);
connect(pPlot, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequest(QPoint)));
}
void PlotHandler::contextMenuRequest(QPoint pos)
{
int i;
QMenu *menu = new QMenu(this);
for(i=0; i<pGroup->getDataLength(); i++)
{
QAction *menuEntry;
menuEntry = new QAction(pPlot->graph(i)->name(), this);
menuEntry->setProperty("graphIdx", i);
menu->addAction(menuEntry);
connect(menuEntry, SIGNAL(triggered()), this, SLOT(addRemoveGraph()));
}
menu->popup(pPlot->mapToGlobal(pos));
}
void PlotHandler::addRemoveGraph()
{
QAction *selectedSignal = qobject_cast<QAction *>(sender());
int tmp = selectedSignal->property("graphIdx").toInt();
if (pPlot->graph(tmp)->visible())
{
pPlot->graph(tmp)->setVisible(false);
}
else
{
pPlot->graph(tmp)->setVisible(true);
}
}
plotHandler.h
class PlotHandler : public QWidget
{
Q_OBJECT
public:
explicit PlotHandler(QStringList groupNames, int startIdx, QWidget *parent = 0);
QString groupRequested();
private:
QCustomPlot *pPlot;
public slots:
void contextMenuRequest(QPoint pos);
void addRemoveGraph();
}
The menu is correctly showed with the right entries and when I click on an action addRemoveGraph is called. In debug it gives back the following message:
The inferior stopped because it triggered an exception. Stopped in
thread 0 by: Exception at 0x5d6c2f9a, code: 0xc0000005: read access
violation at: 0x0, flags=0x0.
trying to execute
int tmp = selectedSignal->property("graphIdx").toInt();
Could anyone point me towards the right direction?
Thanks in advance
You use QObject::setProperty but QAction doesn't have a property named "graphIdx". When you attempt to read the "graphIdx" property from QAction you will always get an invalid QVariant.
int tmp = selectedSignal->property("graphIdx").toInt();
// tmp always is 0;
You can use QAction::setData if you need to store only one property. Otherwise, use QObject::setProperty to set a custom property on any QObject. QAction is a QObject.
Here below the solution that solved the issue.
plotHandler.cpp
#include "plothandler.h"
PlotHandler::PlotHandler(QStringList groupNames, int startIdx, QWidget *parent) :
QWidget(parent), scrolling(false), refreshing(true)
{
pPlot = new QCustomPlot(this);
pPlot->setContextMenuPolicy(Qt::CustomContextMenu);
connect(pPlot, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequest(QPoint)));
}
void PlotHandler::contextMenuRequest(QPoint pos)
{
int i;
QMenu *menu = new QMenu(this);
for(i=0; i<pGroup->getDataLength(); i++)
{
QAction *menuEntry;
menuEntry = new QAction(pPlot->graph(i)->name(), this);
menuEntry->setData(i);
menu->addAction(menuEntry);
}
connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(addRemoveGraph(QAction*)));
menu->popup(pPlot->mapToGlobal(pos));
}
void PlotHandler::addRemoveGraph(QAction *selectedSignal)
{
int tmp = selectedSignal->property("graphIdx").toInt();
if (pPlot->graph(tmp)->visible())
{
pPlot->graph(tmp)->setVisible(false);
}
else
{
pPlot->graph(tmp)->setVisible(true);
}
pPlot->replot();
}
plotHandler.h
class PlotHandler : public QWidget
{
Q_OBJECT
public:
explicit PlotHandler(QStringList groupNames, int startIdx, QWidget *parent = 0);
QString groupRequested();
private:
QCustomPlot *pPlot;
public slots:
void contextMenuRequest(QPoint pos);
void addRemoveGraph();
}
Thanks everyone for your help.

Cannot display a tree (QTreeView) inside a QWidget placed inside another QWidget

I have a problem with a QWidget that contains a tree (QTreeView): I can display it if it is created as a widget on its own, and I cannot do the same if it is a subwidget of another higher level widget. In this second case, what I get is an empty widget without the tree.
This works:
QApplication testApp(argc, argv);
MyTree myTree;
testApp.exec();
This does not work:
class MainWindow : public QMainWindow
{
Q_OBJECT
QSplitter *spl1, *spl2;
QMdiArea *mdiArea;
QTableWidget *other;
public:
MainWindow();
void LoadTree();
MyTree *myTree;
};
MainWindow::MainWindow(QWidget *_parent)
: QMainWindow(_parent), myTree(0)
{
mdiArea = new QMdiArea;
other = new QTableWidget;
spl1 = new QSplitter(Qt::Vertical, this);
spl1->addWidget(mdiArea);
spl1->addWidget(other);
LoadTree();
spl2 = new QSplitter(Qt::Horizontal, this);
spl2->addWidget(myTree);
spl2->addWidget(spl1);
setCentralWidget(spl2);
}
void MainWindow::LoadTree()
{
myTree = new MyTree(this);
}
Here is the code common to the two cases (which should be OK):
class MyTree : public QWidget
{
Q_OBJECT
public:
explicit MyTree(QWidget *_parent = 0);
int RefreshTree();
private slots:
void HandleTreeWidgetEvent(QModelIndex);
private:
QWidget *parent;
QTreeView *pjrTree;
QTreeView *GetNewTree();
};
MyTree::MyTree(QWidget *_parent) :
QWidget(_parent),
parent(_parent)
{
pjrTree = GetNewTree();
if(pjrTree) {
if(parent == 0)
pjrTree->show();
}
else {
// Never gets here
}
}
QTreeView* MyTree::GetNewTree()
{
QFileSystemModel *model = new QFileSystemModel;
model->setReadOnly(true);
model->setRootPath("/my/path/");
QTreeView* pjrTree = new QTreeView;
pjrTree->setModel(model);
pjrTree->setRootIndex(model->index("/my/path/"));
QModelIndex index;
index = model->index(4, 1); // temp values - no effect
return pjrTree;
}
Is the tree view the only widget that does not display? I would suggest passing the splitter it will be contained in as the parent, rather than the main window, when you instantiate the tree.
Deriving the class MyTree from QTreeView, instead of having a pointer to QTreeView as a member variable, fixed my problem.

Getting multiple inputs from QInputDialog in Qt

I would like to get a set of four values from four input labels in Qt. I would like to use QInputDialog but it contains only one inputbox as a default one. So, how can I add four labels and four line-edits and get the value from it?
You don't. The documentation is pretty clear:
The QInputDialog class provides a simple convenience dialog to get a
single value from the user.
If you want multiple values, create a QDialog derived class from scratch with 4 input fields.
For example:
QDialog dialog(this);
// Use a layout allowing to have a label next to each field
QFormLayout form(&dialog);
// Add some text above the fields
form.addRow(new QLabel("The question ?"));
// Add the lineEdits with their respective labels
QList<QLineEdit *> fields;
for(int i = 0; i < 4; ++i) {
QLineEdit *lineEdit = new QLineEdit(&dialog);
QString label = QString("Value %1").arg(i + 1);
form.addRow(label, lineEdit);
fields << lineEdit;
}
// Add some standard buttons (Cancel/Ok) at the bottom of the dialog
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
Qt::Horizontal, &dialog);
form.addRow(&buttonBox);
QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
QObject::connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
// Show the dialog as modal
if (dialog.exec() == QDialog::Accepted) {
// If the user didn't dismiss the dialog, do something with the fields
foreach(QLineEdit * lineEdit, fields) {
qDebug() << lineEdit->text();
}
}
Following alexisdm's answer, here is one way to implement custom QInputDialog.
"inputdialog.h":
#ifndef INPUTDIALOG_H
#define INPUTDIALOG_H
#include <QDialog>
class QLineEdit;
class QLabel;
class InputDialog : public QDialog
{
Q_OBJECT
public:
explicit InputDialog(QWidget *parent = nullptr);
static QStringList getStrings(QWidget *parent, bool *ok = nullptr);
private:
QList<QLineEdit*> fields;
};
#endif // INPUTDIALOG_H
"inputdialog.cpp":
#include "inputdialog.h"
#include <QLabel>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QFormLayout>
InputDialog::InputDialog(QWidget *parent) : QDialog(parent)
{
QFormLayout *lytMain = new QFormLayout(this);
for (int i = 0; i < 4; ++i)
{
QLabel *tLabel = new QLabel(QString("Text_%1:").arg(i), this);
QLineEdit *tLine = new QLineEdit(this);
lytMain->addRow(tLabel, tLine);
fields << tLine;
}
QDialogButtonBox *buttonBox = new QDialogButtonBox
( QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
Qt::Horizontal, this );
lytMain->addWidget(buttonBox);
bool conn = connect(buttonBox, &QDialogButtonBox::accepted,
this, &InputDialog::accept);
Q_ASSERT(conn);
conn = connect(buttonBox, &QDialogButtonBox::rejected,
this, &InputDialog::reject);
Q_ASSERT(conn);
setLayout(lytMain);
}
QStringList InputDialog::getStrings(QWidget *parent, bool *ok)
{
InputDialog *dialog = new InputDialog(parent);
QStringList list;
const int ret = dialog->exec();
if (ok)
*ok = !!ret;
if (ret) {
foreach (auto field, dialog->fields) {
list << field->text();
}
}
dialog->deleteLater();
return list;
}
Now you can use getStrings() method similar to QInputDialog::getText():
QStringList list = InputDialog::getStrings(this);
if (!list.isEmpty()) {
// use list
}
Or
bool ok;
QStringList list = InputDialog::getStrings(this, &ok);
if (ok) {
// use list
}