File indexing completed on 2024-04-28 16:44:26
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2000-2008 David Faure <faure@kde.org> 0003 SPDX-FileCopyrightText: 2008 Urs Wolfer <uwolfer @ kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 // Own 0009 #include "filetypesview.h" 0010 #include "mimetypewriter.h" 0011 0012 // Qt 0013 #include <QHBoxLayout> 0014 #include <QMimeDatabase> 0015 #include <QPushButton> 0016 #include <QStandardPaths> 0017 #include <QVBoxLayout> 0018 #include <qdbusconnection.h> 0019 #include <qdbusmessage.h> 0020 #include <qdebug.h> 0021 0022 // KDE 0023 #include <kbuildsycocaprogressdialog.h> 0024 #include <klineedit.h> 0025 #include <klocalizedstring.h> 0026 #include <kpluginfactory.h> 0027 #include <kservicetypeprofile.h> 0028 0029 #include <ksycoca.h> 0030 0031 // Local 0032 #include "filegroupdetails.h" 0033 #include "filetypedetails.h" 0034 #include "newtypedlg.h" 0035 0036 K_PLUGIN_CLASS_WITH_JSON(FileTypesView, "kcm_filetypes.json") 0037 0038 FileTypesView::FileTypesView(QWidget *parent, const QVariantList &) 0039 : KCModule(parent) 0040 { 0041 m_fileTypesConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); 0042 0043 setQuickHelp( 0044 i18n("<p><h1>File Associations</h1>" 0045 " This module allows you to choose which applications are associated" 0046 " with a given type of file. File types are also referred to as MIME types" 0047 " (MIME is an acronym which stands for \"Multipurpose Internet Mail" 0048 " Extensions\").</p><p> A file association consists of the following:" 0049 " <ul><li>Rules for determining the MIME-type of a file, for example" 0050 " the filename pattern *.png, which means 'all files with names that end" 0051 " in .png', is associated with the MIME type \"image/png\";</li>" 0052 " <li>A short description of the MIME-type, for example the description" 0053 " of the MIME type \"image/png\" is simply 'PNG image';</li>" 0054 " <li>An icon to be used for displaying files of the given MIME-type," 0055 " so that you can easily identify the type of file in a file" 0056 " manager or file-selection dialog (at least for the types you use often);</li>" 0057 " <li>A list of the applications which can be used to open files of the" 0058 " given MIME-type -- if more than one application can be used then the" 0059 " list is ordered by priority.</li></ul>" 0060 " You may be surprised to find that some MIME types have no associated" 0061 " filename patterns; in these cases, KDE is able to determine the" 0062 " MIME-type by directly examining the contents of the file.</p>")); 0063 0064 setButtons(Help | Apply); 0065 QString wtstr; 0066 0067 QHBoxLayout *l = new QHBoxLayout(this); 0068 QVBoxLayout *leftLayout = new QVBoxLayout(); 0069 l->addLayout(leftLayout); 0070 0071 patternFilterLE = new KLineEdit(this); 0072 patternFilterLE->setClearButtonEnabled(true); 0073 patternFilterLE->setTrapReturnKey(true); 0074 patternFilterLE->setPlaceholderText(i18n("Search for file type or filename pattern...")); 0075 leftLayout->addWidget(patternFilterLE); 0076 0077 connect(patternFilterLE, &QLineEdit::textChanged, this, &FileTypesView::slotFilter); 0078 0079 wtstr = i18n( 0080 "Enter a part of a filename pattern, and only file types with a " 0081 "matching file pattern will appear in the list. Alternatively, enter " 0082 "a part of a file type name as it appears in the list."); 0083 0084 patternFilterLE->setWhatsThis(wtstr); 0085 0086 typesLV = new TypesListTreeWidget(this); 0087 0088 typesLV->setHeaderLabel(i18n("Known Types")); 0089 leftLayout->addWidget(typesLV); 0090 connect(typesLV, &QTreeWidget::currentItemChanged, this, &FileTypesView::updateDisplay); 0091 connect(typesLV, &QTreeWidget::itemDoubleClicked, this, &FileTypesView::slotDoubleClicked); 0092 0093 typesLV->setWhatsThis( 0094 i18n("Here you can see a hierarchical list of" 0095 " the file types which are known on your system. Click on the '+' sign" 0096 " to expand a category, or the '-' sign to collapse it. Select a file type" 0097 " (e.g. text/html for HTML files) to view/edit the information for that" 0098 " file type using the controls on the right.")); 0099 0100 QHBoxLayout *btnsLay = new QHBoxLayout(); 0101 leftLayout->addLayout(btnsLay); 0102 btnsLay->addStretch(1); 0103 QPushButton *addTypeB = new QPushButton(i18n("Add..."), this); 0104 addTypeB->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); 0105 connect(addTypeB, &QAbstractButton::clicked, this, &FileTypesView::addType); 0106 btnsLay->addWidget(addTypeB); 0107 0108 addTypeB->setWhatsThis(i18n("Click here to add a new file type.")); 0109 0110 m_removeTypeB = new QPushButton(i18n("&Remove"), this); 0111 m_removeTypeB->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); 0112 connect(m_removeTypeB, &QAbstractButton::clicked, this, &FileTypesView::removeType); 0113 btnsLay->addWidget(m_removeTypeB); 0114 m_removeTypeB->setEnabled(false); 0115 m_removeButtonSaysRevert = false; 0116 0117 // For the right panel, prepare a widget stack 0118 m_widgetStack = new QStackedWidget(this); 0119 0120 l->addWidget(m_widgetStack); 0121 0122 // File Type Details 0123 m_details = new FileTypeDetails(m_widgetStack); 0124 connect(m_details, &FileTypeDetails::changed, this, &FileTypesView::setDirty); 0125 connect(m_details, &FileTypeDetails::embedMajor, this, &FileTypesView::slotEmbedMajor); 0126 m_widgetStack->insertWidget(1, m_details /*id*/); 0127 0128 // File Group Details 0129 m_groupDetails = new FileGroupDetails(m_widgetStack); 0130 connect(m_groupDetails, &FileGroupDetails::changed, this, &FileTypesView::setDirty); 0131 m_widgetStack->insertWidget(2, m_groupDetails /*id*/); 0132 0133 // Widget shown on startup 0134 m_emptyWidget = new QLabel(i18n("Select a file type by name or by extension"), m_widgetStack); 0135 m_emptyWidget->setAlignment(Qt::AlignCenter); 0136 m_widgetStack->insertWidget(3, m_emptyWidget); 0137 0138 m_widgetStack->setCurrentWidget(m_emptyWidget); 0139 0140 connect(KSycoca::self(), qOverload<>(&KSycoca::databaseChanged), this, &FileTypesView::slotDatabaseChanged); 0141 } 0142 0143 FileTypesView::~FileTypesView() 0144 { 0145 } 0146 0147 void FileTypesView::setDirty(bool state) 0148 { 0149 Q_EMIT changed(state); 0150 m_dirty = state; 0151 } 0152 0153 // To order the mimetype list 0154 static bool mimeTypeLessThan(const QMimeType &m1, const QMimeType &m2) 0155 { 0156 return m1.name() < m2.name(); 0157 } 0158 0159 // Note that this method loses any newly-added (and not saved yet) mimetypes. 0160 // So this is really only for load(). 0161 void FileTypesView::readFileTypes() 0162 { 0163 typesLV->clear(); 0164 m_majorMap.clear(); 0165 m_itemList.clear(); 0166 0167 QMimeDatabase db; 0168 QList<QMimeType> mimetypes = db.allMimeTypes(); 0169 std::sort(mimetypes.begin(), mimetypes.end(), mimeTypeLessThan); 0170 auto it2(mimetypes.constBegin()); 0171 for (; it2 != mimetypes.constEnd(); ++it2) { 0172 const QString mimetype = (*it2).name(); 0173 const int index = mimetype.indexOf(QLatin1Char('/')); 0174 const QString maj = mimetype.left(index); 0175 0176 TypesListItem *groupItem = m_majorMap.value(maj); 0177 if (!groupItem) { 0178 groupItem = new TypesListItem(typesLV, maj); 0179 m_majorMap.insert(maj, groupItem); 0180 } 0181 0182 TypesListItem *item = new TypesListItem(groupItem, (*it2)); 0183 m_itemList.append(item); 0184 } 0185 updateDisplay(nullptr); 0186 } 0187 0188 void FileTypesView::slotEmbedMajor(const QString &major, bool &embed) 0189 { 0190 TypesListItem *groupItem = m_majorMap.value(major); 0191 if (!groupItem) { 0192 return; 0193 } 0194 0195 embed = (groupItem->mimeTypeData().autoEmbed() == MimeTypeData::Yes); 0196 } 0197 0198 void FileTypesView::slotFilter(const QString &patternFilter) 0199 { 0200 for (int i = 0; i < typesLV->topLevelItemCount(); ++i) { 0201 typesLV->topLevelItem(i)->setHidden(true); 0202 } 0203 0204 // insert all items and their group that match the filter 0205 for (TypesListItem *it : qAsConst(m_itemList)) { 0206 const MimeTypeData &mimeTypeData = it->mimeTypeData(); 0207 if (patternFilter.isEmpty() || mimeTypeData.matchesFilter(patternFilter)) { 0208 TypesListItem *group = m_majorMap.value(mimeTypeData.majorType()); 0209 Q_ASSERT(group); 0210 if (group) { 0211 group->setHidden(false); 0212 it->setHidden(false); 0213 } 0214 } else { 0215 it->setHidden(true); 0216 } 0217 } 0218 } 0219 0220 void FileTypesView::addType() 0221 { 0222 const QStringList allGroups = m_majorMap.keys(); 0223 0224 NewTypeDialog dialog(allGroups, this); 0225 0226 if (dialog.exec()) { 0227 const QString newMimeType = dialog.group() + QLatin1Char('/') + dialog.text(); 0228 0229 TypesListItem *group = m_majorMap.value(dialog.group()); 0230 if (!group) { 0231 group = new TypesListItem(typesLV, dialog.group()); 0232 m_majorMap.insert(dialog.group(), group); 0233 } 0234 0235 // find out if our group has been filtered out -> insert if necessary 0236 QTreeWidgetItem *item = typesLV->topLevelItem(0); 0237 bool insert = true; 0238 while (item) { 0239 if (item == group) { 0240 insert = false; 0241 break; 0242 } 0243 item = typesLV->itemBelow(item); 0244 } 0245 if (insert) { 0246 typesLV->addTopLevelItem(group); 0247 } 0248 0249 TypesListItem *tli = new TypesListItem(group, newMimeType); 0250 m_itemList.append(tli); 0251 0252 group->setExpanded(true); 0253 tli->setSelected(true); 0254 0255 setDirty(true); 0256 } 0257 } 0258 0259 void FileTypesView::removeType() 0260 { 0261 TypesListItem *current = static_cast<TypesListItem *>(typesLV->currentItem()); 0262 0263 if (!current) { 0264 return; 0265 } 0266 0267 const MimeTypeData &mimeTypeData = current->mimeTypeData(); 0268 0269 // Can't delete groups nor essential mimetypes (but the button should be 0270 // disabled already in these cases, so this is just extra safety). 0271 if (mimeTypeData.isMeta() || mimeTypeData.isEssential()) { 0272 return; 0273 } 0274 0275 if (!mimeTypeData.isNew()) { 0276 removedList.append(mimeTypeData.name()); 0277 } 0278 if (m_removeButtonSaysRevert) { 0279 // Nothing else to do for now, until saving 0280 updateDisplay(current); 0281 } else { 0282 QTreeWidgetItem *li = typesLV->itemAbove(current); 0283 if (!li) { 0284 li = typesLV->itemBelow(current); 0285 } 0286 if (!li) { 0287 li = current->parent(); 0288 } 0289 0290 current->parent()->takeChild(current->parent()->indexOfChild(current)); 0291 m_itemList.removeAll(current); 0292 if (li) { 0293 li->setSelected(true); 0294 } 0295 } 0296 setDirty(true); 0297 } 0298 0299 void FileTypesView::slotDoubleClicked(QTreeWidgetItem *item) 0300 { 0301 if (!item) { 0302 return; 0303 } 0304 item->setExpanded(!item->isExpanded()); 0305 } 0306 0307 void FileTypesView::updateDisplay(QTreeWidgetItem *item) 0308 { 0309 TypesListItem *tlitem = static_cast<TypesListItem *>(item); 0310 updateRemoveButton(tlitem); 0311 0312 if (!item) { 0313 m_widgetStack->setCurrentWidget(m_emptyWidget); 0314 return; 0315 } 0316 0317 const bool wasDirty = m_dirty; 0318 0319 MimeTypeData &mimeTypeData = tlitem->mimeTypeData(); 0320 0321 if (mimeTypeData.isMeta()) { // is a group 0322 m_widgetStack->setCurrentWidget(m_groupDetails); 0323 m_groupDetails->setMimeTypeData(&mimeTypeData); 0324 } else { 0325 m_widgetStack->setCurrentWidget(m_details); 0326 m_details->setMimeTypeData(&mimeTypeData); 0327 } 0328 0329 // Updating the display indirectly called change(true) 0330 if (!wasDirty) { 0331 setDirty(false); 0332 } 0333 } 0334 0335 void FileTypesView::updateRemoveButton(TypesListItem *tlitem) 0336 { 0337 bool canRemove = false; 0338 m_removeButtonSaysRevert = false; 0339 0340 if (tlitem) { 0341 const MimeTypeData &mimeTypeData = tlitem->mimeTypeData(); 0342 if (!mimeTypeData.isMeta() && !mimeTypeData.isEssential()) { 0343 if (mimeTypeData.isNew()) { 0344 canRemove = true; 0345 } else { 0346 // We can only remove mimetypes that we defined ourselves, not those from freedesktop.org 0347 const QString mimeType = mimeTypeData.name(); 0348 qDebug() << mimeType << "hasDefinitionFile:" << MimeTypeWriter::hasDefinitionFile(mimeType); 0349 if (MimeTypeWriter::hasDefinitionFile(mimeType)) { 0350 canRemove = true; 0351 0352 // Is there a global definition for it? 0353 const QStringList mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, // 0354 QLatin1String("mime/") + mimeType + QStringLiteral(".xml")); 0355 qDebug() << mimeFiles; 0356 if (mimeFiles.count() >= 2 /*a local and a global*/) { 0357 m_removeButtonSaysRevert = true; 0358 qDebug() << removedList; 0359 if (removedList.contains(mimeType)) { 0360 canRemove = false; // already on the "to be reverted" list, user needs to save now 0361 } 0362 } 0363 } 0364 } 0365 } 0366 } 0367 0368 if (m_removeButtonSaysRevert) { 0369 m_removeTypeB->setText(i18n("&Revert")); 0370 m_removeTypeB->setToolTip(i18n("Revert this file type to its initial system-wide definition")); 0371 m_removeTypeB->setWhatsThis( 0372 i18n("Click here to revert this file type to its initial system-wide definition, which undoes any changes made to the file type. Note that " 0373 "system-wide file types cannot be deleted. You can however empty their pattern list, to " 0374 "minimize the chances of them being used (but the file type determination from file contents can still end up using them).")); 0375 } else { 0376 m_removeTypeB->setText(i18n("&Remove")); 0377 m_removeTypeB->setToolTip(i18n("Delete this file type definition completely")); 0378 m_removeTypeB->setWhatsThis( 0379 i18n("Click here to delete this file type definition completely. This is only possible for user-defined file types. System-wide file types cannot " 0380 "be deleted. You can however empty their pattern list, to minimize the chances of " 0381 "them being used (but the file type determination from file contents can still end up using them).")); 0382 } 0383 0384 m_removeTypeB->setEnabled(canRemove); 0385 } 0386 0387 void FileTypesView::save() 0388 { 0389 bool needUpdateMimeDb = false; 0390 bool needUpdateSycoca = false; 0391 bool didIt = false; 0392 // first, remove those items which we are asked to remove. 0393 for (const QString &mime : qAsConst(removedList)) { 0394 MimeTypeWriter::removeOwnMimeType(mime); 0395 didIt = true; 0396 needUpdateMimeDb = true; 0397 needUpdateSycoca = true; // remove offers for this mimetype 0398 } 0399 removedList.clear(); 0400 0401 // now go through all entries and sync those which are dirty. 0402 // don't use typesLV, it may be filtered 0403 for (auto it = m_majorMap.cbegin(); it != m_majorMap.cend(); ++it) { 0404 TypesListItem *tli = it.value(); 0405 if (tli->mimeTypeData().isDirty()) { 0406 qDebug() << "Entry " << tli->name() << " is dirty. Saving."; 0407 if (tli->mimeTypeData().sync()) { 0408 needUpdateMimeDb = true; 0409 } 0410 didIt = true; 0411 } 0412 } 0413 0414 for (TypesListItem *tli : qAsConst(m_itemList)) { 0415 if (tli->mimeTypeData().isDirty()) { 0416 if (tli->mimeTypeData().isServiceListDirty()) { 0417 needUpdateSycoca = true; 0418 } 0419 qDebug() << "Entry " << tli->name() << " is dirty. Saving."; 0420 if (tli->mimeTypeData().sync()) { 0421 needUpdateMimeDb = true; 0422 } 0423 didIt = true; 0424 } 0425 } 0426 0427 m_fileTypesConfig->sync(); 0428 0429 setDirty(false); 0430 0431 if (needUpdateMimeDb) { 0432 MimeTypeWriter::runUpdateMimeDatabase(); 0433 } 0434 if (needUpdateSycoca) { 0435 KBuildSycocaProgressDialog::rebuildKSycoca(this); 0436 } 0437 0438 if (didIt) { // TODO make more specific: only if autoEmbed changed? Well, maybe this is useful for icon and glob changes too... 0439 // Trigger reparseConfiguration of filetypesrc in konqueror 0440 // TODO: the same for dolphin. Or we should probably define a global signal for this. 0441 // Or a KGlobalSettings thing. 0442 QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KonqMain"), // 0443 QStringLiteral("org.kde.Konqueror.Main"), 0444 QStringLiteral("reparseConfiguration")); 0445 QDBusConnection::sessionBus().send(message); 0446 } 0447 0448 updateDisplay(typesLV->currentItem()); 0449 } 0450 0451 void FileTypesView::load() 0452 { 0453 setEnabled(false); 0454 setCursor(Qt::WaitCursor); 0455 0456 readFileTypes(); 0457 0458 unsetCursor(); 0459 setDirty(false); 0460 setEnabled(true); 0461 } 0462 0463 void FileTypesView::slotDatabaseChanged() 0464 { 0465 m_details->refresh(); 0466 0467 // ksycoca has new KMimeTypes objects for us, make sure to update 0468 // our 'copies' to be in sync with it. Not important for OK, but 0469 // important for Apply (how to differentiate those 2?). 0470 // See BR 35071. 0471 0472 for (TypesListItem *tli : qAsConst(m_itemList)) { 0473 tli->mimeTypeData().refresh(); 0474 } 0475 } 0476 0477 void FileTypesView::defaults() 0478 { 0479 } 0480 0481 #include "filetypesview.moc"