1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2009 Bastian Holst <bastianholst@gmx.de>
4 //
5 
6 // Self
7 #include "PluginItemDelegate.h"
8 
9 // Marble
10 #include "RenderPluginModel.h"
11 #include "MarbleDebug.h"
12 
13 // Qt
14 #include <QEvent>
15 #include <QSize>
16 #include <QVariant>
17 #include <QAbstractItemView>
18 #include <QMouseEvent>
19 #include <QStandardItemModel>
20 #include <QApplication>
21 #include <QPainter>
22 
23 using namespace Marble;
24 /* TRANSLATOR Marble::PluginItemDelegate */
25 
26 const QSize iconSize( 16, 16 );
27 
PluginItemDelegate(QAbstractItemView * view,QObject * parent)28 PluginItemDelegate::PluginItemDelegate( QAbstractItemView *view, QObject * parent )
29     : QAbstractItemDelegate( parent )
30 {
31     // Enable mouse tracking of itemview makes it possible to find when the mouse if moved
32     // without pressed buttons.
33     view->setMouseTracking( true );
34 }
35 
~PluginItemDelegate()36 PluginItemDelegate::~PluginItemDelegate()
37 {
38 }
39 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const40 void PluginItemDelegate::paint( QPainter *painter,
41                                 const QStyleOptionViewItem& option,
42                                 const QModelIndex& index ) const
43 {
44     Q_ASSERT( index.isValid() );
45     QRect rect = option.rect;
46     QStyle *style = QApplication::style();
47 
48     painter->save();
49 
50     // Drawing the background
51     QStyleOption background = option;
52     style->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter );
53 
54     painter->translate( rect.topLeft() );
55 
56     // rect is now represented in item coordinates
57     rect.moveTopLeft( QPoint( 0, 0 ) );
58     // The point at the top left of the available drawing area.
59     QPoint topLeft( 0, 0 );
60     // The point at the top right of the available drawing area.
61     QPoint topRight( rect.topRight() );
62 
63     QRect nameRect = rect;
64 
65     // Painting the checkbox
66     QStyleOptionButton checkBox = checkboxOption( option, index, topLeft.x(), Qt::AlignLeft );
67     painter->save();
68     style->drawControl( QStyle::CE_CheckBox, &checkBox, painter );
69     painter->restore();
70 
71     nameRect.setLeft( checkBox.rect.right() + 1 );
72 
73     // Painting the About Button
74     QStyleOptionButton button = buttonOption( option, index, PluginItemDelegate::About,
75                                               topRight.x(), Qt::AlignRight );
76     style->drawControl( QStyle::CE_PushButton, &button, painter );
77     topRight -= QPoint( button.rect.width(), 0 );
78 
79     // Painting the Configure Button
80     if ( index.data( RenderPluginModel::ConfigurationDialogAvailable ).toBool() ) {
81         QStyleOptionButton button = buttonOption( option, index, PluginItemDelegate::Configure,
82                                                   topRight.x(), Qt::AlignRight );
83         style->drawControl( QStyle::CE_PushButton, &button, painter );
84         topRight -= QPoint( button.rect.width(), 0 );
85 
86         nameRect.setRight( button.rect.left() -1 );
87     }
88 
89     // Painting the Icon
90     const QIcon icon = index.data( Qt::DecorationRole ).value<QIcon>();
91     const QPixmap iconPixmap = icon.pixmap(16, 16);
92 
93     nameRect.moveBottom( nameRect.bottom()+5 );
94     style->drawItemPixmap( painter,
95                            nameRect,
96                            Qt::AlignLeft,
97                            iconPixmap );
98 
99     nameRect.setLeft( nameRect.left() + 16 + 5 );
100     nameRect.moveBottom( nameRect.bottom()-5 );
101 
102     // Painting the Name string
103     QString name = index.data( Qt::DisplayRole ).toString();
104 
105     style->drawItemText( painter,
106                          nameRect,
107                          Qt::AlignLeft | Qt::AlignVCenter,
108                          option.palette,
109                          true,
110                          name );
111 
112     painter->restore();
113 }
114 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const115 QSize PluginItemDelegate::sizeHint( const QStyleOptionViewItem& option,
116                                     const QModelIndex & index ) const
117 {
118     QSize size;
119 
120     QStyleOptionViewItem opt = option;
121     opt.rect = QRect( 0, 0, 0, 0 );
122     QVector<QSize> elementSize;
123     elementSize.reserve(4);
124     QStyleOptionButton checkBox = checkboxOption( opt, index );
125     elementSize.append( checkBox.rect.size() );
126     QStyleOptionButton aboutButton = buttonOption( opt, index, PluginItemDelegate::About );
127     elementSize.append( aboutButton.rect.size() );
128     QStyleOptionButton configButton = buttonOption( opt, index, PluginItemDelegate::Configure );
129     elementSize.append( configButton.rect.size() );
130     elementSize.append( nameSize( index ) );
131 
132     for( const QSize& buttonSize: elementSize ) {
133         if( buttonSize.height() > size.height() )
134             size.setHeight( buttonSize.height() );
135         size.setWidth( size.width() + buttonSize.width() );
136     }
137 
138     return size;
139 }
140 
setAboutIcon(const QIcon & icon)141 void PluginItemDelegate::setAboutIcon( const QIcon& icon )
142 {
143     m_aboutIcon = icon;
144 }
145 
setConfigIcon(const QIcon & icon)146 void PluginItemDelegate::setConfigIcon( const QIcon& icon )
147 {
148     m_configIcon = icon;
149 }
150 
editorEvent(QEvent * event,QAbstractItemModel * model,const QStyleOptionViewItem & option,const QModelIndex & index)151 bool PluginItemDelegate::editorEvent( QEvent *event,
152                                       QAbstractItemModel *model,
153                                       const QStyleOptionViewItem &option,
154                                       const QModelIndex &index )
155 {
156     Q_ASSERT(event);
157     Q_ASSERT(model);
158 
159     if ( ( event->type() == QEvent::MouseButtonRelease )
160          || ( event->type() == QEvent::MouseButtonDblClick )
161          || ( event->type() == QEvent::MouseButtonPress )
162          || ( event->type() == QEvent::MouseMove ) )
163     {
164         QMouseEvent *me = static_cast<QMouseEvent*>(event);
165         QPoint mousePosition = me->pos() - option.rect.topLeft();
166 
167         if ( ( event->type() == QEvent::MouseMove )
168              && !( me->buttons() & Qt::LeftButton ) )
169         {
170             // If the mouse moves around and no left button is pressed, no pushbutton is pressed
171             // and no other event will be successful.
172             m_aboutPressedIndex = QModelIndex();
173             m_configPressedIndex = QModelIndex();
174             return true;
175         }
176 
177         // Handle checkbox
178         QRect checkRect = checkboxOption( option, index, 0, Qt::AlignLeft ).rect;
179         if ( checkRect.contains( mousePosition )
180              && ( ( event->type() == QEvent::MouseButtonDblClick )
181                    || ( event->type() == QEvent::MouseButtonRelease ) ) )
182         {
183             // make sure that the item is checkable
184             Qt::ItemFlags flags = model->flags(index);
185             if ( !( flags & Qt::ItemIsUserCheckable ) || !( option.state & QStyle::State_Enabled )
186                 || !( flags & Qt::ItemIsEnabled ) )
187                 return false;
188 
189             // make sure that we have a check state
190             QVariant checkValue = index.data( Qt::CheckStateRole );
191             if ( !checkValue.isValid() )
192                 return false;
193 
194             // eat the double click events inside the check rect
195             if ( event->type() == QEvent::MouseButtonDblClick )
196                 return true;
197 
198             Qt::CheckState state = ( static_cast<Qt::CheckState>( checkValue.toInt() ) == Qt::Checked
199                                      ? Qt::Unchecked : Qt::Checked );
200             return model->setData(index, state, Qt::CheckStateRole);
201         }
202 
203         if ( ( event->type() == QEvent::MouseMove )
204              && !( me->buttons() & Qt::LeftButton ) )
205         {
206             m_aboutPressedIndex = QModelIndex();
207             m_configPressedIndex = QModelIndex();
208             return true;
209         }
210 
211         QPoint topRight = option.rect.topRight();
212 
213         // Handle aboutButton
214         {
215             QRect aboutRect = buttonOption( option,
216                                             index,
217                                             PluginItemDelegate::About,
218                                             topRight.x(),
219                                             Qt::AlignRight ).rect;
220             if ( aboutRect.contains( mousePosition ) ) {
221                 if ( event->type() == QEvent::MouseButtonDblClick )
222                     return true;
223                 if ( event->type() == QEvent::MouseButtonPress ) {
224                     m_aboutPressedIndex = index;
225                     m_configPressedIndex = QModelIndex();
226                     return true;
227                 }
228                 if ( event->type() == QEvent::MouseButtonRelease ) {
229                     m_aboutPressedIndex = QModelIndex();
230                     m_configPressedIndex = QModelIndex();
231                     emit aboutPluginClicked( index );
232                     return true;
233                 }
234                 if ( event->type() == QEvent::MouseMove ) {
235                     if ( me->buttons() & Qt::LeftButton ) {
236                         m_aboutPressedIndex = index;
237                         m_configPressedIndex = QModelIndex();
238                         return true;
239                     }
240                     else {
241                         m_aboutPressedIndex = QModelIndex();
242                         m_configPressedIndex = QModelIndex();
243                         return true;
244                     }
245                 }
246             }
247             else {
248                 // If the mouse is on the item and the mouse isn't above the button.
249                 // no about button is pressed.
250                 m_aboutPressedIndex = QModelIndex();
251             }
252             topRight -= QPoint( aboutRect.width(), 0 );
253         }
254 
255         // Handle configButton
256         // make sure we have config button
257         if ( index.data( RenderPluginModel::ConfigurationDialogAvailable ).toBool() ) {
258             QRect configRect = buttonOption( option,
259                                              index,
260                                              PluginItemDelegate::Configure,
261                                              topRight.x(),
262                                              Qt::AlignRight ).rect;
263             if( configRect.contains( mousePosition ) ) {
264                 if ( event->type() == QEvent::MouseButtonDblClick )
265                     return true;
266 
267                 if ( event->type() == QEvent::MouseButtonPress ) {
268                     m_aboutPressedIndex = QModelIndex();
269                     m_configPressedIndex = index;
270                     return true;
271                 }
272                 if ( event->type() == QEvent::MouseButtonRelease ) {
273                     m_aboutPressedIndex = QModelIndex();
274                     m_configPressedIndex = QModelIndex();
275                     emit configPluginClicked( index );
276                     return true;
277                 }
278                 if ( event->type() == QEvent::MouseMove ) {
279                     if ( me->buttons() & Qt::LeftButton ) {
280                         m_aboutPressedIndex = QModelIndex();
281                         m_configPressedIndex = index;
282                         return true;
283                     }
284                     else {
285                         m_aboutPressedIndex = QModelIndex();
286                         m_configPressedIndex = QModelIndex();
287                         return true;
288                     }
289                 }
290             }
291             else {
292                 // If the mouse is on the item and the mouse isn't above the button.
293                 // no config button is pressed.
294                 m_configPressedIndex = QModelIndex();
295             }
296 
297             topRight -= QPoint( configRect.width(), 0 );
298         }
299         else {
300             // If we don't have an config dialog shown and the mouse is over this item,
301             // no config button is pressed.
302             m_configPressedIndex = QModelIndex();
303         }
304     }
305 
306     return false;
307 }
308 
checkboxOption(const QStyleOptionViewItem & option,const QModelIndex & index,int position,Qt::AlignmentFlag alignment)309 QStyleOptionButton PluginItemDelegate::checkboxOption( const QStyleOptionViewItem& option,
310                                                        const QModelIndex& index,
311                                                        int position,
312                                                        Qt::AlignmentFlag alignment )
313 {
314     QStyleOptionButton styleOptionButton;
315     if ( index.data( Qt::CheckStateRole ).toBool() )
316         styleOptionButton.state = option.state | QStyle::State_On;
317     else
318         styleOptionButton.state = option.state | QStyle::State_Off;
319     QSize size = QApplication::style()->sizeFromContents( QStyle::CT_CheckBox, &option, QSize() );
320     if ( size.isEmpty() ) {
321         // A checkbox has definitely a size != 0
322         styleOptionButton.rect.setSize( QSize( 22, 22 ) );
323     }
324     else {
325         styleOptionButton.rect.setSize( QSize( size.width(), size.height() ) );
326     }
327     styleOptionButton.rect = alignRect( styleOptionButton.rect, option.rect, position, alignment );
328     return styleOptionButton;
329 }
330 
buttonOption(const QStyleOptionViewItem & option,const QModelIndex & index,PluginItemDelegate::ButtonType type,int position,Qt::AlignmentFlag alignment) const331 QStyleOptionButton PluginItemDelegate::buttonOption( const QStyleOptionViewItem& option,
332                                                      const QModelIndex& index,
333                                                      PluginItemDelegate::ButtonType type,
334                                                      int position,
335                                                      Qt::AlignmentFlag alignment ) const
336 {
337     QStyleOptionButton buttonOption;
338     buttonOption.state = option.state;
339     buttonOption.state &= ~QStyle::State_HasFocus;
340 
341     buttonOption.rect.setTopLeft( QPoint( 0, 0 ) );
342     buttonOption.palette = option.palette;
343     buttonOption.features = QStyleOptionButton::None;
344 
345     QSize contentSize;
346     if ( type == PluginItemDelegate::About ) {
347         if ( m_aboutIcon.isNull() ) {
348             buttonOption.text = tr( "About" );
349             contentSize = buttonOption.fontMetrics.size( 0, buttonOption.text ) + QSize( 4, 4 );
350         }
351         else {
352             buttonOption.icon = m_aboutIcon;
353             buttonOption.iconSize = iconSize;
354             contentSize = iconSize;
355         }
356 
357         if ( m_aboutPressedIndex == index ) {
358             buttonOption.state |= QStyle::State_Sunken;
359         }
360     }
361     else if ( type == PluginItemDelegate::Configure ) {
362         if ( m_configIcon.isNull() ) {
363             buttonOption.text = tr( "Configure" );
364             contentSize = buttonOption.fontMetrics.size( 0, buttonOption.text ) + QSize( 4, 4 );
365         }
366         else {
367             buttonOption.icon = m_configIcon;
368             buttonOption.iconSize = iconSize;
369             contentSize = iconSize;
370         }
371         if ( m_configPressedIndex == index ) {
372             buttonOption.state |= QStyle::State_Sunken;
373         }
374     }
375 
376     QSize buttonSize = QApplication::style()->sizeFromContents( QStyle::CT_PushButton,
377                                                                 &buttonOption,
378                                                                 contentSize );
379     buttonOption.rect.setSize( buttonSize );
380     buttonOption.rect = alignRect( buttonOption.rect, option.rect, position, alignment );
381     return buttonOption;
382 }
383 
nameSize(const QModelIndex & index)384 QSize PluginItemDelegate::nameSize( const QModelIndex& index )
385 {
386     QString name = index.data( Qt::DisplayRole ).toString();
387     // FIXME: QApplication::fontMetrics() doesn't work for non-application fonts
388     QSize nameSize( QApplication::fontMetrics().size( 0, name ) );
389     return nameSize;
390 }
391 
alignRect(const QRect & object,const QRect & frame,int position,Qt::AlignmentFlag alignment)392 QRect PluginItemDelegate::alignRect( const QRect& object,
393                                      const QRect& frame,
394                                      int position,
395                                      Qt::AlignmentFlag alignment )
396 {
397     QRect rect = object;
398 
399     rect.setTopLeft( QPoint( 0, 0 ) );
400     // Moves the object to the middle of the item.
401     if ( rect.height() < frame.height() ) {
402         rect.moveTop( ( frame.height() - rect.height() ) / 2 );
403     }
404 
405     if ( alignment & Qt::AlignLeft ) {
406         rect.moveLeft( position );
407     }
408     else if ( alignment & Qt::AlignRight ) {
409         rect.moveRight( position );
410     }
411 
412     return rect;
413 }
414 
415 
416 #include "moc_PluginItemDelegate.cpp"
417