KWidgetsAddons

kmimetypechooser.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2001-2004 Anders Lund <[email protected]>
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 <QStandardPaths>
19 #include <QStandardItemModel>
20 #include <QTreeView>
21 #include <QVBoxLayout>
22 
23 //BEGIN KMimeTypeChooserPrivate
24 class KMimeTypeChooserPrivate
25 {
26 public:
27  KMimeTypeChooserPrivate(KMimeTypeChooser *parent)
28  : q(parent)
29  {
30  }
31 
32  void loadMimeTypes(const QStringList &selected = QStringList());
33  QVector<const QStandardItem *> getCheckedItems();
34 
35  void _k_editMimeType();
36  void _k_slotCurrentChanged(const QModelIndex &index);
37  void _k_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 
52 static const char s_keditfiletypeExecutable[] = "keditfiletype5";
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));
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]() { d->_k_editMimeType(); });
131  connect(d->mimeTypeTree, &QAbstractItemView::doubleClicked, this, [this]() { d->_k_editMimeType(); });
132 
133  connect(d->mimeTypeTree, &QTreeView::activated,
134  this, [this](const QModelIndex &index) { d->_k_slotCurrentChanged(index); });
135 
136  d->btnEditMimeType->setToolTip(tr("Launch the MIME type editor", "@info:tooltip"));
137 
138  vboxLayout->addLayout(buttonLayout);
139  }
140  setLayout(vboxLayout);
141 }
142 
143 KMimeTypeChooser::~KMimeTypeChooser()
144 {
145  delete d;
146 }
147 
148 void KMimeTypeChooserPrivate::loadMimeTypes(const QStringList &_selectedMimeTypes)
149 {
150  QStringList selMimeTypes;
151 
152  if (!_selectedMimeTypes.isEmpty()) {
153  selMimeTypes = _selectedMimeTypes;
154  } else {
155  selMimeTypes = q->mimeTypes();
156  }
157 
159  QMimeDatabase db;
160  const QList<QMimeType> mimetypes = db.allMimeTypes();
161 
162  bool agroupisopen = false;
163  QStandardItem *idefault = nullptr; //open this, if all other fails
164  QStandardItem *firstChecked = nullptr; // make this one visible after the loop
165 
166  for (const QMimeType &mt : mimetypes) {
167  const QString mimetype = mt.name();
168  const int index = mimetype.indexOf(QLatin1Char('/'));
169  // e.g. "text", "audio", "inode"
170  const QString maj = mimetype.left(index);
171 
172  if (!groups.isEmpty() && !groups.contains(maj)) {
173  continue;
174  }
175 
176  QStandardItem *groupItem;
177  QMap<QString, QStandardItem *>::Iterator mit = groupItems.find(maj);
178  if (mit == groupItems.end()) {
179  groupItem = new QStandardItem(maj);
180  groupItem->setFlags(Qt::ItemIsEnabled);
181  // a dud item to fill the patterns column next to "groupItem" and setFlags() on it
182  QStandardItem *secondColumn = new QStandardItem();
183  secondColumn->setFlags(Qt::NoItemFlags);
184  QStandardItem *thirdColumn = new QStandardItem();
185  thirdColumn->setFlags(Qt::NoItemFlags);
186  m_model->appendRow({groupItem, secondColumn, thirdColumn});
187  groupItems.insert(maj, groupItem);
188  if (maj == defaultgroup) {
189  idefault = groupItem;
190  }
191  } else {
192  groupItem = mit.value();
193  }
194 
195  // e.g. "html", "plain", "mp4"
196  const QString min = mimetype.mid(index + 1);
197  QStandardItem *mime = new QStandardItem(QIcon::fromTheme(mt.iconName()), min);
199 
200  QStandardItem *comments = nullptr;
201  if (visuals & KMimeTypeChooser::Comments) {
202  comments = new QStandardItem(mt.comment());
204  }
205 
206  QStandardItem *patterns = nullptr;
207 
208  if (visuals & KMimeTypeChooser::Patterns) {
209  patterns = new QStandardItem(mt.globPatterns().join(QLatin1String("; ")));
211  }
212 
213  groupItem->appendRow(QList<QStandardItem *>({mime, comments, patterns}));
214 
215  if (selMimeTypes.contains(mimetype)) {
216  mime->setCheckState(Qt::Checked);
217  const QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(groupItem));
218  mimeTypeTree->expand(index);
219  agroupisopen = true;
220  if (!firstChecked) {
221  firstChecked = mime;
222  }
223  } else {
225  }
226  }
227 
228  m_model->sort(0);
229 
230  if (firstChecked) {
231  const QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(firstChecked));
232  mimeTypeTree->scrollTo(index);
233  }
234 
235  if (!agroupisopen && idefault) {
236  const QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(idefault));
237  mimeTypeTree->expand(index);
238  mimeTypeTree->scrollTo(index);
239  }
240 }
241 
242 void KMimeTypeChooserPrivate::_k_editMimeType()
243 {
244  QModelIndex mimeIndex = m_proxyModel->mapToSource(mimeTypeTree->currentIndex());
245 
246  // skip parent (non-leaf) nodes
247  if (m_model->hasChildren(mimeIndex)) {
248  return;
249  }
250 
251  if (mimeIndex.column() > 0) { // we need the item from column 0 to concatenate "mt" below
252  mimeIndex = m_model->sibling(mimeIndex.row(), 0, mimeIndex);
253  }
254 
255  const QStandardItem *item = m_model->itemFromIndex(mimeIndex);
256  const QString mt = (item->parent())->text() + QLatin1Char('/') + item->text();
258 
259  // KF5 TODO: use a QFileSystemWatcher on one of the shared-mime-info generated files, instead.
260  //q->connect( KSycoca::self(), SIGNAL(databaseChanged(QStringList)),
261  // q, SLOT(_k_slotSycocaDatabaseChanged(QStringList)) );
262 #pragma message("KF5 TODO: use QFileSystemWatcher to be told when keditfiletype changed a MIME type")
263  // or a better idea: a QMimeDatabaseWatcher class in Qt itself
264 
265 }
266 
267 void KMimeTypeChooserPrivate::_k_slotCurrentChanged(const QModelIndex &index)
268 {
269  if (btnEditMimeType) {
270  const QModelIndex srcIndex = m_proxyModel->mapToSource(index);
271  const QStandardItem *currentItem = m_model->itemFromIndex(srcIndex);
272  btnEditMimeType->setEnabled(currentItem && currentItem->parent());
273  }
274 }
275 
276 // TODO: see _k_editMimeType
277 void KMimeTypeChooserPrivate::_k_slotSycocaDatabaseChanged(const QStringList &changedResources)
278 {
279  if (changedResources.contains(QLatin1String("xdgdata-mime"))) {
280  loadMimeTypes();
281  }
282 }
283 
284 QVector<const QStandardItem *> KMimeTypeChooserPrivate::getCheckedItems()
285 {
287  const int rowCount = m_model->rowCount();
288  for (int i = 0; i < rowCount; ++i) {
289  const QStandardItem *groupItem = m_model->item(i);
290  const int childCount = groupItem->rowCount();
291  for (int j = 0; j < childCount; ++j) {
292  const QStandardItem *child = groupItem->child(j);
293  if (child->checkState() == Qt::Checked) {
294  lst.append(child);
295  }
296  }
297  }
298  return lst;
299 }
300 
302 {
303  QStringList mimeList;
304  const QVector<const QStandardItem *> checkedItems = d->getCheckedItems();
305  mimeList.reserve(checkedItems.size());
306  for (const QStandardItem *item : checkedItems) {
307  mimeList.append(item->parent()->text() + QLatin1Char('/') + item->text());
308  }
309  return mimeList;
310 }
311 
313 {
314  QStringList patternList;
315  const QVector<const QStandardItem *> checkedItems = d->getCheckedItems();
316  QMimeDatabase db;
317  for (const QStandardItem *item : checkedItems) {
318  QMimeType mime = db.mimeTypeForName(item->parent()->text() + QLatin1Char('/') + item->text());
319  Q_ASSERT(mime.isValid());
320  patternList += mime.globPatterns();
321  }
322  return patternList;
323 }
324 //END
325 
326 //BEGIN KMimeTypeChooserDialog::Private
327 
328 class Q_DECL_HIDDEN KMimeTypeChooserDialog::Private
329 {
330 public:
331  Private(KMimeTypeChooserDialog *parent)
332  : q(parent)
333  {
334  }
335 
336  void init();
337 
338  KMimeTypeChooserDialog *q;
339  KMimeTypeChooser *m_chooser;
340 };
341 
342 //END
343 
344 //BEGIN KMimeTypeChooserDialog
346  const QString &title,
347  const QString &text,
348  const QStringList &selMimeTypes,
349  const QString &defaultGroup,
350  const QStringList &groupsToShow,
351  int visuals,
352  QWidget *parent)
353  : QDialog(parent), d(new Private(this))
354 {
355  setWindowTitle(title);
356 
357  d->m_chooser = new KMimeTypeChooser(text, selMimeTypes,
358  defaultGroup, groupsToShow, visuals,
359  this);
360  d->init();
361 }
362 
364  const QString &title,
365  const QString &text,
366  const QStringList &selMimeTypes,
367  const QString &defaultGroup,
368  QWidget *parent)
369  : QDialog(parent), d(new Private(this))
370 {
371  setWindowTitle(title);
372 
373  d->m_chooser = new KMimeTypeChooser(text, selMimeTypes,
374  defaultGroup, QStringList(),
376  this);
377  d->init();
378 }
379 
381 {
382  return d->m_chooser;
383 }
384 
385 void KMimeTypeChooserDialog::Private::init()
386 {
388  q->setLayout(layout);
389 
390  layout->addWidget(m_chooser);
391 
392  QDialogButtonBox *buttonBox = new QDialogButtonBox(q);
396  layout->addWidget(buttonBox);
397 }
398 
399 KMimeTypeChooserDialog::~KMimeTypeChooserDialog()
400 {
401  delete d;
402 }
403 
404 QSize 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"
QLayout * layout() const const
void doubleClicked(const QModelIndex &index)
KWIDGETSADDONS_EXPORT void editMimeType(const QString &mimeType, QWidget *widget)
Starts the file types editor for a given MIME type.
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void setContentsMargins(int left, int top, int right, int bottom)
virtual void reject()
void append(const T &value)
void reserve(int alloc)
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString findExecutable(const QString &executableName, const QStringList &paths)
void textChanged(const QString &text)
Show the MIME type comment (e.g. "HTML Document") in a column.
Show the MIME types glob patterns (e.g. "*.html;*.htm") in a column.
QString tr(const char *sourceText, const char *disambiguation, int n)
QString text() const const
Qt::CheckState checkState() const const
void setBuddy(QWidget *buddy)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void append(const T &value)
void setLayout(QLayout *layout)
void appendRow(const QList< QStandardItem * > &items)
void setFlags(Qt::ItemFlags flags)
bool isEmpty() const const
bool isEmpty() const const
QStandardItem * parent() const const
void clicked(bool checked)
int row() const const
QList< QMimeType > allMimeTypes() const const
QMap::iterator end()
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
virtual void accept()
QStandardItem * child(int row, int column) const const
void setEnabled(bool enabled)
bool isValid() const const
QCA_EXPORT void init()
QStringList mimeTypes() const
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.
void activated(const QModelIndex &index)
Show the "Edit" button, allowing to edit the selected type.
QString mid(int position, int n) const const
QFontMetrics fontMetrics() const const
void setStandardButtons(QDialogButtonBox::StandardButtons buttons)
void addStretch(int stretch)
QModelIndex sibling(int row, int column) const const
void setWindowTitle(const QString &)
int column() const const
QString left(int n) const const
KMimeTypeChooser * chooser()
QString fromLatin1(const char *str, int size)
int rowCount() const const
QIcon fromTheme(const QString &name)
QMap::iterator insert(const Key &key, const T &value)
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.
QStringList patterns() const
void setCheckState(Qt::CheckState state)
int averageCharWidth() const const
This widget provides a checkable list of all available MIME types, presented as a treeview...
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
int size() const const
QMap::iterator find(const Key &key)
void addLayout(QLayout *layout, int stretch)
const T value(const Key &key, const T &defaultValue) const const
ItemIsEnabled
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Nov 27 2020 22:45:44 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.