Skip to content

Commit

Permalink
Fixed type associations and added "Default Application" (#635)
Browse files Browse the repository at this point in the history
This is a relatively big code change:

 1. Scheme protocols are added to the end of associations list.
 2. A tab for default apps is added. For now, it supports the default browser and email client.
 3. Default associations are done in a DE-specific way, so that they don't affect other DEs.
 4. The current changes can be undone correctly and reliably.
 5. The deprecated class `XdgDesktopFileCache` and the inefficient class `LXQt::SettingsCache` are removed from the code. `XdgMimeApps` is used instead of the former. The latter had bugs and couldn't do a correct and clean resetting; `QTemporaryFile` is used instead of it.
 6. All memory leaks are fixed. They were found both by rereading the code and by using `valgrind`.

NOTE 1: The patch relies on the latest git `libqtxdg` and SHOULD NOT be used without it.

NOTE 2: `lxqt-config-file-associations` only supports setting of default apps. The support for removing/sorting associations is still missing. Association removal is supported by `libqtxdg` → `XdgMimeApps` and sorting can be added to it.

Closes lxqt/lxqt#1513
  • Loading branch information
tsujan committed Jul 19, 2020
1 parent f5c1450 commit eff9efd
Show file tree
Hide file tree
Showing 6 changed files with 879 additions and 326 deletions.
196 changes: 139 additions & 57 deletions lxqt-config-file-associations/applicationchooser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,41 @@
#include <QTimer>

#include <XdgDesktopFile>
#include <XdgMimeApps>

#include <algorithm>

#include "applicationchooser.h"

Q_DECLARE_METATYPE(XdgDesktopFile*)

ApplicationChooser::ApplicationChooser(const XdgMimeType& mimeInfo, bool showUseAlwaysCheckBox)
ApplicationChooser::ApplicationChooser(const QString& type, bool showUseAlwaysCheckBox)
{
m_MimeInfo = mimeInfo;
m_CurrentDefaultApplication = XdgDesktopFileCache::getDefaultApp(m_MimeInfo.name());
widget.setupUi(this);
m_Type = type;

XdgMimeApps appsDb;
m_CurrentDefaultApplication = appsDb.defaultApp(m_Type);
QMimeDatabase db;
XdgMimeType mimeInfo(db.mimeTypeForName(m_Type));
if (mimeInfo.isValid())
{
widget.mimetypeIconLabel->setPixmap(mimeInfo.icon().pixmap(widget.mimetypeIconLabel->size()));
widget.mimetypeLabel->setText(mimeInfo.comment());
}
else
{
widget.mimetypeIconLabel->setText(m_Type);
widget.mimetypeLabel->hide();
}

widget.mimetypeIconLabel->setPixmap(m_MimeInfo.icon().pixmap(widget.mimetypeIconLabel->size()));
widget.mimetypeLabel->setText(m_MimeInfo.comment());
widget.alwaysUseCheckBox->setVisible(showUseAlwaysCheckBox);
widget.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}

ApplicationChooser::~ApplicationChooser()
{
qDeleteAll(allApps);
}

int ApplicationChooser::exec()
Expand Down Expand Up @@ -98,89 +112,152 @@ void ApplicationChooser::fillApplicationListWidget()
{
widget.applicationTreeWidget->clear();

QSet<XdgDesktopFile*> addedApps;
QList<XdgDesktopFile*> applicationsThatHandleThisMimetype = XdgDesktopFileCache::getApps(m_MimeInfo.name());

QStringList mimetypes;
mimetypes << m_MimeInfo.name() << m_MimeInfo.allAncestors();
QSet<XdgDesktopFile*> addedApps;
XdgMimeApps appsDb;
QList<XdgDesktopFile*> applicationsThatHandleThisMimetype = appsDb.apps(m_Type);

// Adding all apps takes some time. Make the user aware by setting the
// cursor to Wait.
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

QMimeDatabase db;
for(const QString& mts : qAsConst(mimetypes)) {
QMimeType mt = db.mimeTypeForName(mts);
QString heading;
heading = mt.name() == QLatin1String("application/octet-stream") ?
tr("Other applications") :
tr("Applications that handle %1").arg(mt.comment());

QList<XdgDesktopFile*> applications;
applications = mt.name() == QLatin1String("application/octet-stream") ?
XdgDesktopFileCache::getAllFiles() :
XdgDesktopFileCache::getApps(mt.name());

std::sort(applications.begin(), applications.end(), lessThan);

QTreeWidgetItem* headingItem = new QTreeWidgetItem(widget.applicationTreeWidget);
headingItem->setExpanded(true);
headingItem->setFlags(Qt::ItemIsEnabled);
headingItem->setText(0, heading);
headingItem->setSizeHint(0, QSize(0, 25));

addApplicationsToApplicationListWidget(headingItem, applications, addedApps);
XdgMimeType mimeInfo(db.mimeTypeForName(m_Type));
if (mimeInfo.isValid())
{
QStringList mimetypes;
mimetypes << m_Type << mimeInfo.allAncestors();

for(const QString& mts : qAsConst(mimetypes)) {
QMimeType mt = db.mimeTypeForName(mts);
QString heading = mt.name() == QLatin1String("application/octet-stream") ?
tr("Other applications") :
tr("Applications that handle %1").arg(mt.comment());

QList<XdgDesktopFile*> applications = mt.name() == QLatin1String("application/octet-stream") ?
appsDb.allApps() :
appsDb.recommendedApps(mt.name());

std::sort(applications.begin(), applications.end(), lessThan);

QTreeWidgetItem* headingItem = new QTreeWidgetItem(widget.applicationTreeWidget);
headingItem->setExpanded(true);
headingItem->setFlags(Qt::ItemIsEnabled);
headingItem->setText(0, heading);
headingItem->setSizeHint(0, QSize(0, 25));

addApplicationsToApplicationListWidget(headingItem, applications, addedApps);
}
}
else // schemes
{
QStringList types;
types << m_Type << QString();
for(const QString& type : qAsConst(types)) {
QString heading = type.isEmpty() ?
tr("Other applications") :
tr("Applications that handle %1").arg(type);
QList<XdgDesktopFile*> applications = type.isEmpty() ?
appsDb.allApps() :
appsDb.recommendedApps(type);

std::sort(applications.begin(), applications.end(), lessThan);

QTreeWidgetItem* headingItem = new QTreeWidgetItem(widget.applicationTreeWidget);
headingItem->setExpanded(true);
headingItem->setFlags(Qt::ItemIsEnabled);
headingItem->setText(0, heading);
headingItem->setSizeHint(0, QSize(0, 25));

addApplicationsToApplicationListWidget(headingItem, applications, addedApps);
}
}
connect(widget.applicationTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(selectionChanged()));
connect(widget.applicationTreeWidget, &QTreeWidget::currentItemChanged,
this, &ApplicationChooser::selectionChanged);
widget.applicationTreeWidget->setFocus();

if (!applicationsThatHandleThisMimetype.isEmpty()) {
widget.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
qDeleteAll(applicationsThatHandleThisMimetype);
applicationsThatHandleThisMimetype.clear();
}

// delay icon update for faster loading
QTimer::singleShot(0, this, SLOT(updateAllIcons()));
QTimer::singleShot(0, this, &ApplicationChooser::updateAllIcons);
}

void ApplicationChooser::addApplicationsToApplicationListWidget(QTreeWidgetItem* parent,
QList<XdgDesktopFile*> applications,
QSet<XdgDesktopFile*>& alreadyAdded)
{
QIcon placeHolderIcon = QIcon::fromTheme(QStringLiteral("application-x-executable"));
bool noApplication = applications.isEmpty();
bool inserted = false;

if (applications.isEmpty())
// Insert applications in the listwidget, skipping already added applications
// as well as desktop files that aren't applications
while (!applications.isEmpty())
{
XdgDesktopFile* desktopFile = applications.first();

// Only applications
if (desktopFile->type() != XdgDesktopFile::ApplicationType)
{
QTreeWidgetItem* noAppsFoundItem = new QTreeWidgetItem(parent);
noAppsFoundItem->setText(0, tr("No applications found"));
noAppsFoundItem->setFlags(Qt::ItemFlags());
QFont font = noAppsFoundItem->font(0);
font.setStyle(QFont::StyleItalic);
noAppsFoundItem->setFont(0, font);
delete applications.takeFirst();
continue;
}
else

// WARNING: We cannot use QSet::contains() here because different addresses
// can have the same value. Also, see libqtxdg -> XdgDesktopFile::operator==().
bool wasAdded = false;
for (XdgDesktopFile* added : qAsConst(alreadyAdded))
{
// Insert applications in the listwidget, skipping already added applications
for (XdgDesktopFile* desktopFile : qAsConst(applications))
if (*added == *desktopFile)
{
if (alreadyAdded.contains(desktopFile))
continue;
wasAdded = true;
break;
}
}
if (wasAdded) {
delete applications.takeFirst();
continue;
}

// Only applications
if (desktopFile->type() != XdgDesktopFile::ApplicationType)
continue;
QTreeWidgetItem *item = new QTreeWidgetItem(parent);
item->setIcon(0, placeHolderIcon);
item->setText(0, desktopFile->name());
item->setData(0, 32, QVariant::fromValue<XdgDesktopFile*>(desktopFile));

QTreeWidgetItem *item = new QTreeWidgetItem(parent);
item->setIcon(0, placeHolderIcon);
item->setText(0, desktopFile->name());
item->setData(0, 32, QVariant::fromValue<XdgDesktopFile*>(desktopFile));
if (widget.applicationTreeWidget->selectedItems().isEmpty()
&& m_CurrentDefaultApplication
&& *desktopFile == *m_CurrentDefaultApplication)
{
widget.applicationTreeWidget->setCurrentItem(item);
}

if (desktopFile == m_CurrentDefaultApplication)
{
widget.applicationTreeWidget->setCurrentItem(item);
}
inserted = true;
alreadyAdded.insert(desktopFile);

alreadyAdded.insert(desktopFile);
}
allApps.insert(desktopFile);
applications.removeFirst();
}

if (!inserted)
{
QTreeWidgetItem* noAppsFoundItem = new QTreeWidgetItem(parent);
if (noApplication || alreadyAdded.isEmpty())
noAppsFoundItem->setText(0, tr("No applications found"));
else
{
// in this case, applications are found but were already added;
// so, the text should be a little different
noAppsFoundItem->setText(0, tr("No more applications found"));
}
noAppsFoundItem->setFlags(Qt::NoItemFlags);
QFont font = noAppsFoundItem->font(0);
font.setStyle(QFont::StyleItalic);
noAppsFoundItem->setFont(0, font);
}
}

void ApplicationChooser::selectionChanged()
Expand All @@ -191,6 +268,11 @@ void ApplicationChooser::selectionChanged()
if (newItem && newItem->data(0, 32).value<XdgDesktopFile*>())
{
widget.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);

// in d-tor, we delete all app pointers except for m_CurrentDefaultApplication
// because it needs to be returned (and deleted by the caller)
allApps.insert(m_CurrentDefaultApplication);
m_CurrentDefaultApplication = newItem->data(0, 32).value<XdgDesktopFile*>();
allApps.remove(m_CurrentDefaultApplication);
}
}
10 changes: 7 additions & 3 deletions lxqt-config-file-associations/applicationchooser.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ class ApplicationChooser : public QDialog
{
Q_OBJECT
public:
ApplicationChooser(const XdgMimeType& mimeInfo, bool showUseAlwaysCheckBox = false);
ApplicationChooser(const QString& type, bool showUseAlwaysCheckBox = false);

virtual ~ApplicationChooser();
XdgDesktopFile* DefaultApplication() const { return m_CurrentDefaultApplication; }

XdgDesktopFile* DefaultApplication() const {
return m_CurrentDefaultApplication; // should be deleted by the caller
}

virtual int exec();

Expand All @@ -53,9 +56,10 @@ private slots:
void addApplicationsToApplicationListWidget(QTreeWidgetItem* parent,
QList<XdgDesktopFile*> applications,
QSet<XdgDesktopFile*> & alreadyAdded);
XdgMimeType m_MimeInfo;
QString m_Type;
Ui::ApplicationChooser widget;
XdgDesktopFile* m_CurrentDefaultApplication;
QSet<XdgDesktopFile*> allApps; // all app pointers that should be deleted in d-tor
};

#endif /* _APPLICATIONCHOOSER_H */
4 changes: 4 additions & 0 deletions lxqt-config-file-associations/mimetypedata.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class MimeTypeData {
QString inline patterns() const { return mPatterns; };
QString inline comment() const { return mComment; };

void setName(const QString name) {
mName = name;
}

bool matches(const QString& filter) const;

private:
Expand Down

0 comments on commit eff9efd

Please sign in to comment.