"
+ " This module allows you to choose which applications are associated"
+ " with a given type of file. File types are also referred to as MIME types"
+ " (MIME is an acronym which stands for \"Multipurpose Internet Mail"
+ " Extensions\").
A file association consists of the following:"
+ "
Rules for determining the MIME-type of a file, for example"
+ " the filename pattern *.png, which means 'all files with names that end"
+ " in .png', is associated with the MIME type \"image/png\";
"
+ "
A short description of the MIME-type, for example the description"
+ " of the MIME type \"image/png\" is simply 'PNG image';
"
+ "
An icon to be used for displaying files of the given MIME-type,"
+ " so that you can easily identify the type of file in a file"
+ " manager or file-selection dialog (at least for the types you use often);
"
+ "
A list of the applications which can be used to open files of the"
+ " given MIME-type -- if more than one application can be used then the"
+ " list is ordered by priority.
"
+ " You may be surprised to find that some MIME types have no associated"
+ " filename patterns; in these cases, KDE is able to determine the"
+ " MIME-type by directly examining the contents of the file."));
+
+ setButtons(Help | Apply);
+ QString wtstr;
+
+ QHBoxLayout *l = new QHBoxLayout(this);
+ QVBoxLayout *leftLayout = new QVBoxLayout();
+ l->addLayout(leftLayout);
+
+ patternFilterLE = new KLineEdit(this);
+ patternFilterLE->setClearButtonEnabled(true);
+ patternFilterLE->setTrapReturnKey(true);
+ patternFilterLE->setPlaceholderText(i18n("Search for file type or filename pattern..."));
+ leftLayout->addWidget(patternFilterLE);
+
+ connect(patternFilterLE, &QLineEdit::textChanged, this, &FileTypesView::slotFilter);
+
+ wtstr = i18n(
+ "Enter a part of a filename pattern, and only file types with a "
+ "matching file pattern will appear in the list. Alternatively, enter "
+ "a part of a file type name as it appears in the list.");
+
+ patternFilterLE->setWhatsThis(wtstr);
+
+ typesLV = new TypesListTreeWidget(this);
+
+ typesLV->setHeaderLabel(i18n("Known Types"));
+ leftLayout->addWidget(typesLV);
+ connect(typesLV, &QTreeWidget::currentItemChanged, this, &FileTypesView::updateDisplay);
+ connect(typesLV, &QTreeWidget::itemDoubleClicked, this, &FileTypesView::slotDoubleClicked);
+
+ typesLV->setWhatsThis(
+ i18n("Here you can see a hierarchical list of"
+ " the file types which are known on your system. Click on the '+' sign"
+ " to expand a category, or the '-' sign to collapse it. Select a file type"
+ " (e.g. text/html for HTML files) to view/edit the information for that"
+ " file type using the controls on the right."));
+
+ QHBoxLayout *btnsLay = new QHBoxLayout();
+ leftLayout->addLayout(btnsLay);
+ btnsLay->addStretch(1);
+ QPushButton *addTypeB = new QPushButton(i18n("Add..."), this);
+ addTypeB->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
+ connect(addTypeB, &QAbstractButton::clicked, this, &FileTypesView::addType);
+ btnsLay->addWidget(addTypeB);
+
+ addTypeB->setWhatsThis(i18n("Click here to add a new file type."));
+
+ m_removeTypeB = new QPushButton(i18n("&Remove"), this);
+ m_removeTypeB->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
+ connect(m_removeTypeB, &QAbstractButton::clicked, this, &FileTypesView::removeType);
+ btnsLay->addWidget(m_removeTypeB);
+ m_removeTypeB->setEnabled(false);
+ m_removeButtonSaysRevert = false;
+
+ // For the right panel, prepare a widget stack
+ m_widgetStack = new QStackedWidget(this);
+
+ l->addWidget(m_widgetStack);
+
+ // File Type Details
+ m_details = new FileTypeDetails(m_widgetStack);
+ connect(m_details, &FileTypeDetails::changed, this, &FileTypesView::setDirty);
+ connect(m_details, &FileTypeDetails::embedMajor, this, &FileTypesView::slotEmbedMajor);
+ m_widgetStack->insertWidget(1, m_details /*id*/);
+
+ // File Group Details
+ m_groupDetails = new FileGroupDetails(m_widgetStack);
+ connect(m_groupDetails, &FileGroupDetails::changed, this, &FileTypesView::setDirty);
+ m_widgetStack->insertWidget(2, m_groupDetails /*id*/);
+
+ // Widget shown on startup
+ m_emptyWidget = new QLabel(i18n("Select a file type by name or by extension"), m_widgetStack);
+ m_emptyWidget->setAlignment(Qt::AlignCenter);
+ m_widgetStack->insertWidget(3, m_emptyWidget);
+
+ m_widgetStack->setCurrentWidget(m_emptyWidget);
+
+ connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), SLOT(slotDatabaseChanged(QStringList)));
+}
+
+FileTypesView::~FileTypesView()
+{
+}
+
+void FileTypesView::setDirty(bool state)
+{
+ Q_EMIT changed(state);
+ m_dirty = state;
+}
+
+// To order the mimetype list
+static bool mimeTypeLessThan(const QMimeType &m1, const QMimeType &m2)
+{
+ return m1.name() < m2.name();
+}
+
+// Note that this method loses any newly-added (and not saved yet) mimetypes.
+// So this is really only for load().
+void FileTypesView::readFileTypes()
+{
+ typesLV->clear();
+ m_majorMap.clear();
+ m_itemList.clear();
+
+ QMimeDatabase db;
+ QList mimetypes = db.allMimeTypes();
+ std::sort(mimetypes.begin(), mimetypes.end(), mimeTypeLessThan);
+ auto it2(mimetypes.constBegin());
+ for (; it2 != mimetypes.constEnd(); ++it2) {
+ const QString mimetype = (*it2).name();
+ const int index = mimetype.indexOf(QLatin1Char('/'));
+ const QString maj = mimetype.left(index);
+
+ TypesListItem *groupItem = m_majorMap.value(maj);
+ if (!groupItem) {
+ groupItem = new TypesListItem(typesLV, maj);
+ m_majorMap.insert(maj, groupItem);
+ }
+
+ TypesListItem *item = new TypesListItem(groupItem, (*it2));
+ m_itemList.append(item);
+ }
+ updateDisplay(nullptr);
+}
+
+void FileTypesView::slotEmbedMajor(const QString &major, bool &embed)
+{
+ TypesListItem *groupItem = m_majorMap.value(major);
+ if (!groupItem) {
+ return;
+ }
+
+ embed = (groupItem->mimeTypeData().autoEmbed() == MimeTypeData::Yes);
+}
+
+void FileTypesView::slotFilter(const QString &patternFilter)
+{
+ for (int i = 0; i < typesLV->topLevelItemCount(); ++i) {
+ typesLV->topLevelItem(i)->setHidden(true);
+ }
+
+ // insert all items and their group that match the filter
+ for (TypesListItem *it : qAsConst(m_itemList)) {
+ const MimeTypeData &mimeTypeData = it->mimeTypeData();
+ if (patternFilter.isEmpty() || mimeTypeData.matchesFilter(patternFilter)) {
+ TypesListItem *group = m_majorMap.value(mimeTypeData.majorType());
+ Q_ASSERT(group);
+ if (group) {
+ group->setHidden(false);
+ it->setHidden(false);
+ }
+ } else {
+ it->setHidden(true);
+ }
+ }
+}
+
+void FileTypesView::addType()
+{
+ const QStringList allGroups = m_majorMap.keys();
+
+ NewTypeDialog dialog(allGroups, this);
+
+ if (dialog.exec()) {
+ const QString newMimeType = dialog.group() + QLatin1Char('/') + dialog.text();
+
+ TypesListItem *group = m_majorMap.value(dialog.group());
+ if (!group) {
+ group = new TypesListItem(typesLV, dialog.group());
+ m_majorMap.insert(dialog.group(), group);
+ }
+
+ // find out if our group has been filtered out -> insert if necessary
+ QTreeWidgetItem *item = typesLV->topLevelItem(0);
+ bool insert = true;
+ while (item) {
+ if (item == group) {
+ insert = false;
+ break;
+ }
+ item = typesLV->itemBelow(item);
+ }
+ if (insert) {
+ typesLV->addTopLevelItem(group);
+ }
+
+ TypesListItem *tli = new TypesListItem(group, newMimeType);
+ m_itemList.append(tli);
+
+ group->setExpanded(true);
+ tli->setSelected(true);
+
+ setDirty(true);
+ }
+}
+
+void FileTypesView::removeType()
+{
+ TypesListItem *current = static_cast(typesLV->currentItem());
+
+ if (!current) {
+ return;
+ }
+
+ const MimeTypeData &mimeTypeData = current->mimeTypeData();
+
+ // Can't delete groups nor essential mimetypes (but the button should be
+ // disabled already in these cases, so this is just extra safety).
+ if (mimeTypeData.isMeta() || mimeTypeData.isEssential()) {
+ return;
+ }
+
+ if (!mimeTypeData.isNew()) {
+ removedList.append(mimeTypeData.name());
+ }
+ if (m_removeButtonSaysRevert) {
+ // Nothing else to do for now, until saving
+ updateDisplay(current);
+ } else {
+ QTreeWidgetItem *li = typesLV->itemAbove(current);
+ if (!li) {
+ li = typesLV->itemBelow(current);
+ }
+ if (!li) {
+ li = current->parent();
+ }
+
+ current->parent()->takeChild(current->parent()->indexOfChild(current));
+ m_itemList.removeAll(current);
+ if (li) {
+ li->setSelected(true);
+ }
+ }
+ setDirty(true);
+}
+
+void FileTypesView::slotDoubleClicked(QTreeWidgetItem *item)
+{
+ if (!item) {
+ return;
+ }
+ item->setExpanded(!item->isExpanded());
+}
+
+void FileTypesView::updateDisplay(QTreeWidgetItem *item)
+{
+ TypesListItem *tlitem = static_cast(item);
+ updateRemoveButton(tlitem);
+
+ if (!item) {
+ m_widgetStack->setCurrentWidget(m_emptyWidget);
+ return;
+ }
+
+ const bool wasDirty = m_dirty;
+
+ MimeTypeData &mimeTypeData = tlitem->mimeTypeData();
+
+ if (mimeTypeData.isMeta()) { // is a group
+ m_widgetStack->setCurrentWidget(m_groupDetails);
+ m_groupDetails->setMimeTypeData(&mimeTypeData);
+ } else {
+ m_widgetStack->setCurrentWidget(m_details);
+ m_details->setMimeTypeData(&mimeTypeData);
+ }
+
+ // Updating the display indirectly called change(true)
+ if (!wasDirty) {
+ setDirty(false);
+ }
+}
+
+void FileTypesView::updateRemoveButton(TypesListItem *tlitem)
+{
+ bool canRemove = false;
+ m_removeButtonSaysRevert = false;
+
+ if (tlitem) {
+ const MimeTypeData &mimeTypeData = tlitem->mimeTypeData();
+ if (!mimeTypeData.isMeta() && !mimeTypeData.isEssential()) {
+ if (mimeTypeData.isNew()) {
+ canRemove = true;
+ } else {
+ // We can only remove mimetypes that we defined ourselves, not those from freedesktop.org
+ const QString mimeType = mimeTypeData.name();
+ qDebug() << mimeType << "hasDefinitionFile:" << MimeTypeWriter::hasDefinitionFile(mimeType);
+ if (MimeTypeWriter::hasDefinitionFile(mimeType)) {
+ canRemove = true;
+
+ // Is there a global definition for it?
+ const QStringList mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, //
+ QLatin1String("mime/") + mimeType + QStringLiteral(".xml"));
+ qDebug() << mimeFiles;
+ if (mimeFiles.count() >= 2 /*a local and a global*/) {
+ m_removeButtonSaysRevert = true;
+ qDebug() << removedList;
+ if (removedList.contains(mimeType)) {
+ canRemove = false; // already on the "to be reverted" list, user needs to save now
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (m_removeButtonSaysRevert) {
+ m_removeTypeB->setText(i18n("&Revert"));
+ m_removeTypeB->setToolTip(i18n("Revert this file type to its initial system-wide definition"));
+ m_removeTypeB->setWhatsThis(
+ 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 "
+ "system-wide file types cannot be deleted. You can however empty their pattern list, to "
+ "minimize the chances of them being used (but the file type determination from file contents can still end up using them)."));
+ } else {
+ m_removeTypeB->setText(i18n("&Remove"));
+ m_removeTypeB->setToolTip(i18n("Delete this file type definition completely"));
+ m_removeTypeB->setWhatsThis(
+ i18n("Click here to delete this file type definition completely. This is only possible for user-defined file types. System-wide file types cannot "
+ "be deleted. You can however empty their pattern list, to minimize the chances of "
+ "them being used (but the file type determination from file contents can still end up using them)."));
+ }
+
+ m_removeTypeB->setEnabled(canRemove);
+}
+
+void FileTypesView::save()
+{
+ bool needUpdateMimeDb = false;
+ bool needUpdateSycoca = false;
+ bool didIt = false;
+ // first, remove those items which we are asked to remove.
+ for (const QString &mime : qAsConst(removedList)) {
+ MimeTypeWriter::removeOwnMimeType(mime);
+ didIt = true;
+ needUpdateMimeDb = true;
+ needUpdateSycoca = true; // remove offers for this mimetype
+ }
+ removedList.clear();
+
+ // now go through all entries and sync those which are dirty.
+ // don't use typesLV, it may be filtered
+ for (auto it = m_majorMap.cbegin(); it != m_majorMap.cend(); ++it) {
+ TypesListItem *tli = it.value();
+ if (tli->mimeTypeData().isDirty()) {
+ qDebug() << "Entry " << tli->name() << " is dirty. Saving.";
+ if (tli->mimeTypeData().sync()) {
+ needUpdateMimeDb = true;
+ }
+ didIt = true;
+ }
+ }
+
+ for (TypesListItem *tli : qAsConst(m_itemList)) {
+ if (tli->mimeTypeData().isDirty()) {
+ if (tli->mimeTypeData().isServiceListDirty()) {
+ needUpdateSycoca = true;
+ }
+ qDebug() << "Entry " << tli->name() << " is dirty. Saving.";
+ if (tli->mimeTypeData().sync()) {
+ needUpdateMimeDb = true;
+ }
+ didIt = true;
+ }
+ }
+
+ m_fileTypesConfig->sync();
+
+ setDirty(false);
+
+ if (needUpdateMimeDb) {
+ MimeTypeWriter::runUpdateMimeDatabase();
+ }
+ if (needUpdateSycoca) {
+ KBuildSycocaProgressDialog::rebuildKSycoca(this);
+ }
+
+ if (didIt) { // TODO make more specific: only if autoEmbed changed? Well, maybe this is useful for icon and glob changes too...
+ // Trigger reparseConfiguration of filetypesrc in konqueror
+ // TODO: the same for dolphin. Or we should probably define a global signal for this.
+ // Or a KGlobalSettings thing.
+ QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KonqMain"), //
+ QStringLiteral("org.kde.Konqueror.Main"),
+ QStringLiteral("reparseConfiguration"));
+ QDBusConnection::sessionBus().send(message);
+ }
+
+ updateDisplay(typesLV->currentItem());
+}
+
+void FileTypesView::load()
+{
+ setEnabled(false);
+ setCursor(Qt::WaitCursor);
+
+ readFileTypes();
+
+ unsetCursor();
+ setDirty(false);
+ setEnabled(true);
+}
+
+void FileTypesView::slotDatabaseChanged(const QStringList &changedResources)
+{
+ qDebug() << changedResources;
+ if (changedResources.contains(QStringLiteral("xdgdata-mime")) // changes in mimetype definitions
+ || changedResources.contains(QStringLiteral("services"))) { // changes in .desktop files
+ m_details->refresh();
+
+ // ksycoca has new KMimeTypes objects for us, make sure to update
+ // our 'copies' to be in sync with it. Not important for OK, but
+ // important for Apply (how to differentiate those 2?).
+ // See BR 35071.
+
+ for (TypesListItem *tli : qAsConst(m_itemList)) {
+ tli->mimeTypeData().refresh();
+ }
+ }
+}
+
+void FileTypesView::defaults()
+{
+}
+
+#include "filetypesview.moc"
diff --git a/keditfiletype/filetypesview.h b/keditfiletype/filetypesview.h
new file mode 100644
index 0000000..61cfb68
--- /dev/null
+++ b/keditfiletype/filetypesview.h
@@ -0,0 +1,95 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2000-2008 David Faure
+ SPDX-FileCopyrightText: 2008 Urs Wolfer
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+*/
+
+#ifndef FILETYPESVIEW_H
+#define FILETYPESVIEW_H
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "typeslistitem.h"
+
+class QLabel;
+class QTreeWidget;
+class QTreeWidgetItem;
+class QPushButton;
+class KLineEdit;
+class FileTypeDetails;
+class FileGroupDetails;
+class QStackedWidget;
+
+class FileTypesView : public KCModule
+{
+ Q_OBJECT
+public:
+ FileTypesView(QWidget *parent, const QVariantList &args);
+ ~FileTypesView() override;
+
+ void load() override;
+ void save() override;
+ void defaults() override;
+
+protected Q_SLOTS:
+ void addType();
+ void removeType();
+ void updateDisplay(QTreeWidgetItem *);
+ void slotDoubleClicked(QTreeWidgetItem *);
+ void slotFilter(const QString &patternFilter);
+ void setDirty(bool state);
+
+ void slotDatabaseChanged(const QStringList &changedResources);
+ void slotEmbedMajor(const QString &major, bool &embed);
+
+private:
+ void readFileTypes();
+ void updateRemoveButton(TypesListItem *item);
+
+private:
+ QTreeWidget *typesLV;
+ QPushButton *m_removeTypeB;
+
+ QStackedWidget *m_widgetStack;
+ FileTypeDetails *m_details;
+ FileGroupDetails *m_groupDetails;
+ QLabel *m_emptyWidget;
+
+ KLineEdit *patternFilterLE;
+ QStringList removedList;
+ bool m_dirty;
+ bool m_removeButtonSaysRevert;
+ QMap m_majorMap; // groups
+ QList m_itemList;
+
+ KSharedConfig::Ptr m_fileTypesConfig;
+};
+
+// helper class for loading the icon on request instead of preloading lots of probably
+// unused icons which takes quite a lot of time
+class TypesListTreeWidget : public QTreeWidget
+{
+ Q_OBJECT
+public:
+ explicit TypesListTreeWidget(QWidget *parent)
+ : QTreeWidget(parent)
+ {
+ }
+
+protected:
+ void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
+ {
+ static_cast(itemFromIndex(index))->loadIcon();
+
+ QTreeWidget::drawRow(painter, option, index);
+ }
+};
+
+#endif
diff --git a/keditfiletype/kcm_filetypes.desktop b/keditfiletype/kcm_filetypes.desktop
new file mode 100644
index 0000000..05602ae
--- /dev/null
+++ b/keditfiletype/kcm_filetypes.desktop
@@ -0,0 +1,99 @@
+[Desktop Entry]
+Icon=preferences-desktop-filetype-association
+Type=Application
+NoDisplay=true
+Exec=systemsettings kcm_filetypes
+
+Name=File Associations
+Name[af]=Lêer Assosiasies
+Name[ar]=ارتباطات الملفّات
+Name[as]=নথিপত্ৰৰ সম্বন্ধ
+Name[az]=Fayl Bağlantıları
+Name[be]=Асацыяцыі файлаў
+Name[be@latin]=Poviazi dla fajłaŭ
+Name[bg]=Файлови асоциации
+Name[bn]=ফাইল অ্যাসোসিয়েশন
+Name[bn_IN]=ফাইল-অ্যাপ্লিকেশন জুটি
+Name[br]=Kevreañ restroù
+Name[bs]=Pridruženja datotekema
+Name[ca]=Associacions de fitxers
+Name[ca@valencia]=Associacions de fitxers
+Name[cs]=Asociace souborů
+Name[csb]=Pòwiązania lopków
+Name[cy]=Cysylltiadau Ffeiliau
+Name[da]=Filtilknytninger
+Name[de]=Dateizuordnungen
+Name[el]=Συσχετίσεις αρχείων
+Name[en_GB]=File Associations
+Name[eo]=Dosierasocioj
+Name[es]=Asociaciones de archivos
+Name[et]=Failide seosed
+Name[eu]=Fitxategi-elkartzeak
+Name[fa]=تداعی کاربردهای پرونده
+Name[fi]=Tiedostokytkökset
+Name[fr]=Associations de fichiers
+Name[fy]=Triemassosjaasjes
+Name[ga]=Comhcheangail Chomhaid
+Name[gl]=Asociacións de ficheiros
+Name[gu]=ફાઇલ જોડાણો
+Name[he]=שיוכי קבצים
+Name[hi]=फ़ाइल असोसिएशन
+Name[hne]=फाइल असोसिएसन
+Name[hr]=Pridruživanje datoteka
+Name[hsb]=Datajowe asociacije
+Name[hu]=Fájltársítások
+Name[ia]=Associationes de file
+Name[id]=Keterkaitan File
+Name[is]=Skráavensl
+Name[it]=Associazioni dei file
+Name[ja]=ファイルの関連付け
+Name[ka]=ფაილთა მიბმა
+Name[kk]=Файл сәйкестіктері
+Name[km]=ទំនាក់ទំនងឯកសារ
+Name[kn]=ಕಡತ ಸಾಹಚರ್ಯಗಳು
+Name[ko]=파일 연결
+Name[ku]=Têkiliyên Pelan
+Name[lt]=Failų sąsajos
+Name[lv]=Failu asociācijas
+Name[mai]=फाइल असोसिएशन
+Name[mk]=Асоцијации на датотеки
+Name[ml]=ഫയല് ബന്ധപ്പെടുത്തലുകള്
+Name[mr]=फाईल संलग्नता
+Name[nb]=Filtilknytninger
+Name[nds]=Datei-Toornen
+Name[ne]=फाइल संयोजन
+Name[nl]=Bestandsassociaties
+Name[nn]=Filtilknytingar
+Name[oc]=Associacions de fichièrs
+Name[or]=ଫାଇଲ ସଂସ୍ଥା
+Name[pa]=ਫਾਈਲ ਸਬੰਧ
+Name[pl]=Skojarzenia plików
+Name[pt]=Associação de Ficheiros
+Name[pt_BR]=Associações de arquivos
+Name[ro]=Asociere fișiere
+Name[ru]=Привязки файлов
+Name[se]=Fiilačatnagasat
+Name[si]=ගොනු සහායක
+Name[sk]=Asociácie súborov
+Name[sl]=Datotečne asociacije
+Name[sr]=Придружења фајловима
+Name[sr@ijekavian]=Придружења фајловима
+Name[sr@ijekavianlatin]=Pridruženja fajlovima
+Name[sr@latin]=Pridruženja fajlovima
+Name[sv]=Filbindningar
+Name[ta]=கோப்பு தொடர்புகள்
+Name[te]=దస్త్రపు సంభందములు
+Name[tg]=Муносибати файлҳо
+Name[th]=กำหนดการใช้แฟ้ม
+Name[tr]=Dosya İlişkilendirmeleri
+Name[ug]=ھۆججەت باغلىنىش
+Name[uk]=Прив’язка файлів
+Name[uz]=Fayl turi bilan\nbogʻliqlar
+Name[uz@cyrillic]=Файл тури билан\nбоғлиқлар
+Name[vi]=Liên kết tệp
+Name[wa]=Assoçnaedje des fitchîs
+Name[xh]=Uyelemaniso Lwefayile
+Name[x-test]=xxFile Associationsxx
+Name[zh_CN]=文件关联
+Name[zh_TW]=檔案關聯
+
diff --git a/keditfiletype/kcm_filetypes.json b/keditfiletype/kcm_filetypes.json
new file mode 100644
index 0000000..2a21255
--- /dev/null
+++ b/keditfiletype/kcm_filetypes.json
@@ -0,0 +1,193 @@
+{
+ "KPlugin": {
+ "Description": "Configure file associations",
+ "Description[ca@valencia]": "Configura les associacions de fitxers",
+ "Description[ca]": "Configura les associacions de fitxers",
+ "Description[cs]": "Nastavení asociací souborů",
+ "Description[de]": "Dateizuordnungen einrichten",
+ "Description[en_GB]": "Configure file associations",
+ "Description[es]": "Configurar las asociaciones de archivos",
+ "Description[eu]": "Konfiguratu fitxategi-elkartzeak",
+ "Description[fi]": "Tiedostokytkösasetukset",
+ "Description[fr]": "Configurer les associations de fichiers",
+ "Description[ia]": "Configura associationes de file",
+ "Description[it]": "Configura le associazioni dei file",
+ "Description[ko]": "파일 연결 설정",
+ "Description[nl]": "Hier kunt u de bestandsassociaties instellen",
+ "Description[pl]": "Ustawienia skojarzeń plików",
+ "Description[pt_BR]": "Configurar associações dos arquivos",
+ "Description[ru]": "Настройка привязки файлов",
+ "Description[sk]": "Nastavenia asociácií súborov",
+ "Description[sl]": "Nastavi datotečne asociacije",
+ "Description[sv]": "Anpassa filbindningar",
+ "Description[tr]": "Dosya ilişkilendirmelerini yapılandır",
+ "Description[uk]": "Налаштування прив’язки файлів",
+ "Description[vi]": "Cấu hình liên kết tệp",
+ "Description[x-test]": "xxConfigure file associationsxx",
+ "Description[zh_CN]": "配置文件关联",
+ "Icon": "preferences-desktop-filetype-association",
+ "Name": "File Associations",
+ "Name[af]": "Lêer Assosiasies",
+ "Name[ar]": "ارتباطات الملفّات",
+ "Name[as]": "নথিপত্ৰৰ সম্বন্ধ",
+ "Name[az]": "Fayl Bağlantıları",
+ "Name[be@latin]": "Poviazi dla fajłaŭ",
+ "Name[be]": "Асацыяцыі файлаў",
+ "Name[bg]": "Файлови асоциации",
+ "Name[bn]": "ফাইল অ্যাসোসিয়েশন",
+ "Name[bn_IN]": "ফাইল-অ্যাপ্লিকেশন জুটি",
+ "Name[br]": "Kevreañ restroù",
+ "Name[bs]": "Pridruženja datotekema",
+ "Name[ca@valencia]": "Associacions de fitxers",
+ "Name[ca]": "Associacions de fitxers",
+ "Name[cs]": "Asociace souborů",
+ "Name[csb]": "Pòwiązania lopków",
+ "Name[cy]": "Cysylltiadau Ffeiliau",
+ "Name[da]": "Filtilknytninger",
+ "Name[de]": "Dateizuordnungen",
+ "Name[el]": "Συσχετίσεις αρχείων",
+ "Name[en_GB]": "File Associations",
+ "Name[eo]": "Dosierasocioj",
+ "Name[es]": "Asociaciones de archivos",
+ "Name[et]": "Failide seosed",
+ "Name[eu]": "Fitxategi-elkartzeak",
+ "Name[fa]": "تداعی کاربردهای پرونده",
+ "Name[fi]": "Tiedostokytkökset",
+ "Name[fr]": "Associations de fichiers",
+ "Name[fy]": "Triemassosjaasjes",
+ "Name[ga]": "Comhcheangail Chomhaid",
+ "Name[gl]": "Asociacións de ficheiros",
+ "Name[gu]": "ફાઇલ જોડાણો",
+ "Name[he]": "שיוכי קבצים",
+ "Name[hi]": "फ़ाइल असोसिएशन",
+ "Name[hne]": "फाइल असोसिएसन",
+ "Name[hr]": "Pridruživanje datoteka",
+ "Name[hsb]": "Datajowe asociacije",
+ "Name[hu]": "Fájltársítások",
+ "Name[ia]": "Associationes de file",
+ "Name[id]": "Keterkaitan File",
+ "Name[is]": "Skráavensl",
+ "Name[it]": "Associazioni dei file",
+ "Name[ja]": "ファイルの関連付け",
+ "Name[ka]": "ფაილთა მიბმა",
+ "Name[kk]": "Файл сәйкестіктері",
+ "Name[km]": "ទំនាក់ទំនងឯកសារ",
+ "Name[kn]": "ಕಡತ ಸಾಹಚರ್ಯಗಳು",
+ "Name[ko]": "파일 연결",
+ "Name[ku]": "Têkiliyên Pelan",
+ "Name[lt]": "Failų sąsajos",
+ "Name[lv]": "Failu asociācijas",
+ "Name[mai]": "फाइल असोसिएशन",
+ "Name[mk]": "Асоцијации на датотеки",
+ "Name[ml]": "ഫയല് ബന്ധപ്പെടുത്തലുകള്",
+ "Name[mr]": "फाईल संलग्नता",
+ "Name[nb]": "Filtilknytninger",
+ "Name[nds]": "Datei-Toornen",
+ "Name[ne]": "फाइल संयोजन",
+ "Name[nl]": "Bestandsassociaties",
+ "Name[nn]": "Filtilknytingar",
+ "Name[oc]": "Associacions de fichièrs",
+ "Name[or]": "ଫାଇଲ ସଂସ୍ଥା",
+ "Name[pa]": "ਫਾਈਲ ਸਬੰਧ",
+ "Name[pl]": "Skojarzenia plików",
+ "Name[pt]": "Associação de Ficheiros",
+ "Name[pt_BR]": "Associações de arquivos",
+ "Name[ro]": "Asociere fișiere",
+ "Name[ru]": "Привязки файлов",
+ "Name[se]": "Fiilačatnagasat",
+ "Name[si]": "ගොනු සහායක",
+ "Name[sk]": "Asociácie súborov",
+ "Name[sl]": "Datotečne asociacije",
+ "Name[sr@ijekavian]": "Придружења фајловима",
+ "Name[sr@ijekavianlatin]": "Pridruženja fajlovima",
+ "Name[sr@latin]": "Pridruženja fajlovima",
+ "Name[sr]": "Придружења фајловима",
+ "Name[sv]": "Filbindningar",
+ "Name[ta]": "கோப்பு தொடர்புகள்",
+ "Name[te]": "దస్త్రపు సంభందములు",
+ "Name[tg]": "Муносибати файлҳо",
+ "Name[th]": "กำหนดการใช้แฟ้ม",
+ "Name[tr]": "Dosya İlişkilendirmeleri",
+ "Name[ug]": "ھۆججەت باغلىنىش",
+ "Name[uk]": "Прив’язка файлів",
+ "Name[uz@cyrillic]": "Файл тури билан\\nбоғлиқлар",
+ "Name[uz]": "Fayl turi bilan\\nbogʻliqlar",
+ "Name[vi]": "Liên kết tệp",
+ "Name[wa]": "Assoçnaedje des fitchîs",
+ "Name[x-test]": "xxFile Associationsxx",
+ "Name[xh]": "Uyelemaniso Lwefayile",
+ "Name[zh_CN]": "文件关联",
+ "Name[zh_TW]": "檔案關聯",
+ "ServiceTypes": [
+ "KCModule"
+ ]
+ },
+ "X-DocPath": "kcontrol5/filetypes/index.html",
+ "X-KDE-Keywords": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[ar]": "أنواع الملفّات,ارتباطات الملفّات,أنواع مايم,أنماط الملفّات,ملفّات,نمط",
+ "X-KDE-Keywords[az]": "Fayl növləri,Fayl bağlantıları,Mime növləri,Fayl nümunələri,Fayllar,Nümunələr",
+ "X-KDE-Keywords[bg]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,Файлове,Видове,Асоциации,Шаблони",
+ "X-KDE-Keywords[bn]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[bs]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,tipovi datoteka,pridruživanje datoteka,uzorci datoteka,uzorci",
+ "X-KDE-Keywords[ca@valencia]": "Tipus de fitxer,Associacions de fitxer,Tipus MIME,Patrons de fitxer,Fitxers,Patró",
+ "X-KDE-Keywords[ca]": "Tipus de fitxer,Associacions de fitxer,Tipus MIME,Patrons de fitxer,Fitxers,Patró",
+ "X-KDE-Keywords[cs]": "Typy souborů,Asociace souborů, Typy MIME,Vzory souborů,Soubory,Vzor",
+ "X-KDE-Keywords[da]": "filtyper,filtilknytninger,MIME-typer,filmønstre,filer,mønstre",
+ "X-KDE-Keywords[de]": "Dateitypen,Dateizuordnungen,MIME-Typen,Dateimuster,Filter,Muster,Dateien",
+ "X-KDE-Keywords[el]": "Τύποι αρχείων,συσχετίσεις αρχείων,τύποι mime,μοτίβα αρχείων,αρχεία,μοτίβο",
+ "X-KDE-Keywords[en_GB]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[eo]": "Dosieraranĝoj,Dosierasocioj,MIME-tipoj,Dosierŝablonoj,Dosieroj,Ŝablono",
+ "X-KDE-Keywords[es]": "Tipos de archivo,Asociaciones de archivos,tipos Mime,Patrones de archivo,Archivos,Patrón",
+ "X-KDE-Keywords[et]": "failitüübid,failiseosed,MIME tüübid,failimustrid,failid,muster",
+ "X-KDE-Keywords[eu]": "Fitxategi-motak,Fitxategi-elkartzeak,MIME motak,Fitxategi-ereduak,Fitxategiak,Eredua",
+ "X-KDE-Keywords[fa]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[fi]": "Tiedostotyypit,Tiedostokytkökset,MIME-tyypit,Tiedostokuviot,Tiedostot,Kuvio",
+ "X-KDE-Keywords[fr]": "Types de fichiers, associations de fichiers, modèles de fichiers, fichiers, modèle",
+ "X-KDE-Keywords[ga]": "Cineálacha,Comhcheangail,MIME,Patrúin,Patrún,Comhaid",
+ "X-KDE-Keywords[gl]": "Tipos de ficheiro, asociacións de ficheiros, tipos mime, ficheiros, padrón",
+ "X-KDE-Keywords[gu]": "ફાઇલપ્રકારો,ફાઇલ જોડાણો,માઇમ પ્રકારો,ફાઇલ ભાતો,ફાઇલો,ભાતો",
+ "X-KDE-Keywords[he]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,סוגי קבצים,שיוכי קבצים,סוגי קבצים,קבצים",
+ "X-KDE-Keywords[hi]": "फ़ाइल प्रकार, फ़ाइल संघ, माइम प्रकार, फ़ाइल पैटर्न, फ़ाइलें, पैटर्न",
+ "X-KDE-Keywords[hsb]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,mustr, dataja,asociacija",
+ "X-KDE-Keywords[hu]": "Fájltípusok,Fájltársítások,MIME-típusok,Fájlminták,Fájlok,Minta",
+ "X-KDE-Keywords[ia]": "Typos de file,Associationes de file,Typos Mime,Patronos de File,Files,Patrono",
+ "X-KDE-Keywords[id]": "Tipe File,Keterkaitan File,Tipe Mime,Pola File,File,Pola",
+ "X-KDE-Keywords[is]": "Skráagerðir,Skráavensl,Mime-tegund,Skráamynstur,Skrár,Mynstur",
+ "X-KDE-Keywords[it]": "Tipi di file,associazioni di file,tipi mime,schemi di file,file,schema",
+ "X-KDE-Keywords[ja]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[kk]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[km]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[ko]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,파일 형식,파일 연결,MIME 형식",
+ "X-KDE-Keywords[lt]": "Failų tipai,Failai,Sąsajos,Susiejimai,Failo,Mime tipai,Failo šablonai,Failai,Šablonas",
+ "X-KDE-Keywords[lv]": "Failu tipi,failu asociācijas,mime tipi,failu paraugi,failu,paraugs",
+ "X-KDE-Keywords[mr]": "फाईल प्रकार, फाईल संलग्नता, माइम प्रकार, फाईल नमूना",
+ "X-KDE-Keywords[nb]": "Filtyper,Filtilknytninger,Mimetyper,Filnavnmønstre,Filer,Mønster",
+ "X-KDE-Keywords[nds]": "Dateitypen,Dateitoornen,MIME-Typen,Dateimustern,Dateien,Muster",
+ "X-KDE-Keywords[nl]": "Bestandstypen,bestandsassociaties,Mime-typen,bestandspatronen,bestanden,patroon",
+ "X-KDE-Keywords[nn]": "Filtypar,filetternamn,etternamn,filtilknytingar,MIME-typar,filmønster,filer,mønster",
+ "X-KDE-Keywords[pa]": "ਫਾਈਲਕਿਸਮ,ਫਾਈਲ ਸਬੰਧ,ਮਾਈਮ ਕਿਸਮ,ਫਾਈਲ ਪੈਟਰਨ,ਫਾਈਲਾਂ,ਪੈਟਰਨ",
+ "X-KDE-Keywords[pl]": "typy plików,skojarzenia plików,typy mime,wzorce plików,pliki,wzorzec",
+ "X-KDE-Keywords[pt]": "Tipos de ficheiros,Associações de ficheiros,Tipos MIME,Padrões de ficheiros,Ficheiros,Padrão",
+ "X-KDE-Keywords[pt_BR]": "Tipos de arquivos,Associações de arquivos,Tipos MIME,Padrões de arquivos,Arquivos,Padrão",
+ "X-KDE-Keywords[ro]": "tipuri de fișier,asociere de fișiere,tipuri mime,fișiere,model",
+ "X-KDE-Keywords[ru]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,типы файлов,привязки файлов,типы MIME,шаблоны файлов,файлы,шаблон",
+ "X-KDE-Keywords[sk]": "Filetypes,File Associations,Mime Typy,File Patterns,Súbory,Vzory",
+ "X-KDE-Keywords[sl]": "vrste datotek,datotečne vrste,vrste mime,zvrsti mime,datotečne povezave,datoteke,vzorci,datotečni vzorci",
+ "X-KDE-Keywords[sr@ijekavian]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,тип фајла,придружења фајла,МИМЕ тип,образац фајла,образац",
+ "X-KDE-Keywords[sr@ijekavianlatin]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,tip fajla,pridruženja fajla,MIME tip,obrazac fajla,obrazac",
+ "X-KDE-Keywords[sr@latin]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,tip fajla,pridruženja fajla,MIME tip,obrazac fajla,obrazac",
+ "X-KDE-Keywords[sr]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,тип фајла,придружења фајла,МИМЕ тип,образац фајла,образац",
+ "X-KDE-Keywords[sv]": "Filtyper,Filbindningar,Mime-typer,Filmönster,Filer,Mönster",
+ "X-KDE-Keywords[tg]": "Намуди файлҳо,Муносибати файлҳо,Навъи қиёфа,Қолабҳои файл,Файлҳо,Қолабҳо",
+ "X-KDE-Keywords[tr]": "Dosya tipleri,Dosya İlişkileri, Mime Tipleri,Dosya Desenleri,Dosyalar,Desen",
+ "X-KDE-Keywords[ug]": "ھۆججەت تىپى، ھۆججەت مۇناسىۋەتلىرى، Mime تىپلىرى، ھۆججەت ئەندىزىلىرى، ھۆججەتلەر، ئەندىزە",
+ "X-KDE-Keywords[uk]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,файл,файли,типи файлів,тип,прив’язка,прив’язування,відповідність,взірці,шаблон,файли,взірець,файла",
+ "X-KDE-Keywords[vi]": "Kiểu tệp,Tệp tương ứng,Kiểu MIME,Mẫu tệp,Tệp, Mẫu,Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-Keywords[wa]": "Sôres di fitchîs,assoçnaedjes des fitchîs,assoçnaedjes di fitchîs,sôres mime,patrons di fitchîs,patron d' fitchîs,modeles di fitchîs,fitchîs,patron,modele",
+ "X-KDE-Keywords[x-test]": "xxFiletypesxx,xxFile Associationsxx,xxMime Typesxx,xxFile Patternsxx,xxFilesxx,xxPatternxx",
+ "X-KDE-Keywords[zh_CN]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern,文件类型,文件关联,后缀名,文件",
+ "X-KDE-Keywords[zh_TW]": "Filetypes,File Associations,Mime Types,File Patterns,Files,Pattern",
+ "X-KDE-ParentApp": "kcontrol",
+ "X-KDE-System-Settings-Parent-Category": "applications",
+ "X-KDE-Weight": 50
+}
diff --git a/keditfiletype/keditfiletype.cpp b/keditfiletype/keditfiletype.cpp
new file mode 100644
index 0000000..a02f412
--- /dev/null
+++ b/keditfiletype/keditfiletype.cpp
@@ -0,0 +1,203 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2000, 2007 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+// Own
+#include "keditfiletype.h"
+#include "mimetypewriter.h"
+
+// Qt
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// KDE
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+// Local
+#include "filetypedetails.h"
+#include "typeslistitem.h"
+
+FileTypeDialog::FileTypeDialog(MimeTypeData *mime)
+ : QDialog(nullptr)
+ , m_mimeTypeData(mime)
+{
+ init();
+}
+
+FileTypeDialog::~FileTypeDialog()
+{
+ delete m_details;
+}
+
+void FileTypeDialog::init()
+{
+ m_details = new FileTypeDetails(this);
+ m_details->setMimeTypeData(m_mimeTypeData);
+ connect(m_details, &FileTypeDetails::changed, this, &FileTypeDialog::clientChanged);
+
+ m_buttonBox = new QDialogButtonBox;
+ m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
+ connect(m_buttonBox, SIGNAL(accepted()), SLOT(accept()));
+ connect(m_buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &FileTypeDialog::save);
+ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ // This code is very similar to kcdialog.cpp
+ QVBoxLayout *layout = new QVBoxLayout(this);
+ layout->addWidget(m_details);
+ layout->addWidget(m_buttonBox);
+ // TODO setHelp()
+
+ setApplyButtonEnabled(false);
+
+ connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), SLOT(slotDatabaseChanged(QStringList)));
+}
+
+void FileTypeDialog::setApplyButtonEnabled(bool enabled)
+{
+ m_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(enabled);
+}
+
+void FileTypeDialog::save()
+{
+ if (m_mimeTypeData->isDirty()) {
+ const bool servicesDirty = m_mimeTypeData->isServiceListDirty();
+ if (m_mimeTypeData->sync()) {
+ MimeTypeWriter::runUpdateMimeDatabase();
+ }
+ if (servicesDirty) {
+ KBuildSycocaProgressDialog::rebuildKSycoca(this);
+ }
+ // Trigger reparseConfiguration of filetypesrc in konqueror
+ QDBusMessage message =
+ QDBusMessage::createSignal(QStringLiteral("/KonqMain"), QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("reparseConfiguration"));
+ QDBusConnection::sessionBus().send(message);
+ }
+}
+
+void FileTypeDialog::accept()
+{
+ save();
+ QDialog::accept();
+}
+
+void FileTypeDialog::clientChanged(bool state)
+{
+ m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(state);
+ m_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(state);
+}
+
+void FileTypeDialog::slotDatabaseChanged(const QStringList &changedResources)
+{
+ qDebug() << changedResources;
+ if (changedResources.contains(QStringLiteral("xdgdata-mime")) // changes in mimetype definitions
+ || changedResources.contains(QStringLiteral("services"))) { // changes in .desktop files
+ m_details->refresh();
+ }
+}
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+ app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
+ QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-filetype-association")));
+
+ KAboutData aboutData(QStringLiteral("keditfiletype"),
+ i18n("File Type Editor"),
+ QLatin1String(PROJECT_VERSION),
+ i18n("KDE file type editor - simplified version for editing a single file type"),
+ KAboutLicense::GPL,
+ i18n("(c) 2000, KDE developers"));
+ aboutData.addAuthor(i18n("Preston Brown"), QString(), QStringLiteral("pbrown@kde.org"));
+ aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org"));
+ KAboutData::setApplicationData(aboutData);
+
+ QCommandLineParser parser;
+ aboutData.setupCommandLine(&parser);
+ parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("parent"),
+ i18n("Makes the dialog transient for the window specified by winid"),
+ QStringLiteral("winid")));
+ parser.addPositionalArgument(QStringLiteral("mimetype"), i18n("File type to edit (e.g. text/html)"));
+
+ parser.process(app);
+ aboutData.processCommandLine(&parser);
+
+ if (parser.positionalArguments().count() == 0) {
+ parser.showHelp();
+ }
+
+ QMimeDatabase db;
+ const QString arg = parser.positionalArguments().at(0);
+ MimeTypeData *mimeTypeData = nullptr;
+ const bool createType = arg.startsWith(QLatin1Char('*'));
+ if (createType) {
+ QString mimeString = QStringLiteral("application/x-kdeuser%1");
+ QString mimeTypeName;
+ int inc = 0;
+ bool ok = false;
+ do {
+ ++inc;
+ mimeTypeName = mimeString.arg(inc);
+ ok = !db.mimeTypeForName(mimeTypeName).isValid();
+ } while (!ok);
+
+ QStringList patterns;
+ if (arg.length() > 2) {
+ patterns << arg.toLower() << arg.toUpper();
+ }
+ QString comment;
+ if (arg.startsWith(QLatin1String("*.")) && arg.length() >= 3) {
+ const QString type = arg.mid(3).prepend(arg[2].toUpper());
+ comment = i18n("%1 File", type);
+ }
+
+ mimeTypeData = new MimeTypeData(mimeTypeName, true); // new mimetype
+ mimeTypeData->setComment(comment);
+ mimeTypeData->setPatterns(patterns);
+ } else {
+ const QString mimeTypeName = arg;
+ QMimeType mime = db.mimeTypeForName(mimeTypeName);
+ if (!mime.isValid()) {
+ qCritical() << "Mimetype" << mimeTypeName << "not found";
+ return 1;
+ }
+
+ mimeTypeData = new MimeTypeData(mime);
+ }
+
+ FileTypeDialog dlg(mimeTypeData);
+ if (parser.isSet(QStringLiteral("parent"))) {
+ bool ok;
+ long id = parser.value(QStringLiteral("parent")).toLong(&ok);
+ if (ok) {
+ dlg.setAttribute(Qt::WA_NativeWindow, true);
+ KWindowSystem::setMainWindow(dlg.windowHandle(), WId(id));
+ }
+ }
+ if (!createType) {
+ dlg.setWindowTitle(i18n("Edit File Type %1", mimeTypeData->name()));
+ } else {
+ dlg.setWindowTitle(i18n("Create New File Type %1", mimeTypeData->name()));
+ dlg.setApplyButtonEnabled(true);
+ }
+
+ dlg.show(); // non-modal
+
+ return app.exec();
+}
diff --git a/keditfiletype/keditfiletype.h b/keditfiletype/keditfiletype.h
new file mode 100644
index 0000000..3480fed
--- /dev/null
+++ b/keditfiletype/keditfiletype.h
@@ -0,0 +1,46 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2000, 2007 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+#ifndef __keditfiletype_h
+#define __keditfiletype_h
+
+#include
+#include
+#include
+
+class QDialogButtonBox;
+
+class MimeTypeData;
+class FileTypeDetails;
+
+// A dialog for ONE file type to be edited.
+class FileTypeDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit FileTypeDialog(MimeTypeData *mime);
+ ~FileTypeDialog() override;
+
+ void setApplyButtonEnabled(bool);
+
+public Q_SLOTS:
+ void accept() override;
+
+protected Q_SLOTS:
+ void clientChanged(bool state);
+ void slotDatabaseChanged(const QStringList &changedResources);
+
+private Q_SLOTS:
+ void save();
+
+private:
+ void init();
+ FileTypeDetails *m_details;
+ MimeTypeData *m_mimeTypeData;
+ QDialogButtonBox *m_buttonBox;
+};
+
+#endif
diff --git a/keditfiletype/kservicelistwidget.cpp b/keditfiletype/kservicelistwidget.cpp
new file mode 100644
index 0000000..e227a20
--- /dev/null
+++ b/keditfiletype/kservicelistwidget.cpp
@@ -0,0 +1,403 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2003 Waldo Bastian
+ SPDX-FileCopyrightText: 2003 David Faure
+ SPDX-FileCopyrightText: 2002 Daniel Molkentin
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+// Own
+#include "kservicelistwidget.h"
+
+// Qt
+#include
+#include
+#include
+#include
+#include
+
+// KDE
+#include
+#include
+#include
+#include
+
+// Local
+#include "kserviceselectdlg.h"
+#include "mimetypedata.h"
+
+KServiceListItem::KServiceListItem(const KService::Ptr &pService, int kind)
+ : QListWidgetItem()
+ , storageId(pService->storageId())
+ , desktopPath(pService->entryPath())
+{
+ if (kind == KServiceListWidget::SERVICELIST_APPLICATIONS) {
+ setText(pService->name());
+ } else {
+ setText(i18n("%1 (%2)", pService->name(), pService->desktopEntryName()));
+ }
+
+ setIcon(QIcon::fromTheme(pService->icon()));
+
+ if (!pService->isApplication()) {
+ localPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kservices5/") + desktopPath;
+ } else {
+ localPath = pService->locateLocal();
+ }
+}
+
+KServiceListWidget::KServiceListWidget(int kind, QWidget *parent)
+ : QGroupBox(kind == SERVICELIST_APPLICATIONS ? i18n("Application Preference Order") : i18n("Services Preference Order"), parent)
+ , m_kind(kind)
+ , m_mimeTypeData(nullptr)
+{
+ QHBoxLayout *lay = new QHBoxLayout(this);
+
+ servicesLB = new QListWidget(this);
+ connect(servicesLB, &QListWidget::itemSelectionChanged, this, &KServiceListWidget::enableMoveButtons);
+ lay->addWidget(servicesLB);
+ connect(servicesLB, &QListWidget::itemDoubleClicked, this, &KServiceListWidget::editService);
+
+ QString wtstr = (kind == SERVICELIST_APPLICATIONS ? i18n("This is a list of applications associated with files of the selected"
+ " file type. This list is shown in Konqueror's context menus when you select"
+ " \"Open With...\". If more than one application is associated with this file type,"
+ " then the list is ordered by priority with the uppermost item taking precedence"
+ " over the others.")
+ : i18n("This is a list of services associated with files of the selected"
+ " file type. This list is shown in Konqueror's context menus when you select"
+ " a \"Preview with...\" option. If more than one service is associated with this file type,"
+ " then the list is ordered by priority with the uppermost item taking precedence"
+ " over the others."));
+
+ setWhatsThis(wtstr);
+ servicesLB->setWhatsThis(wtstr);
+
+ QVBoxLayout *btnsLay = new QVBoxLayout();
+ lay->addLayout(btnsLay);
+
+ servUpButton = new QPushButton(i18n("Move &Up"), this);
+ servUpButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
+ servUpButton->setEnabled(false);
+ connect(servUpButton, &QAbstractButton::clicked, this, &KServiceListWidget::promoteService);
+ btnsLay->addWidget(servUpButton);
+
+ servUpButton->setWhatsThis(kind == SERVICELIST_APPLICATIONS ? i18n("Assigns a higher priority to the selected\n"
+ "application, moving it up in the list. Note: This\n"
+ "only affects the selected application if the file type is\n"
+ "associated with more than one application.")
+ : i18n("Assigns a higher priority to the selected\n"
+ "service, moving it up in the list."));
+
+ servDownButton = new QPushButton(i18n("Move &Down"), this);
+ servDownButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
+ servDownButton->setEnabled(false);
+ connect(servDownButton, &QAbstractButton::clicked, this, &KServiceListWidget::demoteService);
+ btnsLay->addWidget(servDownButton);
+ servDownButton->setWhatsThis(kind == SERVICELIST_APPLICATIONS ? i18n("Assigns a lower priority to the selected\n"
+ "application, moving it down in the list. Note: This \n"
+ "only affects the selected application if the file type is\n"
+ "associated with more than one application.")
+ : i18n("Assigns a lower priority to the selected\n"
+ "service, moving it down in the list."));
+
+ servNewButton = new QPushButton(i18n("Add..."), this);
+ servNewButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
+ servNewButton->setEnabled(false);
+ connect(servNewButton, &QAbstractButton::clicked, this, &KServiceListWidget::addService);
+ btnsLay->addWidget(servNewButton);
+ servNewButton->setWhatsThis(i18n("Add a new application for this file type."));
+
+ servEditButton = new QPushButton(i18n("Edit..."), this);
+ servEditButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
+ servEditButton->setEnabled(false);
+ connect(servEditButton, &QAbstractButton::clicked, this, &KServiceListWidget::editService);
+ btnsLay->addWidget(servEditButton);
+ servEditButton->setWhatsThis(i18n("Edit command line of the selected application."));
+
+ servRemoveButton = new QPushButton(i18n("Remove"), this);
+ servRemoveButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
+ servRemoveButton->setEnabled(false);
+ connect(servRemoveButton, &QAbstractButton::clicked, this, &KServiceListWidget::removeService);
+ btnsLay->addWidget(servRemoveButton);
+ servRemoveButton->setWhatsThis(i18n("Remove the selected application from the list."));
+
+ btnsLay->addStretch(1);
+}
+
+void KServiceListWidget::setMimeTypeData(MimeTypeData *mimeTypeData)
+{
+ m_mimeTypeData = mimeTypeData;
+ if (servNewButton) {
+ servNewButton->setEnabled(true);
+ }
+ // will need a selection
+ servUpButton->setEnabled(false);
+ servDownButton->setEnabled(false);
+
+ servicesLB->clear();
+ servicesLB->setEnabled(false);
+
+ if (m_mimeTypeData) {
+ const QStringList services = (m_kind == SERVICELIST_APPLICATIONS) ? m_mimeTypeData->appServices() : m_mimeTypeData->embedServices();
+
+ if (services.isEmpty()) {
+ if (m_kind == SERVICELIST_APPLICATIONS) {
+ servicesLB->addItem(i18nc("No applications associated with this file type", "None"));
+ } else {
+ servicesLB->addItem(i18nc("No components associated with this file type", "None"));
+ }
+ } else {
+ for (const QString &service : services) {
+ KService::Ptr pService = KService::serviceByStorageId(service);
+ if (pService) {
+ servicesLB->addItem(new KServiceListItem(pService, m_kind));
+ }
+ }
+ servicesLB->setEnabled(true);
+ }
+ }
+
+ if (servRemoveButton) {
+ servRemoveButton->setEnabled(servicesLB->currentRow() > -1);
+ }
+ if (servEditButton) {
+ servEditButton->setEnabled(servicesLB->currentRow() > -1);
+ }
+}
+
+void KServiceListWidget::promoteService()
+{
+ if (!servicesLB->isEnabled()) {
+ return;
+ }
+
+ int selIndex = servicesLB->currentRow();
+ if (selIndex == 0) {
+ return;
+ }
+
+ QListWidgetItem *selItem = servicesLB->item(selIndex);
+ servicesLB->takeItem(selIndex);
+ servicesLB->insertItem(selIndex - 1, selItem);
+ servicesLB->setCurrentRow(selIndex - 1);
+
+ updatePreferredServices();
+
+ Q_EMIT changed(true);
+}
+
+void KServiceListWidget::demoteService()
+{
+ if (!servicesLB->isEnabled()) {
+ return;
+ }
+
+ int selIndex = servicesLB->currentRow();
+ if (selIndex == servicesLB->count() - 1) {
+ return;
+ }
+
+ QListWidgetItem *selItem = servicesLB->item(selIndex);
+ servicesLB->takeItem(selIndex);
+ servicesLB->insertItem(selIndex + 1, selItem);
+ servicesLB->setCurrentRow(selIndex + 1);
+
+ updatePreferredServices();
+
+ Q_EMIT changed(true);
+}
+
+void KServiceListWidget::addService()
+{
+ if (!m_mimeTypeData) {
+ return;
+ }
+
+ KService::Ptr service;
+ if (m_kind == SERVICELIST_APPLICATIONS) {
+ KOpenWithDialog dlg(m_mimeTypeData->name(), QString(), this);
+ dlg.setSaveNewApplications(true);
+ if (dlg.exec() != QDialog::Accepted) {
+ return;
+ }
+
+ service = dlg.service();
+
+ Q_ASSERT(service);
+ if (!service) {
+ return; // Don't crash if KOpenWith wasn't able to create service.
+ }
+ } else {
+ KServiceSelectDlg dlg(m_mimeTypeData->name(), QString(), this);
+ if (dlg.exec() != QDialog::Accepted) {
+ return;
+ }
+ service = dlg.service();
+ Q_ASSERT(service);
+ if (!service) {
+ return;
+ }
+ }
+
+ // Did the list simply show "None"?
+ const bool hadDummyEntry = (m_kind == SERVICELIST_APPLICATIONS) ? m_mimeTypeData->appServices().isEmpty() : m_mimeTypeData->embedServices().isEmpty();
+
+ if (hadDummyEntry) {
+ delete servicesLB->takeItem(0); // Remove the "None" item.
+ servicesLB->setEnabled(true);
+ } else {
+ // check if it is a duplicate entry
+ for (int index = 0; index < servicesLB->count(); index++) {
+ if (static_cast(servicesLB->item(index))->desktopPath == service->entryPath()) {
+ // ##### shouldn't we make the existing entry the default one?
+ return;
+ }
+ }
+ }
+
+ servicesLB->insertItem(0, new KServiceListItem(service, m_kind));
+ servicesLB->setCurrentItem(nullptr);
+
+ updatePreferredServices();
+
+ Q_EMIT changed(true);
+}
+
+void KServiceListWidget::editService()
+{
+ if (!m_mimeTypeData) {
+ return;
+ }
+ const int selected = servicesLB->currentRow();
+ if (selected < 0) {
+ return;
+ }
+
+ // Only edit applications, not services as
+ // they don't have any parameters
+ if (m_kind != SERVICELIST_APPLICATIONS) {
+ return;
+ }
+
+ // Just like popping up an add dialog except that we
+ // pass the current command line as a default
+ KServiceListItem *selItem = (KServiceListItem *)servicesLB->item(selected);
+ const QString desktopPath = selItem->desktopPath;
+
+ KService::Ptr service = KService::serviceByDesktopPath(desktopPath);
+ if (!service) {
+ return;
+ }
+
+ QString path = service->entryPath();
+ {
+ // If the path to the desktop file is relative, try to get the full
+ // path from QStandardPaths.
+ QString fullPath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, path);
+ if (!fullPath.isEmpty()) {
+ path = fullPath;
+ }
+ }
+
+ KFileItem item(QUrl::fromLocalFile(path), QStringLiteral("application/x-desktop"), KFileItem::Unknown);
+ KPropertiesDialog dlg(item, this);
+ if (dlg.exec() != QDialog::Accepted) {
+ return;
+ }
+
+ // Note that at this point, ksycoca has been updated,
+ // and setMimeTypeData has been called again, so all the items have been recreated.
+
+ // Reload service
+ service = KService::serviceByDesktopPath(desktopPath);
+ if (!service) {
+ return;
+ }
+
+ // Remove the old one...
+ delete servicesLB->takeItem(selected);
+
+ // ...check that it's not a duplicate entry...
+ bool addIt = true;
+ for (int index = 0; index < servicesLB->count(); index++) {
+ if (static_cast(servicesLB->item(index))->desktopPath == service->entryPath()) {
+ addIt = false;
+ break;
+ }
+ }
+
+ // ...and add it in the same place as the old one:
+ if (addIt) {
+ servicesLB->insertItem(selected, new KServiceListItem(service, m_kind));
+ servicesLB->setCurrentRow(selected);
+ }
+
+ updatePreferredServices();
+
+ Q_EMIT changed(true);
+}
+
+void KServiceListWidget::removeService()
+{
+ if (!m_mimeTypeData) {
+ return;
+ }
+
+ int selected = servicesLB->currentRow();
+
+ if (selected >= 0) {
+ delete servicesLB->takeItem(selected);
+ updatePreferredServices();
+
+ Q_EMIT changed(true);
+ }
+
+ // Update buttons and service list again (e.g. to re-add "None")
+ setMimeTypeData(m_mimeTypeData);
+}
+
+void KServiceListWidget::updatePreferredServices()
+{
+ if (!m_mimeTypeData) {
+ return;
+ }
+ QStringList sl;
+ unsigned int count = servicesLB->count();
+
+ for (unsigned int i = 0; i < count; i++) {
+ KServiceListItem *sli = (KServiceListItem *)servicesLB->item(i);
+ sl.append(sli->storageId);
+ }
+ sl.removeDuplicates();
+ if (m_kind == SERVICELIST_APPLICATIONS) {
+ m_mimeTypeData->setAppServices(sl);
+ } else {
+ m_mimeTypeData->setEmbedServices(sl);
+ }
+}
+
+void KServiceListWidget::enableMoveButtons()
+{
+ int idx = servicesLB->currentRow();
+ if (servicesLB->model()->rowCount() <= 1) {
+ servUpButton->setEnabled(false);
+ servDownButton->setEnabled(false);
+ } else if (idx == (servicesLB->model()->rowCount() - 1)) {
+ servUpButton->setEnabled(true);
+ servDownButton->setEnabled(false);
+ } else if (idx == 0) {
+ servUpButton->setEnabled(false);
+ servDownButton->setEnabled(true);
+ } else {
+ servUpButton->setEnabled(true);
+ servDownButton->setEnabled(true);
+ }
+
+ if (servRemoveButton) {
+ servRemoveButton->setEnabled(true);
+ }
+
+ if (servEditButton) {
+ servEditButton->setEnabled(m_kind == SERVICELIST_APPLICATIONS);
+ }
+}
diff --git a/keditfiletype/kservicelistwidget.h b/keditfiletype/kservicelistwidget.h
new file mode 100644
index 0000000..947ba63
--- /dev/null
+++ b/keditfiletype/kservicelistwidget.h
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2003 Waldo Bastian
+ SPDX-FileCopyrightText: 2003 David Faure
+ SPDX-FileCopyrightText: 2002 Daniel Molkentin
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+#ifndef _KSERVICELISTWIDGET_H
+#define _KSERVICELISTWIDGET_H
+
+#include
+#include
+#include
+
+class QPushButton;
+
+class MimeTypeData;
+class KService;
+
+class KServiceListItem : public QListWidgetItem
+{
+public:
+ KServiceListItem(const KService::Ptr &pService, int kind);
+ QString storageId;
+ QString desktopPath;
+ QString localPath;
+};
+
+/**
+ * This widget holds a list of services, with 5 buttons to manage it.
+ * It's a separate class so that it can be used by both tabs of the
+ * module, once for applications and once for services.
+ * The "kind" is determined by the argument given to the constructor.
+ */
+class KServiceListWidget : public QGroupBox
+{
+ Q_OBJECT
+public:
+ enum {
+ SERVICELIST_APPLICATIONS,
+ SERVICELIST_SERVICES,
+ };
+ explicit KServiceListWidget(int kind, QWidget *parent = nullptr);
+
+ void setMimeTypeData(MimeTypeData *item);
+
+Q_SIGNALS:
+ void changed(bool);
+
+protected Q_SLOTS:
+ void promoteService();
+ void demoteService();
+ void addService();
+ void editService();
+ void removeService();
+ void enableMoveButtons();
+
+protected:
+ void updatePreferredServices();
+
+private:
+ int m_kind;
+ QListWidget *servicesLB;
+ QPushButton *servUpButton, *servDownButton;
+ QPushButton *servNewButton, *servEditButton, *servRemoveButton;
+ MimeTypeData *m_mimeTypeData;
+};
+
+#endif
diff --git a/keditfiletype/kserviceselectdlg.cpp b/keditfiletype/kserviceselectdlg.cpp
new file mode 100644
index 0000000..f0a91bc
--- /dev/null
+++ b/keditfiletype/kserviceselectdlg.cpp
@@ -0,0 +1,60 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2000 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "kserviceselectdlg.h"
+#include "kservicelistwidget.h"
+
+#include
+#include
+#include
+
+#include
+
+KServiceSelectDlg::KServiceSelectDlg(const QString & /*serviceType*/, const QString & /*value*/, QWidget *parent)
+ : QDialog(parent)
+{
+ setObjectName(QLatin1String("serviceSelectDlg"));
+ setModal(true);
+ setWindowTitle(i18n("Add Service"));
+
+ QVBoxLayout *layout = new QVBoxLayout(this);
+
+ layout->addWidget(new QLabel(i18n("Select service:")));
+ m_listbox = new QListWidget();
+ m_buttonBox = new QDialogButtonBox;
+ m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+ // Can't make a KTrader query since we don't have a servicetype to give,
+ // we want all services that are not applications.......
+ // So we have to do it the slow way
+ // ### Why can't we query for KParts/ReadOnlyPart as the servicetype? Should work fine!
+ const KService::List allServices = KService::allServices();
+ for (const auto &service : allServices) {
+ if (service->hasServiceType(QStringLiteral("KParts/ReadOnlyPart"))) {
+ m_listbox->addItem(new KServiceListItem(service, KServiceListWidget::SERVICELIST_SERVICES));
+ }
+ }
+
+ m_listbox->model()->sort(0);
+ m_listbox->setMinimumHeight(350);
+ m_listbox->setMinimumWidth(400);
+ layout->addWidget(m_listbox);
+ layout->addWidget(m_buttonBox);
+ connect(m_listbox, &QListWidget::itemDoubleClicked, this, &QDialog::accept);
+ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+KServiceSelectDlg::~KServiceSelectDlg()
+{
+}
+
+KService::Ptr KServiceSelectDlg::service()
+{
+ int selIndex = m_listbox->currentRow();
+ KServiceListItem *selItem = static_cast(m_listbox->item(selIndex));
+ return KService::serviceByDesktopPath(selItem->desktopPath);
+}
diff --git a/keditfiletype/kserviceselectdlg.h b/keditfiletype/kserviceselectdlg.h
new file mode 100644
index 0000000..a5c7a3a
--- /dev/null
+++ b/keditfiletype/kserviceselectdlg.h
@@ -0,0 +1,42 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2000 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef __kserviceselectdlg_h
+#define __kserviceselectdlg_h
+
+#include
+#include
+
+#include
+
+class QDialogButtonBox;
+
+class KServiceSelectDlg : public QDialog
+{
+ Q_OBJECT
+public:
+ /**
+ * Create a dialog to select a service (not application) for a given service type.
+ *
+ * @param serviceType the service type we want to choose a service for.
+ * @param value is the initial service to select (not implemented currently)
+ * @param parent parent widget
+ */
+ explicit KServiceSelectDlg(const QString &serviceType, const QString &value = QString(), QWidget *parent = nullptr);
+
+ ~KServiceSelectDlg() override;
+
+ /**
+ * @return the chosen service
+ */
+ KService::Ptr service();
+
+private:
+ QListWidget *m_listbox;
+ QDialogButtonBox *m_buttonBox;
+};
+
+#endif
diff --git a/keditfiletype/mimetypedata.cpp b/keditfiletype/mimetypedata.cpp
new file mode 100644
index 0000000..7625cad
--- /dev/null
+++ b/keditfiletype/mimetypedata.cpp
@@ -0,0 +1,587 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2003 Waldo Bastian
+ SPDX-FileCopyrightText: 2003, 2007 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+#include "mimetypedata.h"
+#include "mimetypewriter.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+MimeTypeData::MimeTypeData(const QString &major)
+ : m_askSave(AskSaveDefault)
+ , m_bNewItem(false)
+ , m_bFullInit(true)
+ , m_isGroup(true)
+ , m_appServicesModified(false)
+ , m_embedServicesModified(false)
+ , m_userSpecifiedIconModified(false)
+ , m_major(major)
+{
+ m_autoEmbed = readAutoEmbed();
+}
+
+MimeTypeData::MimeTypeData(const QMimeType &mime)
+ : m_mimetype(mime)
+ , m_askSave(AskSaveDefault)
+ , // TODO: the code for initializing this is missing. FileTypeDetails initializes the checkbox instead...
+ m_bNewItem(false)
+ , m_bFullInit(false)
+ , m_isGroup(false)
+ , m_appServicesModified(false)
+ , m_embedServicesModified(false)
+ , m_userSpecifiedIconModified(false)
+{
+ const QString mimeName = m_mimetype.name();
+ Q_ASSERT(!mimeName.isEmpty());
+ const int index = mimeName.indexOf(QLatin1Char('/'));
+ if (index != -1) {
+ m_major = mimeName.left(index);
+ m_minor = mimeName.mid(index + 1);
+ } else {
+ m_major = mimeName;
+ }
+ initFromQMimeType();
+}
+
+MimeTypeData::MimeTypeData(const QString &mimeName, bool)
+ : m_askSave(AskSaveDefault)
+ , m_bNewItem(true)
+ , m_bFullInit(false)
+ , m_isGroup(false)
+ , m_appServicesModified(false)
+ , m_embedServicesModified(false)
+ , m_userSpecifiedIconModified(false)
+{
+ const int index = mimeName.indexOf(QLatin1Char('/'));
+ if (index != -1) {
+ m_major = mimeName.left(index);
+ m_minor = mimeName.mid(index + 1);
+ } else {
+ m_major = mimeName;
+ }
+ m_autoEmbed = UseGroupSetting;
+ // all the rest is empty by default
+}
+
+void MimeTypeData::initFromQMimeType()
+{
+ m_comment = m_mimetype.comment();
+ setPatterns(m_mimetype.globPatterns());
+ m_autoEmbed = readAutoEmbed();
+
+ // Parse XML file to find out if the user specified a custom icon name
+ QString file = name().toLower() + QLatin1String(".xml");
+ QStringList mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime/") + file);
+ if (mimeFiles.isEmpty()) {
+ // This is for shared-mime-info < 1.3 that did not lowecase mime names
+ file = name() + QLatin1String(".xml");
+ mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime/") + file);
+ if (mimeFiles.isEmpty()) {
+ qWarning() << "No file found for" << file << ", even though the file appeared in a directory listing.";
+ qWarning() << "Either it was just removed, or the directory doesn't have executable permission...";
+ qWarning() << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime"), QStandardPaths::LocateDirectory);
+ return;
+ }
+ }
+
+ // Reverse iterator to get global first, then local.
+ for (auto rIt = mimeFiles.crbegin(); rIt != mimeFiles.crend(); ++rIt) {
+ const QString fullPath = *rIt;
+ QFile qfile(fullPath);
+ if (!qfile.open(QFile::ReadOnly)) {
+ continue;
+ }
+
+ QXmlStreamReader xml(&qfile);
+ if (xml.readNextStartElement()) {
+ if (xml.name() != QLatin1String("mime-type")) {
+ continue;
+ }
+ const QString mimeName = xml.attributes().value(QLatin1String("type")).toString();
+ if (mimeName.isEmpty()) {
+ continue;
+ }
+ if (QString::compare(mimeName, name(), Qt::CaseInsensitive) != 0) {
+ qWarning() << "Got name" << mimeName << "in file" << file << "expected" << name();
+ }
+
+ while (xml.readNextStartElement()) {
+ const QStringRef tag = xml.name();
+ if (tag == QLatin1String("icon")) {
+ m_userSpecifiedIcon = xml.attributes().value(QLatin1String("name")).toString();
+ }
+ xml.skipCurrentElement();
+ }
+ }
+ }
+}
+
+MimeTypeData::AutoEmbed MimeTypeData::readAutoEmbed() const
+{
+ const KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals);
+ const QString key = QStringLiteral("embed-") + name();
+ const KConfigGroup group(config, "EmbedSettings");
+ if (m_isGroup) {
+ // embedding is false by default except for image/*, multipart/* and inode/* (hardcoded in konq)
+ const bool defaultValue = (m_major == QLatin1String("image") || m_major == QLatin1String("multipart") || m_major == QLatin1String("inode"));
+ return group.readEntry(key, defaultValue) ? Yes : No;
+ } else {
+ if (group.hasKey(key)) {
+ return group.readEntry(key, false) ? Yes : No;
+ }
+ // TODO if ( !mimetype.property( "X-KDE-LocalProtocol" ).toString().isEmpty() )
+ // TODO return MimeTypeData::Yes; // embed by default for zip, tar etc.
+ return MimeTypeData::UseGroupSetting;
+ }
+}
+
+void MimeTypeData::writeAutoEmbed()
+{
+ KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals);
+ if (!config->isConfigWritable(true)) {
+ return;
+ }
+
+ const QString key = QStringLiteral("embed-") + name();
+ KConfigGroup group(config, "EmbedSettings");
+ if (m_isGroup) {
+ group.writeEntry(key, m_autoEmbed == Yes);
+ } else {
+ if (m_autoEmbed == UseGroupSetting) {
+ group.deleteEntry(key);
+ } else {
+ group.writeEntry(key, m_autoEmbed == Yes);
+ }
+ }
+}
+
+bool MimeTypeData::isEssential() const
+{
+ // Keep in sync with KMimeType::checkEssentialMimeTypes
+ const QString n = name();
+ if (n == QLatin1String("application/octet-stream")) {
+ return true;
+ }
+ if (n == QLatin1String("inode/directory")) {
+ return true;
+ }
+ if (n == QLatin1String("inode/blockdevice")) {
+ return true;
+ }
+ if (n == QLatin1String("inode/chardevice")) {
+ return true;
+ }
+ if (n == QLatin1String("inode/socket")) {
+ return true;
+ }
+ if (n == QLatin1String("inode/fifo")) {
+ return true;
+ }
+ if (n == QLatin1String("application/x-shellscript")) {
+ return true;
+ }
+ if (n == QLatin1String("application/x-executable")) {
+ return true;
+ }
+ if (n == QLatin1String("application/x-desktop")) {
+ return true;
+ }
+ return false;
+}
+
+void MimeTypeData::setUserSpecifiedIcon(const QString &icon)
+{
+ if (icon == m_userSpecifiedIcon) {
+ return;
+ }
+ m_userSpecifiedIcon = icon;
+ m_userSpecifiedIconModified = true;
+}
+
+QStringList MimeTypeData::getAppOffers() const
+{
+ QStringList serviceIds;
+ const KService::List offerList = KApplicationTrader::queryByMimeType(name());
+ for (const auto &servicePtr : offerList) {
+ serviceIds.append(servicePtr->storageId());
+ }
+ return serviceIds;
+}
+
+QStringList MimeTypeData::getPartOffers() const
+{
+ QStringList servicesIds;
+ const KService::List partOfferList = KMimeTypeTrader::self()->query(name(), QStringLiteral("KParts/ReadOnlyPart"));
+ for (const auto &servicePtr : partOfferList) {
+ servicesIds.append(servicePtr->storageId());
+ }
+ return servicesIds;
+}
+
+void MimeTypeData::getMyServiceOffers() const
+{
+ m_appServices = getAppOffers();
+ m_embedServices = getPartOffers();
+ m_bFullInit = true;
+}
+
+QStringList MimeTypeData::appServices() const
+{
+ if (!m_bFullInit) {
+ getMyServiceOffers();
+ }
+ return m_appServices;
+}
+
+QStringList MimeTypeData::embedServices() const
+{
+ if (!m_bFullInit) {
+ getMyServiceOffers();
+ }
+ return m_embedServices;
+}
+
+bool MimeTypeData::isMimeTypeDirty() const
+{
+ Q_ASSERT(!m_isGroup);
+ if (m_bNewItem) {
+ return true;
+ }
+
+ if (!m_mimetype.isValid()) {
+ qWarning() << "MimeTypeData for" << name() << "says 'not new' but is without a mimetype? Should not happen.";
+ return true;
+ }
+
+ if (m_mimetype.comment() != m_comment) {
+ qDebug() << "Mimetype Comment Dirty: old=" << m_mimetype.comment() << "m_comment=" << m_comment;
+ return true;
+ }
+ if (m_userSpecifiedIconModified) {
+ qDebug() << "m_userSpecifiedIcon has changed. Now set to" << m_userSpecifiedIcon;
+ return true;
+ }
+
+ QStringList storedPatterns = m_mimetype.globPatterns();
+ storedPatterns.sort(); // see ctor
+ if (storedPatterns != m_patterns) {
+ qDebug() << "Mimetype Patterns Dirty: old=" << storedPatterns << "m_patterns=" << m_patterns;
+ return true;
+ }
+
+ if (readAutoEmbed() != m_autoEmbed) {
+ return true;
+ }
+ return false;
+}
+
+bool MimeTypeData::isServiceListDirty() const
+{
+ return !m_isGroup && (m_appServicesModified || m_embedServicesModified);
+}
+
+bool MimeTypeData::isDirty() const
+{
+ if (m_bNewItem) {
+ qDebug() << "New item, need to save it";
+ return true;
+ }
+
+ if (!m_isGroup) {
+ if (isServiceListDirty()) {
+ return true;
+ }
+ if (isMimeTypeDirty()) {
+ return true;
+ }
+ } else { // is a group
+ if (readAutoEmbed() != m_autoEmbed) {
+ return true;
+ }
+ }
+
+ if (m_askSave != AskSaveDefault) {
+ return true;
+ }
+
+ // nothing seems to have changed, it's not dirty.
+ return false;
+}
+
+bool MimeTypeData::sync()
+{
+ if (m_isGroup) {
+ writeAutoEmbed();
+ return false;
+ }
+
+ if (m_askSave != AskSaveDefault) {
+ KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals);
+ if (!config->isConfigWritable(true)) {
+ return false;
+ }
+ KConfigGroup cg = config->group("Notification Messages");
+ if (m_askSave == AskSaveYes) {
+ // Ask
+ cg.deleteEntry(QStringLiteral("askSave") + name());
+ cg.deleteEntry(QStringLiteral("askEmbedOrSave") + name());
+ } else {
+ // Do not ask, open
+ cg.writeEntry(QStringLiteral("askSave") + name(), QStringLiteral("no"));
+ cg.writeEntry(QStringLiteral("askEmbedOrSave") + name(), QStringLiteral("no"));
+ }
+ }
+
+ writeAutoEmbed();
+
+ bool needUpdateMimeDb = false;
+ if (isMimeTypeDirty()) {
+ MimeTypeWriter mimeTypeWriter(name());
+ mimeTypeWriter.setComment(m_comment);
+ if (!m_userSpecifiedIcon.isEmpty()) {
+ mimeTypeWriter.setIconName(m_userSpecifiedIcon);
+ }
+ mimeTypeWriter.setPatterns(m_patterns);
+ if (!mimeTypeWriter.write()) {
+ return false;
+ }
+ m_userSpecifiedIconModified = false;
+ needUpdateMimeDb = true;
+ }
+
+ syncServices();
+
+ return needUpdateMimeDb;
+}
+
+static const char s_DefaultApplications[] = "Default Applications";
+static const char s_AddedAssociations[] = "Added Associations";
+static const char s_RemovedAssociations[] = "Removed Associations";
+
+void MimeTypeData::syncServices()
+{
+ if (!m_bFullInit) {
+ return;
+ }
+
+ KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation);
+
+ if (!profile->isConfigWritable(true)) { // warn user if mimeapps.list is root-owned (#155126/#94504)
+ return;
+ }
+
+ const QStringList oldAppServices = getAppOffers();
+ if (oldAppServices != m_appServices) {
+ // Save the default application according to mime-apps-spec 1.0
+ KConfigGroup defaultApp(profile, s_DefaultApplications);
+ saveDefaultApplication(defaultApp, m_appServices);
+ // Save preferred services
+ KConfigGroup addedApps(profile, s_AddedAssociations);
+ saveServices(addedApps, m_appServices);
+ KConfigGroup removedApps(profile, s_RemovedAssociations);
+ saveRemovedServices(removedApps, m_appServices, oldAppServices);
+ }
+
+ const QStringList oldPartServices = getPartOffers();
+ if (oldPartServices != m_embedServices) {
+ // Handle removed services
+ KConfigGroup addedParts(profile, "Added KDE Service Associations");
+ saveServices(addedParts, m_embedServices);
+ KConfigGroup removedParts(profile, "Removed KDE Service Associations");
+ saveRemovedServices(removedParts, m_embedServices, oldPartServices);
+ }
+
+ // Clean out any kde-mimeapps.list which would take precedence any cancel our changes.
+ const QString desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP"));
+ const auto desktopsSplit = desktops.split(QLatin1Char(':'), Qt::SkipEmptyParts);
+ for (const QString &desktop : desktopsSplit) {
+ const QString file =
+ QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + desktop.toLower() + QLatin1String("-mimeapps.list");
+ if (QFileInfo::exists(file)) {
+ qDebug() << "Cleaning up" << file;
+ KConfig conf(file, KConfig::NoGlobals);
+ KConfigGroup(&conf, s_DefaultApplications).deleteEntry(name());
+ KConfigGroup(&conf, s_AddedAssociations).deleteEntry(name());
+ KConfigGroup(&conf, s_RemovedAssociations).deleteEntry(name());
+ }
+ }
+
+ m_appServicesModified = false;
+ m_embedServicesModified = false;
+}
+
+static QStringList collectStorageIds(const QStringList &services)
+{
+ QStringList storageIds;
+
+ for (const QString &service : services) {
+ KService::Ptr pService = KService::serviceByStorageId(service);
+ if (!pService) {
+ qWarning() << "service with storage id" << service << "not found";
+ continue; // Where did that one go?
+ }
+
+ storageIds.append(pService->storageId());
+ }
+
+ return storageIds;
+}
+
+void MimeTypeData::saveRemovedServices(KConfigGroup &config, const QStringList &services, const QStringList &oldServices)
+{
+ QStringList removedServiceList = config.readXdgListEntry(name());
+
+ for (const QString &service : services) {
+ // If removedServiceList.contains(service), then it was previously removed but has been added back
+ removedServiceList.removeAll(service);
+ }
+ for (const QString &oldService : oldServices) {
+ if (!services.contains(oldService)) {
+ // The service was in m_appServices (or m_embedServices) but has been removed
+ removedServiceList.append(oldService);
+ }
+ }
+ if (removedServiceList.isEmpty()) {
+ config.deleteEntry(name());
+ } else {
+ config.writeXdgListEntry(name(), removedServiceList);
+ }
+}
+
+void MimeTypeData::saveServices(KConfigGroup &config, const QStringList &services)
+{
+ if (services.isEmpty()) {
+ config.deleteEntry(name());
+ } else {
+ config.writeXdgListEntry(name(), collectStorageIds(services));
+ }
+}
+
+void MimeTypeData::saveDefaultApplication(KConfigGroup &config, const QStringList &services)
+{
+ if (services.isEmpty()) {
+ config.deleteEntry(name());
+ return;
+ }
+
+ const QStringList storageIds = collectStorageIds(services);
+ if (!storageIds.isEmpty()) {
+ const QString firstStorageId = storageIds.at(0);
+ config.writeXdgListEntry(name(), {firstStorageId});
+ }
+}
+
+void MimeTypeData::refresh()
+{
+ if (m_isGroup) {
+ return;
+ }
+ QMimeDatabase db;
+ m_mimetype = db.mimeTypeForName(name());
+ if (m_mimetype.isValid()) {
+ if (m_bNewItem) {
+ qDebug() << "OK, created" << name();
+ m_bNewItem = false; // if this was a new mimetype, we just created it
+ }
+ if (!isMimeTypeDirty()) {
+ // Update from the xml, in case something was changed from out of this kcm
+ // (e.g. using KOpenWithDialog, or keditfiletype + kcmshell filetypes)
+ initFromQMimeType();
+ }
+ if (!m_appServicesModified && !m_embedServicesModified) {
+ m_bFullInit = false; // refresh services too
+ }
+ }
+}
+
+void MimeTypeData::getAskSave(bool &_askSave)
+{
+ if (m_askSave == AskSaveYes) {
+ _askSave = true;
+ }
+ if (m_askSave == AskSaveNo) {
+ _askSave = false;
+ }
+}
+
+void MimeTypeData::setAskSave(bool _askSave)
+{
+ m_askSave = _askSave ? AskSaveYes : AskSaveNo;
+}
+
+bool MimeTypeData::canUseGroupSetting() const
+{
+ // "Use group settings" isn't available for zip, tar etc.; those have a builtin default...
+ if (!m_mimetype.isValid()) { // e.g. new mimetype
+ return true;
+ }
+ const bool hasLocalProtocolRedirect = !KProtocolManager::protocolForArchiveMimetype(name()).isEmpty();
+ return !hasLocalProtocolRedirect;
+}
+
+void MimeTypeData::setPatterns(const QStringList &p)
+{
+ m_patterns = p;
+ // Sort them, since update-mime-database doesn't respect order (order of globs file != order of xml),
+ // and this code says things like if (m_mimetype.patterns() == m_patterns).
+ // We could also sort in KMimeType::setPatterns but this would just slow down the
+ // normal use case (anything else than this KCM) for no good reason.
+ m_patterns.sort();
+}
+
+bool MimeTypeData::matchesFilter(const QString &filter) const
+{
+ if (name().contains(filter, Qt::CaseInsensitive)) {
+ return true;
+ }
+
+ if (m_comment.contains(filter, Qt::CaseInsensitive)) {
+ return true;
+ }
+
+ if (!m_patterns.filter(filter, Qt::CaseInsensitive).isEmpty()) {
+ return true;
+ }
+
+ return false;
+}
+
+void MimeTypeData::setAppServices(const QStringList &dsl)
+{
+ if (!m_bFullInit) {
+ getMyServiceOffers(); // so that m_bFullInit is true
+ }
+ m_appServices = dsl;
+ m_appServicesModified = true;
+}
+
+void MimeTypeData::setEmbedServices(const QStringList &dsl)
+{
+ if (!m_bFullInit) {
+ getMyServiceOffers(); // so that m_bFullInit is true
+ }
+ m_embedServices = dsl;
+ m_embedServicesModified = true;
+}
+
+QString MimeTypeData::icon() const
+{
+ if (!m_userSpecifiedIcon.isEmpty()) {
+ return m_userSpecifiedIcon;
+ }
+ if (m_mimetype.isValid()) {
+ return m_mimetype.iconName();
+ }
+ return QString();
+}
diff --git a/keditfiletype/mimetypedata.h b/keditfiletype/mimetypedata.h
new file mode 100644
index 0000000..24282ef
--- /dev/null
+++ b/keditfiletype/mimetypedata.h
@@ -0,0 +1,178 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2003 Waldo Bastian
+ SPDX-FileCopyrightText: 2003, 2007 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+#ifndef MIMETYPEDATA_H
+#define MIMETYPEDATA_H
+
+#include
+#include
+
+class KConfigGroup;
+
+/**
+ * This is a non-gui (data) class, that represents a mimetype.
+ * It is a QMimeType plus the changes we made to it.
+ */
+class MimeTypeData
+{
+public:
+ // Constructor used for groups
+ explicit MimeTypeData(const QString &major);
+ // Real constructor, used for an existing mimetype.
+ explicit MimeTypeData(const QMimeType &mime);
+ // Real constructor, used for a new mimetype.
+ explicit MimeTypeData(const QString &mimeName, bool /*unused, just to distinguish from the other QString ctor*/);
+
+ QString name() const
+ {
+ return m_isGroup ? m_major : m_major + QLatin1Char('/') + m_minor;
+ }
+
+ QString majorType() const
+ {
+ return m_major;
+ }
+
+ QString minorType() const
+ {
+ return m_minor;
+ }
+
+ void setMinor(const QString &m)
+ {
+ m_minor = m;
+ }
+
+ QString comment() const
+ {
+ return m_comment;
+ }
+
+ void setComment(const QString &c)
+ {
+ m_comment = c;
+ }
+
+ /**
+ * Returns true if "this" is a group
+ */
+ bool isMeta() const
+ {
+ return m_isGroup;
+ }
+
+ /**
+ * Returns true if the type is essential, i.e. can't be deleted
+ * (see KMimeType::checkEssentialMimeTypes)
+ */
+ bool isEssential() const;
+ QString icon() const;
+ void setUserSpecifiedIcon(const QString &icon);
+ QStringList patterns() const
+ {
+ return m_patterns;
+ }
+
+ void setPatterns(const QStringList &p);
+ QStringList appServices() const;
+ void setAppServices(const QStringList &dsl);
+ QStringList embedServices() const;
+ void setEmbedServices(const QStringList &dsl);
+
+ enum AutoEmbed {
+ Yes = 0,
+ No = 1,
+ UseGroupSetting = 2,
+ };
+ AutoEmbed autoEmbed() const
+ {
+ return m_autoEmbed;
+ }
+
+ void setAutoEmbed(AutoEmbed a)
+ {
+ m_autoEmbed = a;
+ }
+
+ const QMimeType &mimeType() const
+ {
+ return m_mimetype;
+ }
+
+ bool canUseGroupSetting() const;
+
+ void getAskSave(bool &);
+ void setAskSave(bool);
+
+ /**
+ * Returns true if the mimetype data has any unsaved changes.
+ */
+ bool isDirty() const;
+
+ /**
+ * Returns true if the mimetype data has any unsaved changes in the service list.
+ */
+ bool isServiceListDirty() const;
+
+ /**
+ * Save changes to disk.
+ * Does not check isDirty(), so the common idiom is if (data.isDirty()) { needUpdate = data.sync(); }
+ * Returns true if update-mime-database needs to be run afterwards
+ */
+ bool sync();
+ /**
+ * Update m_mimetype from the xml when Apply is pressed
+ */
+ void refresh();
+
+ /**
+ * Return true if this is a new mimetype, i.e. one that is not yet on disk
+ */
+ bool isNew() const
+ {
+ return m_bNewItem;
+ }
+
+ /**
+ * Helper method for the filtering in the listview
+ */
+ bool matchesFilter(const QString &filter) const;
+
+private:
+ void initFromQMimeType();
+ AutoEmbed readAutoEmbed() const;
+ void writeAutoEmbed();
+ bool isMimeTypeDirty() const; // whether the mimetype definition file needs saving
+ QStringList getAppOffers() const;
+ QStringList getPartOffers() const;
+ void getMyServiceOffers() const;
+ void syncServices();
+ void saveServices(KConfigGroup &config, const QStringList &services);
+ void saveDefaultApplication(KConfigGroup &config, const QStringList &services);
+ void saveRemovedServices(KConfigGroup &config, const QStringList &services, const QStringList &oldServices);
+
+ QMimeType m_mimetype;
+ enum AskSave {
+ AskSaveYes = 0,
+ AskSaveNo = 1,
+ AskSaveDefault = 2,
+ };
+ AskSave m_askSave : 3;
+ AutoEmbed m_autoEmbed : 3;
+ bool m_bNewItem : 1;
+ mutable bool m_bFullInit : 1; // lazy init of m_appServices and m_embedServices
+ bool m_isGroup : 1;
+ bool m_appServicesModified : 1;
+ bool m_embedServicesModified : 1;
+ bool m_userSpecifiedIconModified : 1;
+ QString m_major, m_minor, m_comment, m_userSpecifiedIcon;
+ QStringList m_patterns;
+ mutable QStringList m_appServices;
+ mutable QStringList m_embedServices;
+};
+
+#endif /* MIMETYPEDATA_H */
diff --git a/keditfiletype/mimetypewriter.cpp b/keditfiletype/mimetypewriter.cpp
new file mode 100644
index 0000000..bfc83cd
--- /dev/null
+++ b/keditfiletype/mimetypewriter.cpp
@@ -0,0 +1,165 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2007, 2008 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+*/
+
+#include "mimetypewriter.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+class MimeTypeWriterPrivate
+{
+public:
+ QString localFilePath() const;
+
+ QString m_mimeType;
+ QString m_comment;
+ QString m_iconName;
+ QStringList m_patterns;
+ QString m_marker;
+};
+
+MimeTypeWriter::MimeTypeWriter(const QString &mimeType)
+ : d(new MimeTypeWriterPrivate)
+{
+ d->m_mimeType = mimeType;
+ Q_ASSERT(!mimeType.isEmpty());
+}
+
+MimeTypeWriter::~MimeTypeWriter()
+{
+ delete d;
+}
+
+void MimeTypeWriter::setComment(const QString &comment)
+{
+ d->m_comment = comment;
+}
+
+void MimeTypeWriter::setPatterns(const QStringList &patterns)
+{
+ d->m_patterns = patterns;
+}
+
+void MimeTypeWriter::setIconName(const QString &iconName)
+{
+ d->m_iconName = iconName;
+}
+
+void MimeTypeWriter::setMarker(const QString &marker)
+{
+ d->m_marker = marker;
+}
+
+bool MimeTypeWriter::write()
+{
+ const QString packageFileName = d->localFilePath();
+ qDebug() << "writing" << packageFileName;
+ QFile packageFile(packageFileName);
+ if (!packageFile.open(QIODevice::WriteOnly)) {
+ qCritical() << "Couldn't open" << packageFileName << "for writing";
+ return false;
+ }
+ QXmlStreamWriter writer(&packageFile);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ if (!d->m_marker.isEmpty()) {
+ writer.writeComment(d->m_marker);
+ }
+ const QString nsUri = QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info");
+ writer.writeDefaultNamespace(nsUri);
+ writer.writeStartElement(QStringLiteral("mime-info"));
+ writer.writeStartElement(nsUri, QStringLiteral("mime-type"));
+ writer.writeAttribute(QStringLiteral("type"), d->m_mimeType);
+
+ if (!d->m_comment.isEmpty()) {
+ writer.writeStartElement(nsUri, QStringLiteral("comment"));
+ writer.writeCharacters(d->m_comment);
+ writer.writeEndElement(); // comment
+ }
+
+ if (!d->m_iconName.isEmpty()) {
+ // User-specified icon name
+ writer.writeStartElement(nsUri, QStringLiteral("icon"));
+ writer.writeAttribute(QStringLiteral("name"), d->m_iconName);
+ writer.writeEndElement(); // icon
+ }
+
+ // Allow this local definition to override the global definition
+ writer.writeStartElement(nsUri, QStringLiteral("glob-deleteall"));
+ writer.writeEndElement(); // glob-deleteall
+
+ for (const QString &pattern : qAsConst(d->m_patterns)) {
+ writer.writeStartElement(nsUri, QStringLiteral("glob"));
+ writer.writeAttribute(QStringLiteral("pattern"), pattern);
+ writer.writeEndElement(); // glob
+ }
+
+ writer.writeEndElement(); // mime-info
+ writer.writeEndElement(); // mime-type
+ writer.writeEndDocument();
+ return true;
+}
+
+void MimeTypeWriter::runUpdateMimeDatabase()
+{
+ const QString localPackageDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/");
+ Q_ASSERT(!localPackageDir.isEmpty());
+ KProcess proc;
+ proc << QStringLiteral("update-mime-database");
+ proc << localPackageDir;
+ const int exitCode = proc.execute();
+ if (exitCode) {
+ qWarning() << proc.program() << "exited with error code" << exitCode;
+ }
+}
+
+QString MimeTypeWriterPrivate::localFilePath() const
+{
+ // XDG shared mime: we must write into a /share/mime/packages/ file...
+ // To simplify our job, let's use one "input" file per mimetype, in the user's dir.
+ // (this writes into $HOME/.local/share/mime by default)
+ //
+ // We could also use Override.xml, says the spec, but then we'd need to merge with other mimetypes,
+ // and in ~/.local we don't really expect other packages to be installed anyway...
+ QString baseName = m_mimeType;
+ baseName.replace(QLatin1Char('/'), QLatin1Char('-'));
+ QString packagesDirName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/");
+ // create the directory, the saving will fail if it doesn't exist (bug#356237)
+ QDir(packagesDirName).mkpath(QStringLiteral("."));
+ return packagesDirName + baseName + QStringLiteral(".xml");
+}
+
+static QString existingDefinitionFile(const QString &mimeType)
+{
+ QString baseName = mimeType;
+ baseName.replace(QLatin1Char('/'), QLatin1Char('-'));
+ return QStandardPaths::locate(QStandardPaths::GenericDataLocation,
+ QLatin1String("mime/") + QStringLiteral("packages/") + baseName + QStringLiteral(".xml"));
+}
+
+bool MimeTypeWriter::hasDefinitionFile(const QString &mimeType)
+{
+ return !existingDefinitionFile(mimeType).isEmpty();
+}
+
+void MimeTypeWriter::removeOwnMimeType(const QString &mimeType)
+{
+ const QString file = existingDefinitionFile(mimeType);
+ Q_ASSERT(!file.isEmpty());
+ QFile::remove(file);
+ // We must also remove the generated XML file, update-mime-database doesn't do that, for unknown media types
+ QString xmlFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("mime/") + mimeType + QStringLiteral(".xml"));
+ QFile::remove(xmlFile);
+}
+
+/// WARNING: this code is duplicated between apps/nsplugins and runtime/filetypes
diff --git a/keditfiletype/mimetypewriter.h b/keditfiletype/mimetypewriter.h
new file mode 100644
index 0000000..8cdd704
--- /dev/null
+++ b/keditfiletype/mimetypewriter.h
@@ -0,0 +1,87 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2007, 2008 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+*/
+
+#ifndef MIMETYPEWRITER_H
+#define MIMETYPEWRITER_H
+
+class QStringList;
+class QString;
+class MimeTypeWriterPrivate;
+
+/// WARNING: this code is duplicated between apps/nsplugins and runtime/filetypes
+
+/**
+ * MimeTypeWriter writes out the definition of a mimetype
+ * in a XDG shared-mime-info compliant way.
+ */
+class MimeTypeWriter
+{
+public:
+ MimeTypeWriter(const QString &mimeType);
+ ~MimeTypeWriter();
+
+ /**
+ * Sets the comment describing this mimetype.
+ * It is strongly recommended to call this.
+ */
+ void setComment(const QString &comment);
+
+ /**
+ * Define the patterns associated with this mimetype,
+ * like "*.png"
+ */
+ void setPatterns(const QStringList &patterns);
+
+ /**
+ * Optional: set a user-specified icon name for this mimetype.
+ * Otherwise the icon name is based on the mimetype name.
+ */
+ void setIconName(const QString &iconName);
+
+ /**
+ * Sets a string that will be written out as an XML comment
+ * in the XML definition file, to make it possible to recognize
+ * this file later on. Used by nspluginscan.
+ */
+ void setMarker(const QString &marker);
+
+ /**
+ * Write out the mimetype definition file
+ * Returns true on success
+ */
+ bool write();
+
+ /**
+ * Returns true if a mimetype definition file already exists
+ * for the given mimetype.
+ *
+ * NOTE: this is not the same as testing whether the
+ * mimetype is defined in general (for instance by freedesktop.org.xml)
+ * you should use db.mimeTypeForName() for that.
+ * This method is only for mimetypes generated by MimeTypeWriter.
+ */
+ static bool hasDefinitionFile(const QString &mimeType);
+
+ /**
+ * Remove mimetype created by MimeTypeWriter.
+ * Assumes hasDefinitionFile(mimeType).
+ * Remember to call runUpdateMimeDatabase afterwards!
+ */
+ static void removeOwnMimeType(const QString &mimeType);
+
+ /**
+ * Call this once after writing as many mimetypes as you want,
+ * to let update-mime-database process the new mimetype xml files.
+ */
+ static void runUpdateMimeDatabase();
+
+private:
+ MimeTypeWriterPrivate *const d;
+};
+
+/// WARNING: this code is duplicated between apps/nsplugins and runtime/filetypes
+
+#endif /* MIMETYPEWRITER_H */
diff --git a/keditfiletype/newtypedlg.cpp b/keditfiletype/newtypedlg.cpp
new file mode 100644
index 0000000..e12f148
--- /dev/null
+++ b/keditfiletype/newtypedlg.cpp
@@ -0,0 +1,79 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2000 Kurt Granroth
+ SPDX-FileCopyrightText: 2008 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+// Own
+#include "newtypedlg.h"
+
+// Qt
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// KDE
+#include
+#include
+
+NewTypeDialog::NewTypeDialog(const QStringList &groups, QWidget *parent)
+ : QDialog(parent)
+{
+ setModal(true);
+ setWindowTitle(i18n("Create New File Type"));
+
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+ QFormLayout *formLayout = new QFormLayout;
+
+ QLabel *l = new QLabel(i18n("Group:"));
+
+ m_groupCombo = new QComboBox;
+ m_groupCombo->setEditable(true);
+ m_groupCombo->addItems(groups);
+ m_groupCombo->setCurrentIndex(m_groupCombo->findText(QStringLiteral("application"))); // certainly a better default than "all"
+ formLayout->addRow(l, m_groupCombo);
+
+ m_groupCombo->setWhatsThis(
+ i18n("Select the category under which"
+ " the new file type should be added."));
+
+ // Line 1: mimetype name
+
+ l = new QLabel(i18n("Type name:"));
+
+ m_typeEd = new KLineEdit;
+ formLayout->addRow(l, m_typeEd);
+
+ m_typeEd->setWhatsThis(
+ i18n("Type the name of the file type. For instance, if you selected 'image' as category and you type 'custom' here, the file type 'image/custom' will "
+ "be created."));
+
+ m_typeEd->setFocus();
+
+ m_buttonBox = new QDialogButtonBox;
+ m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+ mainLayout->addLayout(formLayout);
+ mainLayout->addWidget(m_buttonBox);
+
+ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ // Set a minimum width so that caption is not half-hidden
+ setMinimumWidth(300);
+}
+
+QString NewTypeDialog::group() const
+{
+ return m_groupCombo->currentText();
+}
+
+QString NewTypeDialog::text() const
+{
+ return m_typeEd->text();
+}
diff --git a/keditfiletype/newtypedlg.h b/keditfiletype/newtypedlg.h
new file mode 100644
index 0000000..3b1e32a
--- /dev/null
+++ b/keditfiletype/newtypedlg.h
@@ -0,0 +1,36 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2000 Kurt Granroth
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+#ifndef _NEWTYPEDLG_H
+#define _NEWTYPEDLG_H
+
+#include
+
+class QDialogButtonBox;
+class QStringList;
+class KLineEdit;
+class QComboBox;
+
+/**
+ * A dialog for creating a new file type, with
+ * - a combobox for choosing the group
+ * - a line-edit for entering the name of the file type
+ * The rest (description, patterns, icon, apps) can be set later in the filetypesview anyway.
+ */
+class NewTypeDialog : public QDialog
+{
+public:
+ explicit NewTypeDialog(const QStringList &groups, QWidget *parent);
+ QString group() const;
+ QString text() const;
+
+private:
+ KLineEdit *m_typeEd;
+ QComboBox *m_groupCombo;
+ QDialogButtonBox *m_buttonBox;
+};
+
+#endif
diff --git a/keditfiletype/org.kde.keditfiletype.desktop b/keditfiletype/org.kde.keditfiletype.desktop
new file mode 100644
index 0000000..1b23ac2
--- /dev/null
+++ b/keditfiletype/org.kde.keditfiletype.desktop
@@ -0,0 +1,80 @@
+[Desktop Entry]
+Name=keditfiletype
+Name[az]=keditfiletype
+Name[ca]=keditfiletype
+Name[ca@valencia]=keditfiletype
+Name[cs]=keditfiletype
+Name[da]=keditfiletype
+Name[de]=keditfiletype
+Name[el]=keditfiletype
+Name[en_GB]=keditfiletype
+Name[es]=keditfiletype
+Name[et]=keditfiletype
+Name[eu]=keditfiletype
+Name[fi]=keditfiletype
+Name[fr]=keditfiletype
+Name[gl]=keditfiletype
+Name[hsb]=keditfiletype
+Name[hu]=keditfiletype
+Name[ia]=keditfiletype
+Name[id]=keditfiletype
+Name[it]=keditfiletype
+Name[ko]=keditfiletype
+Name[lt]=keditfiletype
+Name[nl]=keditfiletype
+Name[nn]=keditfiletype
+Name[pl]=keditfiletype
+Name[pt]=keditfiletype
+Name[pt_BR]=keditfiletype
+Name[ru]=keditfiletype
+Name[sk]=keditfiletype
+Name[sl]=keditfiletype
+Name[sv]=filtypeditor
+Name[tg]=keditfiletype
+Name[uk]=keditfiletype
+Name[x-test]=xxkeditfiletypexx
+Name[zh_CN]=文件类型编辑
+Name[zh_TW]=keditfiletype
+GenericName=File Type Editor
+GenericName[az]=Fayl növü redaktoru
+GenericName[ca]=Editor per als tipus de fitxers
+GenericName[ca@valencia]=Editor per als tipus de fitxers
+GenericName[cs]=Editor typu souboru
+GenericName[da]=Redigering af filtyper
+GenericName[de]=Dateityp-Editor
+GenericName[el]=Επεξεργαστής τ'υπου αρχείου
+GenericName[en_GB]=File Type Editor
+GenericName[es]=Editor de tipo de archivo
+GenericName[et]=Failitüübi redaktor
+GenericName[eu]=Fitxategi-mota editorea
+GenericName[fi]=Tiedostotyyppimuokkain
+GenericName[fr]=Éditeur de type de fichier
+GenericName[gl]=Editor de tipos de ficheiro
+GenericName[hsb]=Wobdźěłar za družinu dataje
+GenericName[hu]=Fájltípusszerkesztő
+GenericName[ia]=Editor de typo de file
+GenericName[id]=Pengedit Tipe File
+GenericName[it]=Editor dei tipi di file
+GenericName[ko]=파일 형식 편집기
+GenericName[lt]=Failų tipų redaktorius
+GenericName[nl]=Bewerker van bestandstype
+GenericName[nn]=Filtyperedigering
+GenericName[pl]=Edytor rodzaju pliku
+GenericName[pt]=Editor de Tipos de Ficheiros
+GenericName[pt_BR]=Editor de tipo de arquivo
+GenericName[ru]=Редактор типов файлов
+GenericName[sk]=Editor súborových typov
+GenericName[sl]=Urejevalnik vrst datotek
+GenericName[sv]=Filtypeditor
+GenericName[tg]=Муҳаррири навъи файл
+GenericName[tr]=Dosya Türü Düzenleyici
+GenericName[uk]=Редактор типів файлів
+GenericName[vi]=Trình biên tập kiểu tệp
+GenericName[x-test]=xxFile Type Editorxx
+GenericName[zh_CN]=文件类型编辑器
+GenericName[zh_TW]=檔案類型編輯工具
+Exec=keditfiletype5
+Icon=preferences-desktop-filetype-association
+Type=Application
+Terminal=false
+NoDisplay=true
diff --git a/keditfiletype/tests/CMakeLists.txt b/keditfiletype/tests/CMakeLists.txt
new file mode 100644
index 0000000..7ae1a14
--- /dev/null
+++ b/keditfiletype/tests/CMakeLists.txt
@@ -0,0 +1,18 @@
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. )
+
+########### filetypestest ###############
+
+add_executable(filetypestest
+ filetypestest.cpp
+ ../mimetypedata.cpp
+ ../mimetypewriter.cpp
+)
+ecm_mark_as_test(filetypestest)
+add_test(NAME filetypestest COMMAND filetypestest)
+target_link_libraries(filetypestest
+ KF5::KIOCore
+ KF5::Service
+ Qt::Core
+ Qt::Gui
+ Qt::Test
+)
diff --git a/keditfiletype/tests/filetypestest.cpp b/keditfiletype/tests/filetypestest.cpp
new file mode 100644
index 0000000..eec14f9
--- /dev/null
+++ b/keditfiletype/tests/filetypestest.cpp
@@ -0,0 +1,418 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2007 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+*/
+
+#include
+
+#include
+#include
+#include
+
+// Qt
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+// Unfortunately this isn't available in non-developer builds of Qt...
+// extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp
+
+class FileTypesTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ QLoggingCategory::setFilterRules(QStringLiteral("kf.coreaddons.kdirwatch.debug=true"));
+
+ QStandardPaths::setTestModeEnabled(true);
+
+ // update-mime-database needs to know about that test directory for the mimetype pattern change in testMimeTypePatterns to have an effect
+ qputenv("XDG_DATA_HOME", QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)));
+
+ m_mimeTypeCreatedSuccessfully = false;
+ QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
+ // qDebug() << appsDirs;
+ m_localApps = appsDirs.first() + QLatin1Char('/');
+ m_localConfig = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
+ QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/packages")));
+
+ QFile::remove(m_localConfig.filePath(QStringLiteral("mimeapps.list")));
+ QFile::remove(m_localConfig.filePath(QStringLiteral("filetypesrc")));
+
+ // Create fake applications for some tests below.
+ fakeApplication = QStringLiteral("fakeapplication.desktop");
+ createDesktopFile(m_localApps + fakeApplication, {QStringLiteral("image/png")});
+ fakeApplication2 = QStringLiteral("fakeapplication2.desktop");
+ createDesktopFile(m_localApps + fakeApplication2, {QStringLiteral("image/png"), QStringLiteral("text/plain")});
+
+ // Cleanup after testMimeTypePatterns if it failed mid-way
+ const QString packageFileName =
+ QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml");
+ if (!packageFileName.isEmpty()) {
+ QFile::remove(packageFileName);
+ MimeTypeWriter::runUpdateMimeDatabase();
+ }
+
+ KService::Ptr fakeApplicationService = KService::serviceByStorageId(fakeApplication);
+ QVERIFY(fakeApplicationService);
+ }
+
+ void testMimeTypeGroupAutoEmbed()
+ {
+ MimeTypeData data(QStringLiteral("text"));
+ QCOMPARE(data.majorType(), QStringLiteral("text"));
+ QCOMPARE(data.name(), QStringLiteral("text"));
+ QVERIFY(data.isMeta());
+ QCOMPARE(data.autoEmbed(), MimeTypeData::No); // text doesn't autoembed by default
+ QVERIFY(!data.isDirty());
+ data.setAutoEmbed(MimeTypeData::Yes);
+ QCOMPARE(data.autoEmbed(), MimeTypeData::Yes);
+ QVERIFY(data.isDirty());
+ QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database)
+ QVERIFY(!data.isDirty());
+ // Check what's on disk by creating another MimeTypeData instance
+ MimeTypeData data2(QStringLiteral("text"));
+ QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes);
+ QVERIFY(!data2.isDirty());
+ data2.setAutoEmbed(MimeTypeData::No); // revert to default, for next time
+ QVERIFY(data2.isDirty());
+ QVERIFY(!data2.sync());
+ QVERIFY(!data2.isDirty());
+
+ // TODO test askSave after cleaning up the code
+ }
+
+ void testMimeTypeAutoEmbed()
+ {
+ QMimeDatabase db;
+ MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain")));
+ QCOMPARE(data.majorType(), QStringLiteral("text"));
+ QCOMPARE(data.minorType(), QStringLiteral("plain"));
+ QCOMPARE(data.name(), QStringLiteral("text/plain"));
+ QVERIFY(!data.isMeta());
+ QCOMPARE(data.autoEmbed(), MimeTypeData::UseGroupSetting);
+ QVERIFY(!data.isDirty());
+ data.setAutoEmbed(MimeTypeData::Yes);
+ QCOMPARE(data.autoEmbed(), MimeTypeData::Yes);
+ QVERIFY(data.isDirty());
+ QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database)
+ QVERIFY(!data.isDirty());
+ // Check what's on disk by creating another MimeTypeData instance
+ MimeTypeData data2(db.mimeTypeForName(QStringLiteral("text/plain")));
+ QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes);
+ QVERIFY(!data2.isDirty());
+ data2.setAutoEmbed(MimeTypeData::UseGroupSetting); // revert to default, for next time
+ QVERIFY(data2.isDirty());
+ QVERIFY(!data2.sync());
+ QVERIFY(!data2.isDirty());
+ }
+
+ void testMimeTypePatterns()
+ {
+ // Given the text/plain mimetype
+ QMimeDatabase db;
+ MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain")));
+ QCOMPARE(data.name(), QStringLiteral("text/plain"));
+ QCOMPARE(data.majorType(), QStringLiteral("text"));
+ QCOMPARE(data.minorType(), QStringLiteral("plain"));
+ QVERIFY(!data.isMeta());
+ QStringList patterns = data.patterns();
+ QVERIFY(patterns.contains(QStringLiteral("*.txt")));
+ QVERIFY(!patterns.contains(QStringLiteral("*.toto")));
+
+ // When the user changes the patterns
+ const QStringList origPatterns = patterns;
+ patterns.removeAll(QStringLiteral("*.txt"));
+ patterns.append(QStringLiteral("*.toto")); // yes, a french guy wrote this, as you can see
+ patterns.sort(); // for future comparisons
+ QVERIFY(!data.isDirty());
+ data.setPatterns(patterns);
+ QVERIFY(data.isDirty());
+ const bool needUpdateMimeDb = data.sync();
+ QVERIFY(needUpdateMimeDb);
+ MimeTypeWriter::runUpdateMimeDatabase();
+
+ // Then the GUI and the QMimeDatabase API should show the new patterns
+ QCOMPARE(data.patterns(), patterns);
+ data.refresh(); // reload from the xml
+ QCOMPARE(data.patterns(), patterns);
+ // Check what's in QMimeDatabase
+ QStringList newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns();
+ newPatterns.sort();
+#if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
+ QEXPECT_FAIL("", "QTBUG-85436 is only fixed in Qt 5.15.2", Continue);
+#endif
+ QCOMPARE(newPatterns, patterns);
+ if (newPatterns == patterns) { // TODO Qt6: remove the if (keep the QVERIFY!)
+ QVERIFY(!data.isDirty());
+ }
+
+ // And then removing the custom file by hand should revert to the initial state
+ const QString packageFileName =
+ QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml");
+ QVERIFY(!packageFileName.isEmpty());
+ QFile::remove(packageFileName);
+ MimeTypeWriter::runUpdateMimeDatabase();
+ // Check what's in QMimeDatabase
+ newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns();
+ newPatterns.sort();
+ QCOMPARE(newPatterns, origPatterns);
+ }
+
+ void testAddService()
+ {
+ QMimeDatabase db;
+ QString mimeTypeName = QStringLiteral("application/rtf"); // use inherited mimetype to test #321706
+ MimeTypeData data(db.mimeTypeForName(mimeTypeName));
+ QStringList appServices = data.appServices();
+ // qDebug() << appServices;
+ QVERIFY(appServices.contains(fakeApplication2));
+ QVERIFY(!appServices.contains(fakeApplication)); // already there? hmm can't really test then
+ QVERIFY(!data.isDirty());
+ appServices.prepend(fakeApplication);
+ data.setAppServices(appServices);
+ QVERIFY(data.isDirty());
+ QVERIFY(!data.sync()); // success, but no need to run update-mime-database
+ runKBuildSycoca();
+ QVERIFY(!data.isDirty());
+ // Check what's in ksycoca
+ checkMimeTypeServices(mimeTypeName, appServices);
+ // Check what's in mimeapps.list
+ checkAddedAssociationsContains(mimeTypeName, fakeApplication);
+
+ // Test reordering apps, i.e. move fakeApplication under oldPreferredApp
+ appServices.removeFirst();
+ appServices.insert(1, fakeApplication);
+ data.setAppServices(appServices);
+ QVERIFY(!data.sync()); // success, but no need to run update-mime-database
+ runKBuildSycoca();
+ QVERIFY(!data.isDirty());
+ // Check what's in ksycoca
+ checkMimeTypeServices(mimeTypeName, appServices);
+ // Check what's in mimeapps.list
+ checkAddedAssociationsContains(mimeTypeName, fakeApplication);
+
+ // Then we get the signal that kbuildsycoca changed
+ data.refresh();
+
+ // Now test removing (in the same test, since it's inter-dependent)
+ QVERIFY(appServices.removeAll(fakeApplication) > 0);
+ data.setAppServices(appServices);
+ QVERIFY(data.isDirty());
+ QVERIFY(!data.sync()); // success, but no need to run update-mime-database
+ runKBuildSycoca();
+ // Check what's in ksycoca
+ checkMimeTypeServices(mimeTypeName, appServices);
+ // Check what's in mimeapps.list
+ checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
+ }
+
+ void testRemoveTwice()
+ {
+ QMimeDatabase db;
+ // Remove fakeApplication from image/png
+ QString mimeTypeName = QStringLiteral("image/png");
+ MimeTypeData data(db.mimeTypeForName(mimeTypeName));
+ QStringList appServices = data.appServices();
+ qDebug() << "initial list for" << mimeTypeName << appServices;
+ QVERIFY(appServices.removeAll(fakeApplication) > 0);
+ data.setAppServices(appServices);
+ QVERIFY(!data.sync()); // success, but no need to run update-mime-database
+ runKBuildSycoca();
+ // Check what's in ksycoca
+ checkMimeTypeServices(mimeTypeName, appServices);
+ // Check what's in mimeapps.list
+ checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
+
+ // Remove fakeApplication2 from image/png; must keep the previous entry in "Removed Associations"
+ qDebug() << "Removing fakeApplication2";
+ QVERIFY(appServices.removeAll(fakeApplication2) > 0);
+ data.setAppServices(appServices);
+ QVERIFY(!data.sync()); // success, but no need to run update-mime-database
+ runKBuildSycoca();
+ // Check what's in ksycoca
+ checkMimeTypeServices(mimeTypeName, appServices);
+ // Check what's in mimeapps.list
+ checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
+ // Check what's in mimeapps.list
+ checkRemovedAssociationsContains(mimeTypeName, fakeApplication2);
+
+ // And now re-add fakeApplication2...
+ qDebug() << "Re-adding fakeApplication2";
+ appServices.prepend(fakeApplication2);
+ data.setAppServices(appServices);
+ QVERIFY(!data.sync()); // success, but no need to run update-mime-database
+ runKBuildSycoca();
+ // Check what's in ksycoca
+ checkMimeTypeServices(mimeTypeName, appServices);
+ // Check what's in mimeapps.list
+ checkRemovedAssociationsContains(mimeTypeName, fakeApplication);
+ checkRemovedAssociationsDoesNotContain(mimeTypeName, fakeApplication2);
+ }
+
+ void testCreateMimeType()
+ {
+ QMimeDatabase db;
+ const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype");
+ // Clean up after previous runs if necessary
+ if (MimeTypeWriter::hasDefinitionFile(mimeTypeName)) {
+ MimeTypeWriter::removeOwnMimeType(mimeTypeName);
+ }
+
+ MimeTypeData data(mimeTypeName, true);
+ data.setComment(QStringLiteral("Fake MimeType"));
+ QStringList patterns = QStringList() << QStringLiteral("*.pkg.tar.gz");
+ data.setPatterns(patterns);
+ QVERIFY(data.isDirty());
+ QVERIFY(data.sync());
+ MimeTypeWriter::runUpdateMimeDatabase();
+ // QMimeDatabase doesn't even try to update the cache if less than
+ // 5000 ms have passed (can't use qmime_secondsBetweenChecks)
+ QTest::qSleep(5000);
+ QMimeType mime = db.mimeTypeForName(mimeTypeName);
+ QVERIFY(mime.isValid());
+ QCOMPARE(mime.comment(), QStringLiteral("Fake MimeType"));
+ QCOMPARE(mime.globPatterns(), patterns); // must sort them if more than one
+
+ // Testcase for the shaman.xml bug
+ QCOMPARE(db.mimeTypeForFile(QStringLiteral("/whatever/foo.pkg.tar.gz")).name(), QStringLiteral("fake/unit-test-fake-mimetype"));
+
+ m_mimeTypeCreatedSuccessfully = true;
+ }
+
+ void testDeleteMimeType()
+ {
+ QMimeDatabase db;
+ if (!m_mimeTypeCreatedSuccessfully) {
+ QSKIP("This test relies on testCreateMimeType");
+ }
+ const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype");
+ QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName));
+ MimeTypeWriter::removeOwnMimeType(mimeTypeName);
+ MimeTypeWriter::runUpdateMimeDatabase();
+ // QMimeDatabase doesn't even try to update the cache if less than
+ // 5000 ms have passed (can't use qmime_secondsBetweenChecks)
+ QTest::qSleep(5000);
+ const QMimeType mime = db.mimeTypeForName(mimeTypeName);
+ QVERIFY2(!mime.isValid(), qPrintable(mimeTypeName));
+ }
+
+ void testModifyMimeTypeComment() // of a system mimetype. And check that it's re-read correctly.
+ {
+ QMimeDatabase db;
+ QString mimeTypeName = QStringLiteral("image/png");
+ MimeTypeData data(db.mimeTypeForName(mimeTypeName));
+ QCOMPARE(data.comment(), QString::fromLatin1("PNG image"));
+ QString fakeComment = QStringLiteral("PNG image [testing]");
+ data.setComment(fakeComment);
+ QVERIFY(data.isDirty());
+ QVERIFY(data.sync());
+ MimeTypeWriter::runUpdateMimeDatabase();
+ QMimeType mime = db.mimeTypeForName(mimeTypeName);
+ QVERIFY(mime.isValid());
+ QCOMPARE(mime.comment(), fakeComment);
+
+ // Cleanup
+ QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName));
+ MimeTypeWriter::removeOwnMimeType(mimeTypeName);
+ }
+
+ void cleanupTestCase()
+ {
+ const QString localAppsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
+ QFile::remove(localAppsDir + QLatin1String("/fakeapplication.desktop"));
+ QFile::remove(localAppsDir + QLatin1String("/fakeapplication2.desktop"));
+ }
+
+private: // helper methods
+ void checkAddedAssociationsContains(const QString &mimeTypeName, const QString &application)
+ {
+ const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals);
+ const KConfigGroup group(&config, "Added Associations");
+ const QStringList addedEntries = group.readXdgListEntry(mimeTypeName);
+ if (!addedEntries.contains(application)) {
+ qWarning() << addedEntries << "does not contain" << application;
+ QVERIFY(addedEntries.contains(application));
+ }
+ }
+
+ void checkRemovedAssociationsContains(const QString &mimeTypeName, const QString &application)
+ {
+ const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals);
+ const KConfigGroup group(&config, "Removed Associations");
+ const QStringList removedEntries = group.readXdgListEntry(mimeTypeName);
+ if (!removedEntries.contains(application)) {
+ qWarning() << removedEntries << "does not contain" << application;
+ QVERIFY(removedEntries.contains(application));
+ }
+ }
+
+ void checkRemovedAssociationsDoesNotContain(const QString &mimeTypeName, const QString &application)
+ {
+ const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals);
+ const KConfigGroup group(&config, "Removed Associations");
+ const QStringList removedEntries = group.readXdgListEntry(mimeTypeName);
+ if (removedEntries.contains(application)) {
+ qWarning() << removedEntries << "contains" << application;
+ QVERIFY(!removedEntries.contains(application));
+ }
+ }
+
+ void runKBuildSycoca()
+ {
+ // Wait for notifyDatabaseChanged DBus signal
+ // (The real KCM code simply does the refresh in a slot, asynchronously)
+
+ QProcess proc;
+ // proc.setProcessChannelMode(QProcess::ForwardedChannels);
+ const QString kbuildsycoca = QStandardPaths::findExecutable(QLatin1String(KBUILDSYCOCA_EXENAME));
+ QVERIFY(!kbuildsycoca.isEmpty());
+ QStringList args;
+ args << QStringLiteral("--testmode");
+ proc.start(kbuildsycoca, args);
+ QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList)));
+ proc.waitForFinished();
+ qDebug() << "waiting for signal";
+ QVERIFY(spy.wait(10000));
+ qDebug() << "got signal";
+ }
+
+ void createDesktopFile(const QString &path, const QStringList &mimeTypes)
+ {
+ KDesktopFile file(path);
+ KConfigGroup group = file.desktopGroup();
+ group.writeEntry("Name", "FakeApplication");
+ group.writeEntry("Type", "Application");
+ group.writeEntry("Exec", "ls");
+ group.writeXdgListEntry("MimeType", mimeTypes);
+ }
+
+ void checkMimeTypeServices(const QString &mimeTypeName, const QStringList &expectedServices)
+ {
+ QMimeDatabase db;
+ MimeTypeData data2(db.mimeTypeForName(mimeTypeName));
+ if (data2.appServices() != expectedServices) {
+ qDebug() << "got" << data2.appServices() << "expected" << expectedServices;
+ }
+ QCOMPARE(data2.appServices(), expectedServices);
+ }
+
+ QString fakeApplication; // storage id of the fake application
+ QString fakeApplication2; // storage id of the fake application2
+ QString m_localApps;
+ QDir m_localConfig;
+ bool m_mimeTypeCreatedSuccessfully;
+};
+
+QTEST_MAIN(FileTypesTest)
+
+#include "filetypestest.moc"
diff --git a/keditfiletype/typeslistitem.cpp b/keditfiletype/typeslistitem.cpp
new file mode 100644
index 0000000..1046894
--- /dev/null
+++ b/keditfiletype/typeslistitem.cpp
@@ -0,0 +1,51 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2003 Waldo Bastian
+ SPDX-FileCopyrightText: 2003, 2007 David Faure
+ SPDX-FileCopyrightText: 2008 Urs Wolfer
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+// Own
+#include "typeslistitem.h"
+
+// Qt
+#include
+
+TypesListItem::TypesListItem(QTreeWidget *parent, const QString &major)
+ : QTreeWidgetItem(parent)
+ , m_mimetypeData(major)
+{
+ setText(0, major);
+}
+
+TypesListItem::TypesListItem(TypesListItem *parent, QMimeType mimetype)
+ : QTreeWidgetItem(parent)
+ , m_mimetypeData(mimetype)
+{
+ setText(0, m_mimetypeData.minorType());
+}
+
+TypesListItem::TypesListItem(TypesListItem *parent, const QString &newMimetype)
+ : QTreeWidgetItem(parent)
+ , m_mimetypeData(newMimetype, true)
+{
+ setText(0, m_mimetypeData.minorType());
+}
+
+TypesListItem::~TypesListItem()
+{
+}
+
+void TypesListItem::setIcon(const QString &icon)
+{
+ m_mimetypeData.setUserSpecifiedIcon(icon);
+ loadIcon(true);
+}
+
+void TypesListItem::loadIcon(bool forceReload)
+{
+ if ((!m_mimetypeData.icon().isEmpty() && icon(0).isNull()) || forceReload) {
+ QTreeWidgetItem::setIcon(0, QIcon::fromTheme(m_mimetypeData.icon()));
+ }
+}
diff --git a/keditfiletype/typeslistitem.h b/keditfiletype/typeslistitem.h
new file mode 100644
index 0000000..148f16e
--- /dev/null
+++ b/keditfiletype/typeslistitem.h
@@ -0,0 +1,61 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2003 Waldo Bastian
+ SPDX-FileCopyrightText: 2003 David Faure
+
+ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+*/
+
+#ifndef TYPESLISTITEM_H
+#define TYPESLISTITEM_H
+
+#include "mimetypedata.h"
+#include
+
+#include
+#include
+
+// TODO different subclasses for mimetypes and groups?
+class TypesListItem : public QTreeWidgetItem
+{
+public:
+ /**
+ * Create a filetype group
+ */
+ TypesListItem(QTreeWidget *parent, const QString &major);
+
+ /**
+ * Create a filetype item inside a group, for an existing mimetype
+ */
+ TypesListItem(TypesListItem *parent, QMimeType mimetype);
+
+ /**
+ * Create a filetype item inside a group, for a new mimetype
+ */
+ TypesListItem(TypesListItem *parent, const QString &newMimetype);
+
+ ~TypesListItem() override;
+
+ void setIcon(const QString &icon);
+
+ QString name() const
+ {
+ return m_mimetypeData.name();
+ }
+
+ const MimeTypeData &mimeTypeData() const
+ {
+ return m_mimetypeData;
+ }
+
+ MimeTypeData &mimeTypeData()
+ {
+ return m_mimetypeData;
+ }
+
+ void loadIcon(bool forceReload = false);
+
+private:
+ MimeTypeData m_mimetypeData;
+};
+
+#endif
diff --git a/kioclient/CMakeLists.txt b/kioclient/CMakeLists.txt
new file mode 100644
index 0000000..ee6c6bb
--- /dev/null
+++ b/kioclient/CMakeLists.txt
@@ -0,0 +1,36 @@
+
+if (NOT TARGET KF5::KIOWidgets)
+ set(KIOCORE_ONLY ON)
+ add_definitions(-DKIOCORE_ONLY=1)
+ set(kio_libs KF5::KIOCore)
+else ()
+ set(kio_libs KF5::KIOWidgets)
+endif ()
+
+function(add_kioclient_interface TARGET_NAME)
+ add_executable(${TARGET_NAME} kioclient.cpp)
+ string(TOUPPER "${TARGET_NAME}" UPPER_TARGET_NAME)
+ target_compile_definitions(${TARGET_NAME} PRIVATE "-DKIOCLIENT_AS_${UPPER_TARGET_NAME}")
+ target_compile_definitions(${TARGET_NAME} PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+ target_link_libraries(${TARGET_NAME} Qt::DBus Qt::Widgets KF5::CoreAddons ${kio_libs} KF5::I18n)
+ install_compat_symlink(${TARGET_NAME})
+ install(TARGETS ${TARGET_NAME} DESTINATION ${KDE_INSTALL_FULL_BINDIR})
+endfunction()
+
+#we compile every file with different definitions, so it will behave slightly different
+add_kioclient_interface(kioclient)
+add_kioclient_interface(kdecp)
+add_kioclient_interface(kdemv)
+
+#kioclient is not marked as nongui since download and openProperties do graphical things
+ecm_mark_nongui_executable(kdemv)
+ecm_mark_nongui_executable(kdecp)
+
+if (NOT KIOCORE_ONLY)
+add_executable(kde-open kioclient.cpp)
+target_compile_definitions(kde-open PRIVATE "-DKIOCLIENT_AS_KDEOPEN")
+target_compile_definitions(kde-open PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+target_link_libraries(kde-open Qt::DBus KF5::CoreAddons KF5::KIOWidgets KF5::I18n)
+install_compat_symlink(kde-open)
+install(TARGETS kde-open DESTINATION ${KDE_INSTALL_FULL_BINDIR})
+endif()
diff --git a/kioclient/Messages.sh b/kioclient/Messages.sh
new file mode 100755
index 0000000..0853231
--- /dev/null
+++ b/kioclient/Messages.sh
@@ -0,0 +1,2 @@
+#! /usr/bin/env bash
+$XGETTEXT *.cpp -o $podir/kioclient.pot
diff --git a/kioclient/kioclient.cpp b/kioclient/kioclient.cpp
new file mode 100644
index 0000000..5024fc7
--- /dev/null
+++ b/kioclient/kioclient.cpp
@@ -0,0 +1,576 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 1999-2006 David Faure
+
+ SPDX-License-Identifier: LGPL-2.0-only
+*/
+
+#include "kioclient.h"
+#include "kio_version.h"
+#include "urlinfo.h"
+
+#include
+#include
+#include
+#include
+#ifndef KIOCORE_ONLY
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+bool ClientApp::m_ok = true;
+static bool s_interactive = false;
+static KIO::JobFlags s_jobFlags = KIO::DefaultFlags;
+
+static QUrl makeURL(const QString &urlArg)
+{
+ return QUrl::fromUserInput(urlArg, QDir::currentPath());
+}
+
+static QList makeUrls(const QStringList &urlArgs)
+{
+ QList ret;
+ for (const QString &url : urlArgs) {
+ ret += makeURL(url);
+ }
+ return ret;
+}
+
+#ifdef KIOCLIENT_AS_KIOCLIENT
+static void usage()
+{
+ puts(i18n("\nSyntax:\n").toLocal8Bit().constData());
+ puts(i18n(" kioclient openProperties 'url'\n"
+ " # Opens a properties menu\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient exec 'url' ['mimetype']\n"
+ " # Tries to open the document pointed to by 'url', in the application\n"
+ " # associated with it in KDE. You may omit 'mimetype'.\n"
+ " # In this case the mimetype is determined\n"
+ " # automatically. Of course URL may be the URL of a\n"
+ " # document, or it may be a *.desktop file.\n"
+ " # 'url' can be an executable, too.\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient move 'src' 'dest'\n"
+ " # Moves the URL 'src' to 'dest'.\n"
+ " # 'src' may be a list of URLs.\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" # 'dest' may be \"trash:/\" to move the files\n"
+ " # to the trash.\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" # the short version kioclient mv\n"
+ " # is also available.\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient download ['src']\n"
+ " # Copies the URL 'src' to a user-specified location'.\n"
+ " # 'src' may be a list of URLs, if not present then\n"
+ " # a URL will be requested.\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient copy 'src' 'dest'\n"
+ " # Copies the URL 'src' to 'dest'.\n"
+ " # 'src' may be a list of URLs.\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" # the short version kioclient cp\n"
+ " # is also available.\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient cat 'url'\n"
+ " # Writes out the contents of 'url' to stdout\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient ls 'url'\n"
+ " # Lists the contents of the directory 'url' to stdout\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient remove 'url'\n"
+ " # Removes the URL\n"
+ " # 'url' may be a list of URLs.\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" # the short version kioclient rm\n"
+ " # is also available.\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient stat 'url'\n"
+ " # Shows all of the available information for 'url'\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient appmenu\n"
+ " # Opens a basic application launcher.\n\n")
+ .toLocal8Bit()
+ .constData());
+
+ puts(i18n("*** Examples:\n").toLocal8Bit().constData());
+ puts(i18n(" kioclient exec file:/home/weis/data/test.html\n"
+ " // Opens the file with default binding\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient exec ftp://localhost/\n"
+ " // Opens new window with URL\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient exec file:/root/Desktop/emacs.desktop\n"
+ " // Starts emacs\n\n")
+ .toLocal8Bit()
+ .constData());
+ puts(i18n(" kioclient exec .\n"
+ " // Opens the current directory. Very convenient.\n\n")
+ .toLocal8Bit()
+ .constData());
+}
+#endif
+
+int main(int argc, char **argv)
+{
+#ifdef KIOCORE_ONLY
+ QCoreApplication app(argc, argv);
+#else
+ QApplication app(argc, argv);
+#endif
+
+ KLocalizedString::setApplicationDomain("kioclient");
+
+ QString appName = QStringLiteral("kioclient");
+ QString programName = i18n("KIO Client");
+ QString description = i18n("Command-line tool for network-transparent operations");
+ QString version = QLatin1String(PROJECT_VERSION);
+ KAboutData data(appName, programName, version, description, KAboutLicense::LGPL_V2);
+ KAboutData::setApplicationData(data);
+
+ QCommandLineParser parser;
+ data.setupCommandLine(&parser);
+ parser.addOption(QCommandLineOption(QStringLiteral("interactive"), i18n("Use message boxes and other native notifications")));
+
+ parser.addOption(QCommandLineOption(QStringLiteral("noninteractive"),
+ i18n("Non-interactive use: no message boxes. If you don't want a "
+ "graphical connection, use --platform offscreen")));
+
+#if !defined(KIOCLIENT_AS_KDEOPEN)
+ parser.addOption(QCommandLineOption(QStringLiteral("overwrite"), i18n("Overwrite destination if it exists (for copy and move)")));
+#endif
+
+#if defined(KIOCLIENT_AS_KDEOPEN)
+ parser.addPositionalArgument(QStringLiteral("url"), i18n("file or URL"), i18n("urls..."));
+#elif defined(KIOCLIENT_AS_KDECP)
+ parser.addPositionalArgument(QStringLiteral("src"), i18n("Source URL or URLs"), i18n("urls..."));
+ parser.addPositionalArgument(QStringLiteral("dest"), i18n("Destination URL"), i18n("url"));
+#elif defined(KIOCLIENT_AS_KDEMV)
+ parser.addPositionalArgument(QStringLiteral("src"), i18n("Source URL or URLs"), i18n("urls..."));
+ parser.addPositionalArgument(QStringLiteral("dest"), i18n("Destination URL"), i18n("url"));
+#elif defined(KIOCLIENT_AS_KIOCLIENT)
+ parser.addOption(QCommandLineOption(QStringLiteral("commands"), i18n("Show available commands")));
+ parser.addPositionalArgument(QStringLiteral("command"), i18n("Command (see --commands)"), i18n("command"));
+ parser.addPositionalArgument(QStringLiteral("URLs"), i18n("Arguments for command"), i18n("urls..."));
+#endif
+
+ // KCmdLineArgs::addTempFileOption();
+
+ parser.process(app);
+ data.processCommandLine(&parser);
+
+#ifdef KIOCLIENT_AS_KIOCLIENT
+ if (argc == 1 || parser.isSet(QStringLiteral("commands"))) {
+ puts(parser.helpText().toLocal8Bit().constData());
+ puts("\n\n");
+ usage();
+ return 0;
+ }
+#endif
+
+ ClientApp client;
+ return client.doIt(parser) ? 0 /*no error*/ : 1 /*error*/;
+}
+
+static void checkArgumentCount(int count, int min, int max)
+{
+ if (count < min) {
+ fputs(i18nc("@info:shell", "%1: Syntax error, not enough arguments\n", qAppName()).toLocal8Bit().constData(), stderr);
+ ::exit(1);
+ }
+ if (max && (count > max)) {
+ fputs(i18nc("@info:shell", "%1: Syntax error, too many arguments\n", qAppName()).toLocal8Bit().constData(), stderr);
+ ::exit(1);
+ }
+}
+
+#ifndef KIOCORE_ONLY
+bool ClientApp::kde_open(const QString &url, const QString &mimeType, bool allowExec)
+{
+ UrlInfo info(url);
+
+ if (!info.atStart()) {
+ QUrlQuery q;
+ q.addQueryItem(QStringLiteral("line"), QString::number(info.line));
+ q.addQueryItem(QStringLiteral("column"), QString::number(info.column));
+ info.url.setQuery(q);
+ }
+
+ auto *job = new KIO::OpenUrlJob(info.url, mimeType);
+ job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
+ job->setRunExecutables(allowExec);
+ job->setFollowRedirections(false);
+ bool job_had_error = false;
+ QObject::connect(job, &KJob::result, this, [&](KJob *job) {
+ if (job->error()) {
+ job_had_error = true;
+ }
+ });
+ job->start();
+ qApp->exec();
+ return !job_had_error;
+}
+#endif
+
+bool ClientApp::doCopy(const QStringList &urls)
+{
+ QList srcLst(makeUrls(urls));
+ QUrl dest = srcLst.takeLast();
+ KIO::Job *job = KIO::copy(srcLst, dest, s_jobFlags);
+ if (!s_interactive) {
+ job->setUiDelegate(nullptr);
+ job->setUiDelegateExtension(nullptr);
+ }
+ connect(job, &KJob::result, this, &ClientApp::slotResult);
+ qApp->exec();
+ return m_ok;
+}
+
+void ClientApp::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
+{
+ for (const auto &entry : list) {
+ // For each file...
+ std::cout << qPrintable(entry.stringValue(KIO::UDSEntry::UDS_NAME)) << '\n';
+ }
+
+ std::cout << std::endl;
+}
+
+bool ClientApp::doList(const QStringList &urls)
+{
+ const QUrl dir = makeURL(urls.at(0));
+
+ KIO::ListJob *job = KIO::listDir(dir, KIO::HideProgressInfo);
+ if (!s_interactive) {
+ job->setUiDelegate(nullptr);
+ job->setUiDelegateExtension(nullptr);
+ }
+
+ connect(job, &KIO::ListJob::entries, this, &ClientApp::slotEntries);
+ connect(job, &KJob::result, this, &ClientApp::slotResult);
+
+ qApp->exec();
+ return m_ok;
+}
+
+bool ClientApp::doMove(const QStringList &urls)
+{
+ QList srcLst(makeUrls(urls));
+ const QUrl dest = srcLst.takeLast();
+
+ KIO::Job *job = KIO::move(srcLst, dest, s_jobFlags);
+ if (!s_interactive) {
+ job->setUiDelegate(nullptr);
+ job->setUiDelegateExtension(nullptr);
+ }
+
+ connect(job, &KJob::result, this, &ClientApp::slotResult);
+
+ qApp->exec();
+ return m_ok;
+}
+
+bool ClientApp::doRemove(const QStringList &urls)
+{
+ KIO::Job *job = KIO::del(makeUrls(urls), s_jobFlags);
+ if (!s_interactive) {
+ job->setUiDelegate(nullptr);
+ job->setUiDelegateExtension(nullptr);
+ }
+
+ connect(job, &KJob::result, this, &ClientApp::slotResult);
+
+ qApp->exec();
+ return m_ok;
+}
+
+bool ClientApp::doStat(const QStringList &urls)
+{
+ KIO::Job *job = KIO::statDetails(makeURL(urls.first()),
+ KIO::StatJob::SourceSide,
+ (KIO::StatBasic | KIO::StatUser | KIO::StatTime | KIO::StatInode | KIO::StatMimeType | KIO::StatAcl),
+ s_jobFlags);
+ if (!s_interactive) {
+ job->setUiDelegate(nullptr);
+ job->setUiDelegateExtension(nullptr);
+ }
+
+ connect(job, &KJob::result, this, &ClientApp::slotStatResult);
+
+ qApp->exec();
+ return m_ok;
+}
+
+bool ClientApp::doIt(const QCommandLineParser &parser)
+{
+ const int argc = parser.positionalArguments().count();
+ checkArgumentCount(argc, 1, 0);
+
+ if (parser.isSet(QStringLiteral("interactive"))) {
+ s_interactive = true;
+ } else {
+ // "noninteractive" is currently the default mode, so we don't check.
+ // The argument still needs to exist for compatibility
+ s_interactive = false;
+ s_jobFlags = KIO::HideProgressInfo;
+ }
+#if !defined(KIOCLIENT_AS_KDEOPEN)
+ if (parser.isSet(QStringLiteral("overwrite"))) {
+ s_jobFlags |= KIO::Overwrite;
+ }
+#endif
+
+#ifdef KIOCLIENT_AS_KDEOPEN
+ return kde_open(parser.positionalArguments().at(0), QString(), false);
+#elif defined(KIOCLIENT_AS_KDECP)
+ checkArgumentCount(argc, 2, 0);
+ return doCopy(parser.positionalArguments());
+#elif defined(KIOCLIENT_AS_KDEMV)
+ checkArgumentCount(argc, 2, 0);
+ return doMove(parser.positionalArguments());
+#else
+ // Normal kioclient mode
+ const QString command = parser.positionalArguments().at(0);
+#ifndef KIOCORE_ONLY
+ if (command == QLatin1String("openProperties")) {
+ checkArgumentCount(argc, 2, 2); // openProperties
+ const QUrl url = makeURL(parser.positionalArguments().constLast());
+
+ KPropertiesDialog *dlg = new KPropertiesDialog(url, nullptr /*no parent*/);
+ QObject::connect(dlg, &QObject::destroyed, qApp, &QCoreApplication::quit);
+ QObject::connect(dlg, &KPropertiesDialog::canceled, this, &ClientApp::slotDialogCanceled);
+ dlg->show();
+
+ qApp->exec();
+ return m_ok;
+ } else
+#endif
+ if (command == QLatin1String("cat")) {
+ checkArgumentCount(argc, 2, 2); // cat
+ const QUrl url = makeURL(parser.positionalArguments().constLast());
+
+ KIO::TransferJob *job = KIO::get(url, KIO::NoReload, s_jobFlags);
+ if (!s_interactive) {
+ job->setUiDelegate(nullptr);
+ job->setUiDelegateExtension(nullptr);
+ }
+ connect(job, &KIO::TransferJob::data, this, &ClientApp::slotPrintData);
+ connect(job, &KJob::result, this, &ClientApp::slotResult);
+
+ qApp->exec();
+ return m_ok;
+ }
+#ifndef KIOCORE_ONLY
+ else if (command == QLatin1String("exec")) {
+ checkArgumentCount(argc, 2, 3);
+ return kde_open(parser.positionalArguments().at(1), argc == 3 ? parser.positionalArguments().constLast() : QString(), true);
+ } else if (command == QLatin1String("appmenu")) {
+ auto *job = new KIO::ApplicationLauncherJob();
+ job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
+ connect(job, &KJob::result, this, &ClientApp::slotResult);
+ job->start();
+
+ qApp->exec();
+ return m_ok;
+ }
+#endif
+ else if (command == QLatin1String("download")) {
+ checkArgumentCount(argc, 0, 0);
+ QStringList args = parser.positionalArguments();
+ args.removeFirst();
+ const QList srcLst = makeUrls(args);
+
+ if (srcLst.isEmpty()) {
+ return m_ok;
+ }
+
+ const QUrl dsturl = QFileDialog::getSaveFileUrl(nullptr, i18n("Destination where to download the files"), srcLst.at(0));
+
+ if (dsturl.isEmpty()) { // canceled
+ return m_ok; // AK - really okay?
+ }
+
+ KIO::Job *job = KIO::copy(srcLst, dsturl, s_jobFlags);
+ if (!s_interactive) {
+ job->setUiDelegate(nullptr);
+ job->setUiDelegateExtension(nullptr);
+ }
+
+ connect(job, &KJob::result, this, &ClientApp::slotResult);
+
+ qApp->exec();
+ return m_ok;
+ } else if (command == QLatin1String("copy") || command == QLatin1String("cp")) {
+ checkArgumentCount(argc, 3, 0); // cp
+ QStringList args = parser.positionalArguments();
+ args.removeFirst();
+ return doCopy(args);
+ } else if (command == QLatin1String("move") || command == QLatin1String("mv")) {
+ checkArgumentCount(argc, 3, 0); // mv
+ QStringList args = parser.positionalArguments();
+ args.removeFirst();
+ return doMove(args);
+ } else if (command == QLatin1String("list") || command == QLatin1String("ls")) {
+ checkArgumentCount(argc, 2, 2); // ls
+ QStringList args = parser.positionalArguments();
+ args.removeFirst();
+ return doList(args);
+ } else if (command == QLatin1String("remove") || command == QLatin1String("rm")) {
+ checkArgumentCount(argc, 2, 0); // rm
+ QStringList args = parser.positionalArguments();
+ args.removeFirst();
+ return doRemove(args);
+ } else if (command == QLatin1String("stat")) {
+ checkArgumentCount(argc, 2, 2); // stat
+ QStringList args = parser.positionalArguments();
+ args.removeFirst();
+ return doStat(args);
+ } else {
+ fputs(i18nc("@info:shell", "%1: Syntax error, unknown command '%2'\n", qAppName(), command).toLocal8Bit().data(), stderr);
+ return false;
+ }
+ Q_UNREACHABLE();
+#endif
+}
+
+void ClientApp::slotResult(KJob *job)
+{
+ if (job->error()) {
+#ifndef KIOCORE_ONLY
+ if (s_interactive) {
+ static_cast(job)->uiDelegate()->showErrorMessage();
+ } else
+#endif
+ {
+ fputs(qPrintable(i18nc("@info:shell", "%1: %2\n", qAppName(), job->errorString())), stderr);
+ }
+ }
+ m_ok = !job->error();
+ if (qApp->topLevelWindows().isEmpty()) {
+ qApp->quit();
+ } else {
+ qApp->setQuitOnLastWindowClosed(true);
+ }
+}
+
+void ClientApp::slotDialogCanceled()
+{
+ m_ok = false;
+ qApp->quit();
+}
+
+void ClientApp::slotPrintData(KIO::Job *, const QByteArray &data)
+{
+ if (!data.isEmpty()) {
+ std::cout.write(data.constData(), data.size());
+ }
+}
+
+static void showStatField(const KIO::UDSEntry &entry, uint field, const char *name)
+{
+ if (!entry.contains(field))
+ return;
+ std::cout << qPrintable(QString::fromLocal8Bit(name).leftJustified(20, ' ')) << " ";
+
+ if (field == KIO::UDSEntry::UDS_ACCESS) {
+ std::cout << qPrintable(QString("0%1").arg(entry.numberValue(field), 3, 8, QLatin1Char('0')));
+ } else if (field == KIO::UDSEntry::UDS_FILE_TYPE) {
+ std::cout << qPrintable(QString("0%1").arg((entry.numberValue(field) & S_IFMT), 6, 8, QLatin1Char('0')));
+ } else if (field & KIO::UDSEntry::UDS_STRING) {
+ std::cout << qPrintable(entry.stringValue(field));
+ } else if ((field & KIO::UDSEntry::UDS_TIME) == KIO::UDSEntry::UDS_TIME) {
+ // The previous comparison is necessary because the value
+ // of UDS_TIME is 0x04000000|UDS_NUMBER which is 0x06000000.
+ // So simply testing with (field & KIO::UDSEntry::UDS_TIME)
+ // would be true for both UDS_TIME and UDS_NUMBER fields.
+ // The same would happen if UDS_NUMBER were tested first.
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(entry.numberValue(field));
+ if (dt.isValid())
+ std::cout << qPrintable(dt.toString(Qt::TextDate));
+ } else if (field & KIO::UDSEntry::UDS_NUMBER) {
+ std::cout << entry.numberValue(field);
+ }
+ std::cout << std::endl;
+}
+
+void ClientApp::slotStatResult(KJob *job)
+{
+ if (!job->error()) {
+ KIO::StatJob *statJob = qobject_cast(job);
+ Q_ASSERT(statJob != nullptr);
+ const KIO::UDSEntry &result = statJob->statResult();
+
+ showStatField(result, KIO::UDSEntry::UDS_NAME, "NAME");
+ showStatField(result, KIO::UDSEntry::UDS_DISPLAY_NAME, "DISPLAY_NAME");
+ showStatField(result, KIO::UDSEntry::UDS_COMMENT, "COMMENT");
+ showStatField(result, KIO::UDSEntry::UDS_SIZE, "SIZE");
+ // This is not requested for the StatJob, so should never be seen
+ showStatField(result, KIO::UDSEntry::UDS_RECURSIVE_SIZE, "RECURSIVE_SIZE");
+
+ showStatField(result, KIO::UDSEntry::UDS_FILE_TYPE, "FILE_TYPE");
+ showStatField(result, KIO::UDSEntry::UDS_USER, "USER");
+ showStatField(result, KIO::UDSEntry::UDS_GROUP, "GROUP");
+ showStatField(result, KIO::UDSEntry::UDS_HIDDEN, "HIDDEN");
+ showStatField(result, KIO::UDSEntry::UDS_DEVICE_ID, "DEVICE_ID");
+ showStatField(result, KIO::UDSEntry::UDS_INODE, "INODE");
+
+ showStatField(result, KIO::UDSEntry::UDS_LINK_DEST, "LINK_DEST");
+ showStatField(result, KIO::UDSEntry::UDS_URL, "URL");
+ showStatField(result, KIO::UDSEntry::UDS_LOCAL_PATH, "LOCAL_PATH");
+ showStatField(result, KIO::UDSEntry::UDS_TARGET_URL, "TARGET_URL");
+
+ showStatField(result, KIO::UDSEntry::UDS_MIME_TYPE, "MIME_TYPE");
+ showStatField(result, KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, "GUESSED_MIME_TYPE");
+
+ showStatField(result, KIO::UDSEntry::UDS_ICON_NAME, "ICON_NAME");
+ showStatField(result, KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES, "ICON_OVERLAY_NAMES");
+
+ showStatField(result, KIO::UDSEntry::UDS_ACCESS, "ACCESS");
+ showStatField(result, KIO::UDSEntry::UDS_EXTENDED_ACL, "EXTENDED_ACL");
+ showStatField(result, KIO::UDSEntry::UDS_ACL_STRING, "ACL_STRING");
+ showStatField(result, KIO::UDSEntry::UDS_DEFAULT_ACL_STRING, "DEFAULT_ACL_STRING");
+
+ showStatField(result, KIO::UDSEntry::UDS_MODIFICATION_TIME, "MODIFICATION_TIME");
+ showStatField(result, KIO::UDSEntry::UDS_ACCESS_TIME, "ACCESS_TIME");
+ showStatField(result, KIO::UDSEntry::UDS_CREATION_TIME, "CREATION_TIME");
+
+ showStatField(result, KIO::UDSEntry::UDS_XML_PROPERTIES, "XML_PROPERTIES");
+ showStatField(result, KIO::UDSEntry::UDS_DISPLAY_TYPE, "DISPLAY_TYPE");
+ }
+
+ slotResult(job);
+}
+
+ClientApp::ClientApp()
+ : QObject()
+{
+}
diff --git a/kioclient/kioclient.h b/kioclient/kioclient.h
new file mode 100644
index 0000000..a205166
--- /dev/null
+++ b/kioclient/kioclient.h
@@ -0,0 +1,47 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 1999-2006 David Faure
+
+ SPDX-License-Identifier: LGPL-2.0-only
+*/
+
+#ifndef __kioclient_h
+#define __kioclient_h
+
+#include
+#include
+
+class QCommandLineParser;
+class KJob;
+namespace KIO
+{
+class Job;
+}
+
+class ClientApp : public QObject
+{
+ Q_OBJECT
+public:
+ ClientApp();
+
+ /** Parse command-line arguments and "do it" */
+ bool doIt(const QCommandLineParser &parser);
+
+private Q_SLOTS:
+ void slotPrintData(KIO::Job *job, const QByteArray &data);
+ void slotEntries(KIO::Job *job, const KIO::UDSEntryList &);
+ void slotResult(KJob *);
+ void slotStatResult(KJob *);
+ void slotDialogCanceled();
+
+private:
+ bool kde_open(const QString &url, const QString &mimeType, bool allowExec);
+ bool doCopy(const QStringList &urls);
+ bool doMove(const QStringList &urls);
+ bool doList(const QStringList &urls);
+ bool doRemove(const QStringList &urls);
+ bool doStat(const QStringList &urls);
+
+ static bool m_ok;
+};
+
+#endif
diff --git a/kioclient/urlinfo.h b/kioclient/urlinfo.h
new file mode 100644
index 0000000..f03ba8f
--- /dev/null
+++ b/kioclient/urlinfo.h
@@ -0,0 +1,107 @@
+/* This file is part of the KDE project
+ SPDX-FileCopyrightText: 2015 Milian Wolff
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef URLINFO_H
+#define URLINFO_H
+
+#include
+#include
+#include
+#include
+
+/**
+ * Represents a file to be opened, consisting of its URL and the cursor to jump to.
+ */
+class UrlInfo
+{
+public:
+ /**
+ * Parses an argument and determines its line number and column and full path
+ * @param pathOrUrl path passed on e.g. command line to parse into an URL or just an URL
+ */
+ UrlInfo(const QString &pathOrUrl)
+ : line(0)
+ , column(0)
+ {
+ /**
+ * first try: just check if the path is an existing file
+ */
+ if (QFile::exists(pathOrUrl)) {
+ /**
+ * create absolute file path, we will e.g. pass this over dbus to other processes
+ * and then we are done, no cursor can be detected here!
+ */
+ url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(pathOrUrl));
+ return;
+ }
+
+ /**
+ * if the path starts with http:// or any other scheme, except file://
+ * we also don't want to do anything with URL
+ */
+ if (!QUrl::fromUserInput(pathOrUrl).isLocalFile()) {
+ url = QUrl::fromUserInput(pathOrUrl, QDir::currentPath(), QUrl::DefaultResolution);
+ // relative paths are not isLocalFile(), but not valid too, so we don't want them
+ if (url.isValid()) {
+ return;
+ }
+ }
+
+ /**
+ * ok, the path as is, is no existing file, now, cut away :xx:yy stuff as cursor
+ * this will make test:50 to test with line 50
+ */
+ QString pathOrUrl2 = pathOrUrl;
+ const auto match = QRegularExpression(QStringLiteral(":(\\d+)(?::(\\d+))?:?$")).match(pathOrUrl2);
+ if (match.isValid()) {
+ /**
+ * cut away the line/column specification from the path
+ */
+ pathOrUrl2.chop(match.capturedLength());
+
+ /**
+ * set right cursor position
+ */
+ line = match.capturedRef(1).toUInt();
+ column = match.capturedRef(2).toUInt();
+ }
+
+ /**
+ * construct url:
+ * - make relative paths absolute using the current working directory
+ * - do not prefer local file, to be able to open things like foo.com in browser
+ */
+ url = QUrl::fromUserInput(pathOrUrl2, QDir::currentPath(), QUrl::DefaultResolution);
+
+ /**
+ * in some cases, this will fail, e.g. if you have line/column specs like test.c:10:1
+ * => fallback: assume a local file and just convert it to an url
+ */
+ if (!url.isValid()) {
+ /**
+ * create absolute file path, we will e.g. pass this over dbus to other processes
+ */
+ url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(pathOrUrl2));
+ }
+ }
+
+ bool atStart() const
+ {
+ return (line == 0 || line == 1) && (column == 0 || column == 1);
+ }
+
+ /**
+ * url computed out of the passed path or URL
+ */
+ QUrl url;
+
+ /**
+ * initial cursor position, if any found inside the path as line/column specification at the end
+ */
+ unsigned line, column;
+};
+
+#endif // URLINFO_H
diff --git a/kmimetypefinder/CMakeLists.txt b/kmimetypefinder/CMakeLists.txt
new file mode 100644
index 0000000..974a96b
--- /dev/null
+++ b/kmimetypefinder/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(kmimetypefinder kmimetypefinder.cpp)
+target_compile_definitions(kmimetypefinder PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+ecm_mark_nongui_executable(kmimetypefinder)
+
+target_link_libraries(kmimetypefinder
+ KF5::CoreAddons # KAboutData
+ KF5::I18n # i18n
+ Qt::Core
+)
+
+install_compat_symlink(kmimetypefinder)
+install(TARGETS kmimetypefinder DESTINATION ${KDE_INSTALL_FULL_BINDIR})
diff --git a/kmimetypefinder/Messages.sh b/kmimetypefinder/Messages.sh
new file mode 100755
index 0000000..513144c
--- /dev/null
+++ b/kmimetypefinder/Messages.sh
@@ -0,0 +1,2 @@
+#! /usr/bin/env bash
+$XGETTEXT *.cpp -o $podir/kmimetypefinder5.pot
diff --git a/kmimetypefinder/kmimetypefinder.cpp b/kmimetypefinder/kmimetypefinder.cpp
new file mode 100644
index 0000000..9886d03
--- /dev/null
+++ b/kmimetypefinder/kmimetypefinder.cpp
@@ -0,0 +1,67 @@
+/*
+ * SPDX-FileCopyrightText: 2002 David Faure
+ * SPDX-FileCopyrightText: 2008 Pino Toscano
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ KLocalizedString::setApplicationDomain("kmimetypefinder5");
+
+ KAboutData aboutData(QLatin1String("kmimetypefinder"), i18n("MIME Type Finder"), QLatin1String(PROJECT_VERSION));
+ aboutData.setShortDescription(i18n("Gives the MIME type for a given file"));
+ KAboutData::setApplicationData(aboutData);
+
+ QCommandLineParser parser;
+ aboutData.setupCommandLine(&parser);
+ parser.addOption(
+ QCommandLineOption(QStringList() << QLatin1String("c") << QLatin1String("content"), i18n("Use only the file content for determining the MIME type.")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("f") << QLatin1String("filename-only"),
+ i18n("Whether use the file name only for determining the MIME type. Not used if -c is specified.")));
+ parser.addPositionalArgument(QLatin1String("filename"), i18n("The filename to test. '-' to read from stdin."));
+
+ parser.process(app);
+ aboutData.processCommandLine(&parser);
+
+ if (parser.positionalArguments().count() < 1) {
+ printf("No filename specified\n");
+ return 1;
+ }
+ const QString fileName = parser.positionalArguments().at(0);
+ QMimeDatabase db;
+ QMimeType mime;
+ if (fileName == QLatin1String("-")) {
+ QFile qstdin;
+ qstdin.open(stdin, QIODevice::ReadOnly);
+ const QByteArray data = qstdin.readAll();
+ mime = db.mimeTypeForData(data);
+ } else if (parser.isSet(QStringLiteral("c"))) {
+ mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
+ } else if (parser.isSet(QStringLiteral("f"))) {
+ mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension);
+ } else {
+ mime = db.mimeTypeForFile(fileName);
+ }
+ if (!mime.isDefault()) {
+ printf("%s\n", mime.name().toLatin1().constData());
+ } else {
+ return 1; // error
+ }
+
+ return 0;
+}
diff --git a/kstart/CMakeLists.txt b/kstart/CMakeLists.txt
new file mode 100644
index 0000000..c9a0835
--- /dev/null
+++ b/kstart/CMakeLists.txt
@@ -0,0 +1,19 @@
+find_package(X11)
+set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries"
+ URL "http://www.x.org"
+ TYPE REQUIRED
+ )
+
+add_executable(kstart kstart.cpp)
+target_compile_definitions(kstart PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+target_link_libraries(kstart
+ Qt::Widgets
+ Qt::X11Extras
+ KF5::I18n
+ KF5::KIOGui
+ KF5::Service
+ KF5::WindowSystem
+ ${X11_X11_LIB})
+
+install_compat_symlink(kstart)
+install(TARGETS kstart DESTINATION ${KDE_INSTALL_FULL_BINDIR})
diff --git a/kstart/Messages.sh b/kstart/Messages.sh
new file mode 100644
index 0000000..0ab3c01
--- /dev/null
+++ b/kstart/Messages.sh
@@ -0,0 +1,2 @@
+#! /usr/bin/env bash
+$XGETTEXT *.cpp -o $podir/kstart5.pot
diff --git a/kstart/kstart.cpp b/kstart/kstart.cpp
new file mode 100644
index 0000000..f23894e
--- /dev/null
+++ b/kstart/kstart.cpp
@@ -0,0 +1,484 @@
+/*
+ * kstart.C. Part of the KDE project.
+ *
+ * SPDX-FileCopyrightText: 1997-2000 Matthias Ettrich
+ * SPDX-FileCopyrightText: David Faure
+ * SPDX-FileCopyrightText: Richard Moore
+ *
+ * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+ */
+
+#include
+
+#include "kstart.h"
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+// some globals
+
+static QString servicePath; // TODO KF6 remove
+static QString serviceName;
+static QString exe;
+static QStringList exeArgs;
+static QString url;
+static QString windowtitle;
+static QString windowclass;
+static int desktop = 0;
+static bool activate = false;
+static bool iconify = false;
+static bool fullscreen = false;
+static NET::States state = {};
+static NET::States mask = {};
+static NET::WindowType windowtype = NET::Unknown;
+
+KStart::KStart()
+ : QObject()
+{
+ bool useRule = false;
+
+#ifdef HAVE_X11
+ if (QX11Info::isPlatformX11()) {
+ NETRootInfo i(QX11Info::connection(), NET::Supported);
+ useRule = i.isSupported(NET::WM2KDETemporaryRules);
+ }
+#endif
+
+ if (useRule) {
+ sendRule();
+ } else {
+ // connect to window add to get the NEW windows
+ connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &KStart::windowAdded);
+ }
+ // propagate the app startup notification info to the started app
+ // We are not using KApplication, so the env remained set
+ KStartupInfoId id = KStartupInfo::currentStartupIdEnv();
+
+ // finally execute the comand
+ if (!servicePath.isEmpty()) { // TODO KF6 remove
+ QString error;
+ QString dbusService;
+ int pid;
+ if (KToolInvocation::startServiceByDesktopPath(exe, url, &error, &dbusService, &pid) == 0) {
+ printf("%s\n", qPrintable(dbusService));
+ } else {
+ qCritical() << error;
+ }
+ } else if (!serviceName.isEmpty()) {
+ KService::Ptr service = KService::serviceByDesktopName(serviceName);
+ if (!service) {
+ qCritical() << "No such service" << exe;
+ } else {
+ auto *job = new KIO::ApplicationLauncherJob(service);
+ if (!url.isEmpty()) {
+ job->setUrls({QUrl(url)}); // TODO use QUrl::fromUserInput(PreferLocalFile)?
+ }
+ job->exec();
+ if (job->error()) {
+ qCritical() << job->errorString();
+ } else {
+ std::cout << job->pid() << std::endl;
+ }
+ }
+ } else {
+ auto *job = new KIO::CommandLauncherJob(exe, exeArgs);
+ job->exec();
+ }
+
+ QTimer::singleShot(useRule ? 0 : 120 * 1000, qApp, SLOT(quit()));
+}
+
+void KStart::sendRule()
+{
+ KXMessages msg;
+ QString message;
+ if (!windowtitle.isEmpty()) {
+ message += QStringLiteral("title=") + windowtitle + QStringLiteral("\ntitlematch=3\n"); // 3 = regexp match
+ }
+ if (!windowclass.isEmpty()) {
+ message += QStringLiteral("wmclass=") + windowclass + QStringLiteral("\nwmclassmatch=1\n") // 1 = exact match
+ + QStringLiteral("wmclasscomplete=")
+ // if windowclass contains a space (i.e. 2 words, use whole WM_CLASS)
+ + (windowclass.contains(QLatin1Char(' ')) ? QStringLiteral("true") : QStringLiteral("false")) + QLatin1Char('\n');
+ }
+ if ((!windowtitle.isEmpty()) || (!windowclass.isEmpty())) {
+ // always ignore these window types
+ message += QStringLiteral("types=")
+ + QString().setNum(-1U & ~(NET::TopMenuMask | NET::ToolbarMask | NET::DesktopMask | NET::SplashMask | NET::MenuMask)) + QLatin1Char('\n');
+ } else {
+ // accept only "normal" windows
+ message += QStringLiteral("types=") + QString().setNum(NET::NormalMask | NET::DialogMask) + QLatin1Char('\n');
+ }
+ if ((desktop > 0 && desktop <= KWindowSystem::numberOfDesktops()) || desktop == NETWinInfo::OnAllDesktops) {
+ message += QStringLiteral("desktop=") + QString().setNum(desktop) + QStringLiteral("\ndesktoprule=3\n");
+ }
+ if (activate) {
+ message += QStringLiteral("fsplevel=0\nfsplevelrule=2\n");
+ }
+ if (iconify) {
+ message += QStringLiteral("minimize=true\nminimizerule=3\n");
+ }
+ if (windowtype != NET::Unknown) {
+ message += QStringLiteral("type=") + QString().setNum(windowtype) + QStringLiteral("\ntyperule=2");
+ }
+ if (state) {
+ if (state & NET::KeepAbove) {
+ message += QStringLiteral("above=true\naboverule=3\n");
+ }
+ if (state & NET::KeepBelow) {
+ message += QStringLiteral("below=true\nbelowrule=3\n");
+ }
+ if (state & NET::SkipTaskbar) {
+ message += QStringLiteral("skiptaskbar=true\nskiptaskbarrule=3\n");
+ }
+ if (state & NET::SkipPager) {
+ message += QStringLiteral("skippager=true\nskippagerrule=3\n");
+ }
+ if (state & NET::MaxVert) {
+ message += QStringLiteral("maximizevert=true\nmaximizevertrule=3\n");
+ }
+ if (state & NET::MaxHoriz) {
+ message += QStringLiteral("maximizehoriz=true\nmaximizehorizrule=3\n");
+ }
+ if (state & NET::FullScreen) {
+ message += QStringLiteral("fullscreen=true\nfullscreenrule=3\n");
+ }
+ }
+
+ msg.broadcastMessage("_KDE_NET_WM_TEMPORARY_RULES", message, -1);
+}
+
+const NET::WindowTypes SUPPORTED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
+ | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask;
+
+void KStart::windowAdded(WId w)
+{
+ KWindowInfo info(w, NET::WMWindowType | NET::WMName);
+
+ // always ignore these window types
+ if (info.windowType(SUPPORTED_WINDOW_TYPES_MASK) == NET::TopMenu || info.windowType(SUPPORTED_WINDOW_TYPES_MASK) == NET::Toolbar
+ || info.windowType(SUPPORTED_WINDOW_TYPES_MASK) == NET::Desktop) {
+ return;
+ }
+
+ if (!windowtitle.isEmpty()) {
+ QString title = info.name().toLower();
+ QRegExp r(windowtitle.toLower());
+ if (!r.exactMatch(title)) {
+ return; // no match
+ }
+ }
+ if (!windowclass.isEmpty()) {
+#ifdef __GNUC__
+#warning "Porting required"
+#endif
+#if 0
+ XClassHint hint;
+ if( !XGetClassHint( QX11Info::display(), w, &hint ))
+ return;
+ Q3CString cls = windowclass.contains( ' ' )
+ ? Q3CString( hint.res_name ) + ' ' + hint.res_class : Q3CString( hint.res_class );
+ cls = cls.toLower();
+ XFree( hint.res_name );
+ XFree( hint.res_class );
+ if( cls != windowclass )
+ return;
+#endif
+ }
+ if (windowtitle.isEmpty() && windowclass.isEmpty()) {
+ // accept only "normal" windows
+ if (info.windowType(SUPPORTED_WINDOW_TYPES_MASK) != NET::Unknown && info.windowType(SUPPORTED_WINDOW_TYPES_MASK) != NET::Normal
+ && info.windowType(SUPPORTED_WINDOW_TYPES_MASK) != NET::Dialog) {
+ return;
+ }
+ }
+ applyStyle(w);
+ QApplication::exit();
+}
+
+// extern Atom qt_wm_state; // defined in qapplication_x11.cpp
+static bool wstate_withdrawn(WId winid)
+{
+ Q_UNUSED(winid);
+
+#ifdef __GNUC__
+#warning "Porting required."
+#endif
+ // Porting info: The Qt4 equivalent for qt_wm_state is qt_x11Data->atoms[QX11Data::WM_STATE]
+ // which can be accessed via the macro ATOM(WM_STATE). Unfortunately, neither of these seem
+ // to be exported out of the Qt environment. This value may have to be acquired from somewhere else.
+ /*
+ Atom type;
+ int format;
+ unsigned long length, after;
+ unsigned char *data;
+ int r = XGetWindowProperty( QX11Info::display(), winid, qt_wm_state, 0, 2,
+ false, AnyPropertyType, &type, &format,
+ &length, &after, &data );
+ bool withdrawn = true;
+ if ( r == Success && data && format == 32 ) {
+ quint32 *wstate = (quint32*)data;
+ withdrawn = (*wstate == WithdrawnState );
+ XFree( (char *)data );
+ }
+ return withdrawn;
+ */
+ return true;
+}
+
+void KStart::applyStyle(WId w)
+{
+ if (state || iconify || windowtype != NET::Unknown || desktop >= 1) {
+ XWithdrawWindow(QX11Info::display(), w, QX11Info::appScreen());
+
+ while (!wstate_withdrawn(w)) {
+ ;
+ }
+ }
+
+ NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
+
+ if ((desktop > 0 && desktop <= KWindowSystem::numberOfDesktops()) || desktop == NETWinInfo::OnAllDesktops) {
+ info.setDesktop(desktop);
+ }
+
+ if (iconify) {
+ XWMHints *hints = XGetWMHints(QX11Info::display(), w);
+ if (hints) {
+ hints->flags |= StateHint;
+ hints->initial_state = IconicState;
+ XSetWMHints(QX11Info::display(), w, hints);
+ XFree(hints);
+ }
+ }
+
+ if (windowtype != NET::Unknown) {
+ info.setWindowType(windowtype);
+ }
+
+ if (state) {
+ info.setState(state, mask);
+ }
+
+ if (fullscreen) {
+ QRect r = QGuiApplication::primaryScreen()->geometry();
+ XMoveResizeWindow(QX11Info::display(), w, r.x(), r.y(), r.width(), r.height());
+ }
+
+ XSync(QX11Info::display(), False);
+
+ XMapWindow(QX11Info::display(), w);
+ XSync(QX11Info::display(), False);
+
+ if (activate) {
+ KWindowSystem::forceActiveWindow(w);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ KLocalizedString::setApplicationDomain("kstart5");
+
+ KAboutData aboutData(QStringLiteral("kstart"),
+ i18n("KStart"),
+ QString::fromLatin1(PROJECT_VERSION),
+ i18n(""
+ "Utility to launch applications with special window properties \n"
+ "such as iconified, maximized, a certain virtual desktop, a special decoration\n"
+ "and so on."),
+ KAboutLicense::GPL,
+ i18n("(C) 1997-2000 Matthias Ettrich (ettrich@kde.org)"));
+
+ aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org"));
+ aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org"));
+ aboutData.addAuthor(i18n("Richard J. Moore"), QString(), QStringLiteral("rich@kde.org"));
+ KAboutData::setApplicationData(aboutData);
+
+ QCommandLineParser parser;
+ aboutData.setupCommandLine(&parser);
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("!+command"), i18n("Command to execute")));
+ // TODO KF6 remove
+ parser.addOption(
+ QCommandLineOption(QStringList() << QLatin1String("service"),
+ i18n("Alternative to : desktop file path to start. D-Bus service will be printed to stdout. Deprecated: use --application"),
+ QLatin1String("desktopfile")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("application"),
+ i18n("Alternative to : desktop file to start."),
+ QLatin1String("desktopfile")));
+ parser.addOption(
+ QCommandLineOption(QStringList() << QLatin1String("url"), i18n("Optional URL to pass , when using --service"), QLatin1String("url")));
+ // "!" means: all options after command are treated as arguments to the command
+ parser.addOption(
+ QCommandLineOption(QStringList() << QLatin1String("window"), i18n("A regular expression matching the window title"), QLatin1String("regexp")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("windowclass"),
+ i18n("A string matching the window class (WM_CLASS property)\n"
+ "The window class can be found out by running\n"
+ "'xprop | grep WM_CLASS' and clicking on a window\n"
+ "(use either both parts separated by a space or only the right part).\n"
+ "NOTE: If you specify neither window title nor window class,\n"
+ "then the very first window to appear will be taken;\n"
+ "omitting both options is NOT recommended."),
+ QLatin1String("class")));
+ parser.addOption(
+ QCommandLineOption(QStringList() << QLatin1String("desktop"), i18n("Desktop on which to make the window appear"), QLatin1String("number")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("currentdesktop"),
+ i18n("Make the window appear on the desktop that was active\nwhen starting the application")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("alldesktops"), i18n("Make the window appear on all desktops")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("iconify"), i18n("Iconify the window")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("maximize"), i18n("Maximize the window")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("maximize-vertically"), i18n("Maximize the window vertically")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("maximize-horizontally"), i18n("Maximize the window horizontally")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("fullscreen"), i18n("Show window fullscreen")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("type"),
+ i18n("The window type: Normal, Desktop, Dock, Toolbar, \nMenu, Dialog, TopMenu or Override"),
+ QLatin1String("type")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("activate"),
+ i18n("Jump to the window even if it is started on a \n"
+ "different virtual desktop")));
+ parser.addOption(
+ QCommandLineOption(QStringList() << QLatin1String("ontop") << QLatin1String("keepabove"), i18n("Try to keep the window above other windows")));
+ parser.addOption(
+ QCommandLineOption(QStringList() << QLatin1String("onbottom") << QLatin1String("keepbelow"), i18n("Try to keep the window below other windows")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("skiptaskbar"), i18n("The window does not get an entry in the taskbar")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("skippager"), i18n("The window does not get an entry on the pager")));
+
+ parser.process(app);
+ aboutData.processCommandLine(&parser);
+
+ if (parser.isSet(QStringLiteral("service"))) {
+ servicePath = parser.value(QStringLiteral("service"));
+ url = parser.value(QStringLiteral("url"));
+ } else if (parser.isSet(QStringLiteral("application"))) {
+ serviceName = parser.value(QStringLiteral("application"));
+ url = parser.value(QStringLiteral("url"));
+ } else {
+ QStringList positionalArgs = parser.positionalArguments();
+ if (positionalArgs.isEmpty()) {
+ qCritical() << i18n("No command specified");
+ parser.showHelp(1);
+ }
+
+ exe = positionalArgs.takeFirst();
+ exeArgs = positionalArgs;
+ }
+
+ desktop = parser.value(QStringLiteral("desktop")).toInt();
+ if (parser.isSet(QStringLiteral("alldesktops"))) {
+ desktop = NETWinInfo::OnAllDesktops;
+ }
+ if (parser.isSet(QStringLiteral("currentdesktop"))) {
+ desktop = KWindowSystem::currentDesktop();
+ }
+
+ windowtitle = parser.value(QStringLiteral("window"));
+ windowclass = parser.value(QStringLiteral("windowclass"));
+ if (!windowclass.isEmpty()) {
+ windowclass = windowclass.toLower();
+ }
+
+ if (windowtitle.isEmpty() && windowclass.isEmpty()) {
+ qWarning() << "Omitting both --window and --windowclass arguments is not recommended";
+ }
+
+ QString s = parser.value(QStringLiteral("type"));
+ if (!s.isEmpty()) {
+ s = s.toLower();
+ if (s == QLatin1String("desktop")) {
+ windowtype = NET::Desktop;
+ } else if (s == QLatin1String("dock")) {
+ windowtype = NET::Dock;
+ } else if (s == QLatin1String("toolbar")) {
+ windowtype = NET::Toolbar;
+ } else if (s == QLatin1String("menu")) {
+ windowtype = NET::Menu;
+ } else if (s == QLatin1String("dialog")) {
+ windowtype = NET::Dialog;
+ } else if (s == QLatin1String("override")) {
+ windowtype = NET::Override;
+ } else if (s == QLatin1String("topmenu")) {
+ windowtype = NET::TopMenu;
+ } else {
+ windowtype = NET::Normal;
+ }
+ }
+
+ if (parser.isSet(QStringLiteral("keepabove"))) {
+ state |= NET::KeepAbove;
+ mask |= NET::KeepAbove;
+ } else if (parser.isSet(QStringLiteral("keepbelow"))) {
+ state |= NET::KeepBelow;
+ mask |= NET::KeepBelow;
+ }
+
+ if (parser.isSet(QStringLiteral("skiptaskbar"))) {
+ state |= NET::SkipTaskbar;
+ mask |= NET::SkipTaskbar;
+ }
+
+ if (parser.isSet(QStringLiteral("skippager"))) {
+ state |= NET::SkipPager;
+ mask |= NET::SkipPager;
+ }
+
+ activate = parser.isSet(QStringLiteral("activate"));
+
+ if (parser.isSet(QStringLiteral("maximize"))) {
+ state |= NET::Max;
+ mask |= NET::Max;
+ }
+ if (parser.isSet(QStringLiteral("maximize-vertically"))) {
+ state |= NET::MaxVert;
+ mask |= NET::MaxVert;
+ }
+ if (parser.isSet(QStringLiteral("maximize-horizontally"))) {
+ state |= NET::MaxHoriz;
+ mask |= NET::MaxHoriz;
+ }
+
+ iconify = parser.isSet(QStringLiteral("iconify"));
+ if (parser.isSet(QStringLiteral("fullscreen"))) {
+ NETRootInfo i(QX11Info::connection(), NET::Supported);
+ if (i.isSupported(NET::FullScreen)) {
+ state |= NET::FullScreen;
+ mask |= NET::FullScreen;
+ } else {
+ windowtype = NET::Override;
+ fullscreen = true;
+ }
+ }
+
+ fcntl(XConnectionNumber(QX11Info::display()), F_SETFD, 1);
+
+ KStart start;
+
+ return app.exec();
+}
diff --git a/kstart/kstart.h b/kstart/kstart.h
new file mode 100644
index 0000000..4084890
--- /dev/null
+++ b/kstart/kstart.h
@@ -0,0 +1,34 @@
+/*
+ * kstart.h Part of the KDE project.
+ *
+ * SPDX-FileCopyrightText: 1997-2000 Matthias Ettrich
+ * SPDX-FileCopyrightText: David Faure
+ *
+ * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+ *
+ */
+
+#ifndef KSTART_H
+#define KSTART_H
+
+#include
+
+class KStart : public QObject
+{
+ Q_OBJECT
+
+public:
+ KStart();
+ ~KStart() override
+ {
+ }
+
+public Q_SLOTS:
+ void windowAdded(WId);
+
+private:
+ void applyStyle(WId);
+ void sendRule();
+};
+
+#endif
diff --git a/ksvgtopng/CMakeLists.txt b/ksvgtopng/CMakeLists.txt
new file mode 100644
index 0000000..47438af
--- /dev/null
+++ b/ksvgtopng/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_executable(ksvgtopng ksvgtopng.cpp)
+ecm_mark_nongui_executable(ksvgtopng)
+target_link_libraries(ksvgtopng Qt::Svg)
+install_compat_symlink(ksvgtopng)
+install(TARGETS ksvgtopng DESTINATION ${KDE_INSTALL_FULL_BINDIR})
diff --git a/ksvgtopng/ksvgtopng.cpp b/ksvgtopng/ksvgtopng.cpp
new file mode 100644
index 0000000..0c65da2
--- /dev/null
+++ b/ksvgtopng/ksvgtopng.cpp
@@ -0,0 +1,55 @@
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+using std::cout;
+using std::endl;
+
+int main(int argc, char **argv)
+{
+ // Initialize Qt application, otherwise for some svg files it can segfault with:
+ // ASSERT failure in QFontDatabase: "A QApplication object needs to be
+ // constructed before FontConfig is used."
+ QApplication app(argc, argv);
+
+ if (argc < 5) {
+ cout << "Usage : ksvgtopng width height svgfilename outputfilename" << endl;
+ cout << "Please use full path name for svgfilename" << endl;
+ return -1;
+ }
+
+ int width = atoi(argv[1]);
+ int height = atoi(argv[2]);
+
+ QImage img(width, height, QImage::Format_ARGB32_Premultiplied);
+ img.fill(0);
+
+ QSvgRenderer renderer(QString::fromLocal8Bit(argv[3]));
+ if (renderer.isValid()) {
+ QPainter p(&img);
+ renderer.render(&p);
+ /*
+ // Apply icon sharpening
+ double factor = 0;
+
+ if(width == 16)
+ factor = 30;
+ else if(width == 32)
+ factor = 20;
+ else if(width == 48)
+ factor = 10;
+ else if(width == 64)
+ factor = 5;
+
+ *img = KImageEffect::sharpen(*img, factor); // use QImageBlitz::sharpen()
+ */
+ }
+
+ img.save(QString::fromLatin1(argv[4]), "PNG");
+
+ return 0;
+}
diff --git a/ktraderclient/CMakeLists.txt b/ktraderclient/CMakeLists.txt
new file mode 100644
index 0000000..542552f
--- /dev/null
+++ b/ktraderclient/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(ktraderclient5 ktraderclient.cpp)
+target_compile_definitions(ktraderclient5 PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+ecm_mark_nongui_executable(ktraderclient5)
+
+target_link_libraries(ktraderclient5
+ KF5::Service # KService, traders...
+ KF5::CoreAddons # KAboutData
+ KF5::I18n # i18n
+ Qt::Core
+)
+
+install(TARGETS ktraderclient5 ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/ktraderclient/Messages.sh b/ktraderclient/Messages.sh
new file mode 100755
index 0000000..1d690af
--- /dev/null
+++ b/ktraderclient/Messages.sh
@@ -0,0 +1,2 @@
+#! /usr/bin/env bash
+$XGETTEXT *.cpp -o $podir/ktraderclient5.pot
diff --git a/ktraderclient/ktraderclient.cpp b/ktraderclient/ktraderclient.cpp
new file mode 100644
index 0000000..9f60e5b
--- /dev/null
+++ b/ktraderclient/ktraderclient.cpp
@@ -0,0 +1,112 @@
+/*
+ * SPDX-FileCopyrightText: 2002, 2003 David Faure
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ KLocalizedString::setApplicationDomain("ktraderclient5");
+ KAboutData aboutData(QLatin1String("ktraderclient"), i18n("KTraderClient"), QLatin1String(PROJECT_VERSION));
+ aboutData.addAuthor(i18n("David Faure"), QString(), QStringLiteral("faure@kde.org"));
+
+ aboutData.setShortDescription(i18n("A command-line tool for querying the KDE trader system"));
+ KAboutData::setApplicationData(aboutData);
+
+ QCommandLineParser parser;
+ aboutData.setupCommandLine(&parser);
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("mimetype"), i18n("A MIME type"), QLatin1String("mimetype")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("servicetype"),
+ i18n("A servicetype, like KParts/ReadOnlyPart or KMyApp/Plugin"),
+ QLatin1String("servicetype")));
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("constraint"),
+ i18n("A constraint expressed in the trader query language"),
+ QLatin1String("constraint")));
+
+ parser.addOption(QCommandLineOption(QStringList() << QLatin1String("short"), i18n("Output only paths to desktop files")));
+
+ parser.process(app);
+ aboutData.processCommandLine(&parser);
+
+ const QString mimetype = parser.value(QStringLiteral("mimetype"));
+ QString servicetype = parser.value(QStringLiteral("servicetype"));
+ const QString constraint = parser.value(QStringLiteral("constraint"));
+ const bool outputProperties = !parser.isSet(QStringLiteral("short"));
+
+ if (mimetype.isEmpty() && servicetype.isEmpty()) {
+ parser.showHelp();
+ }
+
+ if (!mimetype.isEmpty()) {
+ printf("mimetype is : %s\n", qPrintable(mimetype));
+ }
+ if (!servicetype.isEmpty()) {
+ printf("servicetype is : %s\n", qPrintable(servicetype));
+ }
+ if (!constraint.isEmpty()) {
+ printf("constraint is : %s\n", qPrintable(constraint));
+ }
+
+ KService::List offers;
+ if (!mimetype.isEmpty()) {
+ if (servicetype.isEmpty()) {
+ servicetype = QStringLiteral("Application");
+ }
+ offers = KMimeTypeTrader::self()->query(mimetype, servicetype, constraint);
+ } else {
+ offers = KServiceTypeTrader::self()->query(servicetype, constraint);
+ }
+
+ printf("got %d offers.\n", offers.count());
+
+ int i = 0;
+ for (const auto &service : std::as_const(offers)) {
+ if (outputProperties) {
+ printf("---- Offer %d ----\n", i++);
+
+ const QStringList props = service->propertyNames();
+ for (const QString &propName : props) {
+ QVariant prop = service->property(propName);
+
+ if (!prop.isValid()) {
+ printf("Invalid property %s\n", propName.toLocal8Bit().data());
+ continue;
+ }
+
+ QString outp = propName;
+ outp += QStringLiteral(" : '");
+
+ switch (prop.type()) {
+ case QVariant::StringList:
+ outp += prop.toStringList().join(QStringLiteral(" - "));
+ break;
+ case QVariant::Bool:
+ outp += prop.toBool() ? QStringLiteral("TRUE") : QStringLiteral("FALSE");
+ break;
+ default:
+ outp += prop.toString();
+ break;
+ }
+
+ if (!outp.isEmpty()) {
+ printf("%s'\n", outp.toLocal8Bit().constData());
+ }
+ }
+ } else {
+ printf("%s\n", service->entryPath().toLocal8Bit().constData());
+ }
+ }
+ return 0;
+}
diff --git a/plasma-open-settings/CMakeLists.txt b/plasma-open-settings/CMakeLists.txt
new file mode 100644
index 0000000..96f9f08
--- /dev/null
+++ b/plasma-open-settings/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+add_executable(plasma-open-settings main.cpp)
+target_compile_definitions(plasma-open-settings PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+target_link_libraries(plasma-open-settings Qt::Core KF5::CoreAddons KF5::KIOGui KF5::I18n)
+install(TARGETS plasma-open-settings ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+install(FILES org.kde.plasma.settings.open.desktop DESTINATION ${KDE_INSTALL_APPDIR})
diff --git a/plasma-open-settings/Messages.sh b/plasma-open-settings/Messages.sh
new file mode 100644
index 0000000..00e2d77
--- /dev/null
+++ b/plasma-open-settings/Messages.sh
@@ -0,0 +1,5 @@
+#! /usr/bin/env bash
+# SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+$XGETTEXT *.cpp -o $podir/plasma-open-settings.pot
diff --git a/plasma-open-settings/main.cpp b/plasma-open-settings/main.cpp
new file mode 100644
index 0000000..be6916e
--- /dev/null
+++ b/plasma-open-settings/main.cpp
@@ -0,0 +1,58 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+ KLocalizedString::setApplicationDomain("plasma-open-settings");
+
+ KAboutData aboutData(QStringLiteral("plasma-open-settings"), //
+ i18n("App to open Settings app"),
+ QLatin1String(PROJECT_VERSION),
+ i18n("A tool to start system settings"),
+ KAboutLicense::GPL,
+ i18n("(c) 2021, The KDE Developers"));
+
+ aboutData.addAuthor(QStringLiteral("Aleix Pol i Gonzalez"), {}, QStringLiteral("aleixpol@kde.org"));
+
+ const QUrl url(app.arguments().constLast());
+ QString moduleName = url.host().isEmpty() ? url.path() : url.host();
+ if (moduleName.startsWith('/')) {
+ moduleName = moduleName.mid(1);
+ }
+ KIO::CommandLauncherJob *job = nullptr;
+ int ret = 0;
+ if (!QStandardPaths::findExecutable("systemsettings").isEmpty()) {
+ job = new KIO::CommandLauncherJob(QStringLiteral("systemsettings"), {moduleName});
+ job->setDesktopName(QStringLiteral("org.kde.systemsettings"));
+ } else if (!QStandardPaths::findExecutable("plasma-settings").isEmpty()) {
+ job = new KIO::CommandLauncherJob(QStringLiteral("plasma-settings"), {"-m", moduleName});
+ job->setDesktopName(QStringLiteral("org.kde.plasma.settings"));
+ } else if (!QStandardPaths::findExecutable("kcmshell5").isEmpty()) {
+ job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), {moduleName});
+ } else if (!QStandardPaths::findExecutable("kdialog").isEmpty()) {
+ job = new KIO::CommandLauncherJob(QStringLiteral("kdialog"), {"--error", i18n("Could not open: %1", moduleName)});
+ ret = 1;
+ } else {
+ QTextStream err(stderr);
+ err << "Could not open:" << moduleName << url.toString() << Qt::endl;
+ return 32;
+ }
+
+ if (!qEnvironmentVariableIsEmpty("XDG_ACTIVATION_TOKEN")) {
+ job->setStartupId(qgetenv("XDG_ACTIVATION_TOKEN"));
+ }
+ return !job->exec() + ret;
+}
diff --git a/plasma-open-settings/org.kde.plasma.settings.open.desktop b/plasma-open-settings/org.kde.plasma.settings.open.desktop
new file mode 100644
index 0000000..d44a824
--- /dev/null
+++ b/plasma-open-settings/org.kde.plasma.settings.open.desktop
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez
+# SPDX-License-Identifier: CC0-1.0
+
+[Desktop Entry]
+Name=Open System Settings
+Name[az]=Sistem Ayarlarını açın
+Name[ca]=Obre l'Arranjament del sistema
+Name[ca@valencia]=Obri Configuració del sistema
+Name[cs]=Otevřít nastavení systému
+Name[de]=Systemeinstellungen öffnen
+Name[el]=Άνοιγμα ρυθμίσεων συστήματος
+Name[en_GB]=Open System Settings
+Name[es]=Abrir las preferencias del sistema
+Name[eu]=Ireki Sistemako ezarpenak
+Name[fi]=Avaa järjestelmäasetukset
+Name[fr]=Ouvrir la configuration du système
+Name[hsb]=Systemowe nastajenja wočinić
+Name[hu]=Rendszerbeállítások megnyitása
+Name[ia]=Aperi Preferentias de Systema
+Name[it]=Apri le impostazioni di sistema
+Name[ko]=시스템 설정 열기
+Name[nl]=Systeeminstellingen openen
+Name[pl]=Otwórz ustawienia systemowe
+Name[pt]=Abrir a Configuração do Sistema
+Name[pt_BR]=Abrir configurações do sistema
+Name[ru]=Открыть «Параметры системы»
+Name[sk]=Otvoriť Systémové nastavenia
+Name[sl]=Odpri sistemske nastavitve
+Name[sv]=Öppna systeminställningar
+Name[tr]=Sistem Ayarlarını Aç
+Name[uk]=Відкрити «Системні параметри»
+Name[vi]=Mở Thiết lập hệ thống
+Name[x-test]=xxOpen System Settingsxx
+Name[zh_CN]=打开系统设置
+MimeType=x-scheme-handler/systemsettings
+Exec=plasma-open-settings %u
+Icon=systemsettings
+Type=Application
+NoDisplay=true
diff --git a/po/af/kcm5_filetypes.po b/po/af/kcm5_filetypes.po
new file mode 100644
index 0000000..33468ed
--- /dev/null
+++ b/po/af/kcm5_filetypes.po
@@ -0,0 +1,565 @@
+# UTF-8 test:äëïöü
+# Copyright (C) 2001 Free Software Foundation, Inc.
+# Frikkie Thirion , 2001,2002,2005.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: filetypes stable\n"
+"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
+"POT-Creation-Date: 2021-08-06 00:18+0000\n"
+"PO-Revision-Date: 2005-11-22 11:27+0200\n"
+"Last-Translator: Frikkie Thirion \n"
+"Language-Team: AFRIKAANS \n"
+"Language: af\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#, kde-format
+msgctxt "NAME OF TRANSLATORS"
+msgid "Your names"
+msgstr "Frikkie Thirion"
+
+#, kde-format
+msgctxt "EMAIL OF TRANSLATORS"
+msgid "Your emails"
+msgstr "frikkie.thirion@deneloptronics.co.za"
+
+#: filegroupdetails.cpp:22
+#, kde-format
+msgid "Left Click Action (only for Konqueror file manager)"
+msgstr ""
+
+#: filegroupdetails.cpp:26 filetypedetails.cpp:134
+#, kde-format
+msgid "Show file in embedded viewer"
+msgstr "Vertoon lêer in ingelegde kyker"
+
+#: filegroupdetails.cpp:27 filetypedetails.cpp:135
+#, kde-format
+msgid "Show file in separate viewer"
+msgstr "Vertoon lêer in aparte kyker"
+
+#: filegroupdetails.cpp:36
+#, fuzzy, kde-format
+#| msgid ""
+#| "Here you can configure what the Konqueror file manager will do when you "
+#| "click on a file belonging to this group. Konqueror can display the file "
+#| "in an embedded viewer or start up a separate application. You can change "
+#| "this setting for a specific file type in the 'Embedding' tab of the file "
+#| "type configuration."
+msgid ""
+"Here you can configure what the Konqueror file manager will do when you "
+"click on a file belonging to this group. Konqueror can display the file in "
+"an embedded viewer or start up a separate application. You can change this "
+"setting for a specific file type in the 'Embedding' tab of the file type "
+"configuration. Dolphin shows files always in a separate viewer"
+msgstr ""
+"Hier jy kan konfigureer wat die Konqueror lêer bestuurder sal doen wanneer "
+"jy kliek op 'n lêer behorende aan na hierdie groep. Konqueror kan vertoon "
+"die lêer in 'n ingebedde aansig van begin begin 'n aparte aansoek. jy kan "
+"verander hierdie opset vir 'n spesifieke lêer tipe in die 'Embedding' "
+"oortjie van die lêer tipe opstelling."
+
+#: filetypedetails.cpp:62
+#, kde-format
+msgid ""
+"This button displays the icon associated with the selected file type. Click "
+"on it to choose a different icon."
+msgstr ""
+"Hierdie knoppie vertoon die ikoon geassosieer met die gekose lêer tipe. "
+"Kliek op dit na kies 'n ander ikoon."
+
+#: filetypedetails.cpp:68
+#, kde-format
+msgid "Filename Patterns"
+msgstr "Lêernaam Patrone"
+
+#: filetypedetails.cpp:80
+#, kde-format
+msgid ""
+"This box contains a list of patterns that can be used to identify files of "
+"the selected type. For example, the pattern *.txt is associated with the "
+"file type 'text/plain'; all files ending in '.txt' are recognized as plain "
+"text files."
+msgstr ""
+"Hierdie boks bevat 'n lys van patrone wat kan wees gebruik word na "
+"identifiseer lêers van die gekose tipe. Vir voorbeeld, die patroon *.txt is "
+"geassosieer met die lêer tipe 'teks/eenvoudig'; alle lêers wat eindig in '."
+"txt' word herken as eenvoudig teks lêers."
+
+#: filetypedetails.cpp:88 filetypesview.cpp:102 kservicelistwidget.cpp:103
+#, kde-format
+msgid "Add..."
+msgstr "Voeg by..."
+
+#: filetypedetails.cpp:93
+#, kde-format
+msgid "Add a new pattern for the selected file type."
+msgstr "Voeg by 'n nuwe patroon vir die gekose lêer tipe."
+
+#: filetypedetails.cpp:95 kservicelistwidget.cpp:117
+#, kde-format
+msgid "Remove"
+msgstr ""
+
+#: filetypedetails.cpp:100
+#, kde-format
+msgid "Remove the selected filename pattern."
+msgstr "Verwyder die gekiesde lêernaam patroon."
+
+#: filetypedetails.cpp:111
+#, fuzzy, kde-format
+#| msgid "Description"
+msgid "Description:"
+msgstr "Beskrywing"
+
+#: filetypedetails.cpp:116
+#, kde-format
+msgid ""
+"You can enter a short description for files of the selected file type (e.g. "
+"'HTML Page'). This description will be used by applications like Konqueror "
+"to display directory content."
+msgstr ""
+"Jy kan invoer 'n kort beskrywing vir lêers van die gekose lêer tipe (e.g. "
+"'HTML Page'). Hierdie beskrywing sal wees gebruik word deur programme hou "
+"van Konqueror na vertoon gids inhoud."
+
+#: filetypedetails.cpp:129
+#, fuzzy, kde-format
+#| msgid "Left Click Action"
+msgid "Left Click Action in Konqueror"
+msgstr "Links Kliek Aksie"
+
+#: filetypedetails.cpp:138
+#, fuzzy, kde-format
+#| msgid "Ask whether to save to disk instead"
+msgid "Ask whether to save to disk instead (only for Konqueror browser)"
+msgstr "Vra om eerder op die hardeskryf te stoor"
+
+#: filetypedetails.cpp:154
+#, fuzzy, kde-format
+#| msgid ""
+#| "Here you can configure what the Konqueror file manager will do when you "
+#| "click on a file of this type. Konqueror can display the file in an "
+#| "embedded viewer or start up a separate application. If set to 'Use "
+#| "settings for G group', Konqueror will behave according to the settings of "
+#| "the group G this type belongs to, for instance 'image' if the current "
+#| "file type is image/png."
+msgid ""
+"Here you can configure what the Konqueror file manager will do when you "
+"click on a file of this type. Konqueror can either display the file in an "
+"embedded viewer, or start up a separate application. If set to 'Use settings "
+"for G group', the file manager will behave according to the settings of the "
+"group G to which this type belongs; for instance, 'image' if the current "
+"file type is image/png. Dolphin always shows files in a separate viewer."
+msgstr ""
+"Hier jy kan konfigureer wat die Konqueror lêer bestuurder sal doen wanneer "
+"jy kliek op 'n lêer van hierdie tipe. Konqueror kan vertoon die lêer in 'n "
+"ingebedde aansig van begin begin 'n aparte aansoek. As stel na 'Use "
+"instellings vir G groep', Konqueror sal gedra volgens na die instellings van "
+"die groep G hierdie tipe behoort na, vir voorbeeld 'beeld' As die huidige "
+"lêer tipe is beeld/png."
+
+#: filetypedetails.cpp:166
+#, kde-format
+msgid "&General"
+msgstr "Algemeen"
+
+#: filetypedetails.cpp:167
+#, kde-format
+msgid "&Embedding"
+msgstr "Inbed"
+
+#: filetypedetails.cpp:208
+#, kde-format
+msgid "Add New Extension"
+msgstr "Voeg by Nuwe Uitbreiding"
+
+#: filetypedetails.cpp:208
+#, kde-format
+msgid "Extension:"
+msgstr "Uitbreiding:"
+
+#: filetypedetails.cpp:326
+#, fuzzy, kde-format
+#| msgid "Edit File Type %1"
+msgid "File type %1"
+msgstr "Redigeer Lêer Tipe: %1"
+
+#: filetypedetails.cpp:334
+#, kde-format
+msgid "Use settings for '%1' group"
+msgstr "Gebruik instellings vir '%1' groep"
+
+#: filetypesview.cpp:43
+#, fuzzy, kde-format
+#| msgid ""
+#| "
File Associations
This module allows you to choose which "
+#| "applications are associated with a given type of file. File types are "
+#| "also referred to MIME types (MIME is an acronym which stands for "
+#| "\"Multipurpose Internet Mail Extensions\".)
A file association "
+#| "consists of the following:
Rules for determining the MIME-type of "
+#| "a file, for example the filename pattern *.kwd, which means 'all files "
+#| "with names that end in .kwd', is associated with the MIME type \"x-kword"
+#| "\";
A short description of the MIME-type, for example the "
+#| "description of the MIME type \"x-kword\" is simply 'KWord document';
"
+#| "
An icon to be used for displaying files of the given MIME-type, so "
+#| "that you can easily identify the type of file in, say, a Konqueror view "
+#| "(at least for the types you use often);
A list of the "
+#| "applications which can be used to open files of the given MIME-type -- if "
+#| "more than one application can be used then the list is ordered by "
+#| "priority.
You may be surprised to find that some MIME types "
+#| "have no associated filename patterns; in these cases, Konqueror is able "
+#| "to determine the MIME-type by directly examining the contents of the file."
+msgid ""
+"
File Associations
This module allows you to choose which "
+"applications are associated with a given type of file. File types are also "
+"referred to as MIME types (MIME is an acronym which stands for "
+"\"Multipurpose Internet Mail Extensions\").
A file association "
+"consists of the following:
Rules for determining the MIME-type of a "
+"file, for example the filename pattern *.png, which means 'all files with "
+"names that end in .png', is associated with the MIME type \"image/png\";"
+"li>
A short description of the MIME-type, for example the description of "
+"the MIME type \"image/png\" is simply 'PNG image';
An icon to be "
+"used for displaying files of the given MIME-type, so that you can easily "
+"identify the type of file in a file manager or file-selection dialog (at "
+"least for the types you use often);
A list of the applications "
+"which can be used to open files of the given MIME-type -- if more than one "
+"application can be used then the list is ordered by priority.
You "
+"may be surprised to find that some MIME types have no associated filename "
+"patterns; in these cases, KDE is able to determine the MIME-type by directly "
+"examining the contents of the file."
+msgstr ""
+"
Lêer Assosiasies
Hierdie module laat jou toe om programme met lêer "
+"tipes te assosieer. Daar word ook na lêer tipes verwys as MIME tipes. MIME "
+"is 'n akroniem vir \"Multipurpose Internet Mail Extensions\".
'n Lêer "
+"assosiasie bestaan uit die volgende items:
Reëls om te bepaal wat die "
+"MIME-tipe van 'n lêer is, bv. die patroon '*.kwd', wat beteken: 'alle lêers "
+"wat eindig met .kwd is geassosieer met die MIME-tipe \"x-kword\"
'n "
+"Kort beskrywing oor die MIME-tipe, bv. 'K Word dokument' vir \"x-kword"
+"\"
'n Ikoon vir die MIME-tipe, sodat die tipe lêer maklik geidentifiseer "
+"kan word in Konqueror
'n Lys van programme wat hierdie MIME-tipe kan "
+"oopmaak. Die lys is volgens prioriteit gesorteer.
Party programme "
+"het geen MIME-tipe met hulle geassosieer nie. In so 'n geval sal Konquerer "
+"probeer om die MIME-tipe te raai deur die inhoud van die lêer te ondersoek."
+
+#: filetypesview.cpp:73
+#, fuzzy, kde-format
+#| msgid "F&ind filename pattern:"
+msgid "Search for file type or filename pattern..."
+msgstr "Soek lêernaam patroon:"
+
+#: filetypesview.cpp:79
+#, fuzzy, kde-format
+#| msgid ""
+#| "Enter a part of a filename pattern. Only file types with a matching file "
+#| "pattern will appear in the list."
+msgid ""
+"Enter a part of a filename pattern, and only file types with a matching file "
+"pattern will appear in the list. Alternatively, enter a part of a file type "
+"name as it appears in the list."
+msgstr ""
+"Invoer 'n deel van 'n lêernaam patroon in. Slegs lêer tipes met 'n "
+"ooreenstemmende lêer patroon sal in die lys verskyn."
+
+#: filetypesview.cpp:87
+#, kde-format
+msgid "Known Types"
+msgstr "Bekend Tipes"
+
+#: filetypesview.cpp:93
+#, kde-format
+msgid ""
+"Here you can see a hierarchical list of the file types which are known on "
+"your system. Click on the '+' sign to expand a category, or the '-' sign to "
+"collapse it. Select a file type (e.g. text/html for HTML files) to view/edit "
+"the information for that file type using the controls on the right."
+msgstr ""
+"Hier jy kan sien 'n hiërargiese lys van die lêer tipes wat word bekend op "
+"jou stelsel. Kliek op die '+' teken na vergroot 'n kategorie, of die '-' "
+"teken na toevou dit. Kies 'n lêer tipe (e.g. teks/Html vir Html lêers) na "
+"besigtig/redigeer die informasie vir wat lêer tipe te gebruik die kontroles "
+"op die regterkant."
+
+#: filetypesview.cpp:107
+#, kde-format
+msgid "Click here to add a new file type."
+msgstr "Kliek hier na voeg by 'n nuwe lêer tipe."
+
+#: filetypesview.cpp:109 filetypesview.cpp:375
+#, kde-format
+msgid "&Remove"
+msgstr ""
+
+#: filetypesview.cpp:133
+#, kde-format
+msgid "Select a file type by name or by extension"
+msgstr "Kies 'n lêer tipe deur naam of deur uitbreiding"
+
+#: filetypesview.cpp:368
+#, kde-format
+msgid "&Revert"
+msgstr ""
+
+#: filetypesview.cpp:369
+#, kde-format
+msgid "Revert this file type to its initial system-wide definition"
+msgstr ""
+
+#: filetypesview.cpp:371
+#, kde-format
+msgid ""
+"Click here to revert this file type to its initial system-wide definition, "
+"which undoes any changes made to the file type. Note that system-wide file "
+"types cannot be deleted. You can however empty their pattern list, to "
+"minimize the chances of them being used (but the file type determination "
+"from file contents can still end up using them)."
+msgstr ""
+
+#: filetypesview.cpp:376
+#, kde-format
+msgid "Delete this file type definition completely"
+msgstr ""
+
+#: filetypesview.cpp:378
+#, kde-format
+msgid ""
+"Click here to delete this file type definition completely. This is only "
+"possible for user-defined file types. System-wide file types cannot be "
+"deleted. You can however empty their pattern list, to minimize the chances "
+"of them being used (but the file type determination from file contents can "
+"still end up using them)."
+msgstr ""
+
+#: keditfiletype.cpp:122
+#, fuzzy, kde-format
+#| msgid "Edit File Type %1"
+msgid "File Type Editor"
+msgstr "Redigeer Lêer Tipe: %1"
+
+#: keditfiletype.cpp:124
+#, kde-format
+msgid ""
+"KDE file type editor - simplified version for editing a single file type"
+msgstr ""
+"KDE lêer tipe redigeerder - vereenvoudigde weergawe om 'n enkel lêer tipe te "
+"redigeer"
+
+#: keditfiletype.cpp:126
+#, kde-format
+msgid "(c) 2000, KDE developers"
+msgstr "(c) 2000, KDE ontwikkelaars"
+
+#: keditfiletype.cpp:127
+#, kde-format
+msgid "Preston Brown"
+msgstr ""
+
+#: keditfiletype.cpp:128
+#, kde-format
+msgid "David Faure"
+msgstr ""
+
+#: keditfiletype.cpp:134
+#, kde-format
+msgid "Makes the dialog transient for the window specified by winid"
+msgstr "Maak dit 'n oorgang dialoog vir die venster gespesifiseer deur WinID"
+
+#: keditfiletype.cpp:136
+#, kde-format
+msgid "File type to edit (e.g. text/html)"
+msgstr "Lêer tipe om te redigeer (bv. teks/html)"
+
+#: keditfiletype.cpp:167
+#, kde-format
+msgid "%1 File"
+msgstr "%1 Lêer"
+
+#: keditfiletype.cpp:194
+#, kde-format
+msgid "Edit File Type %1"
+msgstr "Redigeer Lêer Tipe: %1"
+
+#: keditfiletype.cpp:196
+#, kde-format
+msgid "Create New File Type %1"
+msgstr "Skep Nuwe Lêer Tipe: %1"
+
+#: kservicelistwidget.cpp:37
+#, kde-format
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+#: kservicelistwidget.cpp:50
+#, kde-format
+msgid "Application Preference Order"
+msgstr "Aansoek Voorkeur Volgorde"
+
+#: kservicelistwidget.cpp:50
+#, kde-format
+msgid "Services Preference Order"
+msgstr "Dienste Voorkeur Volgorde"
+
+#: kservicelistwidget.cpp:61
+#, kde-format
+msgid ""
+"This is a list of applications associated with files of the selected file "
+"type. This list is shown in Konqueror's context menus when you select \"Open "
+"With...\". If more than one application is associated with this file type, "
+"then the list is ordered by priority with the uppermost item taking "
+"precedence over the others."
+msgstr ""
+"Hierdie is 'n lys van programme wat met die lêes van die gekose tipe "
+"geassosieer is. Hierdie lys sal in Konqueror se konteks kieslyste vertoon "
+"word wanneer jy \"Maak oop met...\"kies. As daar meer as een program met 'n "
+"lêer tipe geassosieer is, sal die programme se prioriteit van bo na onder in "
+"die lys bepaal word."
+
+#: kservicelistwidget.cpp:66
+#, fuzzy, kde-format
+#| msgid ""
+#| "This is a list of services associated with files of the selected file "
+#| "type. This list is shown in Konqueror's context menus when you select a "
+#| "\"Preview with...\" option. If more than one application is associated "
+#| "with this file type, then the list is ordered by priority with the "
+#| "uppermost item taking precedence over the others."
+msgid ""
+"This is a list of services associated with files of the selected file type. "
+"This list is shown in Konqueror's context menus when you select a \"Preview "
+"with...\" option. If more than one service is associated with this file "
+"type, then the list is ordered by priority with the uppermost item taking "
+"precedence over the others."
+msgstr ""
+"Hierdie is 'n lys van dienste wat met die lêes van die gekose tipe "
+"geassosieer is. Hierdie lys sal in Konqueror se konteks kieslyste vertoon "
+"word wanneer jy \"Voorskou met...\"kies. As daar meer as een program met 'n "
+"lêer tipe geassosieer is, sal die programme se prioriteit van bo na onder in "
+"die lys bepaal word."
+
+#: kservicelistwidget.cpp:78
+#, kde-format
+msgid "Move &Up"
+msgstr "Beweeg Begin"
+
+#: kservicelistwidget.cpp:84
+#, kde-format
+msgid ""
+"Assigns a higher priority to the selected\n"
+"application, moving it up in the list. Note: This\n"
+"only affects the selected application if the file type is\n"
+"associated with more than one application."
+msgstr ""
+"Ken toe 'n hoër prioriteit na die gekose\n"
+"aansoek, beweeg dit begin in die lys. Nota: Hierdie\n"
+"slegs affekteer die gekose aansoek as die lêer tipe is\n"
+"geassosieer met meer as een aansoek."
+
+#: kservicelistwidget.cpp:88
+#, kde-format
+msgid ""
+"Assigns a higher priority to the selected\n"
+"service, moving it up in the list."
+msgstr ""
+"Ken toe 'n hoër prioriteit na die gekose\n"
+"diens, beweeg dit begin in die lys."
+
+#: kservicelistwidget.cpp:91
+#, kde-format
+msgid "Move &Down"
+msgstr "Beweeg Ondertoe"
+
+#: kservicelistwidget.cpp:96
+#, kde-format
+msgid ""
+"Assigns a lower priority to the selected\n"
+"application, moving it down in the list. Note: This \n"
+"only affects the selected application if the file type is\n"
+"associated with more than one application."
+msgstr ""
+"Ken toe 'n sagter prioriteit na die gekose\n"
+"aansoek, beweeg dit ondertoe in die lys. Nota: Hierdie \n"
+"slegs affekteer die gekose aansoek as die lêer tipe is\n"
+"geassosieer met meer as een aansoek."
+
+#: kservicelistwidget.cpp:100
+#, kde-format
+msgid ""
+"Assigns a lower priority to the selected\n"
+"service, moving it down in the list."
+msgstr ""
+"Ken toe 'n sagter prioriteit na die gekose\n"
+"diens, beweeg dit ondertoe in die lys."
+
+#: kservicelistwidget.cpp:108
+#, kde-format
+msgid "Add a new application for this file type."
+msgstr "Voeg by 'n nuwe aansoek vir hierdie lêer tipe."
+
+#: kservicelistwidget.cpp:110
+#, kde-format
+msgid "Edit..."
+msgstr "Redigeer..."
+
+#: kservicelistwidget.cpp:115
+#, kde-format
+msgid "Edit command line of the selected application."
+msgstr "Redigeer opdrag lyn van die gekose aansoek."
+
+#: kservicelistwidget.cpp:122
+#, kde-format
+msgid "Remove the selected application from the list."
+msgstr "Verwyder die gekose aansoek van die lys."
+
+#: kservicelistwidget.cpp:145
+#, fuzzy, kde-format
+#| msgid "None"
+msgctxt "No applications associated with this file type"
+msgid "None"
+msgstr "Geen"
+
+#: kservicelistwidget.cpp:147
+#, fuzzy, kde-format
+#| msgid "None"
+msgctxt "No components associated with this file type"
+msgid "None"
+msgstr "Geen"
+
+#: kserviceselectdlg.cpp:21
+#, kde-format
+msgid "Add Service"
+msgstr "Voeg diens by"
+
+#: kserviceselectdlg.cpp:25
+#, kde-format
+msgid "Select service:"
+msgstr "Kies die diens:"
+
+#: newtypedlg.cpp:28
+#, kde-format
+msgid "Create New File Type"
+msgstr "Skep Nuwe Lêer Tipe"
+
+#: newtypedlg.cpp:33
+#, kde-format
+msgid "Group:"
+msgstr "Groep:"
+
+#: newtypedlg.cpp:42
+#, kde-format
+msgid "Select the category under which the new file type should be added."
+msgstr "Kies die kategorie onder wat die nuwe lêer tipe moet wees bygevoeg."
+
+#: newtypedlg.cpp:47
+#, kde-format
+msgid "Type name:"
+msgstr "Tipe naam:"
+
+#: newtypedlg.cpp:53
+#, kde-format
+msgid ""
+"Type the name of the file type. For instance, if you selected 'image' as "
+"category and you type 'custom' here, the file type 'image/custom' will be "
+"created."
+msgstr ""
\ No newline at end of file
diff --git a/po/af/kcmshell5.po b/po/af/kcmshell5.po
new file mode 100644
index 0000000..0a0cb8b
--- /dev/null
+++ b/po/af/kcmshell5.po
@@ -0,0 +1,134 @@
+# UTF-8 test:äëïöü
+msgid ""
+msgstr ""
+"Project-Id-Version: kcmshell stable\n"
+"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
+"POT-Creation-Date: 2022-01-18 02:09+0000\n"
+"PO-Revision-Date: 2005-04-18 16:33+0200\n"
+"Last-Translator: Frikkie Thirion \n"
+"Language-Team: AFRIKAANS \n"
+"Language: af\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#, kde-format
+msgctxt "NAME OF TRANSLATORS"
+msgid "Your names"
+msgstr "Frikkie Thirion"
+
+#, kde-format
+msgctxt "EMAIL OF TRANSLATORS"
+msgid "Your emails"
+msgstr "frix@expertron.co.za"
+
+#: main.cpp:182
+#, kde-format
+msgid "System Settings Module"
+msgstr ""
+
+#: main.cpp:184
+#, fuzzy, kde-format
+#| msgid "A tool to start single KDE control modules"
+msgid "A tool to start single system settings modules"
+msgstr "'n Nuts program om enkel KDE beheer modules mee te begin"
+
+#: main.cpp:186
+#, fuzzy, kde-format
+#| msgid "(c) 1999-2004, The KDE Developers"
+msgid "(c) 1999-2016, The KDE Developers"
+msgstr "(c) 1999-2004, The KDE Developers"
+
+#: main.cpp:188
+#, kde-format
+msgid "Frans Englich"
+msgstr ""
+
+#: main.cpp:188
+#, kde-format
+msgid "Maintainer"
+msgstr "Onderhouer"
+
+#: main.cpp:189
+#, kde-format
+msgid "Daniel Molkentin"
+msgstr ""
+
+#: main.cpp:190
+#, kde-format
+msgid "Matthias Hoelzer-Kluepfel"
+msgstr ""
+
+#: main.cpp:191
+#, kde-format
+msgid "Matthias Elter"
+msgstr ""
+
+#: main.cpp:192
+#, kde-format
+msgid "Matthias Ettrich"
+msgstr ""
+
+#: main.cpp:193
+#, kde-format
+msgid "Waldo Bastian"
+msgstr ""
+
+#: main.cpp:199
+#, kde-format
+msgid "List all possible modules"
+msgstr "Lys van alle moontlike modules"
+
+#: main.cpp:200
+#, kde-format
+msgid "Configuration module to open"
+msgstr "Opstel module om oop te maak"
+
+#: main.cpp:201
+#, kde-format
+msgid "Specify a particular language"
+msgstr "Spesifiseer 'n taal"
+
+#: main.cpp:202
+#, kde-format
+msgid "Do not display main window"
+msgstr "Moet nie die hoof venster vertoon nie"
+
+#: main.cpp:203
+#, kde-format
+msgid "Arguments for the module"
+msgstr ""
+
+#: main.cpp:204
+#, kde-format
+msgid "Use a specific icon for the window"
+msgstr ""
+
+#: main.cpp:205
+#, kde-format
+msgid "Use a specific caption for the window"
+msgstr ""
+
+#: main.cpp:214
+#, kde-format
+msgid ""
+"--lang is deprecated. Please set the LANGUAGE environment variable instead"
+msgstr ""
+
+#: main.cpp:218
+#, kde-format
+msgid "The following modules are available:"
+msgstr "Die volgende modules is beskikbaar:"
+
+#: main.cpp:237
+#, kde-format
+msgid "No description available"
+msgstr "Geen beskywing beskikbaar"
+
+#: main.cpp:296
+#, kde-format
+msgid ""
+"Could not find module '%1'. See kcmshell5 --list for the full list of "
+"modules."
+msgstr ""
\ No newline at end of file
diff --git a/po/af/kdesu5.po b/po/af/kdesu5.po
new file mode 100644
index 0000000..b8599df
--- /dev/null
+++ b/po/af/kdesu5.po
@@ -0,0 +1,262 @@
+# UTF-8 test:äëïöü
+# Copyright (C) 2001 Free Software Foundation, Inc.
+# Frikkie Thirion , 2001,2002.
+# Juanita Franz , 2005.
+msgid ""
+msgstr ""
+"Project-Id-Version: kdesu stable\n"
+"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
+"POT-Creation-Date: 2021-08-06 00:18+0000\n"
+"PO-Revision-Date: 2005-06-15 11:29+0200\n"
+"Last-Translator: JUANITA FRANZ \n"
+"Language-Team: AFRIKAANS \n"
+"Language: af\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#, kde-format
+msgctxt "NAME OF TRANSLATORS"
+msgid "Your names"
+msgstr "Frikkie Thirion, Juanita Franz"
+
+#, kde-format
+msgctxt "EMAIL OF TRANSLATORS"
+msgid "Your emails"
+msgstr "frix@expertron.co.za, juanita.franz@vr-web.de"
+
+#: kdesu.cpp:105
+#, kde-format
+msgid "KDE su"
+msgstr "Kde su"
+
+#: kdesu.cpp:107
+#, kde-format
+msgid "Runs a program with elevated privileges."
+msgstr "Loop 'n program met verhoogde voorregte."
+
+#: kdesu.cpp:109
+#, kde-format
+msgid "Copyright (c) 1998-2000 Geert Jansen, Pietro Iglio"
+msgstr ""
+
+#: kdesu.cpp:110
+#, kde-format
+msgid "Geert Jansen"
+msgstr ""
+
+#: kdesu.cpp:110
+#, kde-format
+msgid "Maintainer"
+msgstr "Onderhouer"
+
+#: kdesu.cpp:111
+#, kde-format
+msgid "Pietro Iglio"
+msgstr ""
+
+#: kdesu.cpp:111
+#, kde-format
+msgid "Original author"
+msgstr "Oorspronklike outeur"
+
+#: kdesu.cpp:120
+#, fuzzy, kde-format
+#| msgid "Specifies the command to run"
+msgid "Specifies the command to run as separate arguments"
+msgstr "Spesifiseer die opdrag om te hardloop"
+
+#: kdesu.cpp:121
+#, fuzzy, kde-format
+#| msgid "Specifies the command to run"
+msgid "Specifies the command to run as one string"
+msgstr "Spesifiseer die opdrag om te hardloop"
+
+#: kdesu.cpp:122
+#, kde-format
+msgid "Run command under target uid if is not writable"
+msgstr "Hardloop opdrag onder teiken uid as nie skryfbaar is nie"
+
+#: kdesu.cpp:123
+#, kde-format
+msgid "Specifies the target uid"
+msgstr "Spesifiseer die teiken uid"
+
+#: kdesu.cpp:124
+#, kde-format
+msgid "Do not keep password"
+msgstr "Hou nie wagwoord nie"
+
+#: kdesu.cpp:125
+#, kde-format
+msgid "Stop the daemon (forgets all passwords)"
+msgstr "Stop die bediener (vergeet alle wagwoorde)"
+
+#: kdesu.cpp:126
+#, kde-format
+msgid "Enable terminal output (no password keeping)"
+msgstr "Aktiveer terminaal uitset (geen wagwoord gehou)"
+
+#: kdesu.cpp:128
+#, kde-format
+msgid "Set priority value: 0 <= prio <= 100, 0 is lowest"
+msgstr "Stel prioriteit waarde: 0 <= prioriteit <= 100, 0 is laagste"
+
+#: kdesu.cpp:129
+#, kde-format
+msgid "Use realtime scheduling"
+msgstr "Gebruik intydse skedulering"
+
+#: kdesu.cpp:130
+#, kde-format
+msgid "Do not display ignore button"
+msgstr ""
+
+#: kdesu.cpp:131
+#, kde-format
+msgid "Specify icon to use in the password dialog"
+msgstr "Spesifiseer ikoon wat in die wagwoord dialoog gebruik moet word"
+
+#: kdesu.cpp:132
+#, kde-format
+msgid "Do not show the command to be run in the dialog"
+msgstr "Moenie die opdrag vertoon wat in die dialoog geloop moet word nie"
+
+#: kdesu.cpp:139
+#, kde-format
+msgctxt ""
+"Transient means that the kdesu app will be attached to the app specified by "
+"the winid so that it is like a dialog box rather than some separate program"
+msgid "Makes the dialog transient for an X app specified by winid"
+msgstr ""
+
+#: kdesu.cpp:141
+#, kde-format
+msgid "Embed into a window"
+msgstr ""
+
+#: kdesu.cpp:168
+#, kde-format
+msgid "Cannot execute command '%1'."
+msgstr ""
+
+#: kdesu.cpp:171
+#, kde-format
+msgid "Cannot execute command '%1'. It contains invalid characters."
+msgstr ""
+
+#: kdesu.cpp:245
+#, kde-format
+msgid "Illegal priority: %1"
+msgstr "Onwettige prioriteit: %1"
+
+#: kdesu.cpp:266
+#, kde-format
+msgid "No command specified."
+msgstr "Geen opdrag gespesifiseer"
+
+#: kdesu.cpp:372
+#, kde-format
+msgid "Su returned with an error.\n"
+msgstr "Su teruggestuur met 'n fout.\n"
+
+#: kdesu.cpp:400
+#, kde-format
+msgid "Command:"
+msgstr "Opdrag:"
+
+#: kdesu.cpp:409
+#, kde-format
+msgid "realtime: "
+msgstr "intydse: "
+
+#: kdesu.cpp:413
+#, kde-format
+msgid "Priority:"
+msgstr "Prioriteit:"
+
+#: sudlg.cpp:27
+#, kde-format
+msgid "Run as %1"
+msgstr "Hardloop as %1"
+
+#: sudlg.cpp:31
+#, kde-format
+msgid "Please enter your password below."
+msgstr ""
+
+#: sudlg.cpp:36
+#, fuzzy, kde-format
+msgid ""
+"The action you requested needs