Disable loading previous playlist on startup for now, it causes a bunch of mess due...
[amarok.git] / src / app.cpp
blob5808093d55ed5f6e853f8b8107d9ff3440178617
1 /***************************************************************************
2 app.cpp - description
3 -------------------
4 begin : Mit Okt 23 14:35:18 CEST 2002
5 copyright : (C) 2002 by Mark Kretschmann
6 email : markey@web.de
7 ***************************************************************************/
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17 #include "app.h"
19 #include "amarok.h"
20 #include "amarokconfig.h"
21 #include "amarokdbushandler.h"
22 #include "atomicstring.h"
23 #include "collectiondb.h"
24 #include "CollectionManager.h"
25 #include "ConfigDialog.h"
26 #include "context/ContextView.h"
27 #include "CoverFetcher.h"
28 #include "debug.h"
29 #include "enginebase.h"
30 #include "enginecontroller.h"
31 #include "equalizersetup.h"
32 #include "MainWindow.h"
33 #include "mediabrowser.h"
34 #include "Meta.h"
35 #include "metabundle.h"
36 #include "mountpointmanager.h"
37 #include "osd.h"
38 #include "playlist/PlaylistModel.h"
40 #include "pluginmanager.h"
41 #include "refreshimages.h"
42 #include "scriptmanager.h"
43 #include "scrobbler.h"
44 #include "ContextStatusBar.h"
45 #include "systray.h"
46 #include "threadmanager.h"
47 #include "tracktooltip.h" //engineNewMetaData()
48 #include "TheInstances.h"
49 #include "metadata/tplugins.h"
51 #include <iostream>
53 #include <KAboutData>
54 #include <KAction>
55 #include <KCmdLineArgs> //initCliArgs()
56 #include <KConfigDialogManager>
57 #include <KCursor> //Amarok::OverrideCursor
58 #include <KEditToolBar> //slotConfigToolbars()
59 #include <KGlobalAccel> //initGlobalShortcuts()
60 #include <KGlobalSettings> //applyColorScheme()
61 #include <KIO/CopyJob>
62 #include <KIconLoader> //amarok Icon
63 #include <KJob>
64 #include <KJobUiDelegate>
65 #include <KLocale>
66 #include <KRun> //Amarok::invokeBrowser()
67 #include <kshell.h>
68 #include <KShortcutsDialog> //slotConfigShortcuts()
69 #include <KSplashScreen>
70 #include <KStandardDirs>
72 #include <QDBusInterface>
73 #include <QDBusReply>
74 #include <QEventLoop> //applySettings()
75 #include <QFile>
76 #include <QPixmapCache>
77 #include <QTimer> //showHyperThreadingWarning()
78 #include <QToolTip> //default tooltip for trayicon
79 #include <QtDBus/QtDBus>
81 QMutex Debug::mutex;
82 QMutex Amarok::globalDirsMutex;
84 int App::mainThreadId = 0;
86 #ifdef Q_WS_MAC
87 #include <CoreFoundation/CoreFoundation.h>
88 extern void setupEventHandler_mac(long);
89 #endif
91 AMAROK_EXPORT KAboutData aboutData( "amarok", 0,
92 ki18n( "Amarok" ), APP_VERSION,
93 ki18n( "The audio player for KDE" ), KAboutData::License_GPL,
94 ki18n( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2007, The Amarok Development Squad" ),
95 ki18n( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es\n\nFeedback:\namarok@kde.org\n\n(Build Date: " __DATE__ ")" ),
96 ( "http://amarok.kde.org" ) );
99 App::App()
100 : KUniqueApplication()
101 , m_splash( 0 )
103 DEBUG_BLOCK
105 if( AmarokConfig::showSplashscreen() )
107 QPixmap splashpix( KStandardDirs().findResource("data", "amarok/images/splash_screen.jpg") );
108 m_splash = new KSplashScreen( splashpix, Qt::WindowStaysOnTopHint );
109 m_splash->show();
112 registerTaglibPlugins();
114 qRegisterMetaType<MetaBundle>();
116 qRegisterMetaType<Meta::DataPtr>();
117 qRegisterMetaType<Meta::DataList>();
118 qRegisterMetaType<Meta::TrackPtr>();
119 qRegisterMetaType<Meta::TrackList>();
120 qRegisterMetaType<Meta::AlbumPtr>();
121 qRegisterMetaType<Meta::AlbumList>();
122 qRegisterMetaType<Meta::ArtistPtr>();
123 qRegisterMetaType<Meta::ArtistList>();
124 qRegisterMetaType<Meta::GenrePtr>();
125 qRegisterMetaType<Meta::GenreList>();
126 qRegisterMetaType<Meta::ComposerPtr>();
127 qRegisterMetaType<Meta::ComposerList>();
128 qRegisterMetaType<Meta::YearPtr>();
129 qRegisterMetaType<Meta::YearList>();
132 //make sure we have enough cache space for all our crazy svg stuff
133 QPixmapCache::setCacheLimit ( 20 * 1024 );
135 #ifdef Q_WS_MAC
136 // this is inspired by OpenSceneGraph: osgDB/FilePath.cpp
138 // Start with the the Bundle PlugIns directory.
140 // Get the main bundle first. No need to retain or release it since
141 // we are not keeping a reference
142 CFBundleRef myBundle = CFBundleGetMainBundle();
143 if( myBundle )
145 // CFBundleGetMainBundle will return a bundle ref even if
146 // the application isn't part of a bundle, so we need to
147 // check
148 // if the path to the bundle ends in ".app" to see if it is
149 // a
150 // proper application bundle. If it is, the plugins path is
151 // added
152 CFURLRef urlRef = CFBundleCopyBundleURL(myBundle);
153 if(urlRef)
155 char bundlePath[1024];
156 if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) )
158 QByteArray bp( bundlePath );
159 size_t len = bp.length();
160 if( len > 4 && bp.right( 4 ) == ".app" )
162 bp.append( "/Contents/MacOS" );
163 QByteArray path = getenv( "PATH" );
164 if( path.length() > 0 )
166 path.prepend( ":" );
168 path.prepend( bp );
169 debug() << "setting PATH=" << path;
170 setenv("PATH", path, 1);
173 // docs say we are responsible for releasing CFURLRef
174 CFRelease(urlRef);
177 #endif
179 //needs to be created before the wizard
180 new Amarok::DbusPlayerHandler(); // Must be created first
181 new Amarok::DbusPlaylistHandler();
182 new Amarok::DbusPlaylistBrowserHandler();
183 new Amarok::DbusContextHandler();
184 new Amarok::DbusCollectionHandler();
185 // new Amarok::DbusMediaBrowserHandler();
186 new Amarok::DbusScriptHandler();
188 // tell AtomicString that this is the GUI thread
189 if ( !AtomicString::isMainThread() )
190 qWarning("AtomicString was initialized from a thread other than the GUI "
191 "thread. This could lead to memory leaks.");
193 #ifdef Q_WS_MAC
194 setupEventHandler_mac((long)this);
195 #endif
196 QDBusConnection::sessionBus().registerService("org.kde.amarok");
197 QTimer::singleShot( 0, this, SLOT( continueInit() ) );
200 App::~App()
202 DEBUG_BLOCK
204 delete m_splash;
205 m_splash = 0;
207 // Hiding the OSD before exit prevents crash
208 Amarok::OSD::instance()->hide();
210 EngineBase* const engine = EngineController::engine();
212 if ( AmarokConfig::resumePlayback() ) {
213 if ( engine->state() != Engine::Empty ) {
214 AmarokConfig::setResumeTrack( EngineController::instance()->playingURL().prettyUrl() );
215 AmarokConfig::setResumeTime( engine->position() );
217 else AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time!
220 EngineController::instance()->endSession(); //records final statistics
221 EngineController::instance()->detach( this );
223 // do even if trayicon is not shown, it is safe
224 Amarok::config().writeEntry( "HiddenOnExit", mainWindow()->isHidden() );
226 CollectionDB::instance()->stopScan();
228 ThreadManager::deleteInstance(); //waits for jobs to finish
230 // this must be deleted before the connection to the Xserver is
231 // severed, or we risk a crash when the QApplication is exited,
232 // I asked Trolltech! *smug*
233 delete Amarok::OSD::instance();
235 AmarokConfig::setVersion( APP_VERSION );
236 AmarokConfig::self()->writeConfig();
238 mainWindow()->deleteBrowsers();
239 delete mainWindow();
241 #ifdef Q_WS_WIN
242 // work around for KUniqueApplication being not completely implemented on windows
243 QDBusConnectionInterface* dbusService;
244 if (QDBusConnection::sessionBus().isConnected() && (dbusService = QDBusConnection::sessionBus().interface()))
245 dbusService->unregisterService("org.kde.amarok");
246 #endif
250 #include <QStringList>
252 namespace
254 // grabbed from KsCD source, kompatctdisk.cpp
255 QString urlToDevice(const QString& device)
257 KUrl deviceUrl(device);
258 if (deviceUrl.protocol() == "media" || deviceUrl.protocol() == "system")
260 debug() << "WARNING: urlToDevice needs to be reimplemented with KDE4 technology, it's just a stub at the moment";
261 QDBusInterface mediamanager( "org.kde.kded", "/modules/mediamanager", "org.kde.MediaManager" );
262 QDBusReply<QStringList> reply = mediamanager.call( "properties",deviceUrl.fileName() );
263 if (!reply.isValid()) {
264 debug() << "Invalid reply from mediamanager";
265 return QString();
267 QStringList properties = reply;
268 if( properties.count()< 6 )
269 return QString();
270 debug() << "Reply from mediamanager " << properties[5];
271 return properties[5];
274 return device;
280 void App::handleCliArgs() //static
282 KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
284 if ( args->isSet( "cwd" ) )
286 KCmdLineArgs::setCwd( args->getOption( "cwd" ).toLocal8Bit() );
289 bool haveArgs = false;
290 if ( args->count() > 0 )
292 haveArgs = true;
294 KUrl::List list;
295 for( int i = 0; i < args->count(); i++ )
297 KUrl url = args->url( i );
298 //TODO:PORTME
299 // if( url.protocol() == "itpc" || url.protocol() == "pcast" )
300 // PlaylistBrowserNS::instance()->addPodcast( url );
301 // else
302 list << url;
305 int options = Playlist::AppendAndPlay;
306 if( args->isSet( "queue" ) )
307 options = Playlist::Queue;
308 else if( args->isSet( "append" ) || args->isSet( "enqueue" ) )
309 options = Playlist::Append;
310 else if( args->isSet( "load" ) )
311 options = Playlist::Replace;
313 if( args->isSet( "play" ) )
314 options |= Playlist::DirectPlay;
316 Meta::TrackList tracks = CollectionManager::instance()->tracksForUrls( list );
317 The::playlistModel()->insertOptioned( tracks, options );
320 //we shouldn't let the user specify two of these since it is pointless!
321 //so we prioritise, pause > stop > play > next > prev
322 //thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!)
323 //then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log
324 //I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it
325 else if ( args->isSet( "pause" ) )
327 haveArgs = true;
328 EngineController::instance()->pause();
330 else if ( args->isSet( "stop" ) )
332 haveArgs = true;
333 EngineController::instance()->stop();
335 else if ( args->isSet( "play-pause" ) )
337 haveArgs = true;
338 EngineController::instance()->playPause();
340 else if ( args->isSet( "play" ) ) //will restart if we are playing
342 haveArgs = true;
343 EngineController::instance()->play();
345 else if ( args->isSet( "next" ) )
347 haveArgs = true;
348 EngineController::instance()->next();
350 else if ( args->isSet( "previous" ) )
352 haveArgs = true;
353 EngineController::instance()->previous();
355 else if (args->isSet("cdplay"))
357 haveArgs = true;
358 QString device = args->getOption("cdplay");
359 KUrl::List urls;
360 if (EngineController::engine()->getAudioCDContents(device, urls)) {
361 Meta::TrackList tracks = CollectionManager::instance()->tracksForUrls( urls );
362 The::playlistModel()->insertOptioned(
363 tracks, Playlist::Replace|Playlist::DirectPlay);
364 } else { // Default behaviour
365 debug() <<
366 "Sorry, the engine doesn't support direct play from AudioCD..."
371 if ( args->isSet( "toggle-playlist-window" ) )
373 haveArgs = true;
374 pApp->mainWindow()->showHide();
377 //FIXME Debug output always enabled for now. MUST BE REVERTED BEFORE RELEASE.
378 Amarok::config().writeEntry( "Debug Enabled", true );
379 //Amarok::config().writeEntry( "Debug Enabled", args->isSet( "debug" ) );
381 static bool firstTime = true;
382 if( !firstTime && !haveArgs )
383 pApp->mainWindow()->activate();
384 firstTime = false;
386 args->clear(); //free up memory
390 /////////////////////////////////////////////////////////////////////////////////////
391 // INIT
392 /////////////////////////////////////////////////////////////////////////////////////
394 void App::initCliArgs( int argc, char *argv[] ) //static
396 KCmdLineArgs::reset();
397 KCmdLineArgs::init( argc, argv, &::aboutData ); //calls KCmdLineArgs::addStdCmdLineOptions()
399 KCmdLineOptions options;
400 options.add("+[URL(s)]", ki18n( "Files/URLs to open" ));
401 options.add("r");
402 options.add("previous", ki18n( "Skip backwards in playlist" ));
403 options.add("p");
404 options.add("play", ki18n( "Start playing current playlist" ));
405 options.add("t");
406 options.add("play-pause", ki18n( "Play if stopped, pause if playing" ));
407 options.add("pause", ki18n( "Pause playback" ));
408 options.add("s");
409 options.add("stop", ki18n( "Stop playback" ));
410 options.add("f");
411 options.add("next", ki18n( "Skip forwards in playlist" ));
412 options.add(":", ki18n("Additional options:"));
413 options.add("a");
414 options.add("append", ki18n( "Append files/URLs to playlist" ));
415 options.add("e");
416 options.add("enqueue", ki18n("See append, available for backwards compatability"));
417 options.add("queue", ki18n("Queue URLs after the currently playing track"));
418 options.add("l");
419 options.add("load", ki18n("Load URLs, replacing current playlist"));
420 options.add("d");
421 options.add("debug", ki18n("Print verbose debugging information"));
422 options.add("m");
423 options.add("toggle-playlist-window", ki18n("Toggle the Playlist-window"));
424 options.add("wizard", ki18n( "Run first-run wizard" ));
425 options.add("engine <name>", ki18n( "Use the <name> engine" ));
426 options.add("cwd <directory>", ki18n( "Base for relative filenames/URLs" ));
427 options.add("cdplay <device>", ki18n("Play an AudioCD from <device> or system:/media/<device>"));
428 KCmdLineArgs::addCmdLineOptions( options ); //add our own options
432 void App::initGlobalShortcuts()
434 EngineController* const ec = EngineController::instance();
435 KAction* action;
437 action = new KAction( i18n( "Play" ), mainWindow() );
438 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_X ) );
439 connect( action, SIGNAL( triggered() ), ec, SLOT( play() ) );
441 // m_pGlobalAccel->insert( "pause", i18n( "Pause" ), 0, 0, 0, ec, SLOT( pause() ), true, true );
442 action = new KAction( i18n( "Pause" ), mainWindow() );
443 connect( action, SIGNAL( triggered() ), ec, SLOT( pause() ) );
445 action = new KAction( i18n( "Play/Pause" ), mainWindow() );
446 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_C ) );
447 connect( action, SIGNAL( triggered() ), ec, SLOT( playPause() ) );
449 action = new KAction( i18n( "Stop" ), mainWindow() );
450 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_V ) );
451 connect( action, SIGNAL( triggered() ), ec, SLOT( stop() ) );
453 // m_pGlobalAccel->insert( "stop_after_global", i18n( "Stop Playing After Current Track" ), 0, KKey("WIN+CTRL+v"), 0, Playlist::instance()->qscrollview(), SLOT( toggleStopAfterCurrentTrack() ), true, true );
454 // action = new KAction( i18n( "Stop Playing After Current Track" ), mainWindow() );
455 // action->setGlobalShortcut( KShortcut( Qt::META + Qt::CTRL + Qt::Key_V ) );
456 //Port 2.0
457 // connect( action, SIGNAL( triggered() ), Playlist::instance()->qscrollview(), SLOT( toggleStopAfterCurrentTrack() ) );
459 action = new KAction( i18n( "Next Track" ), mainWindow() );
460 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_B ) );
461 connect( action, SIGNAL( triggered() ), The::playlistModel(), SLOT( next() ) );
463 action = new KAction( i18n( "Previous Track" ), mainWindow() );
464 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Z ) );
465 connect( action, SIGNAL( triggered() ), The::playlistModel(), SLOT( previous() ) );
467 action = new KAction( i18n( "Increase Volume" ), mainWindow() );
468 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Plus ) );
469 connect( action, SIGNAL( triggered() ), ec, SLOT( increaseVolume() ) );
471 action = new KAction( i18n( "Decrease Volume" ), mainWindow() );
472 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Minus ) );
473 connect( action, SIGNAL( triggered() ), ec, SLOT( decreaseVolume() ) );
476 action = new KAction( i18n( "Seek Forward" ), mainWindow() );
477 action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Plus ) );
478 connect( action, SIGNAL( triggered() ), ec, SLOT( seekForward() ) );
481 action = new KAction( i18n( "Seek Backward" ), mainWindow() );
482 action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Minus ) );
483 connect( action, SIGNAL( triggered() ), ec, SLOT( seekBackward() ) );
485 action = new KAction( i18n( "Add Media..." ), mainWindow() );
486 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_A ) );
487 connect( action, SIGNAL( triggered() ), mainWindow(), SLOT( slotAddLocation() ) );
489 action = new KAction( i18n( "Toggle Playlist Window" ), mainWindow() );
490 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_P ) );
491 connect( action, SIGNAL( triggered() ), mainWindow(), SLOT( showHide() ) );
494 action = new KAction( i18n( "Show OSD" ), mainWindow() );
495 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_O ) );
496 connect( action, SIGNAL( triggered() ), Amarok::OSD::instance(), SLOT( forceToggleOSD() ) );
498 action = new KAction( i18n( "Mute Volume" ), mainWindow() );
499 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_M ) );
500 connect( action, SIGNAL( triggered() ), ec, SLOT( mute() ) );
502 action = new KAction( i18n( "Rate Current Track: 1" ), mainWindow() );
503 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_1 ) );
504 connect( action, SIGNAL( triggered() ), SLOT( setRating1() ) );
506 action = new KAction( i18n( "Rate Current Track: 2" ), mainWindow() );
507 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_2 ) );
508 connect( action, SIGNAL( triggered() ), SLOT( setRating2() ) );
510 action = new KAction( i18n( "Rate Current Track: 3" ), mainWindow() );
511 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_3 ) );
512 connect( action, SIGNAL( triggered() ), SLOT( setRating3() ) );
514 action = new KAction( i18n( "Rate Current Track: 4" ), mainWindow() );
515 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_4 ) );
516 connect( action, SIGNAL( triggered() ), SLOT( setRating4() ) );
517 action = new KAction( i18n( "Rate Current Track: 5" ), mainWindow() );
518 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_5 ) );
519 connect( action, SIGNAL( triggered() ), SLOT( setRating5() ) );
522 // KGlobalAccel::self()->setConfigGroup( "Shortcuts" );
523 // KGlobalAccel::self()->readSettings( KGlobal::config().data() );
528 /////////////////////////////////////////////////////////////////////////////////////
529 // METHODS
530 /////////////////////////////////////////////////////////////////////////////////////
532 #include <id3v1tag.h>
533 #include <tbytevector.h>
534 #include <QTextCodec>
535 #include <KGlobal>
537 //this class is only used in this module, so I figured I may as well define it
538 //here and save creating another header/source file combination
540 class ID3v1StringHandler : public TagLib::ID3v1::StringHandler
542 QTextCodec *m_codec;
544 virtual TagLib::String parse( const TagLib::ByteVector &data ) const
546 return QStringToTString( m_codec->toUnicode( data.data(), data.size() ) );
549 virtual TagLib::ByteVector render( const TagLib::String &ts ) const
551 const QByteArray qcs = m_codec->fromUnicode( TStringToQString(ts) );
552 return TagLib::ByteVector( qcs, (uint) qcs.length() );
555 public:
556 ID3v1StringHandler( int codecIndex )
557 : m_codec( QTextCodec::codecForName( QTextCodec::availableCodecs().at( codecIndex ) ) )
559 debug() << "codec: " << m_codec;
560 debug() << "codec-name: " << m_codec->name();
563 ID3v1StringHandler( QTextCodec *codec )
564 : m_codec( codec )
566 debug() << "codec: " << m_codec;
567 debug() << "codec-name: " << m_codec->name();
570 virtual ~ID3v1StringHandler()
574 //SLOT
575 void App::applySettings( bool firstTime )
577 ///Called when the configDialog is closed with OK or Apply
579 DEBUG_BLOCK
581 #ifndef Q_WS_MAC
582 //probably needs to be done in TrayIcon when it receives a QEvent::ToolTip (see QSystemtrayIcon documentation)
583 //TrackToolTip::instance()->removeFromWidget( m_tray );
584 #endif
585 Scrobbler::instance()->applySettings();
586 Amarok::OSD::instance()->applySettings();
587 CollectionDB::instance()->applySettings();
588 #ifndef Q_WS_MAC
589 m_tray->setVisible( AmarokConfig::showTrayIcon() );
590 //TrackToolTip::instance()->addToWidget( m_tray );
591 #endif
594 //on startup we need to show the window, but only if it wasn't hidden on exit
595 //and always if the trayicon isn't showing
596 QWidget* main_window = mainWindow();
597 #ifndef Q_WS_MAC
598 if( ( main_window && firstTime && !Amarok::config().readEntry( "HiddenOnExit", false ) ) || ( main_window && !AmarokConfig::showTrayIcon() ) )
599 #endif
601 main_window->show();
603 //takes longer but feels shorter. Crazy eh? :)
604 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
608 { //<Engine>
609 EngineBase *engine = EngineController::engine();
611 if( firstTime || AmarokConfig::soundSystem() !=
612 PluginManager::getService( engine )->property( "X-KDE-Amarok-name" ).toString() )
614 //will unload engine for us first if necessary
615 engine = EngineController::instance()->loadEngine();
618 engine->setXfadeLength( AmarokConfig::crossfade() ? AmarokConfig::crossfadeLength() : 0 );
619 engine->setVolume( AmarokConfig::masterVolume() );
621 engine->setEqualizerEnabled( AmarokConfig::equalizerEnabled() );
622 if ( AmarokConfig::equalizerEnabled() )
623 engine->setEqualizerParameters( AmarokConfig::equalizerPreamp(), AmarokConfig::equalizerGains() );
625 Amarok::actionCollection()->action("play_audiocd")->setEnabled( EngineController::hasEngineProperty( "HasKIO" ) || EngineController::hasEngineProperty("HasCDDA"));
626 } //</Engine>
628 { //<Collection>
629 //PORT 2.0 CollectionView::instance()->renderView(true);
630 } //</Collection>
631 { //<Context>
632 //PORT 2.0 ContextBrowser::instance()->renderView();
633 } //</Context>
635 { // delete unneeded cover images from cache
636 const QString size = QString::number( AmarokConfig::coverPreviewSize() ) + '@';
637 const QDir cacheDir = Amarok::saveLocation( "albumcovers/cache/" );
638 const QStringList obsoleteCovers = cacheDir.entryList( QStringList("*") );
639 foreach( const QString &it, obsoleteCovers )
640 if ( !it.startsWith( size ) && !it.startsWith( "50@" ) )
641 QFile( cacheDir.filePath( it ) ).remove();
644 //if ( !firstTime )
645 // Bizarrely and ironically calling this causes crashes for
646 // some people! FIXME
647 //AmarokConfig::self()->writeConfig();
651 //SLOT
652 void
653 App::continueInit()
655 DEBUG_BLOCK
656 const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
657 bool restoreSession = args->count() == 0 || args->isSet( "append" ) || args->isSet( "enqueue" )
658 || Amarok::config().readEntry( "AppendAsDefault", false );
660 // Make this instance so it can start receiving signals
661 MoodServer::instance();
663 // Remember old folder setup, so we can detect changes after the wizard was used
664 //const QStringList oldCollectionFolders = MountPointManager::instance()->collectionFolders();
667 // Is this needed in Amarok 2?
668 if( Amarok::config().readEntry( "First Run", true ) || args->isSet( "wizard" ) )
670 std::cout << "STARTUP\n" << std::flush; //hide the splashscreen
671 Amarok::config().writeEntry( "First Run", false );
672 Amarok::config().sync();
675 //CollectionDB::instance()->checkDatabase();
677 m_mainWindow = new MainWindow();
678 #ifndef Q_WS_MAC
679 m_tray = new Amarok::TrayIcon( mainWindow() );
680 #endif
681 mainWindow()->init(); //creates the playlist, browsers, etc.
682 //FIXME: we shouldn't have to do this.
683 pApp->mainWindow()->show();
684 //DON'T DELETE THIS NEXT LINE or the app crashes when you click the X (unless we reimplement closeEvent)
685 //Reason: in ~App we have to call the deleteBrowsers method or else we run afoul of refcount foobar in KHTMLPart
686 //But if you click the X (not Action->Quit) it automatically kills MainWindow because KMainWindow sets this
687 //for us as default (bad KMainWindow)
688 mainWindow()->setAttribute( Qt::WA_DeleteOnClose, false );
689 //init playlist window as soon as the database is guaranteed to be usable
690 //connect( CollectionDB::instance(), SIGNAL( databaseUpdateDone() ), mainWindow(), SLOT( init() ) );
692 // FIXME: something is broken with global shortcuts & windows,
693 // dbus call is causing a ~30 second timeout. Disable for now.
694 #ifndef Q_WS_WIN
695 initGlobalShortcuts();
696 #endif
697 //load previous playlist in separate thread
698 //FIXME: causes a lot of breakage due to the collection not being properly initialized at startup.
699 //Reenable when fixed.
700 // if ( restoreSession && AmarokConfig::savePlaylist() )
701 // {
702 // The::playlistModel()->restoreSession();
703 // }
704 if( args->isSet( "engine" ) ) {
705 // we correct some common errors (case issues, missing -engine off the end)
706 QString engine = args->getOption( "engine" ).toLower();
707 if( engine.startsWith( "gstreamer" ) ) engine = "gst-engine";
708 if( !engine.endsWith( "engine" ) ) engine += "-engine";
710 AmarokConfig::setSoundSystem( engine );
712 Debug::stamp();
713 //create engine, show TrayIcon etc.
714 applySettings( true );
715 Debug::stamp();
716 // Start ScriptManager. Must be created _after_ MainWindow.
717 ScriptManager::instance();
718 Debug::stamp();
720 //do after applySettings(), or the OSD will flicker and other wierdness!
721 //do before restoreSession()!
722 EngineController::instance()->attach( this );
724 //set a default interface
725 engineStateChanged( Engine::Empty );
727 if ( AmarokConfig::resumePlayback() && restoreSession && !args->isSet( "stop" ) ) {
728 //restore session as long as the user didn't specify media to play etc.
729 //do this after applySettings() so OSD displays correctly
730 EngineController::instance()->restoreSession();
733 // Refetch covers every 80 days to comply with Amazon license
734 new RefreshImages();
736 CollectionDB *collDB = CollectionDB::instance();
738 if ( AmarokConfig::monitorChanges() )
739 //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( scanModifiedDirs() ) );
740 CollectionManager::instance()->checkCollectionChanges();
743 handleCliArgs();
745 delete m_splash;
746 m_splash = 0;
749 void App::engineStateChanged( Engine::State state, Engine::State oldState )
751 Meta::TrackPtr track = EngineController::instance()->currentTrack();
752 //track is 0 if the engien state is Empty. we check that in the switch
753 switch( state )
755 case Engine::Empty:
756 mainWindow()->setCaption( "Amarok" );
757 TrackToolTip::instance()->clear();
758 Amarok::OSD::instance()->setImage( QImage( KIconLoader().iconPath( "amarok", -KIconLoader::SizeHuge ) ) );
759 break;
761 case Engine::Playing:
762 if ( oldState == Engine::Paused )
763 Amarok::OSD::instance()->OSDWidget::show( i18nc( "state, as in playing", "Play" ) );
764 if ( !track->prettyName().isEmpty() )
765 //TODO: write a utility function somewhere
766 mainWindow()->setCaption( i18n("Amarok - %1", /*bundle.veryNiceTitle()*/ track->prettyName() ) );
767 break;
769 case Engine::Paused:
770 Amarok::OSD::instance()->OSDWidget::show( i18n("Paused") );
771 break;
773 case Engine::Idle:
774 mainWindow()->setCaption( "Amarok" );
775 break;
777 default:
782 void App::engineNewMetaData( const MetaBundle &bundle, bool /*trackChanged*/ )
784 Amarok::OSD::instance()->show( bundle );
785 if ( !bundle.prettyTitle().isEmpty() )
786 mainWindow()->setCaption( i18n("Amarok - %1", bundle.veryNiceTitle() ) );
788 TrackToolTip::instance()->setTrack( bundle );
791 void App::engineTrackPositionChanged( long position, bool /*userSeek*/ )
793 TrackToolTip::instance()->setPos( position );
796 void App::engineVolumeChanged( int newVolume )
798 Amarok::OSD::instance()->OSDWidget::volChanged( newVolume );
801 void App::slotConfigEqualizer() //SLOT
803 EqualizerSetup::instance()->show();
804 EqualizerSetup::instance()->raise();
808 void App::slotConfigAmarok( const QByteArray& page )
810 DEBUG_THREAD_FUNC_INFO
812 Amarok2ConfigDialog* dialog = static_cast<Amarok2ConfigDialog*>( KConfigDialog::exists( "settings" ) );
814 if( !dialog )
816 //KConfigDialog didn't find an instance of this dialog, so lets create it :
817 dialog = new Amarok2ConfigDialog( mainWindow(), "settings", AmarokConfig::self() );
819 connect( dialog, SIGNAL(settingsChanged(const QString&)), SLOT(applySettings()) );
822 //FIXME it seems that if the dialog is on a different desktop it gets lost
823 // what do to? detect and move it?
825 // if ( page.isNull() )
826 // FIXME
827 // dialog->showPage( AmarokConfigDialog::s_currentPage );
828 // else
829 dialog->showPageByName( page );
831 dialog->show();
832 dialog->raise();
833 dialog->activateWindow();
836 void App::slotConfigShortcuts()
838 KShortcutsDialog::configure( Amarok::actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, mainWindow() );
841 void App::slotConfigToolBars()
843 KEditToolBar dialog( mainWindow()->actionCollection(), mainWindow() );
844 dialog.setResourceFile( mainWindow()->xmlFile() );
846 dialog.showButton( KEditToolBar::Apply, false );
848 // if( dialog.exec() )
849 // {
850 // mainWindow()->reloadXML();
851 // mainWindow()->createGUI();
852 // }
855 void App::setUseScores( bool use )
857 AmarokConfig::setUseScores( use );
858 emit useScores( use );
861 void App::setUseRatings( bool use )
863 AmarokConfig::setUseRatings( use );
864 emit useRatings( use );
867 void App::setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
869 AmarokConfig::setShowMoodbar( show );
870 AmarokConfig::setMakeMoodier( moodier );
871 AmarokConfig::setAlterMood( alter );
872 AmarokConfig::setMoodsWithMusic( withMusic );
873 emit moodbarPrefs( show, moodier, alter, withMusic );
876 KIO::Job *App::trashFiles( const KUrl::List &files )
878 KIO::Job *job = KIO::trash( files );
879 Amarok::ContextStatusBar::instance()->newProgressOperation( job ).setDescription( i18n("Moving files to trash") );
880 connect( job, SIGNAL( result( KJob* ) ), this, SLOT( slotTrashResult( KJob* ) ) );
881 return job;
884 void App::setRating( int n )
886 if( !AmarokConfig::useRatings() ) return;
888 n *= 2;
890 const Engine::State s = EngineController::instance()->engine()->state();
891 if( s == Engine::Playing || s == Engine::Paused || s == Engine::Idle )
893 Meta::TrackPtr track = EngineController::instance()->currentTrack();
894 track->setRating( n );
895 Amarok::OSD::instance()->OSDWidget::ratingChanged( track->rating() );
897 //PORT 2.0
898 // else if( MainWindow::self()->isReallyShown() && Playlist::instance()->qscrollview()->hasFocus() )
899 // Playlist::instance()->setSelectedRatings( n );
902 void App::slotTrashResult( KJob *job )
904 if( job->error() )
905 job->uiDelegate()->showErrorMessage();
908 void App::quit()
910 emit prepareToQuit();
911 if( MediaBrowser::instance() && MediaBrowser::instance()->blockQuit() )
913 // don't quit yet, as some media devices still have to finish transferring data
914 QTimer::singleShot( 100, this, SLOT( quit() ) );
915 return;
917 KApplication::quit();
920 namespace Amarok
922 /// @see amarok.h
924 QWidget *mainWindow()
926 return pApp->mainWindow();
929 KActionCollection *actionCollection()
931 return pApp->mainWindow()->actionCollection();
934 KConfigGroup config( const QString &group )
936 //Slightly more useful config() that allows setting the group simultaneously
937 return KGlobal::config()->group( group );
940 bool invokeBrowser( const QString& url )
942 //URL can be in whatever forms KUrl understands - ie most.
943 const QString cmd = KShell::quoteArg(AmarokConfig::externalBrowser())
944 + ' ' + KShell::quoteArg(KUrl( url ).url());
945 return KRun::runCommand( cmd, 0L ) > 0;
948 namespace ColorScheme
950 QColor Base;
951 QColor Text;
952 QColor Background;
953 QColor Foreground;
954 QColor AltBase;
957 OverrideCursor::OverrideCursor( Qt::CursorShape cursor )
959 QApplication::setOverrideCursor( cursor == Qt::WaitCursor ?
960 Qt::WaitCursor :
961 Qt::BusyCursor );
964 OverrideCursor::~OverrideCursor()
966 QApplication::restoreOverrideCursor();
969 QString saveLocation( const QString &directory )
971 globalDirsMutex.lock();
972 QString result = KGlobal::dirs()->saveLocation( "data", QString("amarok/") + directory, true );
973 globalDirsMutex.unlock();
974 return result;
977 QString cleanPath( const QString &path )
979 QString result = path;
980 // german umlauts
981 result.replace( QChar(0x00e4), "ae" ).replace( QChar(0x00c4), "Ae" );
982 result.replace( QChar(0x00f6), "oe" ).replace( QChar(0x00d6), "Oe" );
983 result.replace( QChar(0x00fc), "ue" ).replace( QChar(0x00dc), "Ue" );
984 result.replace( QChar(0x00df), "ss" );
986 // some strange accents
987 result.replace( QChar(0x00e7), "c" ).replace( QChar(0x00c7), "C" );
988 result.replace( QChar(0x00fd), "y" ).replace( QChar(0x00dd), "Y" );
989 result.replace( QChar(0x00f1), "n" ).replace( QChar(0x00d1), "N" );
991 // czech letters with carons
992 result.replace( QChar(0x0161), "s" ).replace( QChar(0x0160), "S" );
993 result.replace( QChar(0x010d), "c" ).replace( QChar(0x010c), "C" );
994 result.replace( QChar(0x0159), "r" ).replace( QChar(0x0158), "R" );
995 result.replace( QChar(0x017e), "z" ).replace( QChar(0x017d), "Z" );
996 result.replace( QChar(0x0165), "t" ).replace( QChar(0x0164), "T" );
997 result.replace( QChar(0x0148), "n" ).replace( QChar(0x0147), "N" );
998 result.replace( QChar(0x010f), "d" ).replace( QChar(0x010e), "D" );
1000 // accented vowels
1001 QChar a[] = { 'a', 0xe0,0xe1,0xe2,0xe3,0xe5, 0 };
1002 QChar A[] = { 'A', 0xc0,0xc1,0xc2,0xc3,0xc5, 0 };
1003 QChar E[] = { 'e', 0xe8,0xe9,0xea,0xeb,0x11a, 0 };
1004 QChar e[] = { 'E', 0xc8,0xc9,0xca,0xcb,0x11b, 0 };
1005 QChar i[] = { 'i', 0xec,0xed,0xee,0xef, 0 };
1006 QChar I[] = { 'I', 0xcc,0xcd,0xce,0xcf, 0 };
1007 QChar o[] = { 'o', 0xf2,0xf3,0xf4,0xf5,0xf8, 0 };
1008 QChar O[] = { 'O', 0xd2,0xd3,0xd4,0xd5,0xd8, 0 };
1009 QChar u[] = { 'u', 0xf9,0xfa,0xfb,0x16e, 0 };
1010 QChar U[] = { 'U', 0xd9,0xda,0xdb,0x16f, 0 };
1011 QChar nul[] = { 0 };
1012 QChar *replacements[] = { a, A, e, E, i, I, o, O, u, U, nul };
1014 for( int i = 0; i < result.length(); i++ )
1016 QChar c = result[ i ];
1017 for( uint n = 0; replacements[n][0] != QChar(0); n++ )
1019 for( uint k=0; replacements[n][k] != QChar(0); k++ )
1021 if( replacements[n][k] == c )
1023 c = replacements[n][0];
1027 result[ i ] = c;
1029 return result;
1032 QString asciiPath( const QString &path )
1034 QString result = path;
1035 for( int i = 0; i < result.length(); i++ )
1037 QChar c = result[ i ];
1038 if( c > QChar(0x7f) || c == QChar(0) )
1040 c = '_';
1042 result[ i ] = c;
1044 return result;
1047 QString vfatPath( const QString &path )
1049 QString s = path;
1051 for( int i = 0; i < s.length(); i++ )
1053 QChar c = s[ i ];
1054 if( c < QChar(0x20)
1055 || c=='*' || c=='?' || c=='<' || c=='>'
1056 || c=='|' || c=='"' || c==':' || c=='/'
1057 || c=='\\' )
1058 c = '_';
1059 s[ i ] = c;
1062 uint len = s.length();
1063 if( len == 3 || (len > 3 && s[3] == '.') )
1065 QString l = s.left(3).toLower();
1066 if( l=="aux" || l=="con" || l=="nul" || l=="prn" )
1067 s = '_' + s;
1069 else if( len == 4 || (len > 4 && s[4] == '.') )
1071 QString l = s.left(3).toLower();
1072 QString d = s.mid(3,1);
1073 if( (l=="com" || l=="lpt") &&
1074 (d=="0" || d=="1" || d=="2" || d=="3" || d=="4" ||
1075 d=="5" || d=="6" || d=="7" || d=="8" || d=="9") )
1076 s = '_' + s;
1079 while( s.startsWith( '.' ) )
1080 s = s.mid(1);
1082 while( s.endsWith( '.' ) )
1083 s = s.left( s.length()-1 );
1085 s = s.left(255);
1086 len = s.length();
1087 if( s[len-1] == ' ' )
1088 s[len-1] = '_';
1090 return s;
1093 QString decapitateString( const QString &input, const QString &ref )
1095 QString t = ref.toUpper();
1096 int length = t.length();
1097 int commonLength = 0;
1098 while( length > 0 )
1100 if ( input.toUpper().startsWith( t ) )
1102 commonLength = t.length();
1103 t = ref.toUpper().left( t.length() + length/2 );
1104 length = length/2;
1106 else
1108 t = ref.toUpper().left( t.length() - length/2 );
1109 length = length/2;
1112 QString clean = input;
1113 if( t.endsWith( ' ' ) || !ref.at( t.length() ).isLetterOrNumber() ) // common part ends with a space or complete word
1114 clean = input.right( input.length() - commonLength ).trimmed();
1115 return clean;
1118 void setUseScores( bool use ) { App::instance()->setUseScores( use ); }
1119 void setUseRatings( bool use ) { App::instance()->setUseRatings( use ); }
1120 void setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
1121 { App::instance()->setMoodbarPrefs( show, moodier, alter, withMusic ); }
1122 KIO::Job *trashFiles( const KUrl::List &files ) { return App::instance()->trashFiles( files ); }
1125 #include "app.moc"