QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskComboBox.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskComboBox.h"
7
8#include "QskLabelData.h"
9#include "QskMenu.h"
10#include "QskTextOptions.h"
11#include "QskEvent.h"
12
13#include <qpointer.h>
14#include <qquickwindow.h>
15
16QSK_SUBCONTROL( QskComboBox, Panel )
17QSK_SUBCONTROL( QskComboBox, Icon )
18QSK_SUBCONTROL( QskComboBox, Text )
19QSK_SUBCONTROL( QskComboBox, StatusIndicator )
20
21QSK_SYSTEM_STATE( QskComboBox, Pressed, QskAspect::FirstSystemState << 1 )
22QSK_SYSTEM_STATE( QskComboBox, PopupOpen, QskAspect::FirstSystemState << 2 )
23
24static inline void qskTraverseOptions( QskComboBox* comboBox, int steps )
25{
26 const auto count = comboBox->count();
27 if ( count == 0 )
28 return;
29
30 const int index = comboBox->currentIndex();
31
32 if ( index == -1 && steps < 0 )
33 steps++;
34
35 int nextIndex = ( index + steps ) % count;
36 if ( nextIndex < 0 )
37 nextIndex += count;
38
39 if ( nextIndex != index )
40 comboBox->setCurrentIndex( nextIndex );
41}
42
43static inline int qskFindOption( QskComboBox* comboBox, const QString& key )
44{
45 if ( !key.isEmpty() )
46 {
47 const int currentIndex = comboBox->currentIndex();
48 const int count = comboBox->count();
49
50 for ( int i = 1; i < count; i++ )
51 {
52 const int index = ( currentIndex + i ) % count;
53 const auto text = comboBox->textAt( index );
54
55 if ( text.startsWith( key ) )
56 return index;
57 }
58 }
59
60 return -1;
61}
62
63class QskComboBox::PrivateData
64{
65 public:
66 QPointer < QskMenu > menu;
67
68 QVector< QskLabelData > options;
69 QString placeholderText;
70
71 int currentIndex = -1;
72};
73
74QskComboBox::QskComboBox( QQuickItem* parent )
75 : Inherited( parent )
76 , m_data( new PrivateData() )
77{
78 initSizePolicy( QskSizePolicy::Minimum, QskSizePolicy::Fixed );
79
80 setPolishOnResize( true );
81
82 setAcceptedMouseButtons( Qt::LeftButton );
83 setWheelEnabled( true );
84 setFocusPolicy( Qt::StrongFocus );
85
86 setAcceptHoverEvents( true );
87}
88
89QskComboBox::~QskComboBox()
90{
91}
92
93bool QskComboBox::isPressed() const
94{
95 return hasSkinState( Pressed );
96}
97
98void QskComboBox::setPressed( bool on )
99{
100 if ( on == isPressed() )
101 return;
102
103 setSkinStateFlag( Pressed, on );
104 Q_EMIT pressedChanged( on );
105
106 if ( on )
107 Q_EMIT pressed();
108 else
109 Q_EMIT released();
110}
111
112void QskComboBox::setPopupOpen( bool on )
113{
114 if ( on == isPopupOpen() )
115 return;
116
117#if 1
118 if ( on && window() == nullptr )
119 {
120 // We need a delayed open call to avoid this problem.TODO ...
121 qWarning() << "QskComboBox can't be opened before being added to a scene.";
122 return;
123 }
124#endif
125
126 setSkinStateFlag( PopupOpen, on );
127
128 if( on )
129 openPopup();
130 else
131 closePopup();
132}
133
134bool QskComboBox::isPopupOpen() const
135{
136 return hasSkinState( PopupOpen );
137}
138
139void QskComboBox::setTextOptions( const QskTextOptions& textOptions )
140{
141 setTextOptionsHint( Text, textOptions );
142}
143
144QskTextOptions QskComboBox::textOptions() const
145{
146 return textOptionsHint( Text );
147}
148
149int QskComboBox::addOption( const QString& graphicSource, const QString& text )
150{
151 return addOption( QskLabelData( text, QskIcon( graphicSource ) ) );
152}
153
154int QskComboBox::addOption( const QUrl& graphicSource, const QString& text )
155{
156 return addOption( QskLabelData( text, QskIcon( graphicSource ) ) );
157}
158
159int QskComboBox::addOption( const QskLabelData& option )
160{
161 m_data->options += option;
162
164 update();
165
166 if ( isComponentComplete() )
167 Q_EMIT optionsChanged();
168
169 return count() - 1;
170}
171
172void QskComboBox::setOptions( const QStringList& options )
173{
174 setOptions( qskCreateLabelData( options ) );
175}
176
177void QskComboBox::setOptions( const QVector< QskLabelData >& options )
178{
179 if ( options == m_data->options )
180 return;
181
182 m_data->options = options;
183 m_data->currentIndex = -1; // currentIndexChanged ???
184
186 update();
187
188 Q_EMIT optionsChanged();
189}
190
191QVector< QskLabelData > QskComboBox::options() const
192{
193 return m_data->options;
194}
195
196QskLabelData QskComboBox::optionAt( int index ) const
197{
198 return m_data->options.value( index );
199}
200
201QString QskComboBox::placeholderText() const
202{
203 return m_data->placeholderText;
204}
205
206void QskComboBox::setPlaceholderText( const QString& text )
207{
208 if ( m_data->placeholderText == text )
209 return;
210
211 m_data->placeholderText = text;
213
214 if ( m_data->currentIndex < 0 )
215 update();
216
217 Q_EMIT placeholderTextChanged( text );
218}
219
220QString QskComboBox::textAt( int index ) const
221{
222 return optionAt( index ).text();
223}
224
225QString QskComboBox::currentText() const
226{
227 if( m_data->currentIndex >= 0 && m_data->currentIndex < m_data->options.count() )
228 return m_data->options[ m_data->currentIndex ].text();
229
230 return m_data->placeholderText;
231}
232
233void QskComboBox::openPopup()
234{
235 /*
236 maybe we should implement an alternative implementation
237 using a QskSelectionDialog, that could be en/disabled
238 by setting a mode TODO ...
239 */
240
241 if ( m_data->menu )
242 return;
243
244 const auto cr = contentsRect();
245
246 auto menu = new QskMenu();
247
248 menu->setWrapping( false );
249 menu->setOrigin( mapToScene( cr.bottomLeft() ) );
250 menu->setFixedWidth( cr.width() );
251
252 menu->setOptions( m_data->options );
253 menu->setCurrentIndex( currentIndex() );
254
255 menu->setParent( this );
256 menu->setParentItem( window()->contentItem() );
257 menu->setPopupFlag( QskPopup::DeleteOnClose, true );
258
259 connect( menu, &QskMenu::currentIndexChanged,
260 this, &QskComboBox::indexInPopupChanged );
261
262 connect( menu, &QskMenu::triggered,
263 this, &QskComboBox::setCurrentIndex );
264
265 connect( menu, &QskMenu::closed, this,
266 [ this ]() { setPopupOpen( false ); setFocus( true ); } );
267
268 m_data->menu = menu;
269 menu->open();
270}
271
272void QskComboBox::closePopup()
273{
274 if ( m_data->menu )
275 m_data->menu->close();
276}
277
278void QskComboBox::mousePressEvent( QMouseEvent* )
279{
280 setPressed( true );
281 setPopupOpen( true );
282}
283
284void QskComboBox::mouseReleaseEvent( QMouseEvent* )
285{
286 setPressed( false );
287}
288
289void QskComboBox::keyPressEvent( QKeyEvent* event )
290{
291 if ( qskIsButtonPressKey( event ) )
292 {
293 if ( !event->isAutoRepeat() )
294 {
295 setPressed( true );
296 setPopupOpen( true );
297 return;
298 }
299 }
300
301 switch( event->key() )
302 {
303#if 0
304 case Qt::Key_F4:
305 {
306 // QComboBox does this ???
307 setPressed( true );
308 setPopupOpen( true );
309 return;
310 }
311#endif
312 case Qt::Key_Up:
313 case Qt::Key_PageUp:
314 {
315 qskTraverseOptions( this, -1 );
316 return;
317 }
318 case Qt::Key_Down:
319 case Qt::Key_PageDown:
320 {
321 qskTraverseOptions( this, 1 );
322 return;
323 }
324 case Qt::Key_Home:
325 {
326 if ( count() > 0 )
327 setCurrentIndex( 0 );
328 return;
329 }
330 case Qt::Key_End:
331 {
332 if ( count() > 0 )
333 setCurrentIndex( count() - 1 );
334 return;
335 }
336
337 default:
338 {
339 const int index = qskFindOption( this, event->text() );
340 if ( index >= 0 )
341 {
342 setCurrentIndex( index );
343 return;
344 }
345 }
346 }
347
348 Inherited::keyPressEvent( event );
349}
350
351void QskComboBox::keyReleaseEvent( QKeyEvent* event )
352{
353 Inherited::keyReleaseEvent( event );
354}
355
356void QskComboBox::wheelEvent( QWheelEvent* event )
357{
358 if ( isPopupOpen() )
359 {
360 if ( m_data->menu )
361 {
362#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
363 auto wheelEvent = new QWheelEvent(
364 event->position(), event->globalPosition(),
365 event->pixelDelta(), event->angleDelta(),
366 event->buttons(), event->modifiers(),
367 event->phase(), event->inverted(), event->source() );
368#else
369 auto wheelEvent = event->clone();
370#endif
371 QCoreApplication::postEvent( m_data->menu, wheelEvent );
372 }
373 }
374 else
375 {
376 const auto steps = -qRound( qskWheelSteps( event ) );
377 qskTraverseOptions( this, steps );
378 }
379}
380
381void QskComboBox::clear()
382{
383 if ( !m_data->options.isEmpty() )
384 {
385 m_data->options.clear();
386
387 Q_EMIT optionsChanged();
388
389 if ( m_data->currentIndex >= 0 )
390 {
391 m_data->currentIndex = -1;
392 Q_EMIT currentIndexChanged( m_data->currentIndex );
393 }
394
395 update();
396 }
397}
398
399void QskComboBox::setCurrentIndex( int index )
400{
401 if ( m_data->currentIndex != index )
402 {
403 m_data->currentIndex = index;
404 update();
405
406 Q_EMIT currentIndexChanged( index );
407 }
408}
409
410int QskComboBox::currentIndex() const
411{
412 return m_data->currentIndex;
413}
414
415int QskComboBox::count() const
416{
417 return m_data->options.count();
418}
419
420int QskComboBox::indexInPopup() const
421{
422 if ( m_data->menu )
423 return m_data->menu->currentIndex();
424
425 return -1;
426}
427
428#include "moc_QskComboBox.cpp"
Lookup key for a QskSkinHintTable.
Definition QskAspect.h:15
@ FirstSystemState
Definition QskAspect.h:115
QRectF contentsRect() const
void resetImplicitSize()
Definition QskItem.cpp:721
void setSkinStateFlag(QskAspect::State, bool on=true)