How to open an URL in a QTableView - c++

What is the best way to present a clickable URL in a QTableView (or QTreeView, QListView, etc...)
Given a QStandardItemModel where some of the columns contain text with URLs I'd like them to become clickable and then handle the click by using QDesktopServices::openURL()
I was hoping there would be some easy way to leverage QLabel's textInteraction flags and to cram them into the table. I can't believe there's not an easier way to handle this. I really hope I'm missing something.

You'll need to create a delegate to do the painting. The code should look something like this:
void RenderLinkDelegate::paint(
QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index
) const
{
QString text = index.data(Qt::DisplayRole).toString();
if (text.isEmpty())
return;
painter->save();
// I only wanted it for mouse over, but you'll probably want to remove
// this condition
if (option.state & QStyle::State_MouseOver)
{
QFont font = option.font;
font.setUnderline(true);
painter->setFont(font);
painter->setPen(option.palette.link().color());
}
painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, text);
painter->restore();
}

Well, you can use delegates to render rich text in a qtableview with custom delegates reimplementing the paint method such as:
void CHtmlDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt(option);
QLabel *label = new QLabel;
label->setText(index.data().toString());
label->setTextFormat(Qt::RichText);
label->setGeometry(option.rect);
label->setStyleSheet("QLabel { background-color : transparent; }");
painter->translate(option.rect.topLeft());
label->render(painter);
painter->translate(-option.rect.topLeft());
}
However, it will not make hyperlinks clickable.
To do so, you can use the following hack. Reimplement the setModel method of your table/list view and use setIndexWidget.
void MyView::setModel(QAbstractItemModel *m)
{
if (!m)
return;
QTableView::setModel(m);
const int rows = model()->rowCount();
for (int i = 0; i < rows; ++i)
{
QModelIndex idx = model()->index(i, 1);
QLabel *label = new QLabel;
label->setTextFormat(Qt::RichText);
label->setText(model()->data(idx, CTableModel::HtmlRole).toString());
label->setOpenExternalLinks(true);
setIndexWidget(idx, label);
}
}
In the example above, I replace column 1 with qlabels. Note that you need to void the display role in the model to avoid overlapping data.
Anyway, I would be interested in a better solution based on delegates.

Sadly, its not that easy to render a QLabel with setOpenExternalLinks() when using a QTableView (as opposed to using a QTableWidget). There are no magic two lines of code you can call and have the job done.
use a delegate
set the delegate to the column of your table
use QTextDocument combined with setHTML() to render a html link
this means your model needs to provide an HTML fragment containing a href
calculate the geometry of the link and provide event handlers for to intercept the mouse
change the cursor when it is over the link
execute the action when the link is clicked
what a mess :( i want painter->setWidgetToCell()
--
void LabelColumnItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if( option.state & QStyle::State_Selected )
painter->fillRect( option.rect, option.palette.highlight() );
painter->save();
QTextDocument document; // <---- RichText
document.setTextWidth(option.rect.width());
QVariant value = index.data(Qt::DisplayRole);
if (value.isValid() && !value.isNull())
{
document.setHtml(value.toString()); // <---- make sure model contains html
painter->translate(option.rect.topLeft());
document.drawContents(painter);
}
painter->restore();
}

Related

QListWidget change part color of text

Click Here to open the Sample Picture, the red Arrow is what I want, But the output just show all the code and didn't work just like the Blue Arrow does
I try use < font color = red >...< /font > || < span >...< /span > in QListWidget, but There didn't have any Effect
What I want is some thing like:
item1 <font color=red>apple</font> ("item1" black, "apple" will output as red color)
item2 <font color=green>durian</font> (durian will output as green color)
Can Anyone help?
Ps: Accually what I really want is the picture below:
When I type the word "cola", the list of QListwidget will Highlight/Change the color "%cola%" into Different color.
QListWidget by default does not render Html, but for this Qt has the delegate classes that allow customize the view.
In this case we use the following delegate:
#ifndef HTMLDELEGATE_H
#define HTMLDELEGATE_H
#include <QPainter>
#include <QStyledItemDelegate>
#include <QTextDocument>
class HtmlDelegate : public QStyledItemDelegate
{
public:
void paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex &index) const
{
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
painter->translate(options.rect.left(), options.rect.top());
QRect clip(0, 0, options.rect.width(), options.rect.height());
doc.drawContents(painter, clip);
painter->restore();
}
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
QTextDocument doc;
doc.setHtml(options.text);
doc.setTextWidth(options.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
};
#endif // HTMLDELEGATE_H
Then use the setItemDelegate() method of QListWidget as shown below:
ui->listWidget->setItemDelegate(new HtmlDelegate);
Obtaining what is shown in the following image:
The complete example can be found at the following link.

QTreeWidget doesn't display QCombobox delegate

I have the delegate class and method paint in it. I would like to show the comboboxes in the QTreeWidget columns and do for it
ui->treeWidget->setItemDelegateForColumn(2, box);
where box is the object of my delegate
The paint method is
void ComboboxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionComboBox box;
QString text = values[1];
box.currentText = text;
box.rect = option.rect;
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, 0);
QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &box, painter, 0);
}
values is the QMap with two variables - "Hello" and "Goodbye"
But instead of drawing comboboxes treewidget shows just "Hello" strings in second column
Why?
How can i fix it?
If you have qss files and want to customize your QComboBox delegate you should add some combobox in paint() method and replace that:
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, 0);
to:
QComboBox tmp;
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, &tmp);

Correct highlighting with qt custom delegates

I am making a table control that displays some additional text data apart from those in DisplayRole of its model. In all other respects text and cell display should be identical. What i am having trouble with is correct display of highlighted cell.
I am currently using the following code:
void MatchDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
painter->save();
QString str = qvariant_cast<QString>(index.data())+ "\n";
str += QString::number(qvariant_cast<float>(index.data(Qt::UserRole)));
if (option.state & QStyle::State_Selected)
painter->setBrush(option.palette.highlightedText());
else
painter->setBrush(qvariant_cast<QBrush>(index.data(Qt::ForegroundRole)));
painter->drawText(option.rect, qvariant_cast<int>(index.data(Qt::TextAlignmentRole)), str);
painter->restore();
}
However, the result looks like this:
Text color is wrong, there is no dashing line around the cell, and when the control loses focus, the cell remains blue instead of becoming light gray like drawn by default cells do.
How should painting code be changed to fix those issues?
Please try below code, It will work.
Set drawControl with take care to draw dashed line( Let Qt take care it internally ) when selected.
Fixed( Dashed line, Text color and multiline ) while selecting cell.
void MatchDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
const QWidget *widget = option.widget;
QString str = qvariant_cast<QString>(index.data())+ "\n";
str += QString::number(qvariant_cast<float>(index.data(Qt::UserRole)));
opt.text = "";
//option
QStyle *style = widget ? widget->style() : QApplication::style();
if (option.state & QStyle::State_Selected)
{
// Whitee pen while selection
painter->setPen(Qt::white);
painter->setBrush(option.palette.highlightedText());
// This call will take care to draw, dashed line while selecting
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}
else
{
painter->setPen(QPen(option.palette.foreground(), 0));
painter->setBrush(qvariant_cast<QBrush>(index.data(Qt::ForegroundRole)));
}
painter->drawText(option.rect, qvariant_cast<int>(index.data(Qt::TextAlignmentRole)), str);
}

How to create delegate for QTreeWidget?

Here is what I'm trying to do (all parents and children must have a close button on the right, in the future, only the hovered item will be able to show the **close ** button):
My delegate code:
class CloseButton : public QItemDelegate
{
Q_OBJECT
public:
CloseButton( QObject* parent = 0 )
: QItemDelegate( parent )
{};
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
if ( index.column() == 1 )
{
QToolButton* button = new QToolButton( parent );
button->setIcon( QIcon( CLOSE_ICON ) );
//button->setFixedSize( 16, 16 );
//button->setAutoRaise( true );
//button->setVisible( true );
CONNECT( button, SIGNAL( clicked() ), this, SLOT( emitCommitData() ) );
return button;
}
return ( new QWidget );
}
private slots:
void emitCommitData()
{
emit commitData( qobject_cast< QWidget* >( sender() ) );
}
private:
//Q_DISABLE_COPY( CloseButton );
};
With QTreeWidget connection code:
recipientsView()->setItemDelegateForColumn( 1, new CloseButton( this ) );
where recipientsView() is a simple QTreeWidget.
Problem is that QToolButtons are not shown at all (it must be in the second column, i.e. column index in the tree is 1). What I'm doing wrong?
I have checked already all Qt demo examples about delegates and the first Google result about QItemDelegate's and similar stuff.
You can use the QStyledDelegate::paint function to draw the close icon, without using any widget, and the editorEvent to receive mouse events for the item, even if you don't use the editor or make the item editable.
class CloseButton : public QStyledItemDelegate {
Q_OBJECT
public:
explicit CloseButton(QObject *parent = 0,
const QPixmap &closeIcon = QPixmap())
: QStyledItemDelegate(parent)
, m_closeIcon(closeIcon)
{
if(m_closeIcon.isNull())
{
m_closeIcon = qApp->style()
->standardPixmap(QStyle::SP_DialogCloseButton);
}
}
QPoint closeIconPos(const QStyleOptionViewItem &option) const {
return QPoint(option.rect.right() - m_closeIcon.width() - margin,
option.rect.center().y() - m_closeIcon.height()/2);
}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
QStyledItemDelegate::paint(painter, option, index);
// Only display the close icon for top level items...
if(!index.parent().isValid()
// ...and when the mouse is hovering the item
// (mouseTracking must be enabled on the view)
&& (option.state & QStyle::State_MouseOver))
{
painter->drawPixmap(closeIconPos(option), m_closeIcon);
}
}
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QSize size = QStyledItemDelegate::sizeHint(option, index);
// Make some room for the close icon
if(!index.parent().isValid()) {
size.rwidth() += m_closeIcon.width() + margin * 2;
size.setHeight(qMax(size.height(),
m_closeIcon.height() + margin * 2));
}
return size;
}
bool editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
// Emit a signal when the icon is clicked
if(!index.parent().isValid() &&
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
QRect closeButtonRect = m_closeIcon.rect()
.translated(closeIconPos(option));
if(closeButtonRect.contains(mouseEvent->pos()))
{
emit closeIndexClicked(index);
}
}
return false;
}
signals:
void closeIndexClicked(const QModelIndex &);
private:
QPixmap m_closeIcon;
static const int margin = 2; // pixels to keep arount the icon
Q_DISABLE_COPY(CloseButton)
};
First, I should ask if you are really using a QTreeWidget, or rather a QTreeView? You cannot use custom delegates with a QTreeWidget according to the documentation for QTreeView, and will have to use a QTree*View* and some form of QAbstractItemModel for you to be able to use a custom delegate.
Ah, scratch that. I see that you're calling setItemDelegateForColumn, which is a QTreeView function, but you should be aware of the difference, so I'm keeping the above paragraph. :)
I would check that your model's flags() function is returning Qt::ItemIsEditable as part of its item flags. The createEditor() method is called whenever an edit event is reported by the view. (The view events that will trigger an edit depend on the model's EditTriggers) Usually a double-click on the delegate will trigger an edit by default, among other things.
I doubt that you want the close button to appear only on double-click, though. To get the button to appear all the time, you'll have to reimplement the delegate's paint() function to draw a button, Among other things. I found Qt's StarDelegate example to be quite helpful in this regard, and I suspect you'll find it useful too.
You can use a QItemDelegate with a QTreeWidget this way (example in PyQt, sorry):
myTreeWidget = QtGui.QTreeWidget()
myTreeWidget.setItemDelegate(myDelegate())
class myDelegate(QtGui.QItemDelegate):
def paint(self, painter, option, index):
#Custom Draw Column 1
if index.column() == 1:
icon = QtGui.QIcon(index.data(QtCore.Qt.DecorationRole))
if icon:
icon.paint(painter, option.rect)
#You'll probably want to pass a different QRect
#Use the standard routine for other columns
else:
super(myDelegate, self).paint(painter, option, index)

Animation of list items/redrawing Qt QListView

What I'd like to achieve:
A visual indication to draw attention to newly added items in a QListView. I had in mind having the background color 'throb' once (fading from a color to the background).
The setup
I have a model/view using QListView displaying QStandardItems. Qt version 4.7
What I've tried:
I've created a new class derived from QStyledItemDelegate. I have my own paint method to render the item. That part works. I created a QTimeLine object and set it up to create events to redraw the items.
I can't figure out how to trigger redraws of the QListView item.
In the item delegate constructor:
timeLine = new QTimeLine( 3000, this );
timeLine->setFrameRange( 100, 0 );
connect( timeLine, SIGNAL( frameChanged( int ) ), this, SLOT( update() ) );
timeLine->start();
I tried connecting to the sizehintChanged event but this does not work
void myDelegate::update()
{
const QModelIndex index;
emit QStyledItemDelegate::sizeHintChanged( index );
}
Any suggestions? Can this be done with style sheets?
The standard practice to include animations into code is to use state machines.
Animations in Qt cannot be achieved using QtStylesheets. Either use QML or use QStyledItemDelegate and a state machine.
/*CustomItemDelegate*/
int state;
enum states{
animating,
normal
}
void setstate(int state){
this->state = state;
/*Start animation depending on state ,by starting a QTimer and calling
repaint when the timer expires,also change animation variables like opacity ,
angle etc etc*/
}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const{
switch(state){
case animating:
break;
case normal;
break;
}
}
....
/*CustomListView*/
slots:
void dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight ){
( (CustomItemDelegate)itemDelegate(topleft) )->setState(animating);
}
....
/*Mainwindow*/
connect(model,SIGNAL(datachanged(QModelIndex,QModelindex)),view,SLOTS(QModelindex,QModelindex));