kuserprofile.cpp

00001 /*  This file is part of the KDE libraries
00002  *  Copyright (C) 1999 Torben Weis <weis@kde.org>
00003  *
00004  *  This library is free software; you can redistribute it and/or
00005  *  modify it under the terms of the GNU Library General Public
00006  *  License version 2 as published by the Free Software Foundation;
00007  *
00008  *  This library is distributed in the hope that it will be useful,
00009  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00010  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011  *  Library General Public License for more details.
00012  *
00013  *  You should have received a copy of the GNU Library General Public License
00014  *  along with this library; see the file COPYING.LIB.  If not, write to
00015  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00016  *  Boston, MA 02110-1301, USA.
00017  **/
00018 
00019 #include "kuserprofile.h"
00020 #include "kservice.h"
00021 #include "kservicetype.h"
00022 #include "kservicetypefactory.h"
00023 #include "kstandarddirs.h"
00024 
00025 #include <kconfig.h>
00026 #include <kapplication.h>
00027 #include <kglobal.h>
00028 #include <kdebug.h>
00029 #include <kstaticdeleter.h>
00030 
00031 #include <qtl.h>
00032 
00033 #include <qvaluelist.h>
00034 template class QPtrList<KServiceTypeProfile>;
00035 typedef QPtrList<KServiceTypeProfile> KServiceTypeProfileList;
00036 
00037 /*********************************************
00038  *
00039  * KServiceTypeProfile
00040  *
00041  *********************************************/
00042 
00043 KServiceTypeProfileList* KServiceTypeProfile::s_lstProfiles = 0L;
00044 static KStaticDeleter< KServiceTypeProfileList > profileDeleter;
00045 bool KServiceTypeProfile::s_configurationMode = false;
00046 
00047 void KServiceTypeProfile::initStatic()
00048 {
00049   if ( s_lstProfiles )
00050     return;
00051   kdDebug(7014) << "KServiceTypeProfile::initStatic" << endl;
00052 
00053   // Make sure that a KServiceTypeFactory gets created.
00054   (void) KServiceTypeFactory::self();
00055 
00056   profileDeleter.setObject(s_lstProfiles, new KServiceTypeProfileList);
00057   s_lstProfiles->setAutoDelete( true );
00058 
00059   KConfig config( "profilerc", true, false);
00060 
00061   static const QString & defaultGroup = KGlobal::staticQString("<default>");
00062 
00063   QStringList tmpList = config.groupList();
00064   for (QStringList::Iterator aIt = tmpList.begin();
00065        aIt != tmpList.end(); ++aIt) {
00066     if ( *aIt == defaultGroup )
00067       continue;
00068 
00069     config.setGroup( *aIt );
00070 
00071     QString appId = config.readEntry( "Application" );
00072 
00073     KService::Ptr pService = KService::serviceByStorageId(appId);
00074 
00075     if ( pService ) {
00076       QString application = pService->storageId();
00077       QString type = config.readEntry( "ServiceType" );
00078       QString type2 = config.readEntry( "GenericServiceType" );
00079       if (type2.isEmpty()) // compat code
00080           type2 = (pService->type() == "Application") ? "Application" : "KParts/ReadOnlyPart";
00081       int pref = config.readNumEntry( "Preference" );
00082 
00083       if ( !type.isEmpty() /* && pref >= 0*/ ) // Don't test for pref here. We want those in the list, to mark them as forbidden
00084       {
00085         KServiceTypeProfile* p =
00086           KServiceTypeProfile::serviceTypeProfile( type, type2 );
00087 
00088         if ( !p ) {
00089           p = new KServiceTypeProfile( type, type2 );
00090           s_lstProfiles->append( p );
00091         }
00092 
00093         bool allow = config.readBoolEntry( "AllowAsDefault" );
00094         kdDebug(7014) << "KServiceTypeProfile::initStatic adding service " << application << " to profile for " 
00095                       << type << "," << type2 << " with preference " << pref << endl;
00096         p->addService( application, pref, allow );
00097       }
00098     }
00099   }
00100   initKDE4compatibility();
00101 }
00102 
00103   // KDE4 Compatibility
00104   // in KDE4 profilerc is no longer used instead preffered applications
00105   // are saved in the file mimeapps.list in xdgdata-apps.
00106   // The goal of this compatibility code is to parse the mimeapps.list files
00107   // and use the information from that file for KDE3 and the kuserprofile class.
00108   //
00109   // Code is in parts reused from kdelibs 4 /kded/kmimeassociations.cpp
00110 
00111 void KServiceTypeProfile::initKDE4compatibility()
00112 {
00113     // Using the "merged view" from KConfig is not enough since we -add- at every level, we don't replace.
00114     const QStringList mimeappsFiles = KGlobal::dirs()->findAllResources("xdgdata-apps",
00115             QString::fromLatin1("mimeapps.list"), true, false);
00116     if (mimeappsFiles.isEmpty()) {
00117         kdDebug(7014) << "mimeapps files is empty"<< endl;
00118         return;
00119     }
00120 
00121     int basePreference = 1000; // start high :)
00122     QStringList::const_iterator mimeappsIter = mimeappsFiles.begin();
00123     for ( ; mimeappsIter != mimeappsFiles.end(); mimeappsIter++ )
00124     {
00125         parseMimeAppsList(*(mimeappsIter), basePreference, QString::fromLatin1("Added Associations"));
00126         basePreference -= 50;
00127     }
00128 }
00129 void KServiceTypeProfile::parseMimeAppsList(const QString& file, int basePreference, const QString& group)
00130 {
00131     KConfig applist(file, true, false);
00132     kdDebug(7014) << "KServiceTypeProfile::ParseMimeAppsList" << file << endl;
00133     QMap<QString, QString> mimemapping = applist.entryMap(group);
00134 
00135     for (QMap<QString, QString>::Iterator aIt = mimemapping.begin();
00136          aIt != mimemapping.end(); ++aIt) {
00137       // Key is the mimetype like message/rfc822 value is the list of associated
00138       // desktop files
00139 
00140       kdDebug(7014) << "Found mimetype " << aIt.key() << " Mapping: " << aIt.data() << endl;
00141 
00142       QStringList desktopNames = QStringList::split(QString::fromLatin1(";"), aIt.data());
00143       int pref = basePreference;
00144       for (QStringList::const_iterator serviceIt = desktopNames.begin();
00145               serviceIt != desktopNames.end(); serviceIt++) {
00146             KService::Ptr pService = KService::serviceByStorageId(*(serviceIt));
00147             if (!pService) {
00148                 kdDebug(7014) << file << " specifies unknown service: " 
00149                               << *(serviceIt) << " for " << aIt.key() << endl;
00150             } else {
00151                 KServiceTypeProfile* p =
00152                     KServiceTypeProfile::serviceTypeProfile( aIt.key(), "Application" );
00153 
00154                 if ( !p ) {
00155                     kdDebug(7014) << "Created new ServiceTypeProfile" << endl;
00156                     p = new KServiceTypeProfile( aIt.key(), "Application" );
00157                     s_lstProfiles->append( p );
00158                 }
00159 
00160                 kdDebug(7014) << "KServiceTypeProfile::parseMimeAppsList adding service " << *(serviceIt)
00161                               << " to profile for " << aIt.key() << " with preference " << pref << endl;
00162                 p->addService(*(serviceIt), pref, true);
00163                 --pref;
00164             }
00165         }
00166     }
00167 }
00168 
00169 
00170 //static
00171 void KServiceTypeProfile::clear()
00172 {
00173     // HACK ksycoca may open the dummy db, in such case the first call to ksycoca
00174     // in initStatic() leads to closing the dummy db and clear() being called
00175     // in the middle of it, making s_lstProfiles be NULL
00176     if( s_lstProfiles == NULL || s_lstProfiles->count() == 0 )
00177         return;
00178     profileDeleter.destructObject();
00179 }
00180 
00181 //static
00182 KServiceTypeProfile::OfferList KServiceTypeProfile::offers( const QString& _servicetype, const QString& _genericServiceType )
00183 {
00184     OfferList offers;
00185     QStringList serviceList;
00186     //kdDebug(7014) << "KServiceTypeProfile::offers( " << _servicetype << "," << _genericServiceType << " )" << endl;
00187 
00188     // Note that KServiceTypeProfile::offers() calls KServiceType::offers(),
00189     // so we _do_ get the new services, that are available but not in the profile.
00190     if ( _genericServiceType.isEmpty() )
00191     {
00192         initStatic();
00193         // We want all profiles for servicetype, if we have profiles.
00194         // ## Slow loop, if profilerc is big. We should use a map instead?
00195         QPtrListIterator<KServiceTypeProfile> it( *s_lstProfiles );
00196         for( ; it.current(); ++it )
00197             if ( it.current()->m_strServiceType == _servicetype )
00198             {
00199                 offers += it.current()->offers();
00200             }
00201         //kdDebug(7014) << "Found profile: " << offers.count() << " offers" << endl;
00202     }
00203     else
00204     {
00205         KServiceTypeProfile* profile = serviceTypeProfile( _servicetype, _genericServiceType );
00206         if ( profile )
00207         {
00208             //kdDebug(7014) << "Found profile: " << profile->offers().count() << " offers" << endl;
00209             offers += profile->offers();
00210         }
00211         else
00212         {
00213             // Try the other way round, order is not like size, it doesn't matter.
00214             profile = serviceTypeProfile( _genericServiceType, _servicetype );
00215             if ( profile )
00216             {
00217                 //kdDebug(7014) << "Found profile after switching: " << profile->offers().count() << " offers" << endl;
00218                 offers += profile->offers();
00219             }
00220         }
00221     }
00222 
00223     // Collect services, to make the next loop faster
00224     OfferList::Iterator itOffers = offers.begin();
00225     for( ; itOffers != offers.end(); ++itOffers )
00226         serviceList += (*itOffers).service()->desktopEntryPath(); // this should identify each service uniquely
00227     kdDebug(7014) << "serviceList: " << serviceList.join(",") << endl;
00228 
00229     // Now complete with any other offers that aren't in the profile
00230     // This can be because the services have been installed after the profile was written,
00231     // but it's also the case for any service that's neither App nor ReadOnlyPart, e.g. RenameDlg/Plugin
00232     KService::List list = KServiceType::offers( _servicetype );
00233     //kdDebug(7014) << "Using KServiceType::offers, result: " << list.count() << " offers" << endl;
00234     QValueListIterator<KService::Ptr> it = list.begin();
00235     for( ; it != list.end(); ++it )
00236     {
00237         if (_genericServiceType.isEmpty() /*no constraint*/ || (*it)->hasServiceType( _genericServiceType ))
00238         {
00239             // Check that we don't already have it ;)
00240             if ( serviceList.find( (*it)->desktopEntryPath() ) == serviceList.end() )
00241             {
00242                 bool allow = (*it)->allowAsDefault();
00243                 KServiceOffer o( (*it), (*it)->initialPreferenceForMimeType(_servicetype), allow );
00244                 offers.append( o );
00245                 //kdDebug(7014) << "Appending offer " << (*it)->name() << " initial preference=" << (*it)->initialPreference() << " allow-as-default=" << allow << endl;
00246             }
00247             //else
00248             //    kdDebug(7014) << "Already having offer " << (*it)->name() << endl;
00249         }
00250     }
00251 
00252     qBubbleSort( offers );
00253 
00254 #if 1
00255     // debug code, comment if you wish but don't remove.
00256     kdDebug(7014) << "Sorted list:" << endl;
00257     OfferList::Iterator itOff = offers.begin();
00258     for( ; itOff != offers.end(); ++itOff )
00259         kdDebug(7014) << (*itOff).service()->name() << " allow-as-default=" << (*itOff).allowAsDefault() << endl;
00260 #endif
00261 
00262     kdDebug(7014) << "Returning " << offers.count() << " offers" << endl;
00263     return offers;
00264 }
00265 
00266 KServiceTypeProfile::KServiceTypeProfile( const QString& _servicetype, const QString& _genericServiceType )
00267 {
00268   initStatic();
00269 
00270   m_strServiceType = _servicetype;
00271   m_strGenericServiceType = _genericServiceType;
00272 }
00273 
00274 KServiceTypeProfile::~KServiceTypeProfile()
00275 {
00276 }
00277 
00278 void KServiceTypeProfile::addService( const QString& _service,
00279                       int _preference, bool _allow_as_default )
00280 {
00281   m_mapServices[ _service ].m_iPreference = _preference;
00282   m_mapServices[ _service ].m_bAllowAsDefault = _allow_as_default;
00283 }
00284 
00285 int KServiceTypeProfile::preference( const QString& _service ) const
00286 {
00287   KService::Ptr service = KService::serviceByName( _service );
00288   if (!service)
00289     return 0;
00290   QMap<QString,Service>::ConstIterator it = m_mapServices.find( service->storageId() );
00291   if ( it == m_mapServices.end() )
00292     return 0;
00293 
00294   return it.data().m_iPreference;
00295 }
00296 
00297 bool KServiceTypeProfile::allowAsDefault( const QString& _service ) const
00298 {
00299   KService::Ptr service = KService::serviceByName( _service );
00300   if (!service)
00301     return false;
00302 
00303   // Does the service itself not allow that ?
00304   if ( !service->allowAsDefault() )
00305     return false;
00306 
00307   // Look what the user says ...
00308   QMap<QString,Service>::ConstIterator it = m_mapServices.find( service->storageId() );
00309   if ( it == m_mapServices.end() )
00310     return 0;
00311 
00312   return it.data().m_bAllowAsDefault;
00313 }
00314 
00315 KServiceTypeProfile* KServiceTypeProfile::serviceTypeProfile( const QString& _servicetype, const QString& _genericServiceType )
00316 {
00317   initStatic();
00318   static const QString& app_str = KGlobal::staticQString("Application");
00319 
00320   const QString &_genservicetype  = ((!_genericServiceType.isEmpty()) ? _genericServiceType : app_str);
00321 
00322   QPtrListIterator<KServiceTypeProfile> it( *s_lstProfiles );
00323   for( ; it.current(); ++it )
00324     if (( it.current()->m_strServiceType == _servicetype ) &&
00325         ( it.current()->m_strGenericServiceType == _genservicetype))
00326       return it.current();
00327 
00328   return 0;
00329 }
00330 
00331 
00332 KServiceTypeProfile::OfferList KServiceTypeProfile::offers() const
00333 {
00334   OfferList offers;
00335 
00336   //kdDebug(7014) << "KServiceTypeProfile::offers() serviceType=" << m_strServiceType << " genericServiceType=" << m_strGenericServiceType << endl;
00337   KService::List list = KServiceType::offers( m_strServiceType );
00338 
00339   // KDE4 compatibility:
00340   // Also look into the services registrerd for this mimetype in the profile
00341   // but not in the desktop file database.
00342   // This allows a more generic mapping which is used here to correctly find the
00343   // preffered application.
00344   //
00345   // HACK: Search the map to find those services
00346   QMapConstIterator<QString, Service> mapIt = m_mapServices.constBegin();
00347   for ( ; mapIt != m_mapServices.end(); mapIt++ ) {
00348       KService::Ptr pService = KService::serviceByStorageId(mapIt.key());
00349       if ( pService ) {
00350 //          kdDebug(7014) << "Additionally adding " << mapIt.key() << " to available services for: "
00351 //                        << m_strServiceType << " as configured. " << endl;
00352           list.append(pService);
00353       } else {
00354           kdDebug(7014) << mapIt.key() << " specifies unknown service in service map for "
00355                         << m_strServiceType << endl;
00356       }
00357   }
00358 
00359   QValueListIterator<KService::Ptr> it = list.begin();
00360   for( ; it != list.end(); ++it )
00361   {
00362     kdDebug(7014) << "KServiceTypeProfile::offers() considering " << (*it)->name() << endl;
00363     if ( m_strGenericServiceType.isEmpty() || (*it)->hasServiceType( m_strGenericServiceType ) )
00364     {
00365       // Now look into the profile, to find this service's preference.
00366       QMap<QString,Service>::ConstIterator it2 = m_mapServices.find( (*it)->storageId() );
00367 
00368       if( it2 != m_mapServices.end() )
00369       {
00370         //kdDebug(7014) << "found in mapServices pref=" << it2.data().m_iPreference << endl;
00371         if ( it2.data().m_iPreference > 0 ) {
00372           bool allow = (*it)->allowAsDefault();
00373           if ( allow )
00374             allow = it2.data().m_bAllowAsDefault;
00375           KServiceOffer o( (*it), it2.data().m_iPreference, allow );
00376           offers.append( o );
00377         }
00378       }
00379       else
00380       {
00381         //kdDebug(7014) << "not found in mapServices. Appending." << endl;
00382         // We use 0 as the preference to ensure new apps don't take over existing apps (which default to 1)
00383         KServiceOffer o( (*it), 0, (*it)->allowAsDefault() );
00384         offers.append( o );
00385       }
00386     }/* else
00387       kdDebug(7014) << "Doesn't have " << m_strGenericServiceType << endl;*/
00388   }
00389 
00390   qBubbleSort( offers );
00391 
00392   kdDebug(7014) << "KServiceTypeProfile::offers returning " << offers.count() << " offers" << endl;
00393   return offers;
00394 }
00395 
00396 KService::Ptr KServiceTypeProfile::preferredService( const QString & _serviceType, const QString & _genericServiceType )
00397 {
00398   OfferList lst = offers( _serviceType, _genericServiceType );
00399 
00400   OfferList::Iterator itOff = lst.begin();
00401   // Look for the first one that is allowed as default.
00402   // Since the allowed-as-default are first anyway, we only have
00403   // to look at the first one to know.
00404   if( itOff != lst.end() && (*itOff).allowAsDefault() )
00405     return (*itOff).service();
00406 
00407   kdDebug(7014) << "No offers, or none allowed as default" << endl;
00408   return 0L;
00409 }
00410 
00411 /*********************************************
00412  *
00413  * KServiceOffer
00414  *
00415  *********************************************/
00416 
00417 KServiceOffer::KServiceOffer()
00418 {
00419   m_iPreference = -1;
00420 }
00421 
00422 KServiceOffer::KServiceOffer( const KServiceOffer& _o )
00423 {
00424   m_pService = _o.m_pService;
00425   m_iPreference = _o.m_iPreference;
00426   m_bAllowAsDefault = _o.m_bAllowAsDefault;
00427 }
00428 
00429 KServiceOffer::KServiceOffer( KService::Ptr _service, int _pref, bool _default )
00430 {
00431   m_pService = _service;
00432   m_iPreference = _pref;
00433   m_bAllowAsDefault = _default;
00434 }
00435 
00436 
00437 bool KServiceOffer::operator< ( const KServiceOffer& _o ) const
00438 {
00439   // Put offers allowed as default FIRST.
00440   if ( _o.m_bAllowAsDefault && !m_bAllowAsDefault )
00441     return false; // _o is default and not 'this'.
00442   if ( !_o.m_bAllowAsDefault && m_bAllowAsDefault )
00443     return true; // 'this' is default but not _o.
00444  // Both offers are allowed or not allowed as default
00445  // -> use preferences to sort them
00446  // The bigger the better, but we want the better FIRST
00447   return _o.m_iPreference < m_iPreference;
00448 }
KDE Home | KDE Accessibility Home | Description of Access Keys