diff options
author | Leandro Melo <leandro.melo@nokia.com> | 2011-02-22 15:11:50 +0100 |
---|---|---|
committer | Leandro Melo <leandro.melo@nokia.com> | 2011-03-02 10:13:40 +0100 |
commit | 3db53206c2e4908cfc86b65918cad2127f2174cd (patch) | |
tree | e179c08a32375cfb6e91e99fece1d5babe068389 | |
parent | c8f19854f9dae2148ba1a20c93ec5233f1f88aea (diff) | |
download | qt-creator-3db53206c2e4908cfc86b65918cad2127f2174cd.tar.gz |
MIME types: Introduce user extensions feature
Creates a new page in the options dialog which allows
the user to modify MIME type's globl patterns and rule-based
magic matchers.
As a side-effect of this feature our MIME database (and related
components) got some improvements.
Reviewed-by: Friedemann Kleint
-rw-r--r-- | src/plugins/coreplugin/coreconstants.h | 4 | ||||
-rw-r--r-- | src/plugins/coreplugin/coreplugin.cpp | 2 | ||||
-rw-r--r-- | src/plugins/coreplugin/coreplugin.pro | 13 | ||||
-rw-r--r-- | src/plugins/coreplugin/dialogs/shortcutsettings.cpp | 2 | ||||
-rw-r--r-- | src/plugins/coreplugin/mainwindow.cpp | 21 | ||||
-rw-r--r-- | src/plugins/coreplugin/mainwindow.h | 4 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimedatabase.cpp | 516 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimedatabase.h | 90 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimetypemagicdialog.cpp | 109 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimetypemagicdialog.h | 83 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimetypemagicdialog.ui | 318 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimetypesettings.cpp | 605 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimetypesettings.h | 72 | ||||
-rw-r--r-- | src/plugins/coreplugin/mimetypesettingspage.ui | 169 | ||||
-rw-r--r-- | src/plugins/texteditor/generichighlighter/manager.cpp | 51 |
15 files changed, 1956 insertions, 103 deletions
diff --git a/src/plugins/coreplugin/coreconstants.h b/src/plugins/coreplugin/coreconstants.h index cdfc0c06b0..7cf5e13847 100644 --- a/src/plugins/coreplugin/coreconstants.h +++ b/src/plugins/coreplugin/coreconstants.h @@ -253,7 +253,9 @@ const char * const SETTINGS_CATEGORY_CORE = "A.Core"; const char * const SETTINGS_CATEGORY_CORE_ICON = ":/core/images/category_core.png"; const char * const SETTINGS_TR_CATEGORY_CORE = QT_TRANSLATE_NOOP("Core", "Environment"); const char * const SETTINGS_ID_ENVIRONMENT = "A.General"; -const char * const SETTINGS_ID_TOOLS = "G.ExternalTools"; +const char * const SETTINGS_ID_SHORTCUTS = "B.Keyboard"; +const char * const SETTINGS_ID_TOOLS = "C.ExternalTools"; +const char * const SETTINGS_ID_MIMETYPES = "D.MimeTypes"; const char * const SETTINGS_DEFAULTTEXTENCODING = "General/DefaultFileEncoding"; diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index 36ef2b94ce..5092ba8dc4 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -38,6 +38,7 @@ #include "modemanager.h" #include "fileiconprovider.h" #include "designmode.h" +#include "mimedatabase.h" #include <extensionsystem/pluginmanager.h> @@ -99,6 +100,7 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage) void CorePlugin::extensionsInitialized() { + m_mainWindow->mimeDatabase()->syncUserModifiedMimeTypes(); m_mainWindow->extensionsInitialized(); } diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro index aee407e7a0..c25dcb3c43 100644 --- a/src/plugins/coreplugin/coreplugin.pro +++ b/src/plugins/coreplugin/coreplugin.pro @@ -91,7 +91,9 @@ SOURCES += mainwindow.cpp \ externaltool.cpp \ dialogs/externaltoolconfig.cpp \ toolsettings.cpp \ - variablechooser.cpp + variablechooser.cpp \ + mimetypemagicdialog.cpp \ + mimetypesettings.cpp HEADERS += mainwindow.h \ editmode.h \ @@ -180,7 +182,9 @@ HEADERS += mainwindow.h \ externaltool.h \ dialogs/externaltoolconfig.h \ toolsettings.h \ - variablechooser.h + variablechooser.h \ + mimetypemagicdialog.h \ + mimetypesettings.h FORMS += dialogs/newdialog.ui \ actionmanager/commandmappings.ui \ @@ -189,7 +193,10 @@ FORMS += dialogs/newdialog.ui \ editormanager/openeditorsview.ui \ generalsettings.ui \ dialogs/externaltoolconfig.ui \ - variablechooser.ui + variablechooser.ui \ + mimetypesettingspage.ui \ + mimetypemagicdialog.ui + RESOURCES += core.qrc \ fancyactionbar.qrc diff --git a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp index ba9f438212..72c6281c24 100644 --- a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp +++ b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp @@ -75,7 +75,7 @@ ShortcutSettings::~ShortcutSettings() QString ShortcutSettings::id() const { - return QLatin1String("D.Keyboard"); + return QLatin1String(Core::Constants::SETTINGS_ID_SHORTCUTS); } QString ShortcutSettings::displayName() const diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp index ac9ce6d311..05f0844efd 100644 --- a/src/plugins/coreplugin/mainwindow.cpp +++ b/src/plugins/coreplugin/mainwindow.cpp @@ -40,6 +40,7 @@ #include "editormanager.h" #include "externaltool.h" #include "toolsettings.h" +#include "mimetypesettings.h" #include "fancytabwidget.h" #include "filemanager.h" #include "generalsettings.h" @@ -152,6 +153,7 @@ MainWindow::MainWindow() : m_generalSettings(new GeneralSettings), m_shortcutSettings(new ShortcutSettings), m_toolSettings(new ToolSettings), + m_mimeTypeSettings(new MimeTypeSettings), m_systemEditor(new SystemEditor), m_focusToEditor(0), m_newAction(0), @@ -256,6 +258,7 @@ MainWindow::~MainWindow() pm->removeObject(m_shortcutSettings); pm->removeObject(m_generalSettings); pm->removeObject(m_toolSettings); + pm->removeObject(m_mimeTypeSettings); pm->removeObject(m_systemEditor); delete m_externalToolManager; m_externalToolManager = 0; @@ -267,6 +270,8 @@ MainWindow::~MainWindow() m_generalSettings = 0; delete m_toolSettings; m_toolSettings = 0; + delete m_mimeTypeSettings; + m_mimeTypeSettings = 0; delete m_systemEditor; m_systemEditor = 0; delete m_settings; @@ -325,9 +330,9 @@ bool MainWindow::init(QString *errorMessage) pm->addObject(m_generalSettings); pm->addObject(m_shortcutSettings); pm->addObject(m_toolSettings); + pm->addObject(m_mimeTypeSettings); pm->addObject(m_systemEditor); - // Add widget to the bottom, we create the view here instead of inside the // OutputPaneManager, since the StatusBarManager needs to be initialized before m_outputView = new Core::StatusBarWidget; @@ -352,8 +357,6 @@ void MainWindow::extensionsInitialized() readSettings(); updateContext(); - registerUserMimeTypes(); - emit m_coreImpl->coreAboutToOpen(); show(); emit m_coreImpl->coreOpened(); @@ -1397,15 +1400,3 @@ bool MainWindow::showWarningWithOptions(const QString &title, } return false; } - -void MainWindow::registerUserMimeTypes() const -{ - // This is to temporarily allow user specific MIME types (without recompilation). - // Be careful with the file contents. Otherwise unpredictable behavior might arise. - const QString &fileName = m_coreImpl->userResourcePath() + QLatin1String("/mimetypes.xml"); - if (QFile::exists(fileName)) { - QString error; - if (!m_coreImpl->mimeDatabase()->addMimeTypes(fileName, &error)) - qWarning() << error; - } -} diff --git a/src/plugins/coreplugin/mainwindow.h b/src/plugins/coreplugin/mainwindow.h index 1d72a35912..f64731fc65 100644 --- a/src/plugins/coreplugin/mainwindow.h +++ b/src/plugins/coreplugin/mainwindow.h @@ -78,6 +78,7 @@ class GeneralSettings; class ProgressManagerPrivate; class ShortcutSettings; class ToolSettings; +class MimeTypeSettings; class StatusBarManager; class VersionDialog; class SystemEditor; @@ -179,8 +180,6 @@ private: void readSettings(); void writeSettings(); - void registerUserMimeTypes() const; - CoreImpl *m_coreImpl; UniqueIDManager *m_uniqueIDManager; Context m_additionalContexts; @@ -214,6 +213,7 @@ private: GeneralSettings *m_generalSettings; ShortcutSettings *m_shortcutSettings; ToolSettings *m_toolSettings; + MimeTypeSettings *m_mimeTypeSettings; SystemEditor *m_systemEditor; // actions diff --git a/src/plugins/coreplugin/mimedatabase.cpp b/src/plugins/coreplugin/mimedatabase.cpp index 82f02f13bb..ad5853deb2 100644 --- a/src/plugins/coreplugin/mimedatabase.cpp +++ b/src/plugins/coreplugin/mimedatabase.cpp @@ -33,6 +33,7 @@ #include "mimedatabase.h" #include "coreconstants.h" +#include "icore.h" #include <utils/qtcassert.h> @@ -40,9 +41,11 @@ #include <QtCore/QCoreApplication> #include <QtCore/QDebug> #include <QtCore/QFile> +#include <QtCore/QDir> #include <QtCore/QFileInfo> #include <QtCore/QLocale> #include <QtCore/QMap> +#include <QtCore/QHash> #include <QtCore/QMultiHash> #include <QtCore/QRegExp> #include <QtCore/QSharedData> @@ -50,10 +53,11 @@ #include <QtCore/QStringList> #include <QtCore/QTextStream> #include <QtCore/QMutexLocker> - -#include <QtXml/QXmlStreamReader> +#include <QtCore/QXmlStreamReader> +#include <QtCore/QXmlStreamWriter> #include <algorithm> +#include <functional> enum { debugMimeDB = 0 }; @@ -211,6 +215,8 @@ bool HeuristicTextMagicMatcher::matches(const QByteArray &data) const } // namespace Internal // MagicRule +const QChar MagicRule::kColon(QLatin1Char(':')); + MagicRule::MagicRule(int startPos, int endPos) : m_startPos(startPos), m_endPos(endPos) { } @@ -229,6 +235,20 @@ int MagicRule::endPos() const return m_endPos; } +QString MagicRule::toOffset(const QPair<int, int> &startEnd) +{ + return QString(QLatin1String("%1:%2")).arg(startEnd.first).arg(startEnd.second); +} + +QPair<int, int> MagicRule::fromOffset(const QString &offset) +{ + const QStringList &startEnd = offset.split(kColon); + Q_ASSERT(startEnd.size() == 2); + return qMakePair(startEnd.at(0).toInt(), startEnd.at(1).toInt()); +} + +const QString MagicStringRule::kMatchType("string"); + MagicStringRule::MagicStringRule(const QString &s, int startPos, int endPos) : MagicRule(startPos, endPos), m_pattern(s.toUtf8()) { @@ -238,6 +258,16 @@ MagicStringRule::~MagicStringRule() { } +QString MagicStringRule::matchType() const +{ + return kMatchType; +} + +QString MagicStringRule::matchValue() const +{ + return m_pattern; +} + bool MagicStringRule::matches(const QByteArray &data) const { // Quick check @@ -254,26 +284,49 @@ bool MagicStringRule::matches(const QByteArray &data) const return rc; } +const QString MagicByteRule::kMatchType(QLatin1String("byte")); + MagicByteRule::MagicByteRule(const QString &s, int startPos, int endPos) : - MagicRule(startPos, endPos) + MagicRule(startPos, endPos), m_bytesSize(0) +{ + if (validateByteSequence(s, &m_bytes)) + m_bytesSize = m_bytes.size(); + else + m_bytes.clear(); +} + +MagicByteRule::~MagicByteRule() +{ +} + +bool MagicByteRule::validateByteSequence(const QString &sequence, QList<int> *bytes) { // Expect an hex format value like this: \0x7f\0x45\0x4c\0x46 - const QStringList &bytes = s.split(QLatin1Char('\\'), QString::SkipEmptyParts); - foreach (const QString &byte, bytes) { + const QStringList &byteSequence = sequence.split(QLatin1Char('\\'), QString::SkipEmptyParts); + foreach (const QString &byte, byteSequence) { bool ok; const int hex = byte.toInt(&ok, 16); if (ok) { - m_bytes.push_back(hex); + if (bytes) + bytes->push_back(hex); } else { - m_bytes.clear(); - break; + return false; } } - m_bytesSize = m_bytes.size(); + return true; } -MagicByteRule::~MagicByteRule() +QString MagicByteRule::matchType() const { + return kMatchType; +} + +QString MagicByteRule::matchValue() const +{ + QString value; + foreach (int byte, m_bytes) + value.append(QString(QLatin1String("\\0x%1")).arg(byte, 0, 16)); + return value; } bool MagicByteRule::matches(const QByteArray &data) const @@ -307,7 +360,17 @@ MagicRuleMatcher::MagicRuleMatcher() : void MagicRuleMatcher::add(const MagicRuleSharedPointer &rule) { - m_list.push_back(rule); + m_list.append(rule); +} + +void MagicRuleMatcher::add(const MagicRuleList &ruleList) +{ + m_list.append(ruleList); +} + +MagicRuleMatcher::MagicRuleList MagicRuleMatcher::magicRules() const +{ + return m_list; } bool MagicRuleMatcher::matches(const QByteArray &data) const @@ -329,6 +392,20 @@ void MagicRuleMatcher::setPriority(int p) m_priority = p; } +IMagicMatcher::IMagicMatcherList MagicRuleMatcher::createMatchers( + const QHash<int, MagicRuleList> &rulesByPriority) +{ + IMagicMatcher::IMagicMatcherList matchers; + QHash<int, MagicRuleList>::const_iterator ruleIt = rulesByPriority.begin(); + for (; ruleIt != rulesByPriority.end(); ++ruleIt) { + MagicRuleMatcher *magicRuleMatcher = new MagicRuleMatcher(); + magicRuleMatcher->setPriority(ruleIt.key()); + magicRuleMatcher->add(ruleIt.value()); + matchers.append(IMagicMatcher::IMagicMatcherSharedPointer(magicRuleMatcher)); + } + return matchers; +} + // GlobPattern MimeGlobPattern::MimeGlobPattern(const QRegExp ®Exp, unsigned weight) : m_regExp(regExp), m_weight(weight) @@ -353,9 +430,16 @@ unsigned MimeGlobPattern::weight() const class MimeTypeData : public QSharedData { public: typedef QHash<QString,QString> LocaleHash; + + MimeTypeData(); + void clear(); + void assignSuffix(const QString &pattern); + void assignSuffixes(const QStringList &patterns); void debug(QTextStream &str, int indent = 0) const; + const QRegExp suffixPattern; + QString type; QString comment; @@ -365,12 +449,17 @@ public: QStringList subClassesOf; QString preferredSuffix; QStringList suffixes; - - typedef QSharedPointer<IMagicMatcher> IMagicMatcherSharedPointer; - typedef QList<IMagicMatcherSharedPointer> IMagicMatcherList; - IMagicMatcherList magicMatchers; + IMagicMatcher::IMagicMatcherList magicMatchers; }; +MimeTypeData::MimeTypeData() + // RE to match a suffix glob pattern: "*.ext" (and not sth like "Makefile" or + // "*.log[1-9]" + : suffixPattern(QLatin1String("^\\*\\.[\\w+]+$")) +{ + QTC_ASSERT(suffixPattern.isValid(), /**/); +} + void MimeTypeData::clear() { type.clear(); @@ -383,6 +472,22 @@ void MimeTypeData::clear() magicMatchers.clear(); } +void MimeTypeData::assignSuffix(const QString &pattern) +{ + if (suffixPattern.exactMatch(pattern)) { + const QString suffix = pattern.right(pattern.size() - 2); + suffixes.push_back(suffix); + if (preferredSuffix.isEmpty()) + preferredSuffix = suffix; + } +} + +void MimeTypeData::assignSuffixes(const QStringList &patterns) +{ + foreach (const QString &pattern, patterns) + assignSuffix(pattern); +} + void MimeTypeData::debug(QTextStream &str, int indent) const { const QString indentS = QString(indent, QLatin1Char(' ')); @@ -516,6 +621,13 @@ QList<MimeGlobPattern> MimeType::globPatterns() const void MimeType::setGlobPatterns(const QList<MimeGlobPattern> &g) { m_d->globPatterns = g; + + QString oldPrefferedSuffix = m_d->preferredSuffix; + m_d->suffixes.clear(); + m_d->preferredSuffix.clear(); + m_d->assignSuffixes(MimeDatabase::fromGlobPatterns(g)); + if (m_d->preferredSuffix != oldPrefferedSuffix && m_d->suffixes.contains(oldPrefferedSuffix)) + m_d->preferredSuffix = oldPrefferedSuffix; } QStringList MimeType::subClassesOf() const @@ -608,7 +720,7 @@ unsigned MimeType::matchesFileByContent(Internal::FileMatchContext &c) const const QByteArray data = c.data(); if (!data.isEmpty()) { - foreach (const MimeTypeData::IMagicMatcherSharedPointer &matcher, m_d->magicMatchers) { + foreach (const IMagicMatcher::IMagicMatcherSharedPointer &matcher, m_d->magicMatchers) { if (matcher->matches(data)) { const unsigned magicPriority = matcher->priority(); if (magicPriority > priority) @@ -624,14 +736,50 @@ QStringList MimeType::suffixes() const return m_d->suffixes; } -void MimeType::setSuffixes(const QStringList &s) +void MimeType::addMagicMatcher(const IMagicMatcherSharedPointer &matcher) { - m_d->suffixes = s; + m_d->magicMatchers.push_back(matcher); } -void MimeType::addMagicMatcher(const QSharedPointer<IMagicMatcher> &matcher) +const MimeType::IMagicMatcherList &MimeType::magicMatchers() const { - m_d->magicMatchers.push_back(matcher); + return m_d->magicMatchers; +} + +void MimeType::setMagicMatchers(const IMagicMatcherList &matchers) +{ + m_d->magicMatchers = matchers; +} + +namespace { +struct RemovePred : std::unary_function<MimeType::IMagicMatcherSharedPointer, bool> +{ + RemovePred(bool keepRuleBased) : m_keepRuleBase(keepRuleBased) {} + bool m_keepRuleBase; + + bool operator()(const MimeType::IMagicMatcherSharedPointer &matcher) { + if ((m_keepRuleBase && !dynamic_cast<MagicRuleMatcher *>(matcher.data())) + || (!m_keepRuleBase && dynamic_cast<MagicRuleMatcher *>(matcher.data()))) + return true; + return false; + } +}; +} // Anonymous + +MimeType::IMagicMatcherList MimeType::magicRuleMatchers() const +{ + IMagicMatcherList ruleMatchers = m_d->magicMatchers; + ruleMatchers.erase(std::remove_if(ruleMatchers.begin(), ruleMatchers.end(), RemovePred(true)), + ruleMatchers.end()); + return ruleMatchers; +} + +void MimeType::setMagicRuleMatchers(const IMagicMatcherList &matchers) +{ + m_d->magicMatchers.erase(std::remove_if(m_d->magicMatchers.begin(), m_d->magicMatchers.end(), + RemovePred(false)), + m_d->magicMatchers.end()); + m_d->magicMatchers.append(matchers); } QDebug operator<<(QDebug d, const MimeType &mt) @@ -652,7 +800,7 @@ namespace Internal { class BaseMimeTypeParser { Q_DISABLE_COPY(BaseMimeTypeParser) public: - BaseMimeTypeParser(); + BaseMimeTypeParser() {} virtual ~BaseMimeTypeParser() {} bool parse(QIODevice *dev, const QString &fileName, QString *errorMessage); @@ -676,18 +824,8 @@ private: ParseError }; static ParseStage nextStage(ParseStage currentStage, const QStringRef &startElement); - - const QRegExp m_suffixPattern; }; -BaseMimeTypeParser:: BaseMimeTypeParser() : - // RE to match a suffix glob pattern: "*.ext" (and not sth like "Makefile" or - // "*.log[1-9]" - m_suffixPattern(QLatin1String("^\\*\\.[\\w+]+$")) -{ - QTC_ASSERT(m_suffixPattern.isValid(), /**/); -} - void BaseMimeTypeParser::addGlobPattern(const QString &pattern, const QString &weight, MimeTypeData *d) const { if (pattern.isEmpty()) @@ -706,12 +844,7 @@ void BaseMimeTypeParser::addGlobPattern(const QString &pattern, const QString &w else d->globPatterns.push_back(MimeGlobPattern(wildCard, weight.toInt())); - if (m_suffixPattern.exactMatch(pattern)) { - const QString suffix = pattern.right(pattern.size() - 2); - d->suffixes.push_back(suffix); - if (d->preferredSuffix.isEmpty()) - d->preferredSuffix = suffix; - } + d->assignSuffix(pattern); } BaseMimeTypeParser::ParseStage BaseMimeTypeParser::nextStage(ParseStage currentStage, const QStringRef &startElement) @@ -934,10 +1067,10 @@ MimeMapEntry::MimeMapEntry(const MimeType &t, int aLevel) : * - Provide quick lookup by file type. * This basically rules out some pointer-based tree, so the structure chosen * is: - * - An alias map <QString->QString> for mapping aliases to types - * - A Map <QString-MimeMapEntry> for the types (MimeMapEntry being a pair of + * - An alias map QString->QString for mapping aliases to types + * - A Map QString->MimeMapEntry for the types (MimeMapEntry being a pair of * MimeType and (hierarchy) level. - * - A map <QString->QString> representing parent->child relations (enabling + * - A map QString->QString representing parent->child relations (enabling * recursing over children) * Using strings avoids dangling pointers. * The hierarchy level is used for mapping by file types. When findByFile() @@ -958,16 +1091,31 @@ public: bool addMimeTypes(QIODevice *device, QString *errorMessage); bool addMimeType(MimeType mt); - // Returns a mime type or Null one if none found MimeType findByType(const QString &type) const; - // Returns a mime type or Null one if none found MimeType findByFile(const QFileInfo &f) const; - bool setPreferredSuffix(const QString &typeOrAlias, const QString &suffix); + QStringList filterStrings() const; - // Return all known suffixes QStringList suffixes() const; - QStringList filterStrings() const; + bool setPreferredSuffix(const QString &typeOrAlias, const QString &suffix); + + QList<MimeGlobPattern> globPatterns() const; + void setGlobPatterns(const QString &typeOrAlias, const QList<MimeGlobPattern> &globPatterns); + + QList<QSharedPointer<IMagicMatcher> > magicMatchers() const; + void setMagicMatchers(const QString &typeOrAlias, + const QList<QSharedPointer<IMagicMatcher> > &matchers); + + QList<MimeType> mimeTypes() const; + + void syncUserModifiedMimeTypes(); + static QList<MimeType> readUserModifiedMimeTypes(); + static void writeUserModifiedMimeTypes(const QList<MimeType> &mimeTypes); + void clearUserModifiedMimeTypes(); + + static QList<MimeGlobPattern> toGlobPatterns(const QStringList &patterns, + int weight = MimeGlobPattern::MaxWeight); + static QStringList fromGlobPatterns(const QList<MimeGlobPattern> &globPatterns); void debug(QTextStream &str) const; @@ -976,6 +1124,11 @@ private: typedef QHash<QString, QString> AliasMap; typedef QMultiHash<QString, QString> ParentChildrenMap; + static const QChar kSemiColon; + static const QString kModifiedMimeTypesFile; + static QString kModifiedMimeTypesPath; + + bool addMimeTypes(QIODevice *device, const QString &fileName, QString *errorMessage); inline const QString &resolveAlias(const QString &name) const; MimeType findByFile(const QFileInfo &f, unsigned *priority) const; @@ -988,9 +1141,15 @@ private: int m_maxLevel; }; +const QChar MimeDatabasePrivate::kSemiColon(QLatin1Char(';')); +const QString MimeDatabasePrivate::kModifiedMimeTypesFile(QLatin1String("modifiedmimetypes.xml")); +QString MimeDatabasePrivate::kModifiedMimeTypesPath; + MimeDatabasePrivate::MimeDatabasePrivate() : m_maxLevel(-1) { + // Assign here to avoid non-local static data initialization issues. + kModifiedMimeTypesPath = ICore::instance()->userResourcePath() + QLatin1String("/mimetypes/"); } namespace Internal { @@ -1230,6 +1389,197 @@ QStringList MimeDatabasePrivate::filterStrings() const return rc; } +QList<MimeGlobPattern> MimeDatabasePrivate::globPatterns() const +{ + QList<MimeGlobPattern> globPatterns; + const TypeMimeTypeMap::const_iterator cend = m_typeMimeTypeMap.constEnd(); + for (TypeMimeTypeMap::const_iterator it = m_typeMimeTypeMap.constBegin(); it != cend; ++it) + globPatterns.append(it.value().type.globPatterns()); + return globPatterns; +} + +void MimeDatabasePrivate::setGlobPatterns(const QString &typeOrAlias, + const QList<MimeGlobPattern> &globPatterns) +{ + TypeMimeTypeMap::iterator tit = m_typeMimeTypeMap.find(resolveAlias(typeOrAlias)); + if (tit != m_typeMimeTypeMap.end()) + tit.value().type.setGlobPatterns(globPatterns); +} + +QList<QSharedPointer<IMagicMatcher> > MimeDatabasePrivate::magicMatchers() const +{ + QList<QSharedPointer<IMagicMatcher> > magicMatchers; + const TypeMimeTypeMap::const_iterator cend = m_typeMimeTypeMap.constEnd(); + for (TypeMimeTypeMap::const_iterator it = m_typeMimeTypeMap.constBegin(); it != cend; ++it) + magicMatchers.append(it.value().type.magicMatchers()); + return magicMatchers; +} + +void MimeDatabasePrivate::setMagicMatchers(const QString &typeOrAlias, + const QList<QSharedPointer<IMagicMatcher> > &matchers) +{ + TypeMimeTypeMap::iterator tit = m_typeMimeTypeMap.find(resolveAlias(typeOrAlias)); + if (tit != m_typeMimeTypeMap.end()) + tit.value().type.setMagicMatchers(matchers); +} + +QList<MimeType> MimeDatabasePrivate::mimeTypes() const +{ + QList<MimeType> mimeTypes; + const TypeMimeTypeMap::const_iterator cend = m_typeMimeTypeMap.constEnd(); + for (TypeMimeTypeMap::const_iterator it = m_typeMimeTypeMap.constBegin(); it != cend; ++it) + mimeTypes.append(it.value().type); + return mimeTypes; +} + +void MimeDatabasePrivate::syncUserModifiedMimeTypes() +{ + QHash<QString, MimeType> userModified; + const QList<MimeType> &userMimeTypes = readUserModifiedMimeTypes(); + foreach (const MimeType &userMimeType, userMimeTypes) + userModified.insert(userMimeType.type(), userMimeType); + + TypeMimeTypeMap::iterator end = m_typeMimeTypeMap.end(); + QHash<QString, MimeType>::const_iterator userMimeEnd = userModified.end(); + for (TypeMimeTypeMap::iterator it = m_typeMimeTypeMap.begin(); it != end; ++it) { + QHash<QString, MimeType>::const_iterator userMimeIt = + userModified.find(it.value().type.type()); + if (userMimeIt != userMimeEnd) { + it.value().type.setGlobPatterns(userMimeIt.value().globPatterns()); + it.value().type.setMagicRuleMatchers(userMimeIt.value().magicRuleMatchers()); + } + } +} + +QList<MimeType> MimeDatabasePrivate::readUserModifiedMimeTypes() +{ + typedef MagicRuleMatcher::MagicRuleList MagicRuleList; + typedef MagicRuleMatcher::MagicRuleSharedPointer MagicRuleSharedPointer; + + QList<MimeType> mimeTypes; + QFile file(kModifiedMimeTypesPath + kModifiedMimeTypesFile); + if (file.open(QFile::ReadOnly)) { + MimeType mimeType; + QHash<int, MagicRuleList> rules; + QXmlStreamReader reader(&file); + QXmlStreamAttributes atts; + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: + atts = reader.attributes(); + if (reader.name() == mimeTypeTagC) { + mimeType.setType(atts.value(mimeTypeAttributeC).toString()); + const QString &patterns = atts.value(patternAttributeC).toString(); + mimeType.setGlobPatterns(toGlobPatterns(patterns.split(kSemiColon))); + } else if (reader.name() == matchTagC) { + const QString &value = atts.value(matchValueAttributeC).toString(); + const QString &type = atts.value(matchTypeAttributeC).toString(); + const QString &offset = atts.value(matchOffsetAttributeC).toString(); + QPair<int, int> range = MagicRule::fromOffset(offset); + const int priority = atts.value(priorityAttributeC).toString().toInt(); + + MagicRule *magicRule; + if (type == MagicStringRule::kMatchType) + magicRule = new MagicStringRule(value, range.first, range.second); + else + magicRule = new MagicByteRule(value, range.first, range.second); + rules[priority].append(MagicRuleSharedPointer(magicRule)); + } + break; + case QXmlStreamReader::EndElement: + if (reader.name() == mimeTypeTagC) { + mimeType.setMagicRuleMatchers(MagicRuleMatcher::createMatchers(rules)); + mimeTypes.append(mimeType); + mimeType.clear(); + rules.clear(); + } + break; + default: + break; + } + } + if (reader.hasError()) + qWarning() << kModifiedMimeTypesFile << reader.errorString() << reader.lineNumber() + << reader.columnNumber(); + file.close(); + } + return mimeTypes; +} + +void MimeDatabasePrivate::writeUserModifiedMimeTypes(const QList<MimeType> &mimeTypes) +{ + // Keep mime types modified which are already on file. + QList<MimeType> allModifiedMimeTypes = mimeTypes; + allModifiedMimeTypes.append(readUserModifiedMimeTypes()); + + if (QFile::exists(kModifiedMimeTypesPath) || QDir().mkpath(kModifiedMimeTypesPath)) { + QFile file(kModifiedMimeTypesPath + kModifiedMimeTypesFile); + if (file.open(QFile::WriteOnly | QFile::Truncate)) { + // Notice this file only represents user modifications. It is writen in a + // convienient way for synchronization, which is similar to but not exactly the + // same format we use for the embedded mime type files. + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement(QLatin1String(mimeInfoTagC)); + foreach (const MimeType &mimeType, allModifiedMimeTypes) { + writer.writeStartElement(mimeTypeTagC); + writer.writeAttribute(mimeTypeAttributeC, mimeType.type()); + writer.writeAttribute(patternAttributeC, + fromGlobPatterns(mimeType.globPatterns()).join(kSemiColon)); + const QList<QSharedPointer<IMagicMatcher> > &matchers = mimeType.magicMatchers(); + foreach (const QSharedPointer<IMagicMatcher> &matcher, matchers) { + // Only care about rule-based matchers. + if (MagicRuleMatcher *ruleMatcher = + dynamic_cast<MagicRuleMatcher *>(matcher.data())) { + const MagicRuleMatcher::MagicRuleList &rules = ruleMatcher->magicRules(); + foreach (const MagicRuleMatcher::MagicRuleSharedPointer &rule, rules) { + writer.writeStartElement(matchTagC); + writer.writeAttribute(matchValueAttributeC, rule->matchValue()); + writer.writeAttribute(matchTypeAttributeC, rule->matchType()); + writer.writeAttribute(matchOffsetAttributeC, + MagicRule::toOffset( + qMakePair(rule->startPos(), rule->endPos()))); + writer.writeAttribute(priorityAttributeC, + QString::number(ruleMatcher->priority())); + writer.writeEndElement(); + } + } + } + writer.writeEndElement(); + } + writer.writeEndElement(); + writer.writeEndDocument(); + file.close(); + } + } +} + +void MimeDatabasePrivate::clearUserModifiedMimeTypes() +{ + // This removes the user's file. However, the operation will actually take place the next time + // Creator starts, since we currently don't support removing stuff from the mime database. + QFile::remove(kModifiedMimeTypesPath + kModifiedMimeTypesFile); +} + +QList<MimeGlobPattern> MimeDatabasePrivate::toGlobPatterns(const QStringList &patterns, int weight) +{ + QList<MimeGlobPattern> globPatterns; + foreach (const QString &pattern, patterns) { + QRegExp regExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); + globPatterns.append(Core::MimeGlobPattern(regExp, weight)); + } + return globPatterns; +} + +QStringList MimeDatabasePrivate::fromGlobPatterns(const QList<MimeGlobPattern> &globPatterns) +{ + QStringList patterns; + foreach (const MimeGlobPattern &globPattern, globPatterns) + patterns.append(globPattern.regExp().pattern()); + return patterns; +} + void MimeDatabasePrivate::debug(QTextStream &str) const { str << ">MimeDatabase\n"; @@ -1308,7 +1658,7 @@ QStringList MimeDatabase::suffixes() const QStringList MimeDatabase::filterStrings() const { m_mutex.lock(); - const QStringList rc = m_d->filterStrings(); + const QStringList rc = m_d->filterStrings(); m_mutex.unlock(); return rc; } @@ -1336,6 +1686,70 @@ QString MimeDatabase::allFiltersString(QString *allFilesFilter) const return filters.join(QLatin1String(";;")); } +QList<MimeGlobPattern> MimeDatabase::globPatterns() const +{ + m_mutex.lock(); + const QList<MimeGlobPattern> rc = m_d->globPatterns(); + m_mutex.unlock(); + return rc; +} + +void MimeDatabase::setGlobPatterns(const QString &typeOrAlias, + const QList<MimeGlobPattern> &globPatterns) +{ + m_mutex.lock(); + m_d->setGlobPatterns(typeOrAlias, globPatterns); + m_mutex.unlock(); +} + +MimeDatabase::IMagicMatcherList MimeDatabase::magicMatchers() const +{ + m_mutex.lock(); + const IMagicMatcherList rc = m_d->magicMatchers(); + m_mutex.unlock(); + return rc; +} + +void MimeDatabase::setMagicMatchers(const QString &typeOrAlias, + const IMagicMatcherList &matchers) +{ + m_mutex.lock(); + m_d->setMagicMatchers(typeOrAlias, matchers); + m_mutex.unlock(); +} + +QList<MimeType> MimeDatabase::mimeTypes() const +{ + m_mutex.lock(); + const QList<MimeType> &mimeTypes = m_d->mimeTypes(); + m_mutex.unlock(); + return mimeTypes; +} + +void MimeDatabase::syncUserModifiedMimeTypes() +{ + m_mutex.lock(); + m_d->syncUserModifiedMimeTypes(); + m_mutex.unlock(); +} + +void MimeDatabase::clearUserModifiedMimeTypes() +{ + m_mutex.lock(); + m_d->clearUserModifiedMimeTypes(); + m_mutex.unlock(); +} + +QList<MimeType> MimeDatabase::readUserModifiedMimeTypes() +{ + return MimeDatabasePrivate::readUserModifiedMimeTypes(); +} + +void MimeDatabase::writeUserModifiedMimeTypes(const QList<MimeType> &mimeTypes) +{ + MimeDatabasePrivate::writeUserModifiedMimeTypes(mimeTypes); +} + QString MimeDatabase::preferredSuffixByType(const QString &type) const { if (const MimeType mt = findByType(type)) @@ -1358,6 +1772,16 @@ bool MimeDatabase::setPreferredSuffix(const QString &typeOrAlias, const QString return rc; } +QList<MimeGlobPattern> MimeDatabase::toGlobPatterns(const QStringList &patterns, int weight) +{ + return MimeDatabasePrivate::toGlobPatterns(patterns, weight); +} + +QStringList MimeDatabase::fromGlobPatterns(const QList<MimeGlobPattern> &globPatterns) +{ + return MimeDatabasePrivate::fromGlobPatterns(globPatterns); +} + QDebug operator<<(QDebug d, const MimeDatabase &mt) { QString s; diff --git a/src/plugins/coreplugin/mimedatabase.h b/src/plugins/coreplugin/mimedatabase.h index 954cf8e50b..722a015870 100644 --- a/src/plugins/coreplugin/mimedatabase.h +++ b/src/plugins/coreplugin/mimedatabase.h @@ -41,6 +41,7 @@ #include <QtCore/QByteArray> #include <QtCore/QMutex> #include <QtCore/QFileInfo> +#include <QtCore/QPair> QT_BEGIN_NAMESPACE class QIODevice; @@ -66,6 +67,9 @@ class CORE_EXPORT IMagicMatcher protected: IMagicMatcher() {} public: + typedef QSharedPointer<IMagicMatcher> IMagicMatcherSharedPointer; + typedef QList<IMagicMatcherSharedPointer> IMagicMatcherList; + // Check for a match on contents of a file virtual bool matches(const QByteArray &data) const = 0; // Return a priority value from 1..100 @@ -83,13 +87,19 @@ public: MagicRule(int startPos, int endPos); virtual ~MagicRule(); + virtual QString matchType() const = 0; + virtual QString matchValue() const = 0; virtual bool matches(const QByteArray &data) const = 0; -protected: int startPos() const; int endPos() const; + static QString toOffset(const QPair<int, int> &startEnd); + static QPair<int, int> fromOffset(const QString &offset); + private: + static const QChar kColon; + const int m_startPos; const int m_endPos; }; @@ -100,8 +110,12 @@ public: MagicStringRule(const QString &s, int startPos, int endPos); virtual ~MagicStringRule(); + virtual QString matchType() const; + virtual QString matchValue() const; virtual bool matches(const QByteArray &data) const; + static const QString kMatchType; + private: const QByteArray m_pattern; }; @@ -112,11 +126,17 @@ public: MagicByteRule(const QString &s, int startPos, int endPos); virtual ~MagicByteRule(); + virtual QString matchType() const; + virtual QString matchValue() const; virtual bool matches(const QByteArray &data) const; + static bool validateByteSequence(const QString &sequence, QList<int> *bytes = 0); + + static const QString kMatchType; + private: - QList<int> m_bytes; int m_bytesSize; + QList<int> m_bytes; }; /* Utility class: A Magic matcher that checks a number of rules based on @@ -125,17 +145,24 @@ class CORE_EXPORT MagicRuleMatcher : public IMagicMatcher { Q_DISABLE_COPY(MagicRuleMatcher) public: - typedef QSharedPointer<MagicRule> MagicRuleSharedPointer; + typedef QSharedPointer<MagicRule> MagicRuleSharedPointer; + typedef QList<MagicRuleSharedPointer> MagicRuleList; MagicRuleMatcher(); + void add(const MagicRuleSharedPointer &rule); + void add(const MagicRuleList &ruleList); + MagicRuleList magicRules() const; + virtual bool matches(const QByteArray &data) const; virtual int priority() const; void setPriority(int p); + // Create a list of MagicRuleMatchers from a hash of rules indexed by priorities. + static IMagicMatcher::IMagicMatcherList createMatchers(const QHash<int, MagicRuleList> &); + private: - typedef QList<MagicRuleSharedPointer> MagicRuleList; MagicRuleList m_list; int m_priority; }; @@ -169,6 +196,9 @@ private: class CORE_EXPORT MimeType { public: + typedef IMagicMatcher::IMagicMatcherList IMagicMatcherList; + typedef IMagicMatcher::IMagicMatcherSharedPointer IMagicMatcherSharedPointer; + MimeType(); MimeType(const MimeType&); MimeType &operator=(const MimeType&); @@ -200,9 +230,6 @@ public: // Extension over standard mime data QStringList suffixes() const; - void setSuffixes(const QStringList &); - - // Extension over standard mime data QString preferredSuffix() const; bool setPreferredSuffix(const QString&); @@ -216,12 +243,19 @@ public: // Return a filter string usable for a file dialog QString filterString() const; - // Add magic matcher - void addMagicMatcher(const QSharedPointer<IMagicMatcher> &matcher); + void addMagicMatcher(const IMagicMatcherSharedPointer &matcher); + + const IMagicMatcherList &magicMatchers() const; + void setMagicMatchers(const IMagicMatcherList &matchers); + + // Convenience for rule-base matchers. + IMagicMatcherList magicRuleMatchers() const; + void setMagicRuleMatchers(const IMagicMatcherList &matchers); friend QDebug operator<<(QDebug d, const MimeType &mt); - static QString formatFilterString(const QString &description, const QList<MimeGlobPattern> &globs); + static QString formatFilterString(const QString &description, + const QList<MimeGlobPattern> &globs); private: explicit MimeType(const MimeTypeData &d); @@ -243,8 +277,10 @@ class CORE_EXPORT MimeDatabase { Q_DISABLE_COPY(MimeDatabase) public: - MimeDatabase(); + typedef IMagicMatcher::IMagicMatcherList IMagicMatcherList; + typedef IMagicMatcher::IMagicMatcherSharedPointer IMagicMatcherSharedPointer; + MimeDatabase(); ~MimeDatabase(); bool addMimeTypes(const QString &fileName, QString *errorMessage); @@ -256,27 +292,45 @@ public: // Returns a mime type or Null one if none found MimeType findByFile(const QFileInfo &f) const; + // Convenience that mutex-locks the DB and calls a function // of the signature 'void f(const MimeType &, const QFileInfo &, const QString &)' // for each filename of a sequence. This avoids locking the DB for each // single file. template <class Iterator, typename Function> - inline void findByFile(Iterator i1, const Iterator &i2, Function f) const; - - // Convenience - QString preferredSuffixByType(const QString &type) const; - QString preferredSuffixByFile(const QFileInfo &f) const; + inline void findByFile(Iterator i1, const Iterator &i2, Function f) const; // Return all known suffixes QStringList suffixes() const; bool setPreferredSuffix(const QString &typeOrAlias, const QString &suffix); + QString preferredSuffixByType(const QString &type) const; + QString preferredSuffixByFile(const QFileInfo &f) const; QStringList filterStrings() const; + // Return a string with all the possible file filters, for use with file dialogs + QString allFiltersString(QString *allFilesFilter = 0) const; + + QList<MimeGlobPattern> globPatterns() const; + void setGlobPatterns(const QString &typeOrAlias, const QList<MimeGlobPattern> &globPatterns); + + IMagicMatcherList magicMatchers() const; + void setMagicMatchers(const QString &typeOrAlias, const IMagicMatcherList &matchers); + + QList<MimeType> mimeTypes() const; + + // The mime types from the functions bellow are considered only in regard to + // their glob patterns and rule-based magic matchers. + void syncUserModifiedMimeTypes(); + static QList<MimeType> readUserModifiedMimeTypes(); + static void writeUserModifiedMimeTypes(const QList<MimeType> &mimeTypes); + void clearUserModifiedMimeTypes(); + + static QList<MimeGlobPattern> toGlobPatterns(const QStringList &patterns, + int weight = MimeGlobPattern::MaxWeight); + static QStringList fromGlobPatterns(const QList<MimeGlobPattern> &globPatterns); friend QDebug operator<<(QDebug d, const MimeDatabase &mt); - // returns a string with all the possible file filters, for use with file dialogs - QString allFiltersString(QString *allFilesFilter = 0) const; private: MimeType findByFileUnlocked(const QFileInfo &f) const; diff --git a/src/plugins/coreplugin/mimetypemagicdialog.cpp b/src/plugins/coreplugin/mimetypemagicdialog.cpp new file mode 100644 index 0000000000..ea444ba74b --- /dev/null +++ b/src/plugins/coreplugin/mimetypemagicdialog.cpp @@ -0,0 +1,109 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** No Commercial Usage +** +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "mimetypemagicdialog.h" +#include "mimedatabase.h" + +#include <QtCore/QLatin1String> +#include <QtGui/QMessageBox> + +using namespace Core; +using namespace Internal; + +MimeTypeMagicDialog::MimeTypeMagicDialog(QWidget *parent) : + QDialog(parent) +{ + ui.setupUi(this); + setWindowTitle(tr("Magic Header")); + connect(ui.useRecommendedGroupBox, SIGNAL(clicked(bool)), + this, SLOT(applyRecommended(bool))); + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(validateAccept())); +} + +void MimeTypeMagicDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui.retranslateUi(this); + break; + default: + break; + } +} + +void MimeTypeMagicDialog::applyRecommended(bool checked) +{ + if (checked) { + ui.startRangeSpinBox->setValue(0); + ui.endRangeSpinBox->setValue(0); + ui.prioritySpinBox->setValue(50); + } +} + +void MimeTypeMagicDialog::validateAccept() +{ + if (ui.valueLineEdit->text().isEmpty() + || (ui.byteRadioButton->isChecked() + && !Core::MagicByteRule::validateByteSequence(ui.valueLineEdit->text()))) { + QMessageBox::critical(0, tr("Error"), tr("Not a valid byte pattern.")); + return; + } + accept(); +} + +void MimeTypeMagicDialog::setMagicData(const MagicData &data) +{ + ui.valueLineEdit->setText(data.m_value); + if (data.m_type == Core::MagicStringRule::kMatchType) + ui.stringRadioButton->setChecked(true); + else + ui.byteRadioButton->setChecked(true); + ui.startRangeSpinBox->setValue(data.m_start); + ui.endRangeSpinBox->setValue(data.m_end); + ui.prioritySpinBox->setValue(data.m_priority); +} + +MagicData MimeTypeMagicDialog::magicData() const +{ + MagicData data; + data.m_value = ui.valueLineEdit->text(); + if (ui.stringRadioButton->isChecked()) + data.m_type = Core::MagicStringRule::kMatchType; + else + data.m_type = Core::MagicByteRule::kMatchType; + data.m_start = ui.startRangeSpinBox->value(); + data.m_end = ui.endRangeSpinBox->value(); + data.m_priority = ui.prioritySpinBox->value(); + return data; +} diff --git a/src/plugins/coreplugin/mimetypemagicdialog.h b/src/plugins/coreplugin/mimetypemagicdialog.h new file mode 100644 index 0000000000..a9cd3ca97f --- /dev/null +++ b/src/plugins/coreplugin/mimetypemagicdialog.h @@ -0,0 +1,83 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** No Commercial Usage +** +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef MIMETYPEMAGICDIALOG_H +#define MIMETYPEMAGICDIALOG_H + +#include "ui_mimetypemagicdialog.h" + +namespace Core { +namespace Internal { + +struct MagicData +{ + MagicData() {} + MagicData(const QString &value, const QString &type, int start, int end, int p) + : m_value(value) + , m_type(type) + , m_start(start) + , m_end(end) + , m_priority(p) {} + + QString m_value; + QString m_type; + int m_start; + int m_end; + int m_priority; +}; + +class MimeTypeMagicDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MimeTypeMagicDialog(QWidget *parent = 0); + + void setMagicData(const MagicData &data); + MagicData magicData() const; + +protected: + void changeEvent(QEvent *e); + +private slots: + void applyRecommended(bool checked); + void validateAccept(); + +private: + Ui::MimeTypeMagicDialog ui; +}; + +} // Internal +} // Core + +#endif // MIMETYPEMAGICDIALOG_H diff --git a/src/plugins/coreplugin/mimetypemagicdialog.ui b/src/plugins/coreplugin/mimetypemagicdialog.ui new file mode 100644 index 0000000000..3da4ddbeaf --- /dev/null +++ b/src/plugins/coreplugin/mimetypemagicdialog.ui @@ -0,0 +1,318 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MimeTypeMagicDialog</class> + <widget class="QDialog" name="MimeTypeMagicDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>480</width> + <height>286</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="valueLabel"> + <property name="text"> + <string>Value:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="valueLineEdit"/> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="typeGroupBox"> + <property name="title"> + <string>Type</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QRadioButton" name="stringRadioButton"> + <property name="text"> + <string>String</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="byteRadioButton"> + <property name="text"> + <string>Byte</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="useRecommendedGroupBox"> + <property name="title"> + <string>Use Recommended</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="startRangeLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Start range:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="endRangeLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>End range:</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="priorityLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Priority:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="prioritySpinBox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="value"> + <number>50</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="startRangeSpinBox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maximum"> + <number>9999</number> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="endRangeSpinBox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maximum"> + <number>9999</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>233</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="noteLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string><i>Note: Wide range values might impact on Qt Creator's performance when opening files.</i></string> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>11</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>MimeTypeMagicDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>295</x> + <y>297</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>useRecommendedGroupBox</sender> + <signal>clicked(bool)</signal> + <receiver>prioritySpinBox</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>48</x> + <y>171</y> + </hint> + <hint type="destinationlabel"> + <x>105</x> + <y>249</y> + </hint> + </hints> + </connection> + <connection> + <sender>useRecommendedGroupBox</sender> + <signal>clicked(bool)</signal> + <receiver>startRangeSpinBox</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>195</x> + <y>169</y> + </hint> + <hint type="destinationlabel"> + <x>107</x> + <y>149</y> + </hint> + </hints> + </connection> + <connection> + <sender>useRecommendedGroupBox</sender> + <signal>clicked(bool)</signal> + <receiver>endRangeSpinBox</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>195</x> + <y>169</y> + </hint> + <hint type="destinationlabel"> + <x>107</x> + <y>175</y> + </hint> + </hints> + </connection> + <connection> + <sender>useRecommendedGroupBox</sender> + <signal>clicked(bool)</signal> + <receiver>priorityLabel</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>225</x> + <y>178</y> + </hint> + <hint type="destinationlabel"> + <x>99</x> + <y>201</y> + </hint> + </hints> + </connection> + <connection> + <sender>useRecommendedGroupBox</sender> + <signal>clicked(bool)</signal> + <receiver>startRangeLabel</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>225</x> + <y>178</y> + </hint> + <hint type="destinationlabel"> + <x>99</x> + <y>149</y> + </hint> + </hints> + </connection> + <connection> + <sender>useRecommendedGroupBox</sender> + <signal>clicked(bool)</signal> + <receiver>endRangeLabel</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>225</x> + <y>178</y> + </hint> + <hint type="destinationlabel"> + <x>99</x> + <y>175</y> + </hint> + </hints> + </connection> + <connection> + <sender>useRecommendedGroupBox</sender> + <signal>clicked(bool)</signal> + <receiver>noteLabel</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>225</x> + <y>178</y> + </hint> + <hint type="destinationlabel"> + <x>182</x> + <y>225</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/coreplugin/mimetypesettings.cpp b/src/plugins/coreplugin/mimetypesettings.cpp new file mode 100644 index 0000000000..ebede48aa1 --- /dev/null +++ b/src/plugins/coreplugin/mimetypesettings.cpp @@ -0,0 +1,605 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** No Commercial Usage +** +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "mimetypesettings.h" +#include "ui_mimetypesettingspage.h" +#include "mimetypemagicdialog.h" +#include "mimedatabase.h" +#include "coreconstants.h" +#include "editormanager.h" +#include "icore.h" +#include "ieditorfactory.h" +#include "iexternaleditor.h" + +#include <extensionsystem/pluginmanager.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QLatin1String> +#include <QtCore/QStringList> +#include <QtCore/QVector> +#include <QtCore/QSet> +#include <QtCore/QScopedPointer> +#include <QtCore/QAbstractTableModel> +#include <QtCore/QHash> +#include <QtGui/QIcon> +#include <QtGui/QTableWidgetItem> +#include <QtGui/QMessageBox> +#include <QtAlgorithms> + +#include <algorithm> + +namespace Core { +namespace Internal { + +struct MimeTypeComp +{ + bool operator()(const MimeType &a, const MimeType &b) + { return a.type().compare(b.type(), Qt::CaseInsensitive) < 0; } +}; + +// MimeTypeSettingsModel +class MimeTypeSettingsModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + MimeTypeSettingsModel(QObject *parent = 0) + : QAbstractTableModel(parent), m_core(ICore::instance()) {} + virtual ~MimeTypeSettingsModel() {} + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual QVariant data(const QModelIndex &modelIndex, int role = Qt::DisplayRole) const; + + void load(); + void validatePatterns(QStringList *candidates, const MimeType &mimeType) const; + void updateKnownPatterns(const QStringList &oldPatterns, const QStringList &newPatterns); + + ICore *m_core; + QList<MimeType> m_mimeTypes; + QSet<QString> m_knownPatterns; + QHash<QString, QString> m_handlersByMimeType; +}; + +int MimeTypeSettingsModel::rowCount(const QModelIndex &) const +{ + return m_mimeTypes.size(); +} + +int MimeTypeSettingsModel::columnCount(const QModelIndex &) const +{ + return 2; +} + +QVariant MimeTypeSettingsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + + if (section == 0) + return tr("MIME Type"); + else + return tr("Handler"); +} + +QVariant MimeTypeSettingsModel::data(const QModelIndex &modelIndex, int role) const +{ + if (!modelIndex.isValid()) + return QVariant(); + + const int column = modelIndex.column(); + if (role == Qt::DisplayRole) { + const QString &type = m_mimeTypes.at(modelIndex.row()).type(); + if (column == 0) + return type; + else + return m_handlersByMimeType.value(type); + } else if (role == Qt::TextAlignmentRole) { + if (column == 1) + return Qt::AlignCenter; + } + return QVariant(); +} + +void MimeTypeSettingsModel::load() +{ + m_mimeTypes = m_core->mimeDatabase()->mimeTypes(); + qSort(m_mimeTypes.begin(), m_mimeTypes.end(), MimeTypeComp()); + m_knownPatterns = QSet<QString>::fromList( + MimeDatabase::fromGlobPatterns(m_core->mimeDatabase()->globPatterns())); + + foreach (const MimeType &mimeType, m_mimeTypes) { + QString value; + const QList<IEditorFactory *> factories = + m_core->editorManager()->editorFactories(mimeType); + if (!factories.isEmpty()) { + value = factories.front()->displayName(); + } else { + const QList<IExternalEditor *> externalEditors = + m_core->editorManager()->externalEditors(mimeType); + if (!externalEditors.isEmpty()) + value = externalEditors.front()->displayName(); + else + value = tr("Undefined"); + } + m_handlersByMimeType.insert(mimeType.type(), value); + } +} + +void MimeTypeSettingsModel::validatePatterns(QStringList *candidates, + const MimeType &mimeType) const +{ + QSet<QString> oldPatterns = + QSet<QString>::fromList(MimeDatabase::fromGlobPatterns(mimeType.globPatterns())); + + QStringList duplicates; + QStringList::iterator it = candidates->begin(); + while (it != candidates->end()) { + const QString ¤t = *it; + if (!oldPatterns.contains(current) && m_knownPatterns.contains(current)) { + duplicates.append(current); + it = candidates->erase(it); + } else { + ++it; + } + } + + if (!duplicates.isEmpty()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Invalid MIME Type")); + msgBox.setText(tr("Conflicting pattern(s) will be discarded.")); + msgBox.setInformativeText(tr("%n pattern(s) already in use.", 0, duplicates.size())); + msgBox.setDetailedText(duplicates.join(QLatin1String("\n"))); + msgBox.exec(); + } +} + +void MimeTypeSettingsModel::updateKnownPatterns(const QStringList &oldPatterns, + const QStringList &newPatterns) +{ + QStringList all = oldPatterns; + all.append(newPatterns); + all.removeDuplicates(); + foreach (const QString &pattern, all) { + QSet<QString>::iterator it = m_knownPatterns.find(pattern); + if (it == m_knownPatterns.end()) { + // A pattern was added. + m_knownPatterns.insert(pattern); + } else { + // A pattern was removed. + m_knownPatterns.erase(it); + } + } +} + +// MimeTypeSettingsPagePrivate +class MimeTypeSettingsPrivate : public QObject +{ + Q_OBJECT + +public: + MimeTypeSettingsPrivate(); + virtual ~MimeTypeSettingsPrivate(); + + void configureUi(QWidget *w); + static void configureTable(QTableView *tableView); + + bool checkSelectedMimeType() const; + bool checkSelectedMagicHeader() const; + + void markMimeForPatternSync(int index); + void markMimeForMagicSync(int index); + void syncMimePattern(); + void syncMimeMagic(); + void clearSyncData(); + void markAsModified(int index); + + void addMagicHeaderRow(const MagicData &data); + MagicData getMagicHeaderRowData(const int row) const; + void editMagicHeaderRowData(const int row, const MagicData &data); + + void updateMimeDatabase(); + +public slots: + void syncData(const QModelIndex ¤t, const QModelIndex &previous); + void handlePatternEdited(); + void addMagicHeader(); + void removeMagicHeader(); + void editMagicHeader(); + void resetMimeTypes(); + +public: + static const QChar kSemiColon; + + QString m_keywords; + MimeDatabase *m_mimeDatabase; + QScopedPointer<MimeTypeSettingsModel> m_model; + int m_mimeForPatternSync; + int m_mimeForMagicSync; + bool m_reset; + QList<int> m_modifiedMimeTypes; + Ui::MimeTypeSettingsPage m_ui; +}; + +const QChar MimeTypeSettingsPrivate::kSemiColon(QLatin1Char(';')); + +MimeTypeSettingsPrivate::MimeTypeSettingsPrivate() + : m_mimeDatabase(ICore::instance()->mimeDatabase()) + , m_model(new MimeTypeSettingsModel) + , m_mimeForPatternSync(-1) + , m_mimeForMagicSync(-1) + , m_reset(false) +{} + +MimeTypeSettingsPrivate::~MimeTypeSettingsPrivate() +{} + +void MimeTypeSettingsPrivate::configureUi(QWidget *w) +{ + m_ui.setupUi(w); + + m_model->load(); + m_ui.mimeTypesTableView->setModel(m_model.data()); + + configureTable(m_ui.mimeTypesTableView); + configureTable(m_ui.magicHeadersTableWidget); + + connect(m_ui.mimeTypesTableView->selectionModel(), + SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, + SLOT(syncData(QModelIndex,QModelIndex))); + connect(m_ui.patternsLineEdit, SIGNAL(textEdited(QString)), + this, SLOT(handlePatternEdited())); + connect(m_ui.addMagicButton, SIGNAL(clicked()), this, SLOT(addMagicHeader())); + connect(m_ui.removeMagicButton, SIGNAL(clicked()), this, SLOT(removeMagicHeader())); + connect(m_ui.editMagicButton, SIGNAL(clicked()), this, SLOT(editMagicHeader())); + connect(m_ui.resetButton, SIGNAL(clicked()), this, SLOT(resetMimeTypes())); +} + +void MimeTypeSettingsPrivate::configureTable(QTableView *tableView) +{ + tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + tableView->setSelectionMode(QAbstractItemView::SingleSelection); + tableView->verticalHeader()->setVisible(false); + tableView->verticalHeader()->setDefaultSectionSize(20); + tableView->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + tableView->horizontalHeader()->setResizeMode(0, QHeaderView::Interactive); + tableView->horizontalHeader()->resizeSection( + 0, 4 * tableView->horizontalHeader()->defaultSectionSize()); + tableView->horizontalHeader()->setHighlightSections(false); + tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); +} + +bool MimeTypeSettingsPrivate::checkSelectedMimeType() const +{ + const QModelIndex &modelIndex = m_ui.mimeTypesTableView->selectionModel()->currentIndex(); + if (!modelIndex.isValid()) { + QMessageBox::critical(0, tr("Error"), tr("No MIME type selected.")); + return false; + } + return true; +} + +bool MimeTypeSettingsPrivate::checkSelectedMagicHeader() const +{ + const QModelIndex &modelIndex = m_ui.magicHeadersTableWidget->selectionModel()->currentIndex(); + if (!modelIndex.isValid()) { + QMessageBox::critical(0, tr("Error"), tr("No magic header selected.")); + return false; + } + return true; +} + +void MimeTypeSettingsPrivate::markMimeForPatternSync(int index) +{ + if (m_mimeForPatternSync != index) { + m_mimeForPatternSync = index; + markAsModified(index); + } +} + +void MimeTypeSettingsPrivate::markMimeForMagicSync(int index) +{ + if (m_mimeForMagicSync != index) { + m_mimeForMagicSync = index; + markAsModified(index); + } +} + +void MimeTypeSettingsPrivate::markAsModified(int index) +{ + // Duplicates are handled later. + m_modifiedMimeTypes.append(index); +} + +void MimeTypeSettingsPrivate::syncMimePattern() +{ + MimeType &mimeType = m_model->m_mimeTypes[m_mimeForPatternSync]; + QStringList patterns = m_ui.patternsLineEdit->text().split(kSemiColon); + patterns.removeDuplicates(); + m_model->validatePatterns(&patterns, mimeType); + m_model->updateKnownPatterns(MimeDatabase::fromGlobPatterns(mimeType.globPatterns()), patterns); + mimeType.setGlobPatterns(MimeDatabase::toGlobPatterns(patterns)); +} + +void MimeTypeSettingsPrivate::syncMimeMagic() +{ + typedef MagicRuleMatcher::MagicRuleList MagicRuleList; + typedef MagicRuleMatcher::MagicRuleSharedPointer MagicRuleSharedPointer; + + // Gather the magic rules. + QHash<int, MagicRuleList> rulesByPriority; + for (int row = 0; row < m_ui.magicHeadersTableWidget->rowCount(); ++row) { + const MagicData &data = getMagicHeaderRowData(row); + // @TODO: Validate magic rule? + MagicRule *magicRule; + if (data.m_type == MagicStringRule::kMatchType) + magicRule = new MagicStringRule(data.m_value, data.m_start, data.m_end); + else + magicRule = new MagicByteRule(data.m_value, data.m_start, data.m_end); + rulesByPriority[data.m_priority].append(MagicRuleSharedPointer(magicRule)); + } + + const QList<QSharedPointer<IMagicMatcher> > &matchers = + MagicRuleMatcher::createMatchers(rulesByPriority); + m_model->m_mimeTypes[m_mimeForMagicSync].setMagicRuleMatchers(matchers); +} + +void MimeTypeSettingsPrivate::clearSyncData() +{ + m_mimeForPatternSync = -1; + m_mimeForMagicSync = -1; +} + +void MimeTypeSettingsPrivate::syncData(const QModelIndex ¤t, + const QModelIndex &previous) +{ + if (previous.isValid()) { + if (m_mimeForPatternSync == previous.row()) + syncMimePattern(); + if (m_mimeForMagicSync == previous.row()) + syncMimeMagic(); + clearSyncData(); + + m_ui.patternsLineEdit->clear(); + m_ui.magicHeadersTableWidget->clearContents(); + m_ui.magicHeadersTableWidget->setRowCount(0); + } + + if (current.isValid()) { + const MimeType ¤tMimeType = m_model->m_mimeTypes.at(current.row()); + + QStringList formatedPatterns; + foreach (const MimeGlobPattern &pattern, currentMimeType.globPatterns()) + formatedPatterns.append(pattern.regExp().pattern()); + m_ui.patternsLineEdit->setText(formatedPatterns.join(kSemiColon)); + + // Consider only rule-based matchers. + const QList<QSharedPointer<IMagicMatcher> > &matchers = currentMimeType.magicRuleMatchers(); + foreach (const QSharedPointer<IMagicMatcher> &matcher, matchers) { + MagicRuleMatcher *ruleMatcher = static_cast<MagicRuleMatcher *>(matcher.data()); + const int priority = ruleMatcher->priority(); + const MagicRuleMatcher::MagicRuleList &rules = ruleMatcher->magicRules(); + foreach (const MagicRuleMatcher::MagicRuleSharedPointer &rule, rules) + addMagicHeaderRow(MagicData(rule->matchValue(), + rule->matchType(), + rule->startPos(), + rule->endPos(), + priority)); + } + } +} + +void MimeTypeSettingsPrivate::handlePatternEdited() +{ + if (m_mimeForPatternSync == -1) { + const QModelIndex &modelIndex = m_ui.mimeTypesTableView->selectionModel()->currentIndex(); + if (modelIndex.isValid()) + markMimeForPatternSync(modelIndex.row()); + } +} + +void MimeTypeSettingsPrivate::addMagicHeaderRow(const MagicData &data) +{ + const int row = m_ui.magicHeadersTableWidget->rowCount(); + m_ui.magicHeadersTableWidget->insertRow(row); + editMagicHeaderRowData(row, data); +} + +MagicData MimeTypeSettingsPrivate::getMagicHeaderRowData(const int row) const +{ + MagicData data; + data.m_value = m_ui.magicHeadersTableWidget->item(row, 0)->text(); + data.m_type = m_ui.magicHeadersTableWidget->item(row, 1)->text(); + QPair<int, int> startEnd = + MagicRule::fromOffset(m_ui.magicHeadersTableWidget->item(row, 2)->text()); + data.m_start = startEnd.first; + data.m_end = startEnd.second; + data.m_priority = m_ui.magicHeadersTableWidget->item(row, 3)->text().toInt(); + + return data; +} + +void MimeTypeSettingsPrivate::editMagicHeaderRowData(const int row, const MagicData &data) +{ + for (int col = 0; col < m_ui.magicHeadersTableWidget->columnCount(); ++col) { + QTableWidgetItem *item = new QTableWidgetItem; + if (col == 0) { + item->setText(data.m_value); + } else { + item->setTextAlignment(Qt::AlignCenter); + if (col == 1) + item->setText(data.m_type); + else if (col == 2) + item->setText(MagicRule::toOffset(qMakePair(data.m_start, data.m_end))); + else + item->setText(QString::number(data.m_priority)); + } + m_ui.magicHeadersTableWidget->setItem(row, col, item); + } +} + +void MimeTypeSettingsPrivate::addMagicHeader() +{ + if (!checkSelectedMimeType()) + return; + + MimeTypeMagicDialog dlg; + if (dlg.exec()) { + addMagicHeaderRow(dlg.magicData()); + markMimeForMagicSync(m_ui.mimeTypesTableView->selectionModel()->currentIndex().row()); + } +} + +void MimeTypeSettingsPrivate::removeMagicHeader() +{ + if (!checkSelectedMagicHeader()) + return; + + m_ui.magicHeadersTableWidget->removeRow(m_ui.magicHeadersTableWidget->currentRow()); + markMimeForMagicSync(m_ui.mimeTypesTableView->selectionModel()->currentIndex().row()); +} + +void MimeTypeSettingsPrivate::editMagicHeader() +{ + if (!checkSelectedMagicHeader()) + return; + + MimeTypeMagicDialog dlg; + dlg.setMagicData(getMagicHeaderRowData(m_ui.magicHeadersTableWidget->currentRow())); + if (dlg.exec()) { + editMagicHeaderRowData(m_ui.magicHeadersTableWidget->currentRow(), dlg.magicData()); + markMimeForMagicSync(m_ui.mimeTypesTableView->selectionModel()->currentIndex().row()); + } +} + +void MimeTypeSettingsPrivate::updateMimeDatabase() +{ + MimeDatabase *db = ICore::instance()->mimeDatabase(); + // For this case it is a better approach to simply use a list and to remove duplicates + // afterwards than to keep a more complex data structure like a hash table. + qSort(m_modifiedMimeTypes.begin(), m_modifiedMimeTypes.end()); + m_modifiedMimeTypes.erase(std::unique(m_modifiedMimeTypes.begin(), m_modifiedMimeTypes.end()), + m_modifiedMimeTypes.end()); + if (!m_modifiedMimeTypes.isEmpty()) { + QList<MimeType> allModified; + foreach (int index, m_modifiedMimeTypes) { + const MimeType &mimeType = m_model->m_mimeTypes.at(index); + db->setGlobPatterns(mimeType.type(), mimeType.globPatterns()); + db->setMagicMatchers(mimeType.type(), mimeType.magicMatchers()); + allModified.append(mimeType); + } + db->writeUserModifiedMimeTypes(allModified); + } +} + +void MimeTypeSettingsPrivate::resetMimeTypes() +{ + QMessageBox::information(0, + tr("MIME Types"), + tr("Changes will take effect in the next time you start Qt Creator.")); + m_reset = true; +} + +// MimeTypeSettingsPage +MimeTypeSettings::MimeTypeSettings(QObject *parent) + : IOptionsPage(parent) + , m_d(new MimeTypeSettingsPrivate) +{} + +MimeTypeSettings::~MimeTypeSettings() +{} + +QString MimeTypeSettings::id() const +{ + return QLatin1String(Core::Constants::SETTINGS_ID_MIMETYPES); +} + +QString MimeTypeSettings::displayName() const +{ + return tr("MIME Types"); +} + +QString MimeTypeSettings::category() const +{ + return QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE); +} + +QString MimeTypeSettings::displayCategory() const +{ + return QCoreApplication::translate("Core", Core::Constants::SETTINGS_TR_CATEGORY_CORE); +} + +QIcon MimeTypeSettings::categoryIcon() const +{ + return QIcon(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE_ICON)); +} + +bool MimeTypeSettings::matches(const QString &s) const +{ + return m_d->m_keywords.contains(s, Qt::CaseInsensitive); +} + +QWidget *MimeTypeSettings::createPage(QWidget *parent) +{ + QWidget *w = new QWidget(parent); + m_d->configureUi(w); + return w; +} + +void MimeTypeSettings::apply() +{ + if (m_d->m_reset) { + ICore::instance()->mimeDatabase()->clearUserModifiedMimeTypes(); + } else if (!m_d->m_modifiedMimeTypes.isEmpty()) { + const QModelIndex &modelIndex = + m_d->m_ui.mimeTypesTableView->selectionModel()->currentIndex(); + if (modelIndex.isValid()) { + if (m_d->m_mimeForPatternSync == modelIndex.row()) + m_d->syncMimePattern(); + if (m_d->m_mimeForMagicSync == modelIndex.row()) + m_d->syncMimeMagic(); + } + m_d->updateMimeDatabase(); + } +} + +void MimeTypeSettings::finish() +{} + +} // Internal +} // Core + +#include "mimetypesettings.moc" diff --git a/src/plugins/coreplugin/mimetypesettings.h b/src/plugins/coreplugin/mimetypesettings.h new file mode 100644 index 0000000000..6faa93ccd3 --- /dev/null +++ b/src/plugins/coreplugin/mimetypesettings.h @@ -0,0 +1,72 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** No Commercial Usage +** +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef MIMETYPESETTINGSPAGE_H +#define MIMETYPESETTINGSPAGE_H + +#include "ioptionspage.h" + +#include <QtCore/QScopedPointer> + +namespace Core { +namespace Internal { + +class MimeTypeSettingsPrivate; + +class MimeTypeSettings : public IOptionsPage +{ + Q_OBJECT + +public: + MimeTypeSettings(QObject *parent = 0); + virtual ~MimeTypeSettings(); + + virtual QString id() const; + virtual QString displayName() const; + virtual QString category() const; + virtual QString displayCategory() const; + virtual QIcon categoryIcon() const; + virtual bool matches(const QString &s) const; + + virtual QWidget *createPage(QWidget *parent); + virtual void apply(); + virtual void finish(); + +private: + QScopedPointer<MimeTypeSettingsPrivate> m_d; +}; + +} // Internal +} // Core + +#endif // MIMETYPESETTINGSPAGE_H diff --git a/src/plugins/coreplugin/mimetypesettingspage.ui b/src/plugins/coreplugin/mimetypesettingspage.ui new file mode 100644 index 0000000000..5a0c497df1 --- /dev/null +++ b/src/plugins/coreplugin/mimetypesettingspage.ui @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MimeTypeSettingsPage</class> + <widget class="QWidget" name="MimeTypeSettingsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>666</width> + <height>407</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QTableView" name="mimeTypesTableView"/> + </item> + <item> + <widget class="QGroupBox" name="detailsGroupBox"> + <property name="title"> + <string>Details</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="patternsLabel"> + <property name="text"> + <string>Patterns:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="patternsLineEdit"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QTableWidget" name="magicHeadersTableWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>100</height> + </size> + </property> + <column> + <property name="text"> + <string>Magic Header</string> + </property> + </column> + <column> + <property name="text"> + <string>Type</string> + </property> + </column> + <column> + <property name="text"> + <string>Range</string> + </property> + </column> + <column> + <property name="text"> + <string>Priority</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="addMagicButton"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="editMagicButton"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeMagicButton"> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>72</width> + <height>18</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="resetButton"> + <property name="toolTip"> + <string>Reset all to default</string> + </property> + <property name="text"> + <string>Reset All</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>17</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0"> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>504</width> + <height>16</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/texteditor/generichighlighter/manager.cpp b/src/plugins/texteditor/generichighlighter/manager.cpp index 16388f629a..ca68aaf5a4 100644 --- a/src/plugins/texteditor/generichighlighter/manager.cpp +++ b/src/plugins/texteditor/generichighlighter/manager.cpp @@ -184,6 +184,11 @@ void Manager::gatherDefinitionsMimeTypes(QFutureInterface<Core::MimeType> &futur Core::MimeDatabase *mimeDatabase = Core::ICore::instance()->mimeDatabase(); QSet<QString> knownSuffixes = QSet<QString>::fromList(mimeDatabase->suffixes()); + QHash<QString, Core::MimeType> userModified; + const QList<Core::MimeType> &userMimeTypes = mimeDatabase->readUserModifiedMimeTypes(); + foreach (const Core::MimeType &userMimeType, userMimeTypes) + userModified.insert(userMimeType.type(), userMimeType); + foreach (const QString &path, definitionsPaths) { if (path.isEmpty()) continue; @@ -227,25 +232,35 @@ void Manager::gatherDefinitionsMimeTypes(QFutureInterface<Core::MimeType> &futur m_idByMimeType.insert(type, id); Core::MimeType mimeType = mimeDatabase->findByType(type); if (mimeType.isNull()) { - if (globPatterns.isEmpty()) { - foreach (const QString &pattern, metaData->patterns()) { - static const QLatin1String mark("*."); - if (pattern.startsWith(mark)) { - const QString &suffix = pattern.right(pattern.length() - 2); - if (!knownSuffixes.contains(suffix)) - knownSuffixes.insert(suffix); - else - continue; - } - QRegExp regExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); - globPatterns.append(Core::MimeGlobPattern(regExp, 50)); - } - } - mimeType.setType(type); mimeType.setSubClassesOf(textPlain); mimeType.setComment(metaData->name()); - mimeType.setGlobPatterns(globPatterns); + + // If there's a user modification for this mime type, we want to use the + // modified patterns and rule-based matchers. If not, just consider what + // is specified in the definition file. + QHash<QString, Core::MimeType>::const_iterator it = + userModified.find(mimeType.type()); + if (it == userModified.end()) { + if (globPatterns.isEmpty()) { + foreach (const QString &pattern, metaData->patterns()) { + static const QLatin1String mark("*."); + if (pattern.startsWith(mark)) { + const QString &suffix = pattern.right(pattern.length() - 2); + if (!knownSuffixes.contains(suffix)) + knownSuffixes.insert(suffix); + else + continue; + } + QRegExp regExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); + globPatterns.append(Core::MimeGlobPattern(regExp, 50)); + } + } + mimeType.setGlobPatterns(globPatterns); + } else { + mimeType.setGlobPatterns(it.value().globPatterns()); + mimeType.setMagicRuleMatchers(it.value().magicRuleMatchers()); + } mimeDatabase->addMimeType(mimeType); future.reportResult(mimeType); @@ -275,6 +290,8 @@ void Manager::registerMimeTypesFinished() QSharedPointer<HighlightDefinitionMetaData> Manager::parseMetadata(const QFileInfo &fileInfo) { static const QLatin1Char kSemiColon(';'); + static const QLatin1Char kSpace(' '); + static const QLatin1Char kDash('-'); static const QLatin1String kLanguage("language"); static const QLatin1String kArtificial("text/x-artificial-"); @@ -304,7 +321,7 @@ QSharedPointer<HighlightDefinitionMetaData> Manager::parseMetadata(const QFileIn // There are definitions which do not specify a MIME type, but specify file // patterns. Creating an artificial MIME type is a workaround. QString artificialType(kArtificial); - artificialType.append(metaData->name()); + artificialType.append(metaData->name().trimmed().replace(kSpace, kDash)); mimeTypes.append(artificialType); } metaData->setMimeTypes(mimeTypes); |