add more spacing
[personal-kdebase.git] / apps / konsole / src / SessionManager.cpp
blob39d2f0e68dfaf2edd920e7e23fc410208b131d19
1 /*
2 This source file is part of Konsole, a terminal emulator.
4 Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
22 // Own
23 #include "SessionManager.h"
25 // Qt
26 #include <QtCore/QDir>
27 #include <QtCore/QFileInfo>
28 #include <QtCore/QList>
29 #include <QtCore/QSignalMapper>
30 #include <QtCore/QString>
31 #include <QtCore/QTextCodec>
33 // KDE
34 #include <klocale.h>
35 #include <kicon.h>
36 #include <krun.h>
37 #include <kshell.h>
38 #include <kconfig.h>
39 #include <kglobal.h>
40 #include <kdebug.h>
41 #include <kconfiggroup.h>
42 #include <kstandarddirs.h>
43 #include <kdesktopfile.h>
45 // Konsole
46 #include "ColorScheme.h"
47 #include "Session.h"
48 #include "History.h"
49 #include "ShellCommand.h"
51 using namespace Konsole;
53 #if 0
55 bool Profile::isAvailable() const
57 //TODO: Is it necessary to cache the result of the search?
59 QString binary = KRun::binaryName( command(true) , false );
60 binary = KShell::tildeExpand(binary);
62 QString fullBinaryPath = KGlobal::dirs()->findExe(binary);
64 if ( fullBinaryPath.isEmpty() )
65 return false;
66 else
67 return true;
69 #endif
71 SessionManager::SessionManager()
72 : _loadedAllProfiles(false)
73 , _loadedFavorites(false)
75 //map finished() signals from sessions
76 _sessionMapper = new QSignalMapper(this);
77 connect( _sessionMapper , SIGNAL(mapped(QObject*)) , this ,
78 SLOT(sessionTerminated(QObject*)) );
80 //load fallback profile
81 _fallbackProfile = Profile::Ptr(new FallbackProfile);
82 addProfile(_fallbackProfile);
84 //locate and load default profile
85 KSharedConfigPtr appConfig = KGlobal::config();
86 const KConfigGroup group = appConfig->group( "Desktop Entry" );
87 QString defaultSessionFilename = group.readEntry("DefaultProfile","Shell.profile");
89 QString path = KGlobal::dirs()->findResource("data","konsole/"+defaultSessionFilename);
90 if (!path.isEmpty())
92 Profile::Ptr profile = loadProfile(path);
93 if ( profile )
94 _defaultProfile = profile;
97 Q_ASSERT( _types.count() > 0 );
98 Q_ASSERT( _defaultProfile );
100 // get shortcuts and paths of profiles associated with
101 // them - this doesn't load the shortcuts themselves,
102 // that is done on-demand.
103 loadShortcuts();
105 Profile::Ptr SessionManager::loadProfile(const QString& shortPath)
107 // the fallback profile has a 'special' path name, "FALLBACK/"
108 if (shortPath == _fallbackProfile->property<QString>(Profile::Path))
109 return _fallbackProfile;
111 QString path = shortPath;
113 // add a suggested suffix and relative prefix if missing
114 QFileInfo fileInfo(path);
115 if ( fileInfo.suffix().isEmpty() )
116 path.append(".profile");
117 if ( fileInfo.path().isEmpty() || fileInfo.path() == "." )
118 path.prepend(QString("konsole")+QDir::separator());
120 // if the file is not an absolute path, look it up
121 if ( !fileInfo.isAbsolute() )
122 path = KStandardDirs::locate("data",path);
124 // check that we have not already loaded this profile
125 QSetIterator<Profile::Ptr> iter(_types);
126 while ( iter.hasNext() )
128 Profile::Ptr profile = iter.next();
129 if ( profile->path() == path )
130 return profile;
133 // guard to prevent problems if a profile specifies itself as its parent
134 // or if there is recursion in the "inheritance" chain
135 // (eg. two profiles, A and B, specifying each other as their parents)
136 static QStack<QString> recursionGuard;
137 PopStackOnExit<QString> popGuardOnExit(recursionGuard);
139 if (recursionGuard.contains(path))
141 kWarning() << "Ignoring attempt to load profile recursively from" << path;
142 return _fallbackProfile;
144 else
145 recursionGuard.push(path);
147 // load the profile
148 ProfileReader* reader = 0;
149 if ( path.endsWith(".desktop") )
150 reader = 0; // new KDE3ProfileReader;
151 else
152 reader = new KDE4ProfileReader;
154 if (!reader)
156 kWarning() << "Could not create loader to read profile from" << path;
157 return Profile::Ptr();
160 Profile::Ptr newProfile = Profile::Ptr(new Profile(defaultProfile()));
161 newProfile->setProperty(Profile::Path,path);
163 QString parentProfilePath;
164 bool result = reader->readProfile(path,newProfile,parentProfilePath);
166 if ( !parentProfilePath.isEmpty() )
168 Profile::Ptr parentProfile = loadProfile(parentProfilePath);
169 newProfile->setParent(parentProfile);
172 delete reader;
174 if (!result)
176 kWarning() << "Could not load profile from " << path;
177 return Profile::Ptr();
179 else
181 addProfile(newProfile);
182 return newProfile;
185 QStringList SessionManager::availableProfilePaths() const
187 KDE3ProfileReader kde3Reader;
188 KDE4ProfileReader kde4Reader;
190 QStringList profiles;
191 profiles += kde3Reader.findProfiles();
192 profiles += kde4Reader.findProfiles();
194 return profiles;
197 void SessionManager::loadAllProfiles()
199 if ( _loadedAllProfiles )
200 return;
202 QStringList profiles = availableProfilePaths();
204 QListIterator<QString> iter(profiles);
205 while (iter.hasNext())
206 loadProfile(iter.next());
208 _loadedAllProfiles = true;
210 void SessionManager::saveState()
212 // save default profile
213 setDefaultProfile( _defaultProfile );
215 // save shortcuts
216 saveShortcuts();
218 // save favorites
219 saveFavorites();
221 void SessionManager::closeAll()
223 // close remaining sessions
224 foreach( Session* session , _sessions )
226 session->close();
228 _sessions.clear();
230 SessionManager::~SessionManager()
232 if (_sessions.count() > 0)
234 kWarning() << "Konsole SessionManager destroyed with sessions still alive";
235 // ensure that the Session doesn't later try to call back and do things to the
236 // SessionManager
237 foreach(Session* session , _sessions)
238 disconnect(session , 0 , this , 0);
242 const QList<Session*> SessionManager::sessions()
244 return _sessions;
247 void SessionManager::updateSession(Session* session)
249 Profile::Ptr info = _sessionProfiles[session];
251 Q_ASSERT( info );
253 applyProfile(session,info,false);
255 // FIXME - This may update a lot more than just the session
256 // of interest.
257 emit sessionUpdated(session);
260 Session* SessionManager::createSession(Profile::Ptr info)
262 Session* session = 0;
264 if (!info)
265 info = defaultProfile();
267 if (!_types.contains(info))
268 addProfile(info);
270 //configuration information found, create a new session based on this
271 session = new Session();
272 applyProfile(session,info,false);
274 connect( session , SIGNAL(profileChangeCommandReceived(QString)) , this ,
275 SLOT(sessionProfileCommandReceived(QString)) );
277 //ask for notification when session dies
278 _sessionMapper->setMapping(session,session);
279 connect( session , SIGNAL(finished()) , _sessionMapper ,
280 SLOT(map()) );
282 //add session to active list
283 _sessions << session;
284 _sessionProfiles.insert(session,info);
286 Q_ASSERT( session );
288 return session;
291 void SessionManager::sessionTerminated(QObject* sessionObject)
293 Session* session = qobject_cast<Session*>(sessionObject);
295 Q_ASSERT( session );
297 _sessions.removeAll(session);
298 session->deleteLater();
301 QList<Profile::Ptr> SessionManager::loadedProfiles() const
303 return _types.toList();
306 Profile::Ptr SessionManager::defaultProfile() const
308 return _defaultProfile;
310 Profile::Ptr SessionManager::fallbackProfile() const
311 { return _fallbackProfile; }
313 QString SessionManager::saveProfile(Profile::Ptr info)
315 ProfileWriter* writer = new KDE4ProfileWriter;
317 QString newPath = writer->getPath(info);
319 writer->writeProfile(newPath,info);
321 delete writer;
323 return newPath;
326 void SessionManager::changeProfile(Profile::Ptr info ,
327 QHash<Profile::Property,QVariant> propertyMap, bool persistant)
329 Q_ASSERT(info);
331 // insert the changes into the existing Profile instance
332 QListIterator<Profile::Property> iter(propertyMap.keys());
333 while ( iter.hasNext() )
335 const Profile::Property property = iter.next();
336 info->setProperty(property,propertyMap[property]);
339 // when changing a group, iterate through the profiles
340 // in the group and call changeProfile() on each of them
342 // this is so that each profile in the group, the profile is
343 // applied, a change notification is emitted and the profile
344 // is saved to disk
345 ProfileGroup::Ptr group = info->asGroup();
346 if (group)
348 foreach(Profile::Ptr profile, group->profiles())
349 changeProfile(profile,propertyMap,persistant);
350 return;
353 // apply the changes to existing sessions
354 applyProfile(info,true);
356 // notify the world about the change
357 emit profileChanged(info);
359 // save changes to disk, unless the profile is hidden, in which case
360 // it has no file on disk
361 if ( persistant && !info->isHidden() )
363 info->setProperty(Profile::Path,saveProfile(info));
366 void SessionManager::applyProfile(Profile::Ptr info , bool modifiedPropertiesOnly)
368 QListIterator<Session*> iter(_sessions);
369 while ( iter.hasNext() )
371 Session* next = iter.next();
372 if ( _sessionProfiles[next] == info )
373 applyProfile(next,info,modifiedPropertiesOnly);
376 Profile::Ptr SessionManager::sessionProfile(Session* session) const
378 return _sessionProfiles[session];
380 void SessionManager::setSessionProfile(Session* session, Profile::Ptr profile)
382 _sessionProfiles[session] = profile;
383 updateSession(session);
385 void SessionManager::applyProfile(Session* session, const Profile::Ptr info , bool modifiedPropertiesOnly)
387 Q_ASSERT(info);
389 _sessionProfiles[session] = info;
391 ShouldApplyProperty apply(info,modifiedPropertiesOnly);
393 // Basic session settings
394 if ( apply.shouldApply(Profile::Name) )
395 session->setTitle(Session::NameRole,info->name());
397 if ( apply.shouldApply(Profile::Command) )
398 session->setProgram(info->command());
400 if ( apply.shouldApply(Profile::Arguments) )
401 session->setArguments(info->arguments());
403 if ( apply.shouldApply(Profile::Directory) )
404 session->setInitialWorkingDirectory(info->defaultWorkingDirectory());
406 if ( apply.shouldApply(Profile::Environment) )
408 // add environment variable containing home directory of current profile
409 // (if specified)
410 QStringList environment = info->property<QStringList>(Profile::Environment);
411 environment << QString("PROFILEHOME=%1").arg(info->defaultWorkingDirectory());
413 session->setEnvironment(environment);
416 if ( apply.shouldApply(Profile::Icon) )
417 session->setIconName(info->icon());
419 // Key bindings
420 if ( apply.shouldApply(Profile::KeyBindings) )
421 session->setKeyBindings(info->property<QString>(Profile::KeyBindings));
423 // Tab formats
424 if ( apply.shouldApply(Profile::LocalTabTitleFormat) )
425 session->setTabTitleFormat( Session::LocalTabTitle ,
426 info->property<QString>(Profile::LocalTabTitleFormat));
427 if ( apply.shouldApply(Profile::RemoteTabTitleFormat) )
428 session->setTabTitleFormat( Session::RemoteTabTitle ,
429 info->property<QString>(Profile::RemoteTabTitleFormat));
431 // Scrollback / history
432 if ( apply.shouldApply(Profile::HistoryMode) || apply.shouldApply(Profile::HistorySize) )
434 int mode = info->property<int>(Profile::HistoryMode);
435 switch ((Profile::HistoryModeEnum)mode)
437 case Profile::DisableHistory:
438 session->setHistoryType( HistoryTypeNone() );
439 break;
440 case Profile::FixedSizeHistory:
442 int lines = info->property<int>(Profile::HistorySize);
443 session->setHistoryType( HistoryTypeBuffer(lines) );
445 break;
446 case Profile::UnlimitedHistory:
447 session->setHistoryType( HistoryTypeFile() );
448 break;
452 // Terminal features
453 if ( apply.shouldApply(Profile::FlowControlEnabled) )
454 session->setFlowControlEnabled( info->property<bool>(Profile::FlowControlEnabled) );
456 // Encoding
457 if ( apply.shouldApply(Profile::DefaultEncoding) )
459 QByteArray name = info->property<QString>(Profile::DefaultEncoding).toUtf8();
460 session->setCodec( QTextCodec::codecForName(name) );
464 void SessionManager::addProfile(Profile::Ptr type)
466 if ( _types.isEmpty() )
467 _defaultProfile = type;
469 _types.insert(type);
471 emit profileAdded(type);
474 bool SessionManager::deleteProfile(Profile::Ptr type)
476 bool wasDefault = ( type == defaultProfile() );
478 if ( type )
480 // try to delete the config file
481 if ( type->isPropertySet(Profile::Path) && QFile::exists(type->path()) )
483 if (!QFile::remove(type->path()))
485 kWarning() << "Could not delete profile: " << type->path()
486 << "The file is most likely in a directory which is read-only.";
488 return false;
492 // remove from favorites, profile list, shortcut list etc.
493 setFavorite(type,false);
494 setShortcut(type,QKeySequence());
495 _types.remove(type);
497 // mark the profile as hidden so that it does not show up in the
498 // Manage Profiles dialog and is not saved to disk
499 type->setHidden(true);
502 // if we just deleted the default session type,
503 // replace it with a random type from the list
504 if ( wasDefault )
506 setDefaultProfile( _types.toList().first() );
509 emit profileRemoved(type);
511 return true;
513 void SessionManager::setDefaultProfile(Profile::Ptr info)
515 Q_ASSERT ( _types.contains(info) );
517 _defaultProfile = info;
519 QString path = info->path();
521 if ( path.isEmpty() )
522 path = KDE4ProfileWriter().getPath(info);
524 QFileInfo fileInfo(path);
526 KSharedConfigPtr config = KGlobal::config();
527 KConfigGroup group = config->group("Desktop Entry");
528 group.writeEntry("DefaultProfile",fileInfo.fileName());
530 QSet<Profile::Ptr> SessionManager::findFavorites()
532 if (!_loadedFavorites)
533 loadFavorites();
535 return _favorites;
537 void SessionManager::setFavorite(Profile::Ptr info , bool favorite)
539 if (!_types.contains(info))
540 addProfile(info);
542 if ( favorite && !_favorites.contains(info) )
544 _favorites.insert(info);
545 emit favoriteStatusChanged(info,favorite);
547 else if ( !favorite && _favorites.contains(info) )
549 _favorites.remove(info);
550 emit favoriteStatusChanged(info,favorite);
553 void SessionManager::loadShortcuts()
555 KSharedConfigPtr appConfig = KGlobal::config();
556 KConfigGroup shortcutGroup = appConfig->group("Profile Shortcuts");
558 QMap<QString,QString> entries = shortcutGroup.entryMap();
560 QMapIterator<QString,QString> iter(entries);
561 while ( iter.hasNext() )
563 iter.next();
565 QKeySequence shortcut = QKeySequence::fromString(iter.key());
566 QString profilePath = iter.value();
568 ShortcutData data;
569 data.profilePath = profilePath;
571 _shortcuts.insert(shortcut,data);
574 void SessionManager::saveShortcuts()
576 KSharedConfigPtr appConfig = KGlobal::config();
577 KConfigGroup shortcutGroup = appConfig->group("Profile Shortcuts");
578 shortcutGroup.deleteGroup();
580 QMapIterator<QKeySequence,ShortcutData> iter(_shortcuts);
581 while ( iter.hasNext() )
583 iter.next();
585 QString shortcutString = iter.key().toString();
587 shortcutGroup.writeEntry(shortcutString,
588 iter.value().profilePath);
591 void SessionManager::setShortcut(Profile::Ptr info ,
592 const QKeySequence& keySequence )
594 QKeySequence existingShortcut = shortcut(info);
595 _shortcuts.remove(existingShortcut);
597 if (keySequence.isEmpty())
598 return;
600 ShortcutData data;
601 data.profileKey = info;
602 data.profilePath = info->path();
603 // TODO - This won't work if the profile doesn't
604 // have a path yet
605 _shortcuts.insert(keySequence,data);
607 emit shortcutChanged(info,keySequence);
609 void SessionManager::loadFavorites()
611 KSharedConfigPtr appConfig = KGlobal::config();
612 KConfigGroup favoriteGroup = appConfig->group("Favorite Profiles");
614 QSet<QString> favoriteSet;
616 if ( favoriteGroup.hasKey("Favorites") )
618 QStringList list = favoriteGroup.readEntry("Favorites", QStringList());
619 favoriteSet = QSet<QString>::fromList(list);
621 else
623 // if there is no favorites key at all, mark the
624 // supplied 'Shell.profile' as the only favorite
625 favoriteSet << "Shell.profile";
628 // look for favorites amongst those already loaded
629 QSetIterator<Profile::Ptr> iter(_types);
630 while ( iter.hasNext() )
632 Profile::Ptr profile = iter.next();
633 const QString& path = profile->path();
634 if ( favoriteSet.contains( path ) )
636 _favorites.insert( profile );
637 favoriteSet.remove(path);
640 // load any remaining favorites
641 QSetIterator<QString> unloadedFavoriteIter(favoriteSet);
642 while ( unloadedFavoriteIter.hasNext() )
644 Profile::Ptr profile = loadProfile(unloadedFavoriteIter.next());
645 if (profile)
646 _favorites.insert(profile);
649 _loadedFavorites = true;
651 void SessionManager::saveFavorites()
653 KSharedConfigPtr appConfig = KGlobal::config();
654 KConfigGroup favoriteGroup = appConfig->group("Favorite Profiles");
656 QStringList paths;
657 QSetIterator<Profile::Ptr> keyIter(_favorites);
658 while ( keyIter.hasNext() )
660 Profile::Ptr profile = keyIter.next();
662 Q_ASSERT( _types.contains(profile) && profile );
664 paths << profile->path();
667 favoriteGroup.writeEntry("Favorites",paths);
670 QList<QKeySequence> SessionManager::shortcuts()
672 return _shortcuts.keys();
675 Profile::Ptr SessionManager::findByShortcut(const QKeySequence& shortcut)
677 Q_ASSERT( _shortcuts.contains(shortcut) );
679 if ( !_shortcuts[shortcut].profileKey )
681 Profile::Ptr key = loadProfile(_shortcuts[shortcut].profilePath);
682 if (!key)
684 _shortcuts.remove(shortcut);
685 return Profile::Ptr();
687 _shortcuts[shortcut].profileKey = key;
690 return _shortcuts[shortcut].profileKey;
693 void SessionManager::sessionProfileCommandReceived(const QString& text)
695 // FIXME: This is inefficient, it creates a new profile instance for
696 // each set of changes applied. Instead a new profile should be created
697 // only the first time changes are applied to a session
699 Session* session = qobject_cast<Session*>(sender());
700 Q_ASSERT( session );
702 ProfileCommandParser parser;
703 QHash<Profile::Property,QVariant> changes = parser.parse(text);
705 Profile::Ptr newProfile = Profile::Ptr(new Profile(_sessionProfiles[session]));
707 QHashIterator<Profile::Property,QVariant> iter(changes);
708 while ( iter.hasNext() )
710 iter.next();
711 newProfile->setProperty(iter.key(),iter.value());
714 _sessionProfiles[session] = newProfile;
715 applyProfile(newProfile,true);
716 emit sessionUpdated(session);
719 QKeySequence SessionManager::shortcut(Profile::Ptr info) const
721 QMapIterator<QKeySequence,ShortcutData> iter(_shortcuts);
722 while (iter.hasNext())
724 iter.next();
725 if ( iter.value().profileKey == info
726 || iter.value().profilePath == info->path() )
727 return iter.key();
730 return QKeySequence();
733 void SessionManager::saveSessions(KConfig* config)
735 // The session IDs can't be restored.
736 // So we need to map the old ID to the future new ID.
737 int n = 1;
738 _restoreMapping.clear();
740 foreach(Session* session, _sessions)
742 QString name = QLatin1String("Session") + QString::number(n);
743 KConfigGroup group(config, name);
745 group.writePathEntry("Profile",
746 _sessionProfiles.value(session)->path());
747 session->saveSession(group);
748 _restoreMapping.insert(session, n);
749 n++;
752 KConfigGroup group(config, "Number");
753 group.writeEntry("NumberOfSessions", _sessions.count());
756 int SessionManager::getRestoreId(Session* session)
758 return _restoreMapping.value(session);
761 void SessionManager::restoreSessions(KConfig* config)
763 KConfigGroup group(config, "Number");
764 int sessions;
766 // Any sessions saved?
767 if ((sessions = group.readEntry("NumberOfSessions", 0)) > 0)
769 for (int n = 1; n <= sessions; n++)
771 QString name = QLatin1String("Session") + QString::number(n);
772 KConfigGroup sessionGroup(config, name);
774 QString profile = sessionGroup.readPathEntry("Profile", QString());
775 Profile::Ptr ptr = defaultProfile();
776 if (!profile.isEmpty()) ptr = loadProfile(profile);
778 Session* session = createSession(ptr);
779 session->restoreSession(sessionGroup);
784 Session* SessionManager::idToSession(int id)
786 Q_ASSERT(id);
787 foreach(Session* session, _sessions)
788 if (session->sessionId() == id)
789 return session;
791 // this should not happen
792 Q_ASSERT(0);
793 return 0;
796 K_GLOBAL_STATIC( SessionManager , theSessionManager )
797 SessionManager* SessionManager::instance()
799 return theSessionManager;
802 SessionListModel::SessionListModel(QObject* parent)
803 : QAbstractListModel(parent)
807 void SessionListModel::setSessions(const QList<Session*>& sessions)
809 _sessions = sessions;
811 foreach(Session* session, sessions)
812 connect(session,SIGNAL(finished()),this,SLOT(sessionFinished()));
814 reset();
816 QVariant SessionListModel::data(const QModelIndex& index, int role) const
818 Q_ASSERT(index.isValid());
820 int row = index.row();
821 int column = index.column();
823 Q_ASSERT( row >= 0 && row < _sessions.count() );
824 Q_ASSERT( column >= 0 && column < 2 );
826 switch (role)
828 case Qt::DisplayRole:
829 if (column == 1)
830 return _sessions[row]->title(Session::DisplayedTitleRole);
831 else if (column == 0)
832 return _sessions[row]->sessionId();
833 break;
834 case Qt::DecorationRole:
835 if (column == 1)
836 return KIcon(_sessions[row]->iconName());
837 else
838 return QVariant();
841 return QVariant();
843 QVariant SessionListModel::headerData(int section, Qt::Orientation orientation,
844 int role) const
846 if (role != Qt::DisplayRole)
847 return QVariant();
849 if (orientation == Qt::Vertical)
850 return QVariant();
851 else
853 switch (section)
855 case 0:
856 return i18n("Number");
857 case 1:
858 return i18n("Title");
859 default:
860 return QVariant();
865 int SessionListModel::columnCount(const QModelIndex&) const
867 return 2;
869 int SessionListModel::rowCount(const QModelIndex&) const
871 return _sessions.count();
873 QModelIndex SessionListModel::parent(const QModelIndex&) const
875 return QModelIndex();
877 void SessionListModel::sessionFinished()
879 Session* session = qobject_cast<Session*>(sender());
880 int row = _sessions.indexOf(session);
882 if (row != -1)
884 beginRemoveRows(QModelIndex(),row,row);
885 sessionRemoved(session);
886 _sessions.removeAt(row);
887 endRemoveRows();
890 QModelIndex SessionListModel::index(int row, int column, const QModelIndex& parent) const
892 if (hasIndex(row,column,parent))
893 return createIndex(row,column,_sessions[row]);
894 else
895 return QModelIndex();
898 #include "SessionManager.moc"
901 Local Variables:
902 mode: c++
903 c-file-style: "stroustrup"
904 indent-tabs-mode: nil
905 tab-width: 4
906 End: