Ktorrent-like widgets in QTableView/QTableWidget - c++

Is there any documentation about how to put a custom QWidget container (with other stuff like a layout, checkboxes, buttons etc) as a row in a QTableView/QTableWidget like is shown in the picture?
I'm browsing the source code of ktorrent to see how this is done.
Any help will be much appreciated.

For a QTableWiget use
void QTableWidget::setCellWidget (int row, int column, QWidget *widget)
In your case:
class MyWidget : public QWidget {
// a composite widget with layouts and other stuff
};
setCellWidget(0, 0, new MyWidget);
In the case of a QTableView you have to define your custom delegate and set it (e.g.) for a certain column with:
void QAbstractItemView::setItemDelegateForColumn (int column, QAbstractItemDelegate * delegate)
Check this out for an example of a QProgressBar inside a table

Related

Qt set a custom widget inside a QTableView

I need to put a custom widget inside a QTableView cell from a subclassed QAbstractTableModel.
I searched for already given solutions but no one catch my needs.
The custom widget must stay here all the time and not only in editing mode like with the QItemDelegate::createEditor.
The custom widget may be everything, i'm searching for a general solutions for all the widget not only QPushButtons or QCheckBox.
Sorry for my english.
You can use QAbstractItemView::setIndexWidget and overriding QAbstractItemView::dataChanged to achieve what you want as follows:
class MyTableView : public QTableView
{
protected:
void dataChanged(const QModelIndex &topLeft, const QModelIndex & bottomRight, const QVector<int> & roles)
{
QPushButton *pPushButton = qobject_cast<QPushButton*>(indexWidget(topLeft));
if (pPushButton)
pPushButton->setText(model()->data(topLeft, Qt::DisplayRole).toString());
else
QTableView::dataChanged(topLeft, bottomRight, roles);
}
};
void main ()
{
MyTableView table;
table.setIndexWidget(table.model()->index(0, 0, QModelIndex()),new QPushButton());
}
Note that it is an incomplete implementation, but it should show you how you can solve your problem. A real implementation should update all QPushButton between topLeft and bottomRight.

QStyledItemDelegate partially select text of default QLineEdit editor

I have a subclass of QStyledItemDelegate which at the moment does not reimplement any functions (for simplicity of the question).
With default QStyledItemDelegate implementation, when the user begins to edit text in a QTableView, the delegate draws a QLineEdit with the text from the model, and selects all of it (highlights all for editing).
The text represents file names such as "document.pdf". The user is allowed to edit this entire text, however, I only want to initially highlight the base name portion ("document") and not the suffix ("pdf"). How can I do this? (I don't need the logic of how to do this, I need to know how to get the QStyledItemDelegate to highlight a portion of text)
I've tried:
in setEditorData() used QLineEdit::setSelection() to highlight some text. This has no effect.
in paint() attempted to paint based on what other respondents have recommended to similar questions, but not success. I have little experience with QPainter. Here is an example: Adjusting the selection behaviour of QStandardItem with QStyledItemDelegate
Please help, and thanks in advance. A code snippet with say selecting the first 3 characters of text would be greatly appreciated.
As noted in my comments to the question, the problem with subclassing QStyledItemDelegate and trying to set any default selection in setEditorData like this:
void setEditorData(QWidget* editor, const QModelIndex &index)const{
QStyledItemDelegate::setEditorData(editor, index);
if(index.column() == 0){ //the column with file names in it
//try to cast the default editor to QLineEdit
QLineEdit* le= qobject_cast<QLineEdit*>(editor);
if(le){
//set default selection in the line edit
int lastDotIndex= le->text().lastIndexOf(".");
le->setSelection(0,lastDotIndex);
}
}
}
is that (in Qt code) after the view calls our setEditorData here, it tries to call selectAll() here when the editor widget is a QLineEdit. That means that whatever selection we provide in setEditorData will be changed afterwards.
The only solution I could come up with, was to provide our selection in a queued manner. So that, our selection is set when execution is back into the event loop. Here is working example:
#include <QApplication>
#include <QtWidgets>
class FileNameDelegate : public QStyledItemDelegate{
public:
explicit FileNameDelegate(QObject* parent= nullptr)
:QStyledItemDelegate(parent){}
~FileNameDelegate(){}
void setEditorData(QWidget* editor, const QModelIndex &index)const{
QStyledItemDelegate::setEditorData(editor, index);
//the column with file names in it
if(index.column() == 0){
//try to cast the default editor to QLineEdit
QLineEdit* le= qobject_cast<QLineEdit*>(editor);
if(le){
QObject src;
//the lambda function is executed using a queued connection
connect(&src, &QObject::destroyed, le, [le](){
//set default selection in the line edit
int lastDotIndex= le->text().lastIndexOf(".");
le->setSelection(0,lastDotIndex);
}, Qt::QueuedConnection);
}
}
}
};
//Demo program
int main(int argc, char** argv){
QApplication a(argc, argv);
QStandardItemModel model;
QList<QStandardItem*> row;
QStandardItem item("document.pdf");
row.append(&item);
model.appendRow(row);
FileNameDelegate delegate;
QTableView tableView;
tableView.setModel(&model);
tableView.setItemDelegate(&delegate);
tableView.show();
return a.exec();
}
This may sound like a hack, but I decided to write this until someone has a better approach to the problem.

Multiple buttons on click same function

This is a follow up question of Efficient way to make an array of labels.
I have an array of buttons made by code (not designer) which are all added to a gridlayout. What I want is to be able to click any button on that gridlayout and call one same function with the row and column as parameters. Why I want this is because I do not feel like writing 15x15 functions which all do the same thing.
Is there a way or should I try to find another solution?
Ps. All my other input is made in the qt designer via "go to slot" so if it has to happen otherwise, I'll be clueless about how to.
Edit: The array of labels is now an array of buttons.
You could connect all of your buttons to a slot with no parameters and then get the position of the sender in this steps:
Cast the sender QObject to a QWidget via qobject_cast
Retrieve the index of that QWidget using QLayout::indexOf(QWidget *widget)
Then get the row, column, column span and row span with the QGridLayout::getItemPosition(int index, int *row, int *column, int *rowSpan, int *columnSpan)
The example code would look like this:
void MyWidgetWithAllLabels::commonSlot()
{
QWidget *buttonWidget = qobject_cast<QWidget*>(sender());
if (!buttonWidget)
return;
int indexOfButton = ui->gridLayout->indexOf(buttonWidget);
int rowOfButton, columnOfButton, rowSpanOfButton, columnSpanOfButton;
ui->gridLayout->getItemPosition(indexOfButton,
&rowOfButton, &columnOfButton, &rowSpanOfButton, &columnSpanOfLabel);
// Now you can get a reference to that specific QPushButton
QLayoutItem *item = ui->gridLayout->itemAtPosition(rowOfButton, columnOfButton);
QPushButton *clickedButton = qobject_cast<QPushButton*>(item->widget());
if (!clickedButton)
return;
// ... do something with that clickedButton
}
Referring to the code in your related post, you can connect your buttons to that slot like this:
connect( ui->tile_0_0, SIGNAL(clicked()),
this, SLOT(commonSlot()));
connect( ui->tile_0_1, SIGNAL(clicked()),
this, SLOT(commonSlot()));
// ...
By default, a QLabel has no "clicked" signal.
But you can do your own QLabel with 2 integers (row, col) and when you've got a mouseReleaseEvent (or mousePressEvent), you send a custom signal that looks like this: clicked(int row, int col).
You can also use a QSignalMapper:
http://qt-project.org/doc/qt-4.8/qsignalmapper.html#details

How do I place the QScrollBar on the content of the QScrollArea?

I want to make a scrollbar that fades in and out depending on usage. I subclassed QScrollBar and got the look that I want. The problem is that the scrollbar is placed next to the content. How do I instead make it go on top of the content?
I created a new QScrollbar which I connected to the original via signals and then used widget->setParent and then widget->setGeometry() to paint it on top
I quicker solution is to reparent the QScrollBars that the QScrollArea creates and add it to a new QLayout to position it how you want.
QScrollArea *scrollArea = new QScrollArea();
QScrollBar *scrollBar = scrollArea->horizontalScrollBar();
scrollBar->setParent(scrollArea);
scrollBar->setFixedHeight(20);//required for later
QVBoxLayout *scrollAreaLayout = new QVBoxLayout(scrollArea);
scrollAreaLayout->setContentsMargins(0, 0, 0, 10);//use whatever margins you want
scrollAreaLayout->addStretch(1);
scrollAreaLayout->addWidget(scrollBar);
This gets the basic functionality working, however the QScrollArea still adds space where the scrollbar would have been. To remove this, subclass QProxyStyle and override pixelMetric().
#include <QProxyStyle>
class StyleFixes : public QProxyStyle
{
public:
int pixelMetric(PixelMetric metric, const QStyleOption *option = Q_NULLPTR, const QWidget *widget = Q_NULLPTR) const override
{
if(metric == PM_ScrollBarExtent)
{
return 0;
}
return QProxyStyle::pixelMetric(metric, option, widget);
}
};
Then just apply it in main.cpp
QApplication::setStyle(new StyleFixes);
This will remove the arrows on the scrollbar however so you'll need to style it yourself.

Get previous value of QComboBox, which is in a QTableWidget, when the value is changed

Say I have a QTableWidget and in each row there is a QComboBox and a QSpinBox. Consider that I store their values is a QMap<QString /*Combo box val*/,int /*spin box val*/> theMap;
When comboBoxes value or spin boxes value is being changed I want to update theMap. So I should know what was the former value of the combo box in order to replace with the new value of the comboBox and also take care of the value of the spin box.
How can I do this?
P.S. I have decided to create a slot that when you click on a table, it stores the current value of the combo box of that row. But this works only when you press on row caption. In other places (clicking on a combobox or on a spinbox) itemSelectionChanged() signal of QTableWidget does not work.
So in general my problem is to store the value of the combo box of selected row, and the I will get ComboBox or SpinBox change even and will process theMap easily.
How about creating your own, derived QComboBox class, something along the lines of:
class MyComboBox : public QComboBox
{
Q_OBJECT
private:
QString _oldText;
public:
MyComboBox(QWidget *parent=0) : QComboBox(parent), _oldText()
{
connect(this,SIGNAL(editTextChanged(const QString&)), this,
SLOT(myTextChangedSlot(const QString&)));
connect(this,SIGNAL(currentIndexChanged(const QString&)), this,
SLOT(myTextChangedSlot(const QString&)));
}
private slots:
myTextChangedSlot(const QString &newText)
{
emit myTextChangedSignal(_oldText, newText);
_oldText = newText;
}
signals:
myTextChangedSignal(const QString &oldText, const QString &newText);
};
And then just connect to myTextChangedSignal instead, which now additionally provides the old combo box text.
I hope that helps.
A bit late but I had the same problem and solved in this way:
class CComboBox : public QComboBox
{
Q_OBJECT
public:
CComboBox(QWidget *parent = 0) : QComboBox(parent) {}
QString GetPreviousText() { return m_PreviousText; }
protected:
void mousePressEvent(QMouseEvent *e)
{
m_PreviousText = this->currentText();
QComboBox::mousePressEvent(e);
}
private:
QString m_PreviousText;
};
My suggestion is to implement a model, which would help you make a clean separation between the data, and the UI editing the data. Your model would then get notified that a given model index (row and column) changed to the new data, and you could change whatever other data you needed to at that point.
I was just having a similar issue, but for me i needed the previous index for something very trivial so defining and implementing a whole class for it was unjustified.
So what I did instead was keep an argument called say 'previousIndex' and updated it's value only after I had done everything I needed with it