toolbarBreak disappears when deleting QToolBar - qt

When I execute the following example code, I first create two toolbars with a break between it and then I delete the second toolbar after that. The problem is that the toolbarBreak also disappears when I do that.
When I reinitialize t2, there is no break anymore. Is the toolbarBreak really "gone" here?
To fix this, I need to call addToolBarBreak(); before i reinitialize the toolbar. But do I create a duplicate of the toolbarBreak I had before?
Or should I call removeToolbarBreak(t2) before deleting t2?
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto* t1 = new QToolBar(this);
t1->setMovable(false);
t1->addAction("Hello t1 Action");
auto* t2 = new QToolBar(this);
t2->setMovable(false);
t2->addAction("Hello t2 Action");
addToolBar(t1);
addToolBarBreak();
addToolBar(t2);
delete t2;
t2 = new QToolBar(this);
t2->setMovable(false);
t2->addAction("Hello t2 Action");
// addToolBarBreak(); // fixes the problem
addToolBar(t2);
}

Yes, the Layout takes care of it! I debugged the Qt code myself today.
See here, in QToolBarAreaLayout::takeAt(int *x, int index) the break will be removed.

Related

How to update a TableView with progress data for multiple ProgressBars?

I've started to extend the qGet DownloadManager to emit the progress of a TransferItem, so that i can connect to it. I'm inserting the progress data into a cell of a TableView model for display with an Delegate, finally the delegate paints the progress bar. That works in theory, but i'm running into the following
Problem: when there are multiple downloads in parallel, then i get progress updates from both signals into both cells!
Both progress bars show progress data, but the signal is kind of mixed and not unique to the current index (QModelIndex index / index.row()).
(Please ignore the small transitioning problem between UserRoles (after clicking the download button "ActionCell" is displayed and then "Install", before the "ProgressBar" shows up.). That is not the main problem here. My question is about the index problem.) The text "112" and "113" is the int index.row.
Questions:
How to update a TableView with progress data for multiple ProgressBars?
What must i change to render a progress bar for each download?
Source
Emit progress of a download
I've added the following things to re-emit the signal through the classes, until it bubbles up to the top, where it becomes connectable from the GUI.
a connection from QNetworkReply - downloadProgress(qint64,qint64) to TransferItem - updateDownloadProgress(qint64,qint64)
void TransferItem::startRequest()
{
reply = nam.get(request);
connect(reply, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
this, SLOT(updateDownloadProgress(qint64,qint64)));
connect(reply, SIGNAL(finished()), this, SLOT(finished()));
timer.start();
}
the SLOT function TransferItem - updateDownloadProgress(qint64,qint64) as receiver calculates the progress and stores it in progress (QMap<QString, QVariant>).
After the calculation the downloadProgress(this) signal is emitted.
// SLOT
void TransferItem::updateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
progress["bytesReceived"] = QString::number(bytesReceived);
progress["bytesTotal"] = QString::number(bytesTotal);
progress["size"] = getSizeHumanReadable(outputFile->size());
progress["speed"] = QString::number((double)outputFile->size()/timer.elapsed(),'f',0).append(" KB/s");
progress["time"] = QString::number((double)timer.elapsed()/1000,'f',2).append("s");
progress["percentage"] = (bytesTotal > 0) ? QString::number(bytesReceived*100/bytesTotal).append("%") : "0 %";
emit downloadProgress(this);
}
QString TransferItem::getSizeHumanReadable(qint64 bytes)
{
float num = bytes; QStringList list;
list << "KB" << "MB" << "GB" << "TB";
QStringListIterator i(list); QString unit("bytes");
while(num >= 1024.0 && i.hasNext()) {
unit = i.next(); num /= 1024.0;
}
return QString::fromLatin1("%1 %2").arg(num, 3, 'f', 1).arg(unit);
}
When a new download is enqueued, i'm connecting the emitted downloadProgress(this) to the Slot DownloadManager - downloadProgress(TransferItem*). (dl is DownloadItem which extends TransferItem).
void DownloadManager::get(const QNetworkRequest &request)
{
DownloadItem *dl = new DownloadItem(request, nam);
transfers.append(dl);
FilesToDownloadCounter = transfers.count();
connect(dl, SIGNAL(downloadProgress(TransferItem*)),
SLOT(downloadProgress(TransferItem*)));
connect(dl, SIGNAL(downloadFinished(TransferItem*)),
SLOT(downloadFinished(TransferItem*)));
}
Finally, i'm re-emitting the download progress one more time:
void DownloadManager::downloadProgress(TransferItem *item)
{
emit signalProgress(item->progress);
}
Now the TableView with Delegate, doDownload(index) and ProgressBarUpdater
QTableView
with added QSortFilterProxyModel (for case-insensitivity)
with added ColumnDelegate, which renders DownloadButton and ProgressBar based on custom UserRoles. The delegate handles the button click: the SIGNAL downloadButtonClicked(index) is emited from the editorEvent(event, model, option, index) method.
actionDelegate = new Updater::ActionColumnItemDelegate;
ui->tableView->setItemDelegateForColumn(Columns::Action, actionDelegate);
connect(actionDelegate, SIGNAL(downloadButtonClicked(QModelIndex)), this, SLOT(doDownload(QModelIndex)));
The doDownload method receives the index and fetches the download URL from the model. Then the URL is added to the DownloadManager
and i'm setting up a ProgressBarUpdater object to set the progress data to the model at the given index. Finally i'm, connecting downloadManager::signalProgress to progressBar::updateProgress and invoke the downloadManager::checkForAllDone to start the download processing.
void UpdaterDialog::doDownload(const QModelIndex &index)
{
QUrl downloadURL = getDownloadUrl(index);
if (!validateURL(downloadURL)) return;
QNetworkRequest request(downloadURL);
downloadManager.get(request); // QueueMode is Parallel by default
ProgressBarUpdater *progressBar = new ProgressBarUpdater(this, index.row());
progressBar->setObjectName("ProgressBar_in_Row_" + QString::number(index.row()) );
connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)),
progressBar, SLOT(updateProgress(QMap<QString, QVariant>)));
QMetaObject::invokeMethod(&downloadManager, "checkForAllDone", Qt::QueuedConnection);
}
The model update part: the ProgressBarUpdater takes the index and the progress and should update the model at the given index.
ProgressBarUpdater::ProgressBarUpdater(UpdaterDialog *parent, int currentIndexRow) :
QObject(parent), currentIndexRow(currentIndexRow)
{
model = parent->ui->tableView_1->model();
}
void ProgressBarUpdater::updateProgress(QMap<QString, QVariant> progress)
{
QModelIndex actionIndex = model->index(currentIndexRow, UpdaterDialog::Columns::Action);
// set progress to model
model->setData(actionIndex, progress, ActionColumnItemDelegate::DownloadProgressBarRole);
model->dataChanged(actionIndex, actionIndex);
}
The rendering part: i'm rendering the fake ProgressBar from the delegate; fetching the progress data with index.model()->data(index, DownloadProgressBarRole).
void ActionColumnItemDelegate::drawDownloadProgressBar(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionProgressBarV2 opt;
opt.initFrom(bar);
opt.rect = option.rect;
opt.rect.adjust(3,3,-3,-3);
opt.textVisible = true;
opt.textAlignment = Qt::AlignCenter;
opt.state = QStyle::State_Enabled | QStyle::State_Active;
// get progress from model
QMap<QString, QVariant> progress =
index.model()->data(index, DownloadProgressBarRole).toMap();
QString text = QString::fromLatin1(" %1 %2 %3 %4 %5 ")
.arg(QString::number(index.row()))
.arg(progress["percentage"].toString())
.arg(progress["size"].toString())
.arg(progress["speed"].toString())
.arg(progress["time"].toString());
opt.minimum = 0;
opt.maximum = progress["bytesTotal"].toFloat();
opt.progress = progress["bytesReceived"].toFloat();
opt.text = text;
bar->style()->drawControl(QStyle::CE_ProgressBar,&opt,painter,bar);
}
I've added QString::number(index.row() to the progress bar text, so that each ProgressBar gets its row number rendered. In other words: the rendering is unique to the row, but the incoming progress data is somehow mixed.
I'm stuck on the index problem for a while now. Thank you in advance for your help.
Update: The issue is resolved!
Thank you very much ddriver!! I followed your suggestions and fixed it:
The DownloadManager tracks the progress for all transfers, and you keep each transfer item's data in the respective TransferItem.
The logical thing IMO would be to have a connection from each TransferItem to the corresponding ProgressBarUpdater, and emit from the transfer item.
However, in your case, you are reporting progress not from each individual transfer item, but from the download manager. So each time you are emitting a progress, you are emitting the progress for a particular transfer item to all progress bars.
connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)),
progressBar, SLOT(updateProgress(QMap<QString, QVariant>)));
So instead of a
TransferItem --progress--> CorrespondingUI
you have a:
TransferItem --transferItem--> DownloadManager --progress--> AllUIs
This leads to having one single and varying progress for all progress bars, which corresponds to the last download that happen to report progress before the UI updated. Which is why you get no more variation after the first download is completed, as the manager only updates the progress for the second.
Finally, i'm re-emitting the download progress one more time:
void DownloadManager::downloadProgress(TransferItem *item)
{
emit signalProgress(item->progress);
}
And who exactly needs an anonymous progress, containing no information whatsoever to which transfer it applies? Aside from the bug of course.
Would you be so nice to explain, how to simplify it?
I was at the end of my mental rope yesterday when I commented, on a clear head it doesn't look all that overdone, but still I'd probably go for something more streamlined, involving 3 key components only:
DownloadsManager -> DownloadController -> UI
-> DownloadController -> UI
It just seems redundant to have a DownloadItem and then also a TransferItem, considering that a download is a transfer.
The model and view are totally unnecessary as well, as is storing the progress in the model rather than just having it as a member of the progress bar. You could go with just a regular widget for each download, and place them in a vertical layout.
Update:
The excessive, unnecessary compartmentalization has led to a level of fragmentation which makes it hard to get to the data, needed to make it work once you put everything together. The main issue is you have no way to tie a transfer item to the right progress bar updater, and since you still haven't posted all of the relevant code, the simplest possible solution I can offer involves the following minor changes:
// in DownloadManager
void signalProgress(QMap<QString, QVariant>); // this signal is unnecessary, remove
void DownloadManager::downloadProgress(TransferItem *item) // change this
{
registry[item->request.url()]->updateProgress(item->progress);
}
QMap<QUrl, ProgressBarUpdater *> registry; // add this
// in UpdaterDialog
void UpdaterDialog::doDownload(const QModelIndex &index)
{
QUrl downloadURL = getDownloadUrl(index);
if (!validateURL(downloadURL)) return;
QNetworkRequest request(downloadURL);
downloadManager.get(request); // QueueMode is Parallel by default
ProgressBarUpdater *progressBar = new ProgressBarUpdater(this, index.row());
progressBar->setObjectName("ProgressBar_in_Row_" + QString::number(index.row()) );
// remove the connection - source of the bug, instead register the updater
downloadManager.registry[downloadURL] = progressBar;
QMetaObject::invokeMethod(&downloadManager, "checkForAllDone", Qt::QueuedConnection);
}
That's pretty much it, the progress updater is associated with the URL, and in DownloadManager::downloadProgress instead of emitting the progress to all progress updaters, you simply lookup the one which actually corresponds to the particular download, and only update its progress. It is somewhat clumsy, but as I said, if your design is proper, it would not be needed and you wouldn't have the problem in the first place.
There are other solutions as well:
change the DownloadManager's signal to void signalProgress(TransferItem *), and the body of downloadProgress toemit signalProgress(item); , change to void ProgressBarUpdater::updateProgress(TransferItem *), and in the body compare the transfer item's request's url to the one in the model at the currentIndexRow, and only model-setData() if it is the same. This solution is not very efficient, since it will emit to all progress updaters just to modify one.
cut out the middleman, what I've been suggesting from the start, make DownloadManager ::get() return a pointer to the DownloadItem/TransferItem created in its body, then in UpdaterDialog::doDownload() you can connect the transfer item directly to the appropriate progress updater, so you will no longer need DownloadManager::downloadProgress() and the signalProgress signal, you only need to change the signature of the signal in TransferItem to void downloadProgress(QMap<QString, QVariant>); and emit the progress rather than the item. This is actually the most efficient solution, as it involves nothing extra, jsut the removal of unnecessary stuff.

Making a dynamic QMenu in QT

I have a QMenu, where I want to add QActions. The problem is, everytime the table is clicked, the existing menu adds onto itself. How do I stop it from doing that?
So when I click on the table, it should be new menus everytime.
void MainWindow::onTableClicked(const QModelIndex &index){
QAction *action;
// action->setAutoRepeat(false);
if (index.isValid()) {
QString cellText = index.data().toString();
QString indexRow = QString::number(index.row());
QString indexCol = QString::number(index.column());
ui->textBrowser->setText(indexRow + "\n"+ indexCol);
QSet<int> possiblevalues = findPossibleValues(index.row(),index.column());
for(int n : possiblevalues){
listofPossibleValues.push_back(QString::number(n));
}
for(auto s :listofPossibleValues){
action = myMenu.addAction("Set Value "+s);
}
}
}
The reason why it looked like myMenu.clear wasnt working was because i did not remove listofPossibleValues after I added the menuaction. I was able to fix it by adding
//At the beginning
myMenu.clear();
//At the end
listofPossibleValues.clear();

Qt How to check state checkbox in QtableWidget

Recently I found the way that checkbox places in the middle of QtableWidget item.
However, I do not know how to check state whether or not button is clicked.
Could you tell me how to check button state?
here is what Ive found code:
QWidget *pWidget = new QWidget();
QCheckBox *pCheckBox = new QCheckBox();
QHBoxLayout *pLayout = new QHBoxLayout(pWidget);
pCheckBox->setCheckState(Qt::Checked);
pLayout->addWidget(pCheckBox);
pLayout->setAlignment(Qt::AlignCenter);
pLayout->setContentsMargins(0,0,0,0);
pWidget->setLayout(pLayout);
ui->tableWidget2->setCellWidget(2,2, pWidget);
Although this is very late you can solve it like this:
auto field = ui->tableWidget2->cellWidget(2, 2, pWidget);
std::cout << qobject_cast<QCheckBox*>(field)->isChecked() << std::endl;
This works for other types as well (QComboBox etc.). Although it would probably be better to just use the checkbox functionality that QTableWidgetItem already has.
This example might not work if you are using a tristate checkbox in which case you should call: checkState() and compare it to Qt::CheckState. If qobject_cast<T> does not work out you can use a reinterpret_cast<T>.
I assume you created your checkboxes in the QWidgetTable like this:
int row...;int column...;
...
QTableWidgetItem *checkBoxItem = new QTableWidgetItem();
checkBoxItem->setCheckState(Qt::Unchecked);
ui->Table->setItem(row, column, checkBoxItem);
You can check the status of the item that corresponds to your widget in another function like this:
void MainWindow::on_Table_cellClicked(int row, int column)
{
QTableWidgetItem *checkBoxState = ui->Table->item(row, column);
if(ui->Table->item(row,column)->checkState())
{
checkBoxState->setCheckState(Qt::Unchecked);
ui->Table->setItem(row, column, checkBoxState);
}
else
{
checkBoxState->setCheckState(Qt::Checked);
ui->Table->setItem(row, column, checkBoxState);
}
}

QTableWidget edit only one column and leave the rest non-editable

In QT 4.7, I am trying to make one QTableWidgetItem in a QTableWidget Editable and the rest all columns should be read only for me. I am having problems here.
I have checked a number of samples through google and stackoverflow but failed to achieve this. Some of the options I tried are,
I create rows by calling insertRow(rownumber) for adding rows.
Trial 1: I do the following while inserting a row dynamically
Enable Edit triggers in the UI Dialog
Add columns using the following code for disabling edit
QTableWidgetItem qit("");
qit.setflags(qit.flags() & ~Qt::ItemIsEditable)
qtable.setitem(row,column, &qit);
And for others columns I don't set the flags
This above approach did not work. I am able edit all columns (even the one I negated the editable option)
Trial 2:
Do all the above with just qtable.setEditTriggers(Qt::NoEditTriggers) and then set the columns editable wherever required.
But this option renders all columns non-editable.
But I don't see anyone complaining like this in any forums. So I must be making some stupid mistake.
Have someone come across such an issue, if yes please help by answering.
Working example of QTableWidget
First item in added row is editable, second one is not editable.
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Prepare layout
QMainWindow *window = new QMainWindow;
QTableWidget *tablewidget = new QTableWidget;
window->setCentralWidget(tablewidget);
// Add data
tablewidget->insertRow(0);
tablewidget->insertColumn(0);
tablewidget->insertColumn(1);
QTableWidgetItem *item;
item = new QTableWidgetItem("editable");
tablewidget->setItem(0,0,item);
item = new QTableWidgetItem("non editable");
item->setFlags(item->flags() & ~Qt::ItemIsEditable); // non editable
tablewidget->setItem(0,1,item);
window->show();
return a.exec();
}

How to maintain vertical scroll position in a QTableWidget

This a very simple problem to which I can find no solution:
This is my code:
qint32 pos = ui->twShow->verticalScrollBar()->value();
ui->twShow->blockSignals(true);
//Code for updating the contents QTableWidget twShow, this is done by erasing all cells and adding them again, in case it matters.
ui->twShow->blockSignals(false);
if (pos > 0){
ui->twShow->verticalScrollBar()->setValue(pos);
}
What I want to accomplish is simply to maintain the vertical scroll position. However the setValue function ignores the value pos (I've checked by printing the value before and after the instruction and both times its cero).
I have also tried:
QScrollBar *bar = ui->twShow->verticalScrollBar();
// Same code as before
ui->twShow->setVerticalScrollBar(bar); //This line crashes de program
However the last line crashes the program (which I've checked by commenting it, and it works fine).
Any advice would be greatly appreciated...
Thank you very much
QTableWidget * tw;
int desiredRow;
// before update
desiredRow = tw->row(tw->itemAt(1,1));
...
// update code
...
tw->scrollToItem( tw->item( desiredRow, 0),
QAbstractItemView::EnsureVisible | QAbstractItemView::PositionAtTop );
QAbstractItemView::EnsureVisible = 0.
The 'or' flag converts the result to an integer which is not allowed as parameter of the scrollToItem method. On the other hand enums are not intended to be used as combined flags.

Resources