KWidgetsAddons

kmimetypechooser.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2001-2004 Anders Lund <anders@alweb.dk>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "kmimetypechooser.h"
9
10#include "kmimetypeeditor.h"
11#include <qmimedatabase.h>
12
13#include <QDialogButtonBox>
14#include <QLabel>
15#include <QLineEdit>
16#include <QPushButton>
17#include <QSortFilterProxyModel>
18#include <QStandardItemModel>
19#include <QStandardPaths>
20#include <QTreeView>
21#include <QVBoxLayout>
22
23// BEGIN KMimeTypeChooserPrivate
24class KMimeTypeChooserPrivate
25{
26public:
27 KMimeTypeChooserPrivate(KMimeTypeChooser *parent)
28 : q(parent)
29 {
30 }
31
32 void loadMimeTypes(const QStringList &selected = QStringList());
33 QList<const QStandardItem *> getCheckedItems();
34
35 void editMimeType();
36 void slotCurrentChanged(const QModelIndex &index);
37 void slotSycocaDatabaseChanged(const QStringList &);
38
39 KMimeTypeChooser *const q;
40 QTreeView *mimeTypeTree = nullptr;
41 QStandardItemModel *m_model = nullptr;
42 QSortFilterProxyModel *m_proxyModel = nullptr;
43 QLineEdit *m_filterLineEdit = nullptr;
44 QPushButton *btnEditMimeType = nullptr;
45
46 QString defaultgroup;
47 QStringList groups;
48 int visuals;
49};
50// END
51
52static const char s_keditfiletypeExecutable[] = "keditfiletype";
53
54// BEGIN KMimeTypeChooser
56 const QStringList &selMimeTypes,
57 const QString &defaultGroup,
58 const QStringList &groupsToShow,
59 int visuals,
60 QWidget *parent)
61 : QWidget(parent)
62 , d(new KMimeTypeChooserPrivate(this))
63{
64 d->defaultgroup = defaultGroup;
65 d->groups = groupsToShow;
66 if (visuals & EditButton) {
67 if (QStandardPaths::findExecutable(QString::fromLatin1(s_keditfiletypeExecutable)).isEmpty()) {
68 visuals &= ~EditButton;
69 }
70 }
71 d->visuals = visuals;
72
73 QVBoxLayout *vboxLayout = new QVBoxLayout(this);
74 vboxLayout->setContentsMargins(0, 0, 0, 0);
75 if (!text.isEmpty()) {
76 vboxLayout->addWidget(new QLabel(text, this));
77 }
78
79 d->mimeTypeTree = new QTreeView(this);
80 d->m_model = new QStandardItemModel(d->mimeTypeTree);
81 d->m_proxyModel = new QSortFilterProxyModel(d->mimeTypeTree);
82 d->m_proxyModel->setRecursiveFilteringEnabled(true);
83 d->m_proxyModel->setFilterKeyColumn(-1);
84 d->m_proxyModel->setSourceModel(d->m_model);
85 d->mimeTypeTree->setModel(d->m_proxyModel);
86
87 d->m_filterLineEdit = new QLineEdit(this);
88 d->m_filterLineEdit->setPlaceholderText(tr("Search for file type or filename pattern…", "@info:placeholder"));
89 QLabel *filterLabel = new QLabel(tr("&Filter:", "@label:textbox"));
90 filterLabel->setBuddy(d->m_filterLineEdit);
91 connect(d->m_filterLineEdit, &QLineEdit::textChanged, this, [this](const QString &text) {
92 d->m_proxyModel->setFilterRegularExpression(
93 QRegularExpression(text, QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption));
94 });
95
96 QHBoxLayout *filterLayout = new QHBoxLayout();
97 filterLayout->addWidget(filterLabel);
98 filterLayout->addWidget(d->m_filterLineEdit);
99 vboxLayout->addLayout(filterLayout);
100 d->m_filterLineEdit->setFocus();
101
102 vboxLayout->addWidget(d->mimeTypeTree);
103 QStringList headerLabels({tr("MIME Type", "@title:column")});
104
105 if (visuals & Comments) {
106 headerLabels.append(tr("Comment", "@title:column"));
107 }
108
109 if (visuals & Patterns) {
110 headerLabels.append(tr("Patterns", "@title:column"));
111 }
112
113 d->m_model->setColumnCount(headerLabels.count());
114 d->m_model->setHorizontalHeaderLabels(headerLabels);
115 QFontMetrics fm(d->mimeTypeTree->fontMetrics());
116 // big enough for most names/comments, but not for the insanely long ones
117 const int optWidth = 20 * fm.averageCharWidth();
118 d->mimeTypeTree->setColumnWidth(0, optWidth);
119 d->mimeTypeTree->setColumnWidth(1, optWidth);
120
121 d->loadMimeTypes(selMimeTypes);
122
123 if (visuals & EditButton) {
124 QHBoxLayout *buttonLayout = new QHBoxLayout();
125 buttonLayout->addStretch(1);
126 d->btnEditMimeType = new QPushButton(tr("&Edit…", "@action:button"), this);
127 buttonLayout->addWidget(d->btnEditMimeType);
128 d->btnEditMimeType->setEnabled(false);
129
130 connect(d->btnEditMimeType, &QPushButton::clicked, this, [this]() {
131 d->editMimeType();
132 });
133 connect(d->mimeTypeTree, &QAbstractItemView::doubleClicked, this, [this]() {
134 d->editMimeType();
135 });
136
137 connect(d->mimeTypeTree, &QTreeView::activated, this, [this](const QModelIndex &index) {
138 d->slotCurrentChanged(index);
139 });
140
141 d->btnEditMimeType->setToolTip(tr("Launch the MIME type editor", "@info:tooltip"));
142
143 vboxLayout->addLayout(buttonLayout);
144 }
145}
146
147KMimeTypeChooser::~KMimeTypeChooser() = default;
148
149void KMimeTypeChooserPrivate::loadMimeTypes(const QStringList &_selectedMimeTypes)
150{
151 QStringList selMimeTypes;
152
153 if (!_selectedMimeTypes.isEmpty()) {
154 selMimeTypes = _selectedMimeTypes;
155 } else {
156 selMimeTypes = q->mimeTypes();
157 }
158
159 std::vector<QStandardItem *> parentGroups;
160 QMimeDatabase db;
161 const QList<QMimeType> mimetypes = db.allMimeTypes();
162
163 bool agroupisopen = false;
164 QStandardItem *idefault = nullptr; // open this, if all other fails
165 QStandardItem *firstChecked = nullptr; // make this one visible after the loop
166
167 for (const QMimeType &mt : mimetypes) {
168 const QString mimetype = mt.name();
169 const int index = mimetype.indexOf(QLatin1Char('/'));
170 // e.g. "text", "audio", "inode"
171 const QString maj = mimetype.left(index);
172
173 if (!groups.isEmpty() && !groups.contains(maj)) {
174 continue;
175 }
176
177 QStandardItem *groupItem;
178
179 auto it = std::find_if(parentGroups.cbegin(), parentGroups.cend(), [maj](const QStandardItem *item) {
180 return maj == item->text();
181 });
182
183 if (it == parentGroups.cend()) {
184 groupItem = new QStandardItem(maj);
185 groupItem->setFlags(Qt::ItemIsEnabled);
186 // a dud item to fill the patterns column next to "groupItem" and setFlags() on it
187 QStandardItem *secondColumn = new QStandardItem();
188 secondColumn->setFlags(Qt::NoItemFlags);
189 QStandardItem *thirdColumn = new QStandardItem();
190 thirdColumn->setFlags(Qt::NoItemFlags);
191 m_model->appendRow({groupItem, secondColumn, thirdColumn});
192 parentGroups.push_back(groupItem);
193 if (maj == defaultgroup) {
194 idefault = groupItem;
195 }
196 } else {
197 groupItem = *it;
198 }
199
200 // e.g. "html", "plain", "mp4"
201 const QString min = mimetype.mid(index + 1);
202 QStandardItem *mime = new QStandardItem(QIcon::fromTheme(mt.iconName()), min);
204
205 QStandardItem *comments = nullptr;
206 if (visuals & KMimeTypeChooser::Comments) {
207 comments = new QStandardItem(mt.comment());
209 }
210
211 QStandardItem *patterns = nullptr;
212
213 if (visuals & KMimeTypeChooser::Patterns) {
214 patterns = new QStandardItem(mt.globPatterns().join(QLatin1String("; ")));
216 }
217
218 groupItem->appendRow(QList<QStandardItem *>({mime, comments, patterns}));
219
220 if (selMimeTypes.contains(mimetype)) {
222 const QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(groupItem));
223 mimeTypeTree->expand(index);
224 agroupisopen = true;
225 if (!firstChecked) {
226 firstChecked = mime;
227 }
228 } else {
230 }
231 }
232
233 m_model->sort(0);
234
235 if (firstChecked) {
236 const QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(firstChecked));
237 mimeTypeTree->scrollTo(index);
238 }
239
240 if (!agroupisopen && idefault) {
241 const QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(idefault));
242 mimeTypeTree->expand(index);
243 mimeTypeTree->scrollTo(index);
244 }
245}
246
247void KMimeTypeChooserPrivate::editMimeType()
248{
249 QModelIndex mimeIndex = m_proxyModel->mapToSource(mimeTypeTree->currentIndex());
250
251 // skip parent (non-leaf) nodes
252 if (m_model->hasChildren(mimeIndex)) {
253 return;
254 }
255
256 if (mimeIndex.column() > 0) { // we need the item from column 0 to concatenate "mt" below
257 mimeIndex = m_model->sibling(mimeIndex.row(), 0, mimeIndex);
258 }
259
260 const QStandardItem *item = m_model->itemFromIndex(mimeIndex);
261 const QString mt = (item->parent())->text() + QLatin1Char('/') + item->text();
263
264 // TODO: use a QFileSystemWatcher on one of the shared-mime-info generated files, instead.
265 // q->connect( KSycoca::self(), SIGNAL(databaseChanged(QStringList)),
266 // q, SLOT(slotSycocaDatabaseChanged(QStringList)) );
267 // TODO: use QFileSystemWatcher to be told when keditfiletype changed a MIME type
268 // or a better idea: a QMimeDatabaseWatcher class in Qt itself
269}
270
271void KMimeTypeChooserPrivate::slotCurrentChanged(const QModelIndex &index)
272{
273 if (btnEditMimeType) {
274 const QModelIndex srcIndex = m_proxyModel->mapToSource(index);
275 const QStandardItem *currentItem = m_model->itemFromIndex(srcIndex);
276 btnEditMimeType->setEnabled(currentItem && currentItem->parent());
277 }
278}
279
280// TODO: see editMimeType
281void KMimeTypeChooserPrivate::slotSycocaDatabaseChanged(const QStringList &changedResources)
282{
283 if (changedResources.contains(QLatin1String("xdgdata-mime"))) {
284 loadMimeTypes();
285 }
286}
287
288QList<const QStandardItem *> KMimeTypeChooserPrivate::getCheckedItems()
289{
291 const int rowCount = m_model->rowCount();
292 for (int i = 0; i < rowCount; ++i) {
293 const QStandardItem *groupItem = m_model->item(i);
294 const int childCount = groupItem->rowCount();
295 for (int j = 0; j < childCount; ++j) {
296 const QStandardItem *child = groupItem->child(j);
297 if (child->checkState() == Qt::Checked) {
298 lst.append(child);
299 }
300 }
301 }
302 return lst;
303}
304
306{
307 QStringList mimeList;
308 const QList<const QStandardItem *> checkedItems = d->getCheckedItems();
309 mimeList.reserve(checkedItems.size());
310 for (const QStandardItem *item : checkedItems) {
311 mimeList.append(item->parent()->text() + QLatin1Char('/') + item->text());
312 }
313 return mimeList;
314}
315
317{
318 QStringList patternList;
319 const QList<const QStandardItem *> checkedItems = d->getCheckedItems();
320 QMimeDatabase db;
321 for (const QStandardItem *item : checkedItems) {
322 QMimeType mime = db.mimeTypeForName(item->parent()->text() + QLatin1Char('/') + item->text());
323 Q_ASSERT(mime.isValid());
324 patternList += mime.globPatterns();
325 }
326 return patternList;
327}
328// END
329
330// BEGIN KMimeTypeChooserDialogPrivate
331
332class KMimeTypeChooserDialogPrivate
333{
334public:
335 KMimeTypeChooserDialogPrivate(KMimeTypeChooserDialog *parent)
336 : q(parent)
337 {
338 }
339
340 void init();
341
343 KMimeTypeChooser *m_chooser;
344};
345
346// END
347
348// BEGIN KMimeTypeChooserDialog
350 const QString &text,
351 const QStringList &selMimeTypes,
352 const QString &defaultGroup,
353 const QStringList &groupsToShow,
354 int visuals,
355 QWidget *parent)
356 : QDialog(parent)
357 , d(new KMimeTypeChooserDialogPrivate(this))
358{
359 setWindowTitle(title);
360
361 d->m_chooser = new KMimeTypeChooser(text, selMimeTypes, defaultGroup, groupsToShow, visuals, this);
362 d->init();
363}
364
366 const QString &text,
367 const QStringList &selMimeTypes,
368 const QString &defaultGroup,
369 QWidget *parent)
370 : QDialog(parent)
371 , d(new KMimeTypeChooserDialogPrivate(this))
372{
373 setWindowTitle(title);
374
375 d->m_chooser = new KMimeTypeChooser(text,
376 selMimeTypes,
377 defaultGroup,
378 QStringList(),
380 this);
381 d->init();
382}
383
385{
386 return d->m_chooser;
387}
388
389void KMimeTypeChooserDialogPrivate::init()
390{
391 QVBoxLayout *layout = new QVBoxLayout(q);
392
393 layout->addWidget(m_chooser);
394
395 QDialogButtonBox *buttonBox = new QDialogButtonBox(q);
399 layout->addWidget(buttonBox);
400}
401
402KMimeTypeChooserDialog::~KMimeTypeChooserDialog() = default;
403
404QSize KMimeTypeChooserDialog::sizeHint() const
405{
407 const int viewableSize = fm.averageCharWidth() * 60;
408 return QSize(viewableSize, viewableSize);
409}
410
411// END KMimeTypeChooserDialog
412
413#include "moc_kmimetypechooser.cpp"
A dialog to select MIME types from the list of available ones on the system.
KMimeTypeChooser * chooser()
KMimeTypeChooserDialog(const QString &title=QString(), const QString &text=QString(), const QStringList &selectedMimeTypes=QStringList(), const QString &defaultGroup=QString(), const QStringList &groupsToShow=QStringList(), int visuals=KMimeTypeChooser::Comments|KMimeTypeChooser::Patterns|KMimeTypeChooser::EditButton, QWidget *parent=nullptr)
Create a KMimeTypeChooser dialog.
This widget provides a checkable list of all available MIME types, presented as a treeview,...
KMimeTypeChooser(const QString &text=QString(), const QStringList &selectedMimeTypes=QStringList(), const QString &defaultGroup=QString(), const QStringList &groupsToShow=QStringList(), int visuals=Comments|Patterns|EditButton, QWidget *parent=nullptr)
Create a new KMimeTypeChooser.
@ Patterns
Show the MIME types glob patterns (e.g. "*.html;*.htm") in a column.
@ EditButton
Show the "Edit" button, allowing to edit the selected type.
@ Comments
Show the MIME type comment (e.g. "HTML Document") in a column.
QStringList mimeTypes() const
QStringList patterns() const
KIOCORE_EXPORT MimetypeJob * mimetype(const QUrl &url, JobFlags flags=DefaultFlags)
void editMimeType(const QString &mimeType, QWidget *widget)
Starts the file types editor for a given MIME type.
KEDUVOCDOCUMENT_EXPORT QStringList comments(const QString &language=QString())
void clicked(bool checked)
virtual QModelIndex sibling(int row, int column, const QModelIndex &index) const const
void activated(const QModelIndex &index)
QModelIndex currentIndex() const const
void doubleClicked(const QModelIndex &index)
void addLayout(QLayout *layout, int stretch)
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
virtual void accept()
virtual void reject()
void setStandardButtons(StandardButtons buttons)
int averageCharWidth() const const
QIcon fromTheme(const QString &name)
void setBuddy(QWidget *buddy)
void setContentsMargins(const QMargins &margins)
void textChanged(const QString &text)
void append(QList< T > &&value)
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
QList< QMimeType > allMimeTypes() const const
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
bool isValid() const const
int column() const const
int row() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
void appendRow(QStandardItem *item)
Qt::CheckState checkState() const const
QStandardItem * child(int row, int column) const const
QStandardItem * parent() const const
int rowCount() const const
void setCheckState(Qt::CheckState state)
void setFlags(Qt::ItemFlags flags)
QString text() const const
void appendRow(QStandardItem *item)
virtual bool hasChildren(const QModelIndex &parent) const const override
QModelIndex indexFromItem(const QStandardItem *item) const const
QStandardItem * item(int row, int column) const const
QStandardItem * itemFromIndex(const QModelIndex &index) const const
virtual int rowCount(const QModelIndex &parent) const const override
virtual void sort(int column, Qt::SortOrder order) override
QString findExecutable(const QString &executableName, const QStringList &paths)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
ItemIsEnabled
void expand(const QModelIndex &index)
virtual void scrollTo(const QModelIndex &index, ScrollHint hint) override
void setEnabled(bool)
QFontMetrics fontMetrics() const const
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 19 2024 11:51:03 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.