diff --git a/share/qtcreator/qml/qmldump/main.cpp b/share/qtcreator/qml/qmldump/main.cpp index a753ebba8e8..301daee4d23 100644 --- a/share/qtcreator/qml/qmldump/main.cpp +++ b/share/qtcreator/qml/qmldump/main.cpp @@ -31,6 +31,9 @@ ** **************************************************************************/ +// WARNING: This code is shared with the qmlplugindump tool code in Qt. +// Modifications to this file need to be applied there. + #include #include #include diff --git a/share/qtcreator/qml/qmldump/qmlstreamwriter.cpp b/share/qtcreator/qml/qmldump/qmlstreamwriter.cpp index 94fee55b796..d80ed761aa4 100644 --- a/share/qtcreator/qml/qmldump/qmlstreamwriter.cpp +++ b/share/qtcreator/qml/qmldump/qmlstreamwriter.cpp @@ -31,6 +31,9 @@ ** **************************************************************************/ +// WARNING: This code is shared with the qmlplugindump tool code in Qt. +// Modifications to this file need to be applied there. + #include "qmlstreamwriter.h" #include diff --git a/share/qtcreator/qml/qmldump/qmlstreamwriter.h b/share/qtcreator/qml/qmldump/qmlstreamwriter.h index 8d552d6a8e1..6aa30871cfe 100644 --- a/share/qtcreator/qml/qmldump/qmlstreamwriter.h +++ b/share/qtcreator/qml/qmldump/qmlstreamwriter.h @@ -31,6 +31,9 @@ ** **************************************************************************/ +// WARNING: This code is shared with the qmlplugindump tool code in Qt. +// Modifications to this file need to be applied there. + #ifndef QMLSTREAMWRITER_H #define QMLSTREAMWRITER_H diff --git a/share/qtcreator/templates/wizards/qml-extension/wizard.xml b/share/qtcreator/templates/wizards/qml-extension/wizard.xml index 307b2fdc49b..4a8edf7c900 100644 --- a/share/qtcreator/templates/wizards/qml-extension/wizard.xml +++ b/share/qtcreator/templates/wizards/qml-extension/wizard.xml @@ -56,11 +56,11 @@ leave room for the Qt 4 target page. Custom QML Extension Plugin Parameters - + Object Class-name: - + URI: diff --git a/src/plugins/coreplugin/coreconstants.h b/src/plugins/coreplugin/coreconstants.h index cdfc0c06b06..7cf5e13847d 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 36ef2b94cea..5092ba8dc4b 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 @@ -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 aee407e7a05..c25dcb3c436 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 ba9f438212a..72c6281c24d 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 ac9ce6d3110..05f0844efdc 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 1d72a35912b..f64731fc654 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 82f02f13bbd..ad5853deb24 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 @@ -40,9 +41,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -50,10 +53,11 @@ #include #include #include - -#include +#include +#include #include +#include 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 &startEnd) +{ + return QString(QLatin1String("%1:%2")).arg(startEnd.first).arg(startEnd.second); +} + +QPair 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,28 +284,51 @@ 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) { - // Expect an hex format value like this: \0x7f\0x45\0x4c\0x46 - const QStringList &bytes = s.split(QLatin1Char('\\'), QString::SkipEmptyParts); - foreach (const QString &byte, bytes) { - bool ok; - const int hex = byte.toInt(&ok, 16); - if (ok) { - m_bytes.push_back(hex); - } else { - m_bytes.clear(); - break; - } - } - m_bytesSize = m_bytes.size(); + if (validateByteSequence(s, &m_bytes)) + m_bytesSize = m_bytes.size(); + else + m_bytes.clear(); } MagicByteRule::~MagicByteRule() { } +bool MagicByteRule::validateByteSequence(const QString &sequence, QList *bytes) +{ + // Expect an hex format value like this: \0x7f\0x45\0x4c\0x46 + const QStringList &byteSequence = sequence.split(QLatin1Char('\\'), QString::SkipEmptyParts); + foreach (const QString &byte, byteSequence) { + bool ok; + const int hex = byte.toInt(&ok, 16); + if (ok) { + if (bytes) + bytes->push_back(hex); + } else { + return false; + } + } + return true; +} + +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 { if (m_bytesSize == 0) @@ -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 &rulesByPriority) +{ + IMagicMatcher::IMagicMatcherList matchers; + QHash::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 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 IMagicMatcherSharedPointer; - typedef QList 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 MimeType::globPatterns() const void MimeType::setGlobPatterns(const QList &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,16 +736,52 @@ QStringList MimeType::suffixes() const return m_d->suffixes; } -void MimeType::setSuffixes(const QStringList &s) -{ - m_d->suffixes = s; -} - -void MimeType::addMagicMatcher(const QSharedPointer &matcher) +void MimeType::addMagicMatcher(const IMagicMatcherSharedPointer &matcher) { m_d->magicMatchers.push_back(matcher); } +const MimeType::IMagicMatcherList &MimeType::magicMatchers() const +{ + return m_d->magicMatchers; +} + +void MimeType::setMagicMatchers(const IMagicMatcherList &matchers) +{ + m_d->magicMatchers = matchers; +} + +namespace { +struct RemovePred : std::unary_function +{ + RemovePred(bool keepRuleBased) : m_keepRuleBase(keepRuleBased) {} + bool m_keepRuleBase; + + bool operator()(const MimeType::IMagicMatcherSharedPointer &matcher) { + if ((m_keepRuleBase && !dynamic_cast(matcher.data())) + || (!m_keepRuleBase && dynamic_cast(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) { QString s; @@ -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> for mapping aliases to types - * - A Map 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> 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; + QStringList filterStrings() const; + + QStringList suffixes() const; bool setPreferredSuffix(const QString &typeOrAlias, const QString &suffix); - // Return all known suffixes - QStringList suffixes() const; - QStringList filterStrings() const; + QList globPatterns() const; + void setGlobPatterns(const QString &typeOrAlias, const QList &globPatterns); + + QList > magicMatchers() const; + void setMagicMatchers(const QString &typeOrAlias, + const QList > &matchers); + + QList mimeTypes() const; + + void syncUserModifiedMimeTypes(); + static QList readUserModifiedMimeTypes(); + static void writeUserModifiedMimeTypes(const QList &mimeTypes); + void clearUserModifiedMimeTypes(); + + static QList toGlobPatterns(const QStringList &patterns, + int weight = MimeGlobPattern::MaxWeight); + static QStringList fromGlobPatterns(const QList &globPatterns); void debug(QTextStream &str) const; @@ -976,6 +1124,11 @@ private: typedef QHash AliasMap; typedef QMultiHash 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 MimeDatabasePrivate::globPatterns() const +{ + QList 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 &globPatterns) +{ + TypeMimeTypeMap::iterator tit = m_typeMimeTypeMap.find(resolveAlias(typeOrAlias)); + if (tit != m_typeMimeTypeMap.end()) + tit.value().type.setGlobPatterns(globPatterns); +} + +QList > MimeDatabasePrivate::magicMatchers() const +{ + QList > 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 > &matchers) +{ + TypeMimeTypeMap::iterator tit = m_typeMimeTypeMap.find(resolveAlias(typeOrAlias)); + if (tit != m_typeMimeTypeMap.end()) + tit.value().type.setMagicMatchers(matchers); +} + +QList MimeDatabasePrivate::mimeTypes() const +{ + QList 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 userModified; + const QList &userMimeTypes = readUserModifiedMimeTypes(); + foreach (const MimeType &userMimeType, userMimeTypes) + userModified.insert(userMimeType.type(), userMimeType); + + TypeMimeTypeMap::iterator end = m_typeMimeTypeMap.end(); + QHash::const_iterator userMimeEnd = userModified.end(); + for (TypeMimeTypeMap::iterator it = m_typeMimeTypeMap.begin(); it != end; ++it) { + QHash::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 MimeDatabasePrivate::readUserModifiedMimeTypes() +{ + typedef MagicRuleMatcher::MagicRuleList MagicRuleList; + typedef MagicRuleMatcher::MagicRuleSharedPointer MagicRuleSharedPointer; + + QList mimeTypes; + QFile file(kModifiedMimeTypesPath + kModifiedMimeTypesFile); + if (file.open(QFile::ReadOnly)) { + MimeType mimeType; + QHash 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 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 &mimeTypes) +{ + // Keep mime types modified which are already on file. + QList 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 > &matchers = mimeType.magicMatchers(); + foreach (const QSharedPointer &matcher, matchers) { + // Only care about rule-based matchers. + if (MagicRuleMatcher *ruleMatcher = + dynamic_cast(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 MimeDatabasePrivate::toGlobPatterns(const QStringList &patterns, int weight) +{ + QList 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 &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 MimeDatabase::globPatterns() const +{ + m_mutex.lock(); + const QList rc = m_d->globPatterns(); + m_mutex.unlock(); + return rc; +} + +void MimeDatabase::setGlobPatterns(const QString &typeOrAlias, + const QList &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 MimeDatabase::mimeTypes() const +{ + m_mutex.lock(); + const QList &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 MimeDatabase::readUserModifiedMimeTypes() +{ + return MimeDatabasePrivate::readUserModifiedMimeTypes(); +} + +void MimeDatabase::writeUserModifiedMimeTypes(const QList &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 MimeDatabase::toGlobPatterns(const QStringList &patterns, int weight) +{ + return MimeDatabasePrivate::toGlobPatterns(patterns, weight); +} + +QStringList MimeDatabase::fromGlobPatterns(const QList &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 954cf8e50be..722a0158705 100644 --- a/src/plugins/coreplugin/mimedatabase.h +++ b/src/plugins/coreplugin/mimedatabase.h @@ -41,6 +41,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE class QIODevice; @@ -66,6 +67,9 @@ class CORE_EXPORT IMagicMatcher protected: IMagicMatcher() {} public: + typedef QSharedPointer IMagicMatcherSharedPointer; + typedef QList 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 &startEnd); + static QPair 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 *bytes = 0); + + static const QString kMatchType; + private: - QList m_bytes; int m_bytesSize; + QList 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 MagicRuleSharedPointer; + typedef QSharedPointer MagicRuleSharedPointer; + typedef QList 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 &); + private: - typedef QList 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 &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 &globs); + static QString formatFilterString(const QString &description, + const QList &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 - 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 globPatterns() const; + void setGlobPatterns(const QString &typeOrAlias, const QList &globPatterns); + + IMagicMatcherList magicMatchers() const; + void setMagicMatchers(const QString &typeOrAlias, const IMagicMatcherList &matchers); + + QList 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 readUserModifiedMimeTypes(); + static void writeUserModifiedMimeTypes(const QList &mimeTypes); + void clearUserModifiedMimeTypes(); + + static QList toGlobPatterns(const QStringList &patterns, + int weight = MimeGlobPattern::MaxWeight); + static QStringList fromGlobPatterns(const QList &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 00000000000..ea444ba74be --- /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 +#include + +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 00000000000..a9cd3ca97fd --- /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 00000000000..3da4ddbeaf8 --- /dev/null +++ b/src/plugins/coreplugin/mimetypemagicdialog.ui @@ -0,0 +1,318 @@ + + + MimeTypeMagicDialog + + + + 0 + 0 + 480 + 286 + + + + Dialog + + + + + + + + Value: + + + + + + + + + + + + Type + + + + + + String + + + true + + + + + + + Byte + + + + + + + + + + Use Recommended + + + true + + + + + + + + false + + + Start range: + + + + + + + false + + + End range: + + + + + + + false + + + Priority: + + + + + + + false + + + 1 + + + 50 + + + + + + + false + + + 9999 + + + + + + + false + + + 9999 + + + + + + + + + Qt::Horizontal + + + + 233 + 20 + + + + + + + + false + + + <i>Note: Wide range values might impact on Qt Creator's performance when opening files.</i> + + + Qt::RichText + + + + + + + + + + Qt::Vertical + + + + 20 + 11 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + rejected() + MimeTypeMagicDialog + reject() + + + 295 + 297 + + + 286 + 274 + + + + + useRecommendedGroupBox + clicked(bool) + prioritySpinBox + setDisabled(bool) + + + 48 + 171 + + + 105 + 249 + + + + + useRecommendedGroupBox + clicked(bool) + startRangeSpinBox + setDisabled(bool) + + + 195 + 169 + + + 107 + 149 + + + + + useRecommendedGroupBox + clicked(bool) + endRangeSpinBox + setDisabled(bool) + + + 195 + 169 + + + 107 + 175 + + + + + useRecommendedGroupBox + clicked(bool) + priorityLabel + setDisabled(bool) + + + 225 + 178 + + + 99 + 201 + + + + + useRecommendedGroupBox + clicked(bool) + startRangeLabel + setDisabled(bool) + + + 225 + 178 + + + 99 + 149 + + + + + useRecommendedGroupBox + clicked(bool) + endRangeLabel + setDisabled(bool) + + + 225 + 178 + + + 99 + 175 + + + + + useRecommendedGroupBox + clicked(bool) + noteLabel + setDisabled(bool) + + + 225 + 178 + + + 182 + 225 + + + + + diff --git a/src/plugins/coreplugin/mimetypesettings.cpp b/src/plugins/coreplugin/mimetypesettings.cpp new file mode 100644 index 00000000000..ebede48aa12 --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 m_mimeTypes; + QSet m_knownPatterns; + QHash 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::fromList( + MimeDatabase::fromGlobPatterns(m_core->mimeDatabase()->globPatterns())); + + foreach (const MimeType &mimeType, m_mimeTypes) { + QString value; + const QList factories = + m_core->editorManager()->editorFactories(mimeType); + if (!factories.isEmpty()) { + value = factories.front()->displayName(); + } else { + const QList 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 oldPatterns = + QSet::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::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 m_model; + int m_mimeForPatternSync; + int m_mimeForMagicSync; + bool m_reset; + QList 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 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 > &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 > &matchers = currentMimeType.magicRuleMatchers(); + foreach (const QSharedPointer &matcher, matchers) { + MagicRuleMatcher *ruleMatcher = static_cast(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 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 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 00000000000..6faa93ccd35 --- /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 + +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 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 00000000000..5a0c497df13 --- /dev/null +++ b/src/plugins/coreplugin/mimetypesettingspage.ui @@ -0,0 +1,169 @@ + + + MimeTypeSettingsPage + + + + 0 + 0 + 666 + 407 + + + + Form + + + + + + + + + + + Details + + + + + + + + Patterns: + + + + + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 100 + + + + + Magic Header + + + + + Type + + + + + Range + + + + + Priority + + + + + + + + + + Add + + + + + + + Edit + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 72 + 18 + + + + + + + + + + + + + + + + + + + Reset all to default + + + Reset All + + + + + + + Qt::Vertical + + + + 18 + 17 + + + + + + + + + + Qt::Vertical + + + + 504 + 16 + + + + + + + + + diff --git a/src/plugins/debugger/gdb/codagdbadapter.cpp b/src/plugins/debugger/gdb/codagdbadapter.cpp index 9eb53c934a2..ed32b6e4a9e 100644 --- a/src/plugins/debugger/gdb/codagdbadapter.cpp +++ b/src/plugins/debugger/gdb/codagdbadapter.cpp @@ -1043,7 +1043,7 @@ void CodaGdbAdapter::startAdapter() m_codaSocketIODevice = codaSocket; } else { m_codaDevice = SymbianUtils::SymbianDeviceManager::instance() - ->getTcfPort(parameters.remoteChannel); + ->getCodaDevice(parameters.remoteChannel); bool ok = m_codaDevice && m_codaDevice->device()->isOpen(); if (!ok) { @@ -1233,7 +1233,7 @@ void CodaGdbAdapter::cleanup() // Ensure process is stopped after being suspended. sendRunControlTerminateCommand(); disconnect(m_codaDevice.data(), 0, this, 0); - SymbianUtils::SymbianDeviceManager::instance()->releaseTcfPort(m_codaDevice); + SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(m_codaDevice); } } diff --git a/src/plugins/debugger/registerpostmortemaction.cpp b/src/plugins/debugger/registerpostmortemaction.cpp index 5efd58be82b..85c4a0200e4 100644 --- a/src/plugins/debugger/registerpostmortemaction.cpp +++ b/src/plugins/debugger/registerpostmortemaction.cpp @@ -55,9 +55,9 @@ namespace Internal { void RegisterPostMortemAction::registerNow(const QVariant &value) { const bool boolValue = value.toBool(); - const QString debuggerExe = QCoreApplication::applicationDirPath() + QLatin1Char('/') - + debuggerApplicationFileC + QLatin1String(".exe"); - const std::wstring debuggerWString = QDir::toNativeSeparators(debuggerExe).toStdWString(); + const QString debuggerExe = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QLatin1Char('/') + + debuggerApplicationFileC + QLatin1String(".exe")); + const ushort *debuggerWString = debuggerExe.utf16(); CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); SHELLEXECUTEINFO shExecInfo; @@ -65,7 +65,7 @@ void RegisterPostMortemAction::registerNow(const QVariant &value) shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; shExecInfo.hwnd = NULL; shExecInfo.lpVerb = L"runas"; - shExecInfo.lpFile = debuggerWString.data(); + shExecInfo.lpFile = reinterpret_cast(debuggerWString); shExecInfo.lpParameters = boolValue ? L"-register" : L"-unregister"; shExecInfo.lpDirectory = NULL; shExecInfo.nShow = SW_SHOWNORMAL; diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index 5d260250051..e82176dde1b 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -3755,7 +3755,7 @@ void FakeVimHandler::Private::highlightMatches(const QString &needle0) QString needle = needle0; vimPatternToQtPattern(&needle, &flags); QRegExp needleExp(needle); - while (1) { + while (!tc.atEnd()) { tc = tc.document()->find(needleExp, tc.position(), flags); if (tc.isNull()) break; diff --git a/src/plugins/qt4projectmanager/qt-s60/codaruncontrol.cpp b/src/plugins/qt4projectmanager/qt-s60/codaruncontrol.cpp index 3dd0474289f..00f57c365bd 100644 --- a/src/plugins/qt4projectmanager/qt-s60/codaruncontrol.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/codaruncontrol.cpp @@ -117,7 +117,7 @@ bool CodaRunControl::setupLauncher() if (m_serialPort.length()) { // We get the port from SymbianDeviceManager appendMessage(tr("Connecting to '%1'...").arg(m_serialPort), NormalMessageFormat); - m_codaDevice = SymbianUtils::SymbianDeviceManager::instance()->getTcfPort(m_serialPort); + m_codaDevice = SymbianUtils::SymbianDeviceManager::instance()->getCodaDevice(m_serialPort); bool ok = m_codaDevice && m_codaDevice->device()->isOpen(); if (!ok) { @@ -333,7 +333,7 @@ void CodaRunControl::finishRunControl() m_runningProcessId.clear(); if (m_codaDevice) { disconnect(m_codaDevice.data(), 0, this, 0); - SymbianUtils::SymbianDeviceManager::instance()->releaseTcfPort(m_codaDevice); + SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(m_codaDevice); } m_state = StateUninit; emit finished(); @@ -344,7 +344,7 @@ QMessageBox *CodaRunControl::createCodaWaitingMessageBox(QWidget *parent) const QString title = tr("Waiting for CODA"); const QString text = tr("Qt Creator is waiting for the CODA application to connect.
" "Please make sure the application is running on " - "your mobile phone and the right IP address and port are " + "your mobile phone and the right IP address and/or port are " "configured in the project settings."); QMessageBox *mb = new QMessageBox(QMessageBox::Information, title, text, QMessageBox::Cancel, parent); return mb; diff --git a/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.cpp b/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.cpp index 4dd5ed7bb73..3b52bf6e088 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.cpp @@ -357,7 +357,10 @@ char S60DeployConfiguration::installationDrive() const void S60DeployConfiguration::setInstallationDrive(char drive) { + if (m_installationDrive == drive) + return; m_installationDrive = drive; + emit installationDriveChanged(); } bool S60DeployConfiguration::silentInstall() const @@ -412,6 +415,17 @@ void S60DeployConfiguration::setCommunicationChannel(CommunicationChannel channe } } +void S60DeployConfiguration::setAvailableDeviceDrives(QList drives) +{ + m_availableDeviceDrives = drives; + emit availableDeviceDrivesChanged(); +} + +const QList &S60DeployConfiguration::availableDeviceDrives() const +{ + return m_availableDeviceDrives; +} + // ======== S60DeployConfigurationFactory S60DeployConfigurationFactory::S60DeployConfigurationFactory(QObject *parent) : diff --git a/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.h b/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.h index d57f4dd1f57..639e4bc4357 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.h +++ b/src/plugins/qt4projectmanager/qt-s60/s60deployconfiguration.h @@ -63,6 +63,8 @@ public: CommunicationCodaTcpConnection }; + typedef QPair DeviceDrive; + explicit S60DeployConfiguration(ProjectExplorer::Target *parent); virtual ~S60DeployConfiguration(); @@ -92,6 +94,9 @@ public: void setCommunicationChannel(CommunicationChannel channel); S60DeployConfiguration::CommunicationChannel communicationChannel() const; + void setAvailableDeviceDrives(QList drives); + const QList &availableDeviceDrives() const; + QStringList signedPackages() const; QStringList packageFileNamesWithTargetInfo() const; QStringList packageTemplateFileNames() const; @@ -107,6 +112,8 @@ signals: void communicationChannelChanged(); void deviceAddressChanged(); void devicePortChanged(); + void availableDeviceDrivesChanged(); + void installationDriveChanged(); private slots: void updateActiveBuildConfiguration(ProjectExplorer::BuildConfiguration *buildConfiguration); @@ -134,6 +141,8 @@ private: QString m_deviceAddress; QString m_devicePort; CommunicationChannel m_communicationChannel; + + QList m_availableDeviceDrives; }; class S60DeployConfigurationFactory : public ProjectExplorer::DeployConfigurationFactory diff --git a/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.cpp b/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.cpp index 6a6b8293320..bccf9cdd9f4 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include "trkruncontrol.h" @@ -74,6 +75,51 @@ namespace Internal { const char STARTING_DRIVE_LETTER = 'C'; const char LAST_DRIVE_LETTER = 'Z'; +static const quint32 CODA_UID = 0x20021f96; + +QString formatDriveText(const S60DeployConfiguration::DeviceDrive &drive) +{ + char driveLetter = QChar::toUpper(static_cast(drive.first)); + if (drive.second <= 0) + return QString("%1:").arg(driveLetter); + if (drive.second >= 1024) + return QString("%1:%2 MB").arg(driveLetter).arg(drive.second); + return QString("%1:%2 kB").arg(driveLetter).arg(drive.second); +} + +void startTable(QString &text) +{ + const char startTableC[] = ""; + if (text.contains(startTableC)) + return; + text.append(startTableC); +} + +void finishTable(QString &text) +{ + const char stopTableC[] = "
"; + text.replace(stopTableC, QLatin1String("")); + text.append(stopTableC); +} + +void addToTable(QTextStream &stream, const QString &key, const QString &value) +{ + const char tableRowStartC[] = ""; + const char tableRowSeparatorC[] = ""; + const char tableRowEndC[] = ""; + stream << tableRowStartC << key << tableRowSeparatorC << value << tableRowEndC; +} + +void addErrorToTable(QTextStream &stream, const QString &key, const QString &value) +{ + const char tableRowStartC[] = ""; + const char tableRowSeparatorC[] = ""; + const char tableRowEndC[] = ""; + const char errorSpanStartC[] = ""; + const char errorSpanEndC[] = ""; + stream << tableRowStartC << errorSpanStartC << key << tableRowSeparatorC << value << errorSpanEndC << tableRowEndC; +} + S60DeployConfigurationWidget::S60DeployConfigurationWidget(QWidget *parent) : ProjectExplorer::DeployConfigurationWidget(parent), m_detailsWidget(new Utils::DetailsWidget), @@ -178,6 +224,7 @@ void S60DeployConfigurationWidget::init(ProjectExplorer::DeployConfiguration *dc QHBoxLayout *infoHBoxLayout = new QHBoxLayout; m_deviceInfoLabel->setWordWrap(true); m_deviceInfoLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + m_deviceInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); infoHBoxLayout->addWidget(m_deviceInfoLabel); infoHBoxLayout->addWidget(m_deviceInfoButton); m_deviceInfoButton->setIcon(qApp->style()->standardIcon(QStyle::SP_MessageBoxInformation)); @@ -187,6 +234,8 @@ void S60DeployConfigurationWidget::init(ProjectExplorer::DeployConfiguration *dc updateTargetInformation(); connect(m_deployConfiguration, SIGNAL(targetInformationChanged()), this, SLOT(updateTargetInformation())); + connect(m_deployConfiguration, SIGNAL(availableDeviceDrivesChanged()), + this, SLOT(updateInstallationDrives())); } QWidget *S60DeployConfigurationWidget::createCommunicationChannel() @@ -240,14 +289,28 @@ QWidget *S60DeployConfigurationWidget::createCommunicationChannel() void S60DeployConfigurationWidget::updateInstallationDrives() { m_installationDriveCombo->clear(); - for (int i = STARTING_DRIVE_LETTER; i <= LAST_DRIVE_LETTER; ++i) { - m_installationDriveCombo->addItem(QString("%1:").arg(static_cast(i)), qVariantFromValue(i)); + const QList &availableDrives(m_deployConfiguration->availableDeviceDrives()); + int index = 0; + char currentDrive = QChar::toUpper(static_cast(m_deployConfiguration->installationDrive())); + if (availableDrives.isEmpty()) { + for (int i = STARTING_DRIVE_LETTER; i <= LAST_DRIVE_LETTER; ++i) { + m_installationDriveCombo->addItem(QString("%1:").arg(static_cast(i)), QChar(i)); + } + index = currentDrive - STARTING_DRIVE_LETTER; + } else { + for (int i = 0; i < availableDrives.count(); ++i) { + const S60DeployConfiguration::DeviceDrive& drive(availableDrives.at(i)); + char driveLetter = QChar::toUpper(static_cast(drive.first)); + m_installationDriveCombo->addItem(formatDriveText(drive), + QChar(driveLetter)); + if (currentDrive == driveLetter) + index = i; + } } - int index = QChar::toUpper(static_cast(m_deployConfiguration->installationDrive())) - STARTING_DRIVE_LETTER; - - Q_ASSERT(index >= 0 && index <= LAST_DRIVE_LETTER-STARTING_DRIVE_LETTER); + QTC_ASSERT(index >= 0 && index <= m_installationDriveCombo->count(), return); m_installationDriveCombo->setCurrentIndex(index); + setInstallationDrive(index); } void S60DeployConfigurationWidget::silentInstallChanged(int state) @@ -280,7 +343,8 @@ void S60DeployConfigurationWidget::updateSerialDevices() const QString newPortName = device(newIndex).portName(); m_deployConfiguration->setSerialPortName(newPortName); } - if (m_deployConfiguration->communicationChannel() != S60DeployConfiguration::CommunicationTrkSerialConnection) + if (m_deployConfiguration->communicationChannel() != S60DeployConfiguration::CommunicationTrkSerialConnection + && m_deployConfiguration->communicationChannel() != S60DeployConfiguration::CommunicationCodaSerialConnection) m_deviceInfoButton->setEnabled(false); } @@ -309,7 +373,11 @@ void S60DeployConfigurationWidget::updateTargetInformation() void S60DeployConfigurationWidget::setInstallationDrive(int index) { - m_deployConfiguration->setInstallationDrive(static_cast(STARTING_DRIVE_LETTER + index)); + QTC_ASSERT(index >= 0, return); + QTC_ASSERT(index < m_installationDriveCombo->count(), return); + + QChar driveLetter(m_installationDriveCombo->itemData(index).toChar()); + m_deployConfiguration->setInstallationDrive(driveLetter.toAscii()); } void S60DeployConfigurationWidget::setSerialPort(int index) @@ -425,10 +493,10 @@ void S60DeployConfigurationWidget::slotWaitingForTrkClosed() void S60DeployConfigurationWidget::updateDeviceInfo() { - //TODO: No CODA device info! Implement it when it is available if (m_deployConfiguration->communicationChannel() == S60DeployConfiguration::CommunicationTrkSerialConnection) { QTC_ASSERT(!m_infoLauncher, return) - setDeviceInfoLabel(tr("Connecting...")); + + setDeviceInfoLabel(tr("Connecting...")); // Do a launcher run with the ping protocol. Prompt to connect and // go asynchronous afterwards to pop up launch trk box if a timeout occurs. QString message; @@ -466,9 +534,185 @@ void S60DeployConfigurationWidget::updateDeviceInfo() } // Wait for either timeout or results m_deviceInfoButton->setEnabled(false); + } else if (m_deployConfiguration->communicationChannel() == S60DeployConfiguration::CommunicationCodaSerialConnection) { + setDeviceInfoLabel(tr("Connecting...")); + const SymbianUtils::SymbianDevice commDev = currentDevice(); + m_codaInfoDevice = SymbianUtils::SymbianDeviceManager::instance()->getCodaDevice(commDev.portName()); + if (!m_codaInfoDevice->device()->isOpen()) { + setDeviceInfoLabel(m_codaInfoDevice->device()->errorString(), true); + return; + } + //TODO error handling - for now just throw the command at coda + m_codaInfoDevice->sendSymbianOsDataGetQtVersionCommand(Coda::CodaCallback(this, &S60DeployConfigurationWidget::getQtVersionCommandResult)); + m_deviceInfoButton->setEnabled(false); } else - setDeviceInfoLabel(tr("Information about the device is not available when using CODA."), true); + setDeviceInfoLabel(tr("Currently there is no information about device for this connection type."), true); } + void S60DeployConfigurationWidget::getQtVersionCommandResult(const Coda::CodaCommandResult &result) + { + if (result.type == Coda::CodaCommandResult::FailReply) { + setDeviceInfoLabel(tr("No device information available"), true); + m_deviceInfoButton->setEnabled(true); + return; + } else if (result.type == Coda::CodaCommandResult::CommandErrorReply){ + QString message; + startTable(message); + QTextStream str(&message); + addErrorToTable(str, tr("Qt version: "), tr("Not installed on device")); + finishTable(message); + setDeviceInfoLabel(message, false); + } else { + QString resultString; + if (result.values.count()) { + QHash obj = result.values[0].toVariant().toHash(); + QString ver = obj.value("qVersion").toString(); + + startTable(resultString); + QTextStream str(&resultString); + addToTable(str, tr("Qt version: "), ver); + QString systemVersion; + + int symVer = obj.value("symbianVersion").toInt(); + // Ugh why won't QSysInfo define these on non-symbian builds... + switch (symVer) { + case 10: + systemVersion.append("Symbian OS v9.2"); + break; + case 20: + systemVersion.append("Symbian OS v9.3"); + break; + case 30: + systemVersion.append("Symbian OS v9.4 / Symbian^1"); + break; + case 40: + systemVersion.append("Symbian^2"); + break; + case 50: + systemVersion.append("Symbian^3"); + break; + case 60: + systemVersion.append("Symbian^4"); + break; + default: + systemVersion.append(tr("Unrecognised Symbian version 0x%1").arg(symVer, 0, 16)); + break; + } + systemVersion.append(", "); + int s60Ver = obj.value("s60Version").toInt(); + switch (s60Ver) { + case 10: + systemVersion.append("S60 3rd Edition Feature Pack 1"); + break; + case 20: + systemVersion.append("S60 3rd Edition Feature Pack 2"); + break; + case 30: + systemVersion.append("S60 5th Edition"); + break; + case 40: + systemVersion.append("S60 5th Edition Feature Pack 1"); + break; + case 50: + systemVersion.append("S60 5th Edition Feature Pack 2"); + break; + default: + systemVersion.append(tr("Unrecognised S60 version 0x%1").arg(symVer, 0, 16)); + break; + } + addToTable(str, tr("OS version: "), systemVersion); + finishTable(resultString); + } + setDeviceInfoLabel(resultString); + } + m_codaInfoDevice->sendSymbianOsDataGetRomInfoCommand(Coda::CodaCallback(this, &S60DeployConfigurationWidget::getRomInfoResult)); + } + + void S60DeployConfigurationWidget::getRomInfoResult(const Coda::CodaCommandResult &result) + { + if (result.type == Coda::CodaCommandResult::SuccessReply && result.values.count()) { + QString resultString = m_deviceInfoLabel->text(); + startTable(resultString); + QTextStream str(&resultString); + + QVariantHash obj = result.values[0].toVariant().toHash(); + QString romVersion = obj.value("romVersion", tr("unknown")).toString(); + romVersion.replace('\n', " "); // The ROM string is split across multiple lines, for some reason. + addToTable(str, tr("ROM version: "), romVersion); + + QString pr = obj.value("prInfo").toString(); + if (pr.length()) + addToTable(str, tr("Release:"), pr); + finishTable(resultString); + setDeviceInfoLabel(resultString); + } + + QList packagesOfInterest; + packagesOfInterest.append(CODA_UID); + m_codaInfoDevice->sendSymbianInstallGetPackageInfoCommand(Coda::CodaCallback(this, &S60DeployConfigurationWidget::getInstalledPackagesResult), packagesOfInterest); + } + + void S60DeployConfigurationWidget::getInstalledPackagesResult(const Coda::CodaCommandResult &result) + { + if (result.type == Coda::CodaCommandResult::SuccessReply && result.values.count()) { + QString resultString = m_deviceInfoLabel->text(); + startTable(resultString); + QTextStream str(&resultString); + + QVariantList resultsList = result.values[0].toVariant().toList(); + foreach (const QVariant& var, resultsList) { + QVariantHash obj = var.toHash(); + if (obj.value("uid").toString().toUInt(0, 16) == CODA_UID) { + if (!obj.value("error").isNull()) { + // How can coda not be installed? Presumably some UID wrongness... + addErrorToTable(str, tr("CODA version: "), tr("Error reading CODA version")); + } else { + QVariantList version = obj.value("version").toList(); + addToTable(str, tr("CODA version: "), + QString("%1.%2.%3").arg(version[0].toInt()) + .arg(version[1].toInt()) + .arg(version[2].toInt())); + } + } + } + finishTable(resultString); + setDeviceInfoLabel(resultString); + } + + QStringList keys; + keys << QLatin1String("EDisplayXPixels"); + keys << QLatin1String("EDisplayYPixels"); + //keys << "EMemoryRAMFree"; + m_codaInfoDevice->sendSymbianOsDataGetHalInfoCommand(Coda::CodaCallback(this, &S60DeployConfigurationWidget::getHalResult), keys); + } + + void S60DeployConfigurationWidget::getHalResult(const Coda::CodaCommandResult &result) + { + if (result.type == Coda::CodaCommandResult::SuccessReply && result.values.count()) { + QString resultString = m_deviceInfoLabel->text(); + QVariantList resultsList = result.values[0].toVariant().toList(); + int x = 0; + int y = 0; + foreach (const QVariant& var, resultsList) { + QVariantHash obj = var.toHash(); + if (obj.value("name").toString() == "EDisplayXPixels") + x = obj.value("value").toInt(); + else if (obj.value("name").toString() == "EDisplayYPixels") + y = obj.value("value").toInt(); + } + if (x && y) { + startTable(resultString); + QTextStream str(&resultString); + addToTable(str, tr("Screen size: "), QString("%1x%2").arg(x).arg(y)); + finishTable(resultString); + setDeviceInfoLabel(resultString); + } + } + + // Done with collecting info + m_deviceInfoButton->setEnabled(true); + SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(m_codaInfoDevice); + } + } // namespace Internal } // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.h b/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.h index 2feb862f21b..30be5f4d88c 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.h +++ b/src/plugins/qt4projectmanager/qt-s60/s60deployconfigurationwidget.h @@ -61,6 +61,11 @@ namespace SymbianUtils { class SymbianDevice; } +namespace Coda { + class CodaDevice; + struct CodaCommandResult; +} + namespace Qt4ProjectManager { namespace Internal { @@ -102,6 +107,11 @@ private: QWidget * createCommunicationChannel(); + void getQtVersionCommandResult(const Coda::CodaCommandResult &result); + void getRomInfoResult(const Coda::CodaCommandResult &result); + void getInstalledPackagesResult(const Coda::CodaCommandResult &result); + void getHalResult(const Coda::CodaCommandResult &result); + S60DeployConfiguration *m_deployConfiguration; Utils::DetailsWidget *m_detailsWidget; QComboBox *m_serialPortsCombo; @@ -117,6 +127,7 @@ private: Utils::IpAddressLineEdit *m_ipAddress; QRadioButton *m_trkRadioButton; QRadioButton *m_codaRadioButton; + QSharedPointer m_codaInfoDevice; }; } // namespace Internal diff --git a/src/plugins/qt4projectmanager/qt-s60/s60deploystep.cpp b/src/plugins/qt4projectmanager/qt-s60/s60deploystep.cpp index a774965522a..9835bca70bc 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60deploystep.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60deploystep.cpp @@ -317,7 +317,7 @@ void S60DeployStep::stop() } else { if (m_codaDevice) { disconnect(m_codaDevice.data(), 0, this, 0); - SymbianUtils::SymbianDeviceManager::instance()->releaseTcfPort(m_codaDevice); + SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(m_codaDevice); } } emit finished(false); @@ -384,7 +384,7 @@ void S60DeployStep::startDeployment() } } else if (m_channel == S60DeployConfiguration::CommunicationCodaSerialConnection) { appendMessage(tr("Deploying application to '%1'...").arg(m_serialPortFriendlyName), false); - m_codaDevice = SymbianUtils::SymbianDeviceManager::instance()->getTcfPort(m_serialPortName); + m_codaDevice = SymbianUtils::SymbianDeviceManager::instance()->getCodaDevice(m_serialPortName); bool ok = m_codaDevice && m_codaDevice->device()->isOpen(); if (!ok) { QString deviceError = tr("No such port"); @@ -442,7 +442,7 @@ void S60DeployStep::run(QFutureInterface &fi) if (m_codaDevice) { disconnect(m_codaDevice.data(), 0, this, 0); - SymbianUtils::SymbianDeviceManager::instance()->releaseTcfPort(m_codaDevice); + SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(m_codaDevice); } delete m_eventLoop; diff --git a/src/plugins/texteditor/generichighlighter/manager.cpp b/src/plugins/texteditor/generichighlighter/manager.cpp index 16388f629a8..ca68aaf5a40 100644 --- a/src/plugins/texteditor/generichighlighter/manager.cpp +++ b/src/plugins/texteditor/generichighlighter/manager.cpp @@ -184,6 +184,11 @@ void Manager::gatherDefinitionsMimeTypes(QFutureInterface &futur Core::MimeDatabase *mimeDatabase = Core::ICore::instance()->mimeDatabase(); QSet knownSuffixes = QSet::fromList(mimeDatabase->suffixes()); + QHash userModified; + const QList &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 &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::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 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 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); diff --git a/src/shared/symbianutils/codadevice.cpp b/src/shared/symbianutils/codadevice.cpp index 57f09a353ad..ccf9ebe1661 100644 --- a/src/shared/symbianutils/codadevice.cpp +++ b/src/shared/symbianutils/codadevice.cpp @@ -936,8 +936,6 @@ void CodaDevice::sendProcessStartCommand(const CodaCallback &callBack, const QString sysBin = QLatin1String("c:/sys/bin"); const QString binaryFileName = slashPos == -1 ? binaryIn : binaryIn.mid(slashPos + 1); - // Fixup: Does argv[0] convention exist on Symbian? - arguments.push_front(binaryFileName); if (workingDirectory.isEmpty()) workingDirectory = sysBin; @@ -968,6 +966,20 @@ void CodaDevice::sendProcessStartCommand(const CodaCallback &callBack, sendCodaMessage(MessageWithReply, ProcessesService, "start", startData, callBack, cookie); } +void CodaDevice::sendRunProcessCommand(const CodaCallback &callBack, + const QString &processName, + QStringList arguments, + const QVariant &cookie) +{ + QByteArray startData; + JsonInputStream startStr(startData); + startStr << "" //We don't really know the drive of the working dir + << '\0' << processName << '\0' << arguments << '\0' + << QStringList() << '\0' // Env is an array ["PATH=value"] (non-standard) + << false; // Don't attach debugger + sendCodaMessage(MessageWithReply, ProcessesService, "start", startData, callBack, cookie); +} + void CodaDevice::sendSettingsEnableLogCommand() { @@ -1302,8 +1314,7 @@ void CodaDevice::sendRegistersSetCommand(const CodaCallback &callBack, value, cookie); } -//static const char outputListenerIDC[] = "org.eclipse.cdt.debug.edc.ui.ProgramOutputConsoleLogger"; -static const char outputListenerIDC[] = "ProgramOutputConsoleLogger"; //TODO: this one might be the correct one +static const char outputListenerIDC[] = "ProgramOutputConsoleLogger"; void CodaDevice::sendLoggingAddListenerCommand(const CodaCallback &callBack, const QVariant &cookie) @@ -1332,6 +1343,34 @@ void CodaDevice::sendSymbianOsDataFindProcessesCommand(const CodaCallback &callB sendCodaMessage(MessageWithReply, SymbianOSData, "findRunningProcesses", data, callBack, cookie); } +void CodaDevice::sendSymbianOsDataGetQtVersionCommand(const CodaCallback &callBack, + const QVariant &cookie) +{ + sendCodaMessage(MessageWithReply, SymbianOSData, "getQtVersion", QByteArray(), callBack, cookie); +} + +void CodaDevice::sendSymbianOsDataGetRomInfoCommand(const CodaCallback &callBack, + const QVariant &cookie) +{ + sendCodaMessage(MessageWithReply, SymbianOSData, "getRomInfo", QByteArray(), callBack, cookie); +} + +void CodaDevice::sendSymbianOsDataGetHalInfoCommand(const CodaCallback &callBack, + const QStringList &keys, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << '['; + for (int i = 0; i < keys.count(); ++i) { + if (i) + str << ','; + str << keys[i]; + } + str << ']'; + sendCodaMessage(MessageWithReply, SymbianOSData, "getHalInfo", data, callBack, cookie); +} + void Coda::CodaDevice::sendFileSystemOpenCommand(const Coda::CodaCallback &callBack, const QByteArray &name, unsigned flags, @@ -1395,4 +1434,24 @@ void Coda::CodaDevice::sendSymbianInstallUIInstallCommand(const Coda::CodaCallba str << file; sendCodaMessage(MessageWithReply, SymbianInstallService, "installWithUI", data, callBack, cookie); } + +void Coda::CodaDevice::sendSymbianInstallGetPackageInfoCommand(const Coda::CodaCallback &callBack, + const QList &packages, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << '['; + for (int i = 0; i < packages.count(); ++i) { + if (i) + str << ','; + QString pkgString; + pkgString.setNum(packages[i], 16); + str << pkgString; + } + str << ']'; + sendCodaMessage(MessageWithReply, SymbianInstallService, "getPackageInfo", data, callBack, cookie); +} + + } // namespace Coda diff --git a/src/shared/symbianutils/codadevice.h b/src/shared/symbianutils/codadevice.h index f2e029be91c..48c0dcd9161 100644 --- a/src/shared/symbianutils/codadevice.h +++ b/src/shared/symbianutils/codadevice.h @@ -131,11 +131,6 @@ http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Services * Commands can be sent along with callbacks that are passed a * CodaCommandResult and an opaque QVariant cookie. In addition, events are emitted. * - * Note: As of 11.8.2010, TCF Trk 4.0.5 does not currently support 'Registers::getm' - * (get multiple registers). So, CodaDevice emulates it by sending a sequence of - * single commands. As soon as 'Registers::getm' is natively supported, all code - * related to 'FakeRegisterGetm' should be removed. The workaround requires that - * the register name is known. * CODA notes: * - Commands are accepted only after receiving the Locator Hello event * - Serial communication initiation sequence: @@ -213,6 +208,12 @@ public: const QStringList &additionalLibraries = QStringList(), const QVariant &cookie = QVariant()); + // Just launch a process, don't attempt to attach the debugger to it + void sendRunProcessCommand(const CodaCallback &callBack, + const QString &processName, + QStringList arguments = QStringList(), + const QVariant &cookie = QVariant()); + // Preferred over Processes:Terminate by TCF TRK. void sendRunControlTerminateCommand(const CodaCallback &callBack, const QByteArray &id, @@ -345,6 +346,10 @@ public: const QByteArray &file, const QVariant &cookie = QVariant()); + void sendSymbianInstallGetPackageInfoCommand(const Coda::CodaCallback &callBack, + const QList &packages, + const QVariant &cookie = QVariant()); + void sendLoggingAddListenerCommand(const CodaCallback &callBack, const QVariant &cookie = QVariant()); @@ -357,6 +362,16 @@ public: const QByteArray &uid, const QVariant &cookie = QVariant()); + void sendSymbianOsDataGetQtVersionCommand(const CodaCallback &callBack, + const QVariant &cookie = QVariant()); + + void sendSymbianOsDataGetRomInfoCommand(const CodaCallback &callBack, + const QVariant &cookie = QVariant()); + + void sendSymbianOsDataGetHalInfoCommand(const CodaCallback &callBack, + const QStringList &keys = QStringList(), + const QVariant &cookie = QVariant()); + // Settings void sendSettingsEnableLogCommand(); diff --git a/src/shared/symbianutils/json.cpp b/src/shared/symbianutils/json.cpp index 980fa741161..e29f67cf14e 100644 --- a/src/shared/symbianutils/json.cpp +++ b/src/shared/symbianutils/json.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -415,6 +416,45 @@ QByteArray JsonValue::toString(bool multiline, int indent) const return result; } + +QVariant JsonValue::toVariant() const +{ + switch (m_type) { + case String: + return QString(m_data); + case Number: { + bool ok; + qint64 val = QString(m_data).toLongLong(&ok); + if (ok) + return val; + return QVariant(); + } + case Object: { + QHash hash; + for (int i = 0; i < m_children.size(); ++i) { + QString name(m_children[i].name()); + QVariant val = m_children[i].toVariant(); + hash.insert(name, val); + } + return hash; + } + case Array: { + QList list; + for (int i = 0; i < m_children.size(); ++i) { + list.append(m_children[i].toVariant()); + } + return list; + } + case Boolean: + return data() == QByteArray("true"); + case Invalid: + case NullObject: + default: + return QVariant(); + } +} + + void JsonValue::fromString(const QByteArray &ba) { const char *from = ba.constBegin(); diff --git a/src/shared/symbianutils/json.h b/src/shared/symbianutils/json.h index 6ab556e12a1..8dfa475a73b 100644 --- a/src/shared/symbianutils/json.h +++ b/src/shared/symbianutils/json.h @@ -87,6 +87,8 @@ public: void fromString(const QByteArray &str); void setStreamOutput(const QByteArray &name, const QByteArray &content); + QVariant toVariant() const; + private: static QByteArray parseCString(const char *&from, const char *to); static QByteArray parseNumber(const char *&from, const char *to); diff --git a/src/shared/symbianutils/symbiandevicemanager.cpp b/src/shared/symbianutils/symbiandevicemanager.cpp index 79dab3ad14e..5d340b5062d 100644 --- a/src/shared/symbianutils/symbiandevicemanager.cpp +++ b/src/shared/symbianutils/symbiandevicemanager.cpp @@ -355,7 +355,7 @@ SymbianDeviceManager::TrkDevicePtr return rc; } -CodaDevicePtr SymbianDeviceManager::getTcfPort(const QString &port) +CodaDevicePtr SymbianDeviceManager::getCodaDevice(const QString &port) { ensureInitialized(); const int idx = findByPortName(port); @@ -419,7 +419,7 @@ void SymbianDeviceManager::customEvent(QEvent *event) } } -void SymbianDeviceManager::releaseTcfPort(CodaDevicePtr &port) +void SymbianDeviceManager::releaseCodaDevice(CodaDevicePtr &port) { if (port) { // Check if this was the last reference to the port, if so close it after a short delay @@ -613,4 +613,106 @@ SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &sdm) return d; } -} // namespace SymbianUtilsInternal +OstChannel *SymbianDeviceManager::getOstChannel(const QString &port, uchar channelId) +{ + CodaDevicePtr coda = getCodaDevice(port); + if (coda.isNull() || !coda->device()->isOpen()) + return 0; + return new OstChannel(coda, channelId); +} + +struct OstChannelPrivate +{ + CodaDevicePtr m_codaPtr; + QByteArray m_dataBuffer; + uchar m_channelId; + bool m_hasReceivedData; +}; + +OstChannel::OstChannel(const CodaDevicePtr &codaPtr, uchar channelId) + : d(new OstChannelPrivate) +{ + d->m_codaPtr = codaPtr; + d->m_channelId = channelId; + d->m_hasReceivedData = false; + connect(codaPtr.data(), SIGNAL(unknownEvent(uchar, QByteArray)), this, SLOT(ostDataReceived(uchar,QByteArray))); + connect(codaPtr->device().data(), SIGNAL(aboutToClose()), this, SLOT(deviceAboutToClose())); + QIODevice::open(ReadWrite|Unbuffered); +} + +void OstChannel::close() +{ + QIODevice::close(); + if (d && d->m_codaPtr.data()) { + disconnect(d->m_codaPtr.data(), 0, this, 0); + SymbianDeviceManager::instance()->releaseCodaDevice(d->m_codaPtr); + } +} + +OstChannel::~OstChannel() +{ + close(); + delete d; +} + +void OstChannel::flush() +{ + //TODO d->m_codaPtr->device()- +} + +qint64 OstChannel::bytesAvailable() const +{ + return d->m_dataBuffer.size(); +} + +bool OstChannel::isSequential() const +{ + return true; +} + +qint64 OstChannel::readData(char *data, qint64 maxSize) +{ + qint64 amount = qMin(maxSize, (qint64)d->m_dataBuffer.size()); + qMemCopy(data, d->m_dataBuffer.constData(), amount); + d->m_dataBuffer.remove(0, amount); + return amount; +} + +qint64 OstChannel::writeData(const char *data, qint64 maxSize) +{ + static const qint64 KMaxOstPayload = 1022; + // If necessary, split the packet up + while (maxSize) { + QByteArray dataBuf = QByteArray::fromRawData(data, qMin(KMaxOstPayload, maxSize)); + d->m_codaPtr->writeCustomData(d->m_channelId, dataBuf); + data += dataBuf.length(); + maxSize -= dataBuf.length(); + } + return maxSize; +} + +void OstChannel::ostDataReceived(uchar channelId, const QByteArray &aData) +{ + if (channelId == d->m_channelId) { + d->m_hasReceivedData = true; + d->m_dataBuffer.append(aData); + emit readyRead(); + } +} + +Coda::CodaDevice& OstChannel::codaDevice() const +{ + return *d->m_codaPtr; +} + +bool OstChannel::hasReceivedData() const +{ + return isOpen() && d->m_hasReceivedData; +} + +void OstChannel::deviceAboutToClose() +{ + close(); +} + +} // namespace SymbianUtils diff --git a/src/shared/symbianutils/symbiandevicemanager.h b/src/shared/symbianutils/symbiandevicemanager.h index 6b12340a446..35753f86124 100644 --- a/src/shared/symbianutils/symbiandevicemanager.h +++ b/src/shared/symbianutils/symbiandevicemanager.h @@ -36,7 +36,7 @@ #include "symbianutils_global.h" -#include +#include #include #include @@ -56,6 +56,7 @@ namespace SymbianUtils { struct SymbianDeviceManagerPrivate; class SymbianDeviceData; +class OstChannel; enum DeviceCommunicationType { SerialPortCommunication = 0, @@ -152,11 +153,15 @@ public: // Gets the CodaDevice, which may or may not be open depending on what other clients have already acquired it. // Therefore once clients have set up any signals and slots they required, they should check CodaDevice::device()->isOpen() // and if false, the open failed and they should check device()->errorString() if required. - // Caller should call releaseTcfPort if they want the port to auto-close itself - CodaDevicePtr getTcfPort(const QString &port); + // Caller should call releaseCodaDevice if they want the port to auto-close itself + CodaDevicePtr getCodaDevice(const QString &port); + + // Note this function makes no guarantee that someone else isn't already listening on this channel id, or that there is anything on the other end + // Returns NULL if the port couldn't be opened + OstChannel *getOstChannel(const QString &port, uchar channelId); // Caller is responsible for disconnecting any signals from aPort - do not assume the CodaDevice will be deleted as a result of this call. On return aPort will be clear()ed. - void releaseTcfPort(CodaDevicePtr &aPort); + void releaseCodaDevice(CodaDevicePtr &aPort); int findByPortName(const QString &p) const; QString friendlyNameForPort(const QString &port) const; @@ -188,6 +193,38 @@ private: SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &); +struct OstChannelPrivate; + +class SYMBIANUTILS_EXPORT OstChannel : public QIODevice +{ + Q_OBJECT + +public: + void close(); + ~OstChannel(); + void flush(); + + qint64 bytesAvailable() const; + bool isSequential() const; + bool hasReceivedData() const; + + Coda::CodaDevice &codaDevice() const; + +private slots: + void ostDataReceived(uchar channelId, const QByteArray &aData); + void deviceAboutToClose(); + +private: + OstChannel(const CodaDevicePtr &codaPtr, uchar channelId); + Q_DISABLE_COPY(OstChannel) + qint64 readData(char *data, qint64 maxSize); + qint64 writeData(const char *data, qint64 maxSize); + +private: + OstChannelPrivate *d; + friend class SymbianDeviceManager; +}; + } // namespace SymbianUtils #endif // SYMBIANDEVICEMANAGER_H