You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
20 KiB
676 lines
20 KiB
/* |
|
This source file is part of Konsole, a terminal emulator. |
|
|
|
Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; if not, write to the Free Software |
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
02110-1301 USA. |
|
*/ |
|
|
|
// Own |
|
#include "ProfileManager.h" |
|
|
|
// Qt |
|
#include <QtCore/QDir> |
|
#include <QtCore/QFileInfo> |
|
#include <QtCore/QList> |
|
#include <QtCore/QString> |
|
|
|
// KDE |
|
#include <KSharedConfig> |
|
#include <KConfig> |
|
#include <QDebug> |
|
#include <KConfigGroup> |
|
#include <KLocalizedString> |
|
|
|
// Konsole |
|
#include "ProfileReader.h" |
|
#include "ProfileWriter.h" |
|
|
|
using namespace Konsole; |
|
|
|
static bool profileIndexLessThan(const Profile::Ptr& p1, const Profile::Ptr& p2) |
|
{ |
|
return p1->menuIndexAsInt() <= p2->menuIndexAsInt(); |
|
} |
|
|
|
static bool profileNameLessThan(const Profile::Ptr& p1, const Profile::Ptr& p2) |
|
{ |
|
return QString::localeAwareCompare(p1->name(), p2->name()) <= 0; |
|
} |
|
|
|
static bool stringLessThan(const QString& p1, const QString& p2) |
|
{ |
|
return QString::localeAwareCompare(p1, p2) <= 0; |
|
} |
|
|
|
static void sortByIndexProfileList(QList<Profile::Ptr>& list) |
|
{ |
|
qStableSort(list.begin(), list.end(), profileIndexLessThan); |
|
} |
|
|
|
static void sortByNameProfileList(QList<Profile::Ptr>& list) |
|
{ |
|
qStableSort(list.begin(), list.end(), profileNameLessThan); |
|
} |
|
|
|
ProfileManager::ProfileManager() |
|
: _loadedAllProfiles(false) |
|
, _loadedFavorites(false) |
|
{ |
|
//load fallback profile |
|
_fallbackProfile = Profile::Ptr(new FallbackProfile); |
|
addProfile(_fallbackProfile); |
|
|
|
// lookup the default profile specified in <App>rc |
|
// for stand-alone Konsole, appConfig is just konsolerc |
|
// for konsolepart, appConfig might be yakuakerc, dolphinrc, katerc... |
|
KSharedConfigPtr appConfig = KSharedConfig::openConfig(); |
|
KConfigGroup group = appConfig->group("Desktop Entry"); |
|
QString defaultProfileFileName = group.readEntry("DefaultProfile", ""); |
|
|
|
// if the hosting application of konsolepart does not specify its own |
|
// default profile, use the default profile of stand-alone Konsole. |
|
if (defaultProfileFileName.isEmpty()) { |
|
KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig(QStringLiteral("konsolerc")); |
|
group = konsoleConfig->group("Desktop Entry"); |
|
defaultProfileFileName = group.readEntry("DefaultProfile", ""); |
|
} |
|
|
|
_defaultProfile = _fallbackProfile; |
|
if (!defaultProfileFileName.isEmpty()) { |
|
// load the default profile |
|
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/") + defaultProfileFileName); |
|
|
|
if (!path.isEmpty()) { |
|
Profile::Ptr profile = loadProfile(path); |
|
if (profile) |
|
_defaultProfile = profile; |
|
} |
|
} |
|
|
|
Q_ASSERT(_profiles.count() > 0); |
|
Q_ASSERT(_defaultProfile); |
|
|
|
// get shortcuts and paths of profiles associated with |
|
// them - this doesn't load the shortcuts themselves, |
|
// that is done on-demand. |
|
loadShortcuts(); |
|
} |
|
|
|
ProfileManager::~ProfileManager() |
|
{ |
|
} |
|
|
|
Q_GLOBAL_STATIC(ProfileManager, theProfileManager) |
|
ProfileManager* ProfileManager::instance() |
|
{ |
|
return theProfileManager; |
|
} |
|
|
|
Profile::Ptr ProfileManager::loadProfile(const QString& shortPath) |
|
{ |
|
// the fallback profile has a 'special' path name, "FALLBACK/" |
|
if (shortPath == _fallbackProfile->path()) |
|
return _fallbackProfile; |
|
|
|
QString path = shortPath; |
|
|
|
// add a suggested suffix and relative prefix if missing |
|
QFileInfo fileInfo(path); |
|
|
|
if (fileInfo.isDir()) |
|
return Profile::Ptr(); |
|
|
|
if (fileInfo.suffix() != QLatin1String("profile")) |
|
path.append(".profile"); |
|
if (fileInfo.path().isEmpty() || fileInfo.path() == QLatin1String(".")) |
|
path.prepend(QLatin1String("konsole") + QDir::separator()); |
|
|
|
// if the file is not an absolute path, look it up |
|
if (!fileInfo.isAbsolute()) |
|
path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, path); |
|
|
|
// if the file is not found, return immediately |
|
if (path.isEmpty()) { |
|
return Profile::Ptr(); |
|
} |
|
|
|
// check that we have not already loaded this profile |
|
foreach(const Profile::Ptr& profile, _profiles) { |
|
if (profile->path() == path) |
|
return profile; |
|
} |
|
|
|
// guard to prevent problems if a profile specifies itself as its parent |
|
// or if there is recursion in the "inheritance" chain |
|
// (eg. two profiles, A and B, specifying each other as their parents) |
|
static QStack<QString> recursionGuard; |
|
PopStackOnExit<QString> popGuardOnExit(recursionGuard); |
|
|
|
if (recursionGuard.contains(path)) { |
|
qWarning() << "Ignoring attempt to load profile recursively from" << path; |
|
return _fallbackProfile; |
|
} else { |
|
recursionGuard.push(path); |
|
} |
|
|
|
// load the profile |
|
ProfileReader* reader = new KDE4ProfileReader; |
|
|
|
Profile::Ptr newProfile = Profile::Ptr(new Profile(fallbackProfile())); |
|
newProfile->setProperty(Profile::Path, path); |
|
|
|
QString parentProfilePath; |
|
bool result = reader->readProfile(path, newProfile, parentProfilePath); |
|
|
|
if (!parentProfilePath.isEmpty()) { |
|
Profile::Ptr parentProfile = loadProfile(parentProfilePath); |
|
newProfile->setParent(parentProfile); |
|
} |
|
|
|
delete reader; |
|
|
|
if (!result) { |
|
qWarning() << "Could not load profile from " << path; |
|
return Profile::Ptr(); |
|
} else { |
|
addProfile(newProfile); |
|
return newProfile; |
|
} |
|
} |
|
QStringList ProfileManager::availableProfilePaths() const |
|
{ |
|
KDE4ProfileReader kde4Reader; |
|
|
|
QStringList paths; |
|
paths += kde4Reader.findProfiles(); |
|
|
|
qStableSort(paths.begin(), paths.end(), stringLessThan); |
|
|
|
return paths; |
|
} |
|
|
|
QStringList ProfileManager::availableProfileNames() const |
|
{ |
|
QStringList names; |
|
|
|
foreach(Profile::Ptr profile, ProfileManager::instance()->allProfiles()) { |
|
if (!profile->isHidden()) { |
|
names.push_back(profile->name()); |
|
} |
|
} |
|
|
|
qStableSort(names.begin(), names.end(), stringLessThan); |
|
|
|
return names; |
|
} |
|
|
|
void ProfileManager::loadAllProfiles() |
|
{ |
|
if (_loadedAllProfiles) |
|
return; |
|
|
|
const QStringList& paths = availableProfilePaths(); |
|
foreach(const QString& path, paths) { |
|
loadProfile(path); |
|
} |
|
|
|
_loadedAllProfiles = true; |
|
} |
|
|
|
void ProfileManager::sortProfiles(QList<Profile::Ptr>& list) |
|
{ |
|
QList<Profile::Ptr> lackingIndices; |
|
QList<Profile::Ptr> havingIndices; |
|
|
|
for (int i = 0; i < list.size(); ++i) { |
|
// dis-regard the fallback profile |
|
if (list.at(i)->path() == _fallbackProfile->path()) |
|
continue; |
|
|
|
if (list.at(i)->menuIndexAsInt() == 0) |
|
lackingIndices.append(list.at(i)); |
|
else |
|
havingIndices.append(list.at(i)); |
|
} |
|
|
|
// sort by index |
|
sortByIndexProfileList(havingIndices); |
|
|
|
// sort alphabetically those w/o an index |
|
sortByNameProfileList(lackingIndices); |
|
|
|
// Put those with indices in sequential order w/o any gaps |
|
int i = 0; |
|
for (i = 0; i < havingIndices.size(); ++i) { |
|
Profile::Ptr tempProfile = havingIndices.at(i); |
|
tempProfile->setProperty(Profile::MenuIndex, QString::number(i + 1)); |
|
havingIndices.replace(i, tempProfile); |
|
} |
|
// Put those w/o indices in sequential order |
|
for (int j = 0; j < lackingIndices.size(); ++j) { |
|
Profile::Ptr tempProfile = lackingIndices.at(j); |
|
tempProfile->setProperty(Profile::MenuIndex, QString::number(j + 1 + i)); |
|
lackingIndices.replace(j, tempProfile); |
|
} |
|
|
|
// combine the 2 list: first those who had indices |
|
list.clear(); |
|
list.append(havingIndices); |
|
list.append(lackingIndices); |
|
} |
|
|
|
void ProfileManager::saveSettings() |
|
{ |
|
// save default profile |
|
saveDefaultProfile(); |
|
|
|
// save shortcuts |
|
saveShortcuts(); |
|
|
|
// save favorites |
|
saveFavorites(); |
|
|
|
// ensure default/favorites/shortcuts settings are synced into disk |
|
KSharedConfigPtr appConfig = KSharedConfig::openConfig(); |
|
appConfig->sync(); |
|
} |
|
|
|
QList<Profile::Ptr> ProfileManager::sortedFavorites() |
|
{ |
|
QList<Profile::Ptr> favorites = findFavorites().toList(); |
|
|
|
sortProfiles(favorites); |
|
return favorites; |
|
} |
|
|
|
QList<Profile::Ptr> ProfileManager::allProfiles() |
|
{ |
|
loadAllProfiles(); |
|
|
|
return _profiles.toList(); |
|
} |
|
|
|
QList<Profile::Ptr> ProfileManager::loadedProfiles() const |
|
{ |
|
return _profiles.toList(); |
|
} |
|
|
|
Profile::Ptr ProfileManager::defaultProfile() const |
|
{ |
|
return _defaultProfile; |
|
} |
|
Profile::Ptr ProfileManager::fallbackProfile() const |
|
{ |
|
return _fallbackProfile; |
|
} |
|
|
|
QString ProfileManager::saveProfile(Profile::Ptr profile) |
|
{ |
|
ProfileWriter* writer = new KDE4ProfileWriter; |
|
|
|
QString newPath = writer->getPath(profile); |
|
|
|
writer->writeProfile(newPath, profile); |
|
|
|
delete writer; |
|
|
|
return newPath; |
|
} |
|
|
|
void ProfileManager::changeProfile(Profile::Ptr profile, |
|
QHash<Profile::Property, QVariant> propertyMap, bool persistent) |
|
{ |
|
Q_ASSERT(profile); |
|
|
|
// never save a profile with empty name into disk! |
|
persistent = persistent && !profile->name().isEmpty(); |
|
|
|
Profile::Ptr newProfile; |
|
|
|
// If we are asked to store the fallback profile (which has an |
|
// invalid path by design), we reset the path to an empty string |
|
// which will make the profile writer automatically generate a |
|
// proper path. |
|
if (persistent && profile->path() == _fallbackProfile->path()) { |
|
|
|
// Generate a new name, so it is obvious what is actually built-in |
|
// in the profile manager |
|
QList<Profile::Ptr> existingProfiles = allProfiles(); |
|
QStringList existingProfileNames; |
|
foreach(Profile::Ptr existingProfile, existingProfiles) { |
|
existingProfileNames.append(existingProfile->name()); |
|
} |
|
|
|
int nameSuffix = 1; |
|
QString newName; |
|
QString newTranslatedName; |
|
do { |
|
newName = QStringLiteral("Profile ") + QString::number(nameSuffix); |
|
newTranslatedName = i18nc("The default name of a profile", "Profile #%1", nameSuffix); |
|
// TODO: remove the # above and below - too many issues |
|
newTranslatedName.remove('#'); |
|
nameSuffix++; |
|
} while (existingProfileNames.contains(newName)); |
|
|
|
newProfile = Profile::Ptr(new Profile(ProfileManager::instance()->fallbackProfile())); |
|
newProfile->clone(profile, true); |
|
newProfile->setProperty(Profile::UntranslatedName, newName); |
|
newProfile->setProperty(Profile::Name, newTranslatedName); |
|
newProfile->setProperty(Profile::MenuIndex, QString("0")); |
|
newProfile->setHidden(false); |
|
|
|
addProfile(newProfile); |
|
setDefaultProfile(newProfile); |
|
|
|
} else { |
|
newProfile = profile; |
|
}; |
|
|
|
// insert the changes into the existing Profile instance |
|
QListIterator<Profile::Property> iter(propertyMap.keys()); |
|
while (iter.hasNext()) { |
|
const Profile::Property property = iter.next(); |
|
newProfile->setProperty(property, propertyMap[property]); |
|
} |
|
|
|
// when changing a group, iterate through the profiles |
|
// in the group and call changeProfile() on each of them |
|
// |
|
// this is so that each profile in the group, the profile is |
|
// applied, a change notification is emitted and the profile |
|
// is saved to disk |
|
ProfileGroup::Ptr group = newProfile->asGroup(); |
|
if (group) { |
|
foreach(const Profile::Ptr & groupProfile, group->profiles()) { |
|
changeProfile(groupProfile, propertyMap, persistent); |
|
} |
|
return; |
|
} |
|
|
|
// save changes to disk, unless the profile is hidden, in which case |
|
// it has no file on disk |
|
if (persistent && !newProfile->isHidden()) { |
|
newProfile->setProperty(Profile::Path, saveProfile(newProfile)); |
|
} |
|
|
|
// notify the world about the change |
|
emit profileChanged(newProfile); |
|
} |
|
|
|
void ProfileManager::addProfile(Profile::Ptr profile) |
|
{ |
|
if (_profiles.isEmpty()) |
|
_defaultProfile = profile; |
|
|
|
_profiles.insert(profile); |
|
|
|
emit profileAdded(profile); |
|
} |
|
|
|
bool ProfileManager::deleteProfile(Profile::Ptr profile) |
|
{ |
|
bool wasDefault = (profile == defaultProfile()); |
|
|
|
if (profile) { |
|
// try to delete the config file |
|
if (profile->isPropertySet(Profile::Path) && QFile::exists(profile->path())) { |
|
if (!QFile::remove(profile->path())) { |
|
qWarning() << "Could not delete profile: " << profile->path() |
|
<< "The file is most likely in a directory which is read-only."; |
|
|
|
return false; |
|
} |
|
} |
|
|
|
// remove from favorites, profile list, shortcut list etc. |
|
setFavorite(profile, false); |
|
setShortcut(profile, QKeySequence()); |
|
_profiles.remove(profile); |
|
|
|
// mark the profile as hidden so that it does not show up in the |
|
// Manage Profiles dialog and is not saved to disk |
|
profile->setHidden(true); |
|
} |
|
|
|
// If we just deleted the default profile, replace it with the first |
|
// profile in the list. |
|
if (wasDefault) { |
|
const QList<Profile::Ptr> existingProfiles = allProfiles(); |
|
setDefaultProfile(existingProfiles.at(0)); |
|
} |
|
|
|
emit profileRemoved(profile); |
|
|
|
return true; |
|
} |
|
|
|
void ProfileManager::setDefaultProfile(Profile::Ptr profile) |
|
{ |
|
Q_ASSERT(_profiles.contains(profile)); |
|
|
|
_defaultProfile = profile; |
|
} |
|
|
|
void ProfileManager::saveDefaultProfile() |
|
{ |
|
QString path = _defaultProfile->path(); |
|
|
|
if (path.isEmpty()) |
|
path = KDE4ProfileWriter().getPath(_defaultProfile); |
|
|
|
QFileInfo fileInfo(path); |
|
|
|
KSharedConfigPtr appConfig = KSharedConfig::openConfig(); |
|
KConfigGroup group = appConfig->group("Desktop Entry"); |
|
group.writeEntry("DefaultProfile", fileInfo.fileName()); |
|
} |
|
|
|
QSet<Profile::Ptr> ProfileManager::findFavorites() |
|
{ |
|
loadFavorites(); |
|
|
|
return _favorites; |
|
} |
|
void ProfileManager::setFavorite(Profile::Ptr profile , bool favorite) |
|
{ |
|
if (!_profiles.contains(profile)) |
|
addProfile(profile); |
|
|
|
if (favorite && !_favorites.contains(profile)) { |
|
_favorites.insert(profile); |
|
emit favoriteStatusChanged(profile, favorite); |
|
} else if (!favorite && _favorites.contains(profile)) { |
|
_favorites.remove(profile); |
|
emit favoriteStatusChanged(profile, favorite); |
|
} |
|
} |
|
void ProfileManager::loadShortcuts() |
|
{ |
|
KSharedConfigPtr appConfig = KSharedConfig::openConfig(); |
|
KConfigGroup shortcutGroup = appConfig->group("Profile Shortcuts"); |
|
|
|
QMap<QString, QString> entries = shortcutGroup.entryMap(); |
|
|
|
QMapIterator<QString, QString> iter(entries); |
|
while (iter.hasNext()) { |
|
iter.next(); |
|
|
|
QKeySequence shortcut = QKeySequence::fromString(iter.key()); |
|
QString profilePath = iter.value(); |
|
|
|
ShortcutData data; |
|
|
|
// if the file is not an absolute path, look it up |
|
QFileInfo fileInfo(profilePath); |
|
if (!fileInfo.isAbsolute()) { |
|
profilePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/") + profilePath); |
|
} |
|
|
|
data.profilePath = profilePath; |
|
_shortcuts.insert(shortcut, data); |
|
} |
|
} |
|
void ProfileManager::saveShortcuts() |
|
{ |
|
KSharedConfigPtr appConfig = KSharedConfig::openConfig(); |
|
KConfigGroup shortcutGroup = appConfig->group("Profile Shortcuts"); |
|
shortcutGroup.deleteGroup(); |
|
|
|
QMapIterator<QKeySequence, ShortcutData> iter(_shortcuts); |
|
while (iter.hasNext()) { |
|
iter.next(); |
|
|
|
QString shortcutString = iter.key().toString(); |
|
|
|
// if the profile path in "Profile Shortcuts" is an absolute path, |
|
// take the profile name |
|
QFileInfo fileInfo(iter.value().profilePath); |
|
QString profileName; |
|
if (fileInfo.isAbsolute()) { |
|
// Check to see if file is under KDE's data locations. If not, |
|
// store full path. |
|
const QString location = QStandardPaths::locate(QStandardPaths::GenericDataLocation, |
|
QStringLiteral("konsole/") + fileInfo.fileName()); |
|
if (location.isEmpty()) { |
|
profileName = iter.value().profilePath; |
|
} else { |
|
profileName = fileInfo.fileName(); |
|
} |
|
} else { |
|
profileName = iter.value().profilePath; |
|
} |
|
|
|
shortcutGroup.writeEntry(shortcutString, profileName); |
|
} |
|
} |
|
void ProfileManager::setShortcut(Profile::Ptr profile , |
|
const QKeySequence& keySequence) |
|
{ |
|
QKeySequence existingShortcut = shortcut(profile); |
|
_shortcuts.remove(existingShortcut); |
|
|
|
if (keySequence.isEmpty()) |
|
return; |
|
|
|
ShortcutData data; |
|
data.profileKey = profile; |
|
data.profilePath = profile->path(); |
|
// TODO - This won't work if the profile doesn't |
|
// have a path yet |
|
_shortcuts.insert(keySequence, data); |
|
|
|
emit shortcutChanged(profile, keySequence); |
|
} |
|
void ProfileManager::loadFavorites() |
|
{ |
|
if (_loadedFavorites) |
|
return; |
|
|
|
KSharedConfigPtr appConfig = KSharedConfig::openConfig(); |
|
KConfigGroup favoriteGroup = appConfig->group("Favorite Profiles"); |
|
|
|
QSet<QString> favoriteSet; |
|
|
|
if (favoriteGroup.hasKey("Favorites")) { |
|
QStringList list = favoriteGroup.readEntry("Favorites", QStringList()); |
|
favoriteSet = QSet<QString>::fromList(list); |
|
} |
|
|
|
// look for favorites among those already loaded |
|
foreach(const Profile::Ptr& profile, _profiles) { |
|
const QString& path = profile->path(); |
|
if (favoriteSet.contains(path)) { |
|
_favorites.insert(profile); |
|
favoriteSet.remove(path); |
|
} |
|
} |
|
// load any remaining favorites |
|
foreach(const QString& favorite, favoriteSet) { |
|
Profile::Ptr profile = loadProfile(favorite); |
|
if (profile) |
|
_favorites.insert(profile); |
|
} |
|
|
|
_loadedFavorites = true; |
|
} |
|
void ProfileManager::saveFavorites() |
|
{ |
|
KSharedConfigPtr appConfig = KSharedConfig::openConfig(); |
|
KConfigGroup favoriteGroup = appConfig->group("Favorite Profiles"); |
|
|
|
QStringList paths; |
|
foreach(const Profile::Ptr& profile, _favorites) { |
|
Q_ASSERT(_profiles.contains(profile) && profile); |
|
|
|
QFileInfo fileInfo(profile->path()); |
|
QString profileName; |
|
|
|
if (fileInfo.isAbsolute()) { |
|
// Check to see if file is under KDE's data locations. If not, |
|
// store full path. |
|
const QString location = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("konsole/") + fileInfo.fileName()); |
|
|
|
if (location.isEmpty()) { |
|
profileName = profile->path(); |
|
} else { |
|
profileName = fileInfo.fileName(); |
|
} |
|
} else { |
|
profileName = profile->path(); |
|
} |
|
|
|
paths << profileName; |
|
} |
|
|
|
favoriteGroup.writeEntry("Favorites", paths); |
|
} |
|
|
|
QList<QKeySequence> ProfileManager::shortcuts() |
|
{ |
|
return _shortcuts.keys(); |
|
} |
|
|
|
Profile::Ptr ProfileManager::findByShortcut(const QKeySequence& shortcut) |
|
{ |
|
Q_ASSERT(_shortcuts.contains(shortcut)); |
|
|
|
if (!_shortcuts[shortcut].profileKey) { |
|
Profile::Ptr key = loadProfile(_shortcuts[shortcut].profilePath); |
|
if (!key) { |
|
_shortcuts.remove(shortcut); |
|
return Profile::Ptr(); |
|
} |
|
_shortcuts[shortcut].profileKey = key; |
|
} |
|
|
|
return _shortcuts[shortcut].profileKey; |
|
} |
|
|
|
|
|
QKeySequence ProfileManager::shortcut(Profile::Ptr profile) const |
|
{ |
|
QMapIterator<QKeySequence, ShortcutData> iter(_shortcuts); |
|
while (iter.hasNext()) { |
|
iter.next(); |
|
if (iter.value().profileKey == profile |
|
|| iter.value().profilePath == profile->path()) |
|
return iter.key(); |
|
} |
|
|
|
return QKeySequence(); |
|
} |
|
|
|
|