QTreeView items spacing - c++

How can I set some space between QTreeView items?
qss margin/paddin - cant use, it's just make item higher (looks bad when selected).
Delegate - Cant too, it's breaks branch element. I tried inherit QTreeView and make something in child, but nothing (and it's my best variant now).
class Vias: public {
public:
Vias(QWidget *parent) : QTreeView(parent)
{}
protected:
QRect visualRect(const QModelIndex &index) const override
{
QRect rect = QTreeView::visualRect(index);
return QRect({QPoint{rect.x(),rect.y()+6},QSize{rect.width(),rect.height()-12}});
}
};
I'm also using delegate, where adding 12 to height. Result. Some glitches here

Related

How to make text selectable on a QListView custom widget?

Context
TLTR: For a command-line console widget, I need to be able to select the texts on a QListView row.
In a similar way that your browser command-line (e.g. pressing F12 on Firefox and going to "Console", similar for Chrome, others). I am creating a command-line console for interacting with my application.
Each command and it result is pushed into a list above the input text-box, allowing each item to be drawn nicely and user-friendly:
The text goes through a QSyntaxHighlighter
Long lines, or multiple lines are elided
Results which are objects can be expanded or collapsed
etc..
Most of those goals are not yet implemented, but it's clear that I need a custom-widget to represent each row.
Now, I need the text on that QListView item to be selectable, and copiable.
The problem
TLTR: Selecting the texts requiers to enter edit-mode, I don't like having to double-click first for selecting text.
Using a QStyledItemDelegate,
Overriding paint: I managed to have a draft of the apparence I need. But that apparence is mostly static, no interaction exists.
Overriding createEditor and setEditorData, the content can be edited. I can set the QTextEdit as readOnly and then, it is just selectable as required.
However, the list items needs to be double-clicked, or selected+clicked in order to get into edit-mode, and being able to select the text.
But, the user expects to select the text as it is, just by pressing+moving+relasing the mouse.
The text should be selectable as it is, without double-clicking the row for getting into edit-mode
Some code
#include <QApplication>
#include <QListView>
#include <QStyledItemDelegate>
#include <QLabel>
#include <QPainter>
#include <QPaintEvent>
#include <QStandardItemModel>
#include <QTextEdit>
class CommandLineItemDelegate: public QStyledItemDelegate
{
Q_OBJECT
mutable QTextEdit m_editor;
public:
void paint(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QRect rect(QPoint(0, 0), option.rect.size());
m_editor.setPlainText( index.data().toString());
m_editor.resize(option.rect.size());
p->save();
p->translate(option.rect.topLeft());
m_editor.render(p, QPoint(), QRegion());
p->restore();
}
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override
{
return QSize(200,50);
}
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
auto* edit = new QTextEdit(parent);
edit->setGeometry(option.rect);
edit->setReadOnly(true);
return edit;
}
void setEditorData(QWidget *editor, const QModelIndex &index) const override
{
auto* textEditor = dynamic_cast<QTextEdit*>(editor);
if (textEditor != nullptr)
{
textEditor->setPlainText(index.data().toString());
}
}
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
{
auto* textEditor = dynamic_cast<QTextEdit*>(editor);
if (textEditor != nullptr)
{
model->setData(index, textEditor->toPlainText());
}
}
};
class CommandLineListView: public QListView
{
Q_OBJECT
CommandLineItemDelegate m_delegate;
QStandardItemModel m_model;
public:
explicit CommandLineListView( QWidget* parent=nullptr)
: QListView(parent)
, m_delegate()
{
setModel(&m_model);
m_model.insertColumn(0);
m_model.insertRows(0,3);
m_model.setData(m_model.index(0,0),"var adri = function(a,b){return a+b; }; // function to sum");
m_model.setData(m_model.index(1,0),"Math.PI");
m_model.setData(m_model.index(2,0),"2+2");
setSelectionMode(QAbstractItemView::SelectionMode::NoSelection); //Text selection, but no row selection.
setItemDelegate(&m_delegate);
}
};
#include "main.moc"
int main(int argn, char* argv[])
{
QApplication app(argn, argv);
CommandLineListView list;
list.show();
app.exec();
}
As explained before, this "mostly" works, except that the user needs to double-click the row to enter edit-mode and selecting the text, which is not acceptable.
Quick solution: easy, but not very performant for large list views
A quick solution is to keep all the editors open, by calling openPersistentEditor for each added row.
Note that this is not the most performant solution (for very large list view), but may be good enough for your use case.
Alternative 1: Implement your own QStyledItemDelegate
This allows full customisation of the formatting, but also requires that you implement the text selection feature yourself.
Alternative 2: Use HTML
Displaying it as HTML (which is probably what Chrome and Firefox do) allows you full customisation and using the built-in selection feature.
Alternative 3: Use QML
QML is often easier to (rapidly) create a custom user interface.

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.

Why do I not see the drop indicator in a QTableView?

I use drag and drop in my QTableView (works). However, I do not see any drop indicator. I should see a line where the drop is supposed to be inserted, shouldn't I? At least here they say so.
My init is pretty much standard.
// see model for implementing logic of drag
this->viewport()->setAcceptDrops(allowDrop);
this->setDragEnabled(allowDrag);
this->setDropIndicatorShown(true);
this->m_model->allowDrop(allowDrop);
I have no idea why I do not see the indicator. A style sheet is used with the views, could that be the reason. However, I have disabled the stylesheet and still do not see it.
The view uses entire rows for selection, not sure if this causes an issue. So any hint is appreciated.
-- Edit --
As of the comment below, tried all selection modes: single, multi or extended, no visual effect. Also tried cell instead of row selection, again no improvement.
-- Edit 2 --
Currently evaluating another style proxy example, similar to the one below, originally referenced here
-- Related --
QTreeView draw drop indicator
How to highlight the entire row on mouse hover in QTableWidget: Qt5
https://forum.qt.io/topic/12794/mousehover-entire-row-selection-in-qtableview/7
https://stackoverflow.com/a/23111484/356726
I faced the same problem, I tried two options which both worked for me. IIRC, the help came from an answer on SO.
if you are subclassing QTreeView, you can override its paintEvent() method. It is calling by default the drawTree() method and the paintDropIndicator() one (the latter being part of QAbstractItemView private class).
You can call drawTree() from your paintEvent(), and it should override the default drag and drop indicator as well :
class MyTreeView : public QTreeView
{
public:
explicit MyTreeView(QWidget* parent = 0) : QTreeView(parent) {}
void paintEvent(QPaintEvent * event)
{
QPainter painter(viewport());
drawTree(&painter, event->region());
}
};
the other method is to subclass QProxyStyle and overriding the drawPrimitive() method. When you get the element QStyle::PE_IndicatorItemViewItemDrop as a parameter, you can paint it your own way.
The code will look like this:
class MyOwnStyle : public QProxyStyle
{
public:
MyOwnStyle(QStyle* style = 0) : QProxyStyle(style) {}
void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
if (element == QStyle::PE_IndicatorItemViewItemDrop)
{
//custom paint here, you can do nothing as well
QColor c(Qt::white);
QPen pen(c);
pen.setWidth(1);
painter->setPen(pen);
if (!option->rect.isNull())
painter->drawLine(option->rect.topLeft(), option->rect.topRight());
}
else
{
// the default style is applied
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
};

QTreeView selection clears text color

I have subclassed QTreeView and made model subclassed from QAbstractTableModeland everything works fine. If something is being changed in QTreeView from code (not by user), then that row's text color becomes red. I have implemented this trough checking Qt::TextColorRole from data() function and returning Qt::red.
But if that particular row is being selected, then text color changes automatically to black (and background color to light green, which is normal). After deselecting that row everything is OK again. In debug mode I've seen that data() function returns true value for selected row (Qt::red).
Now how can I solve this problem, what may cause to this incorrect behaviour?
Thank you in advance!
I've found a way of doing this trough a delegate. Here is the code
class TextColorDelegate: public QItemDelegate
{
public:
explicit TextColorDelegate(QObject* parent = 0) : QItemDelegate(parent)
{ }
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyleOptionViewItem ViewOption(option);
QColor itemForegroundColor = index.data(Qt::ForegroundRole).value<QColor>();
if (itemForegroundColor.isValid())
{
if (itemForegroundColor != option.palette.color(QPalette::WindowText))
ViewOption.palette.setColor(QPalette::HighlightedText, itemForegroundColor);
}
QItemDelegate::paint(painter, ViewOption, index);
}
};
And for using delegate you should write something like this
pTable->setItemDelegate(new TextColorDelegate(this));
where pTable's type is QTableView*;
Does the following code keep your text color red?
QPalette p = view->palette();
p.setColor(QPalette::HighlightedText, QColor(Qt::red));
view->setPalette(p);

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.