1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2008-08-21
7  * Description : a combo box with a width not depending of text
8  *               content size
9  *
10  * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C)      2008 by Andi Clemens <andi dot clemens at googlemail dot com>
12  * Copyright (C)      2005 by Tom Albers <tomalbers at kde dot nl>
13  *
14  * This program is free software; you can redistribute it
15  * and/or modify it under the terms of the GNU General
16  * Public License as published by the Free Software Foundation;
17  * either version 2, or (at your option)
18  * any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * ============================================================ */
26 
27 #include "squeezedcombobox.h"
28 
29 // Qt includes
30 
31 #include <QComboBox>
32 #include <QTimer>
33 #include <QStyle>
34 #include <QApplication>
35 #include <QResizeEvent>
36 
37 namespace Digikam
38 {
39 
40 class Q_DECL_HIDDEN SqueezedComboBox::Private
41 {
42 public:
43 
Private()44     explicit Private()
45       : timer(nullptr)
46     {
47     }
48 
49     QMap<int, QString> originalItems;
50 
51     QTimer*            timer;
52 };
53 
SqueezedComboBox(QWidget * const parent,const char * name)54 SqueezedComboBox::SqueezedComboBox(QWidget* const parent, const char* name)
55     : QComboBox(parent),
56       d        (new Private)
57 {
58     setObjectName(QString::fromUtf8(name));
59     setMinimumWidth(100);
60 
61     d->timer = new QTimer(this);
62     d->timer->setSingleShot(true);
63 
64     connect(d->timer, &QTimer::timeout,
65             this, &SqueezedComboBox::slotTimeOut);
66 
67     connect(this, static_cast<void (SqueezedComboBox::*)(int)>(&SqueezedComboBox::activated),
68             this, &SqueezedComboBox::slotUpdateToolTip);
69 }
70 
~SqueezedComboBox()71 SqueezedComboBox::~SqueezedComboBox()
72 {
73     d->originalItems.clear();
74     delete d->timer;
75     delete d;
76 }
77 
contains(const QString & text) const78 bool SqueezedComboBox::contains(const QString& text) const
79 {
80     if (text.isEmpty())
81     {
82         return false;
83     }
84 
85     for (QMap<int, QString>::const_iterator it = d->originalItems.constBegin() ;
86          it != d->originalItems.constEnd() ; ++it)
87     {
88         if (it.value() == text)
89         {
90             return true;
91         }
92     }
93 
94     return false;
95 }
96 
findOriginalText(const QString & text,Qt::CaseSensitivity cs) const97 int SqueezedComboBox::findOriginalText(const QString& text,
98                                        Qt::CaseSensitivity cs) const
99 {
100     if (text.isEmpty())
101     {
102         return -1;
103     }
104 
105     for (QMap<int, QString>::const_iterator it = d->originalItems.constBegin() ;
106          it != d->originalItems.constEnd() ; ++it)
107     {
108         if (it.value().compare(text, cs) == 0)
109         {
110             return it.key();
111         }
112     }
113 
114     return -1;
115 }
116 
sizeHint() const117 QSize SqueezedComboBox::sizeHint() const
118 {
119     ensurePolished();
120     QFontMetrics fm = fontMetrics();
121 
122 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
123 
124     int maxW        = count() ? 18 : 7 * fm.horizontalAdvance(QLatin1Char('x')) + 18;
125 
126 #else
127 
128     int maxW        = count() ? 18 : 7 * fm.width(QLatin1Char('x')) + 18;
129 
130 #endif
131 
132     int maxH        = qMax(fm.lineSpacing(), 14) + 2;
133 
134     QStyleOptionComboBox options;
135     options.initFrom(this);
136 
137     return style()->sizeFromContents(QStyle::CT_ComboBox, &options,
138                                      QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut());
139 }
140 
insertSqueezedItem(const QString & newItem,int index,const QVariant & userData)141 void SqueezedComboBox::insertSqueezedItem(const QString& newItem, int index,
142                                           const QVariant& userData)
143 {
144     d->originalItems[index] = newItem;
145     QComboBox::insertItem(index, squeezeText(newItem), userData);
146 
147     // if this is the first item, set the tooltip.
148 
149     if (index == 0)
150     {
151         slotUpdateToolTip(0);
152     }
153 }
154 
insertSqueezedList(const QStringList & newItems,int index)155 void SqueezedComboBox::insertSqueezedList(const QStringList& newItems, int index)
156 {
157     for (QStringList::const_iterator it = newItems.constBegin() ;
158          it != newItems.constEnd() ; ++it)
159     {
160         insertSqueezedItem(*it, index);
161         ++index;
162     }
163 }
164 
addSqueezedItem(const QString & newItem,const QVariant & userData)165 void SqueezedComboBox::addSqueezedItem(const QString& newItem,
166                                        const QVariant& userData)
167 {
168     insertSqueezedItem(newItem, count(), userData);
169 }
170 
setCurrent(const QString & itemText)171 void SqueezedComboBox::setCurrent(const QString& itemText)
172 {
173     QString squeezedText = squeezeText(itemText);
174     qint32 itemIndex     = findText(squeezedText);
175 
176     if (itemIndex >= 0)
177     {
178         setCurrentIndex(itemIndex);
179     }
180 }
181 
resizeEvent(QResizeEvent *)182 void SqueezedComboBox::resizeEvent(QResizeEvent *)
183 {
184     d->timer->start(200);
185 }
186 
slotTimeOut()187 void SqueezedComboBox::slotTimeOut()
188 {
189     for (QMap<int, QString>::iterator it = d->originalItems.begin() ;
190          it != d->originalItems.end() ; ++it)
191     {
192         setItemText(it.key(), squeezeText(it.value()));
193     }
194 }
195 
squeezeText(const QString & original) const196 QString SqueezedComboBox::squeezeText(const QString& original) const
197 {
198     // not the complete widgetSize is usable. Need to compensate for that.
199 
200     int widgetSize = width() - 30;
201     QFontMetrics fm(fontMetrics());
202 
203     // If we can fit the full text, return that.
204 
205 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
206 
207     if (fm.horizontalAdvance(original) < widgetSize)
208 
209 #else
210 
211     if (fm.width(original) < widgetSize)
212 
213 #endif
214 
215     {
216         return(original);
217     }
218 
219     // We need to squeeze.
220 
221     QString sqItem = original; // prevent empty return value;
222 
223 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
224 
225     widgetSize     = widgetSize-fm.horizontalAdvance(QLatin1String("..."));
226 
227 #else
228 
229     widgetSize     = widgetSize-fm.width(QLatin1String("..."));
230 
231 #endif
232 
233     for (int i = 0 ; i != original.length() ; ++i)
234     {
235 
236 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
237 
238         if ((int)fm.horizontalAdvance(original.right(i)) > widgetSize)
239 
240 #else
241 
242         if ((int)fm.width(original.right(i)) > widgetSize)
243 
244 #endif
245 
246         {
247             sqItem = QString(original.left(i) + QLatin1String("..."));
248             break;
249         }
250     }
251 
252     return sqItem;
253 }
254 
slotUpdateToolTip(int index)255 void SqueezedComboBox::slotUpdateToolTip(int index)
256 {
257      setToolTip(d->originalItems[index]);
258 }
259 
itemHighlighted() const260 QString SqueezedComboBox::itemHighlighted() const
261 {
262     int curItem = currentIndex();
263     return d->originalItems[curItem];
264 }
265 
item(int index) const266 QString SqueezedComboBox::item(int index) const
267 {
268     return d->originalItems[index];
269 }
270 
271 } // namespace Digikam
272