make keyboard seeking work in 5 second increments as discussed on irc
[amarok.git] / src / app.cpp
blob08fb5fede75cb4061ddc2a6f704bbfd341e3a3a2
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 ***************************************************************************/
18 #include "amarok.h"
19 #include "amarokconfig.h"
20 #include "amarokdbushandler.h"
21 #include "app.h"
22 #include "atomicstring.h"
23 #include "configdialog.h"
24 #include "contextbrowser.h"
25 #include "collectionbrowser.h"
26 //#include "dbsetup.h" //firstRunWizard()
27 #include "debug.h"
28 #include "devicemanager.h"
29 #include "mediadevicemanager.h"
30 #include "enginebase.h"
31 #include "enginecontroller.h"
32 #include "equalizersetup.h"
33 //#include "firstrunwizard.h"
34 #include "mediabrowser.h"
35 #include "metabundle.h"
36 #include "mountpointmanager.h"
37 #include "osd.h"
38 #include "playlist.h"
39 #include "playlistbrowser.h"
40 #include "playlistwindow.h"
41 #include "pluginmanager.h"
42 #include "refreshimages.h"
43 #include "scriptmanager.h"
44 #include "scrobbler.h"
45 #include "sidebar.h"
46 #include "statusbar.h"
47 #include "systray.h"
48 #include "threadmanager.h"
49 #include "tracktooltip.h" //engineNewMetaData()
51 #include <iostream>
53 #include <kaboutdata.h>
54 #include <kaction.h>
55 #include <kcmdlineargs.h> //initCliArgs()
56 #include <kcombobox.h> //firstRunWizard()
57 #include <kconfigdialogmanager.h>
58 #include <kcursor.h> //Amarok::OverrideCursor
59 #include <kedittoolbar.h> //slotConfigToolbars()
60 #include <kglobalaccel.h> //initGlobalShortcuts()
61 #include <kglobalsettings.h> //applyColorScheme()
62 #include <kiconloader.h> //amarok Icon
63 #include <kio/copyjob.h>
64 #include <kjob.h>
65 #include <kjobuidelegate.h>
66 #include <kkeydialog.h> //slotConfigShortcuts()
67 #include <klocale.h>
68 #include <kmessagebox.h> //applySettings(), genericEventHandler()
69 #include <krun.h> //Amarok::invokeBrowser()
70 #include <kstandarddirs.h>
72 #include <QCloseEvent>
73 #include <QDBusInterface>
74 #include <QDBusReply>
75 #include <QEvent> //genericEventHandler()
76 #include <QEventLoop> //applySettings()
77 #include <QFile>
78 #include <Q3PopupMenu> //genericEventHandler
79 #include <QTimer> //showHyperThreadingWarning()
80 #include <QToolTip> //default tooltip for trayicon
83 QMutex Debug::mutex;
84 QMutex Amarok::globalDirsMutex;
86 int App::mainThreadId = 0;
88 #ifdef Q_WS_MAC
89 #include <Carbon/Carbon.h>
91 static AEEventHandlerUPP appleEventProcessorUPP = 0;
93 OSStatus
94 appleEventProcessor(const AppleEvent *ae, AppleEvent *, long /*handlerRefCon*/)
96 OSType aeID = typeWildCard;
97 OSType aeClass = typeWildCard;
98 AEGetAttributePtr(ae, keyEventClassAttr, typeType, 0, &aeClass, sizeof(aeClass), 0);
99 AEGetAttributePtr(ae, keyEventIDAttr, typeType, 0, &aeID, sizeof(aeID), 0);
101 if(aeClass == kCoreEventClass)
103 if(aeID == kAEReopenApplication)
105 if( PlaylistWindow::self() )
106 PlaylistWindow::self()->show();
108 return noErr;
110 return eventNotHandledErr;
112 #endif
114 AMAROK_EXPORT KAboutData aboutData( "amarok",
115 I18N_NOOP( "Amarok" ), APP_VERSION,
116 I18N_NOOP( "The audio player for KDE" ), KAboutData::License_GPL,
117 I18N_NOOP( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2007, The Amarok Development Squad" ),
118 I18N_NOOP( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es\n\nFeedback:\namarok@kde.org\n\n(Build Date: " __DATE__ ")" ),
119 ( "http://amarok.kde.org" ) );
121 App::App()
122 : KApplication()
124 DEBUG_BLOCK
126 qRegisterMetaType<MetaBundle>();
128 #ifdef Q_WS_MAC
129 // this is inspired by OpenSceneGraph: osgDB/FilePath.cpp
131 // Start with the the Bundle PlugIns directory.
133 // Get the main bundle first. No need to retain or release it since
134 // we are not keeping a reference
135 CFBundleRef myBundle = CFBundleGetMainBundle();
136 if( myBundle )
138 // CFBundleGetMainBundle will return a bundle ref even if
139 // the application isn't part of a bundle, so we need to
140 // check
141 // if the path to the bundle ends in ".app" to see if it is
142 // a
143 // proper application bundle. If it is, the plugins path is
144 // added
145 CFURLRef urlRef = CFBundleCopyBundleURL(myBundle);
146 if(urlRef)
148 char bundlePath[1024];
149 if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) )
151 QByteArray bp( bundlePath );
152 size_t len = bp.length();
153 if( len > 4 && bp.right( 4 ) == ".app" )
155 bp.append( "/Contents/MacOS" );
156 QByteArray path = getenv( "PATH" );
157 if( path.length() > 0 )
159 path.prepend( ":" );
161 path.prepend( bp );
162 debug() << "setting PATH=" << path << endl;
163 setenv("PATH", path, 1);
166 // docs say we are responsible for releasing CFURLRef
167 CFRelease(urlRef);
170 #endif
172 //needs to be created before the wizard
173 new Amarok::DbusPlayerHandler(); // Must be created first
174 new Amarok::DbusPlaylistHandler();
175 new Amarok::DbusPlaylistBrowserHandler();
176 new Amarok::DbusContextBrowserHandler();
177 new Amarok::DbusCollectionHandler();
178 new Amarok::DbusMediaBrowserHandler();
179 new Amarok::DbusScriptHandler();
180 new Amarok::DbusDevicesHandler();
182 // tell AtomicString that this is the GUI thread
183 if ( !AtomicString::isMainThread() )
184 qWarning("AtomicString was initialized from a thread other than the GUI "
185 "thread. This could lead to memory leaks.");
187 #ifdef Q_WS_MAC
188 appleEventProcessorUPP = AEEventHandlerUPP(appleEventProcessor);
189 AEInstallEventHandler(kCoreEventClass, kAEReopenApplication, appleEventProcessorUPP, (long)this, true);
190 #endif
191 QDBusConnection::sessionBus().registerService("org.kde.amarok");
192 QTimer::singleShot( 0, this, SLOT( continueInit() ) );
195 App::~App()
197 DEBUG_BLOCK
199 // Hiding the OSD before exit prevents crash
200 Amarok::OSD::instance()->hide();
202 EngineBase* const engine = EngineController::engine();
204 if ( AmarokConfig::resumePlayback() ) {
205 if ( engine->state() != Engine::Empty ) {
206 AmarokConfig::setResumeTrack( EngineController::instance()->playingURL().prettyUrl() );
207 AmarokConfig::setResumeTime( engine->position() );
209 else AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time!
212 EngineController::instance()->endSession(); //records final statistics
213 EngineController::instance()->detach( this );
215 // do even if trayicon is not shown, it is safe
216 Amarok::config()->writeEntry( "HiddenOnExit", mainWindow()->isHidden() );
218 CollectionDB::instance()->stopScan();
220 ThreadManager::deleteInstance(); //waits for jobs to finish
222 // this must be deleted before the connection to the Xserver is
223 // severed, or we risk a crash when the QApplication is exited,
224 // I asked Trolltech! *smug*
225 delete Amarok::OSD::instance();
227 AmarokConfig::setVersion( APP_VERSION );
228 AmarokConfig::writeConfig();
230 //need to unload the engine before the kapplication is destroyed
231 PluginManager::unload( engine );
235 #include <QStringList>
237 namespace
239 // grabbed from KsCD source, kompatctdisk.cpp
240 QString urlToDevice(const QString& device)
242 KUrl deviceUrl(device);
243 if (deviceUrl.protocol() == "media" || deviceUrl.protocol() == "system")
245 debug() << "WARNING: urlToDevice needs to be reimplemented with KDE4 technology, it's just a stub at the moment" << endl;
246 QDBusInterface mediamanager( "org.kde.kded", "/modules/mediamanager", "org.kde.MediaManager" );
247 QDBusReply<QStringList> reply = mediamanager.call( "properties",deviceUrl.fileName() );
248 if (!reply.isValid()) {
249 debug() << "Invalid reply from mediamanager" << endl;
250 return QString();
252 QStringList properties = reply;
253 if( properties.count()< 6 )
254 return QString();
255 debug() << "Reply from mediamanager " << properties[5] << endl;
256 return properties[5];
259 return device;
265 void App::handleCliArgs() //static
267 static char cwd[PATH_MAX];
268 KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
270 if ( args->isSet( "cwd" ) )
272 strncpy(cwd, args->getOption( "cwd" ), sizeof(cwd) );
273 cwd[sizeof(cwd)-1] = '\0';
274 KCmdLineArgs::setCwd( cwd );
277 bool haveArgs = false;
278 if ( args->count() > 0 )
280 haveArgs = true;
282 KUrl::List list;
283 for( int i = 0; i < args->count(); i++ )
285 KUrl url = args->url( i );
286 if( url.protocol() == "itpc" || url.protocol() == "pcast" )
287 PlaylistBrowser::instance()->addPodcast( url );
288 else
289 list << url;
292 int options = Playlist::DefaultOptions;
293 if( args->isSet( "queue" ) )
294 options = Playlist::Queue;
295 else if( args->isSet( "append" ) || args->isSet( "enqueue" ) )
296 options = Playlist::Append;
297 else if( args->isSet( "load" ) )
298 options = Playlist::Replace;
300 if( args->isSet( "play" ) )
301 options |= Playlist::DirectPlay;
303 Playlist::instance()->insertMedia( list, options );
306 //we shouldn't let the user specify two of these since it is pointless!
307 //so we prioritise, pause > stop > play > next > prev
308 //thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!)
309 //then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log
310 //I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it
311 else if ( args->isSet( "pause" ) )
313 haveArgs = true;
314 EngineController::instance()->pause();
316 else if ( args->isSet( "stop" ) )
318 haveArgs = true;
319 EngineController::instance()->stop();
321 else if ( args->isSet( "play-pause" ) )
323 haveArgs = true;
324 EngineController::instance()->playPause();
326 else if ( args->isSet( "play" ) ) //will restart if we are playing
328 haveArgs = true;
329 EngineController::instance()->play();
331 else if ( args->isSet( "next" ) )
333 haveArgs = true;
334 EngineController::instance()->next();
336 else if ( args->isSet( "previous" ) )
338 haveArgs = true;
339 EngineController::instance()->previous();
341 else if (args->isSet("cdplay"))
343 haveArgs = true;
344 QString device = args->getOption("cdplay");
345 device = DeviceManager::instance()->convertMediaUrlToDevice( device );
346 KUrl::List urls;
347 if (EngineController::engine()->getAudioCDContents(device, urls)) {
348 Playlist::instance()->insertMedia(
349 urls, Playlist::Replace|Playlist::DirectPlay);
350 } else { // Default behaviour
351 debug() <<
352 "Sorry, the engine doesn't support direct play from AudioCD..."
353 << endl;
357 if ( args->isSet( "toggle-playlist-window" ) )
359 haveArgs = true;
360 pApp->m_pPlaylistWindow->showHide();
363 static bool firstTime = true;
364 if( !firstTime && !haveArgs )
365 pApp->m_pPlaylistWindow->activate();
366 firstTime = false;
368 args->clear(); //free up memory
372 /////////////////////////////////////////////////////////////////////////////////////
373 // INIT
374 /////////////////////////////////////////////////////////////////////////////////////
376 void App::initCliArgs( int argc, char *argv[] ) //static
378 static KCmdLineOptions options[] =
380 { "+[URL(s)]", I18N_NOOP( "Files/URLs to open" ), 0 },
381 { "r", 0, 0 },
382 { "previous", I18N_NOOP( "Skip backwards in playlist" ), 0 },
383 { "p", 0, 0 },
384 { "play", I18N_NOOP( "Start playing current playlist" ), 0 },
385 { "t", 0, 0 },
386 { "play-pause", I18N_NOOP( "Play if stopped, pause if playing" ), 0 },
387 { "pause", I18N_NOOP( "Pause playback" ), 0 },
388 { "s", 0, 0 },
389 { "stop", I18N_NOOP( "Stop playback" ), 0 },
390 { "f", 0, 0 },
391 { "next", I18N_NOOP( "Skip forwards in playlist" ), 0 },
392 { ":", I18N_NOOP("Additional options:"), 0 },
393 { "a", 0, 0 },
394 { "append", I18N_NOOP( "Append files/URLs to playlist" ), 0 },
395 { "e", 0, 0 },
396 { "enqueue", I18N_NOOP("See append, available for backwards compatability"), 0 },
397 { "queue", I18N_NOOP("Queue URLs after the currently playing track"), 0 },
398 { "l", 0, 0 },
399 { "load", I18N_NOOP("Load URLs, replacing current playlist"), 0 },
400 { "m", 0, 0 },
401 { "toggle-playlist-window", I18N_NOOP("Toggle the Playlist-window"), 0 },
402 { "wizard", I18N_NOOP( "Run first-run wizard" ), 0 },
403 { "engine <name>", I18N_NOOP( "Use the <name> engine" ), 0 },
404 { "cwd <directory>", I18N_NOOP( "Base for relative filenames/URLs" ), 0 },
405 { "cdplay <device>", I18N_NOOP("Play an AudioCD from <device> or system:/media/<device>"), 0 },
406 { 0, 0, 0 }
409 KCmdLineArgs::reset();
410 KCmdLineArgs::init( argc, argv, &::aboutData ); //calls KCmdLineArgs::addStdCmdLineOptions()
411 KCmdLineArgs::addCmdLineOptions( options ); //add our own options
415 void App::initGlobalShortcuts()
417 EngineController* const ec = EngineController::instance();
418 KAction* action;
420 // m_pGlobalAccel->insert( "play", i18n( "Play" ), 0, KKey("WIN+x"), 0, ec, SLOT( play() ), true, true );
421 action = new KAction( i18n( "Play" ), m_pPlaylistWindow );
422 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_X ) );
423 connect( action, SIGNAL( triggered() ), ec, SLOT( play() ) );
425 // m_pGlobalAccel->insert( "pause", i18n( "Pause" ), 0, 0, 0, ec, SLOT( pause() ), true, true );
426 action = new KAction( i18n( "Pause" ), m_pPlaylistWindow );
427 connect( action, SIGNAL( triggered() ), ec, SLOT( pause() ) );
429 // m_pGlobalAccel->insert( "play_pause", i18n( "Play/Pause" ), 0, KKey("WIN+c"), 0, ec, SLOT( playPause() ), true, true );
430 action = new KAction( i18n( "Play/Pause" ), m_pPlaylistWindow );
431 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_C ) );
432 connect( action, SIGNAL( triggered() ), ec, SLOT( playPause() ) );
434 // m_pGlobalAccel->insert( "stop", i18n( "Stop" ), 0, KKey("WIN+v"), 0, ec, SLOT( stop() ), true, true );
435 action = new KAction( i18n( "Stop" ), m_pPlaylistWindow );
436 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_V ) );
437 connect( action, SIGNAL( triggered() ), ec, SLOT( stop() ) );
439 // 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 );
440 action = new KAction( i18n( "Stop Playing After Current Track" ), m_pPlaylistWindow );
441 action->setGlobalShortcut( KShortcut( Qt::META + Qt::CTRL + Qt::Key_V ) );
442 connect( action, SIGNAL( triggered() ), Playlist::instance()->qscrollview(), SLOT( toggleStopAfterCurrentTrack() ) );
444 // m_pGlobalAccel->insert( "next", i18n( "Next Track" ), 0, KKey("WIN+b"), 0, ec, SLOT( next() ), true, true );
445 action = new KAction( i18n( "Next Track" ), m_pPlaylistWindow );
446 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_B ) );
447 connect( action, SIGNAL( triggered() ), ec, SLOT( next() ) );
449 // m_pGlobalAccel->insert( "prev", i18n( "Previous Track" ), 0, KKey("WIN+z"), 0, ec, SLOT( previous() ), true, true );
450 action = new KAction( i18n( "Previous Track" ), m_pPlaylistWindow );
451 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Z ) );
452 connect( action, SIGNAL( triggered() ), ec, SLOT( previous() ) );
454 // m_pGlobalAccel->insert( "volup", i18n( "Increase Volume" ), 0, KKey("WIN+KP_Add"), 0, ec, SLOT( increaseVolume() ), true, true );
455 action = new KAction( i18n( "Increase Volume" ), m_pPlaylistWindow );
456 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Plus ) );
457 connect( action, SIGNAL( triggered() ), ec, SLOT( increaseVolume() ) );
459 // m_pGlobalAccel->insert( "voldn", i18n( "Decrease Volume" ), 0, KKey("WIN+KP_Subtract"), 0, ec, SLOT( decreaseVolume() ), true, true );
460 action = new KAction( i18n( "Decrease Volume" ), m_pPlaylistWindow );
461 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Minus ) );
462 connect( action, SIGNAL( triggered() ), ec, SLOT( decreaseVolume() ) );
465 // m_pGlobalAccel->insert( "seekforward", i18n( "Seek Forward" ), 0, KKey("WIN+Shift+KP_Add"), 0, ec, SLOT( seekForward() ), true, true );
466 action = new KAction( i18n( "Seek Forward" ), m_pPlaylistWindow );
467 action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Plus ) );
468 connect( action, SIGNAL( triggered() ), ec, SLOT( seekForward() ) );
471 // m_pGlobalAccel->insert( "seekbackward", i18n( "Seek Backward" ), 0, KKey("WIN+Shift+KP_Subtract"), 0, ec, SLOT( seekBackward() ), true, true );
472 action = new KAction( i18n( "Seek Backward" ), m_pPlaylistWindow );
473 action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Minus ) );
474 connect( action, SIGNAL( triggered() ), ec, SLOT( seekBackward() ) );
476 // m_pGlobalAccel->insert( "playlist_add", i18n( "Add Media..." ), 0, KKey("WIN+a"), 0, m_pPlaylistWindow, SLOT( slotAddLocation() ), true, true );
477 action = new KAction( i18n( "Add Media..." ), m_pPlaylistWindow );
478 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_A ) );
479 connect( action, SIGNAL( triggered() ), m_pPlaylistWindow, SLOT( slotAddLocation() ) );
481 // m_pGlobalAccel->insert( "show", i18n( "Toggle Playlist Window" ), 0, KKey("WIN+p"), 0, m_pPlaylistWindow, SLOT( showHide() ), true, true );
482 action = new KAction( i18n( "Toggle Playlist Window" ), m_pPlaylistWindow );
483 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_P ) );
484 connect( action, SIGNAL( triggered() ), m_pPlaylistWindow, SLOT( showHide() ) );
486 #ifdef Q_WS_X11
487 // m_pGlobalAccel->insert( "osd", i18n( "Show OSD" ), 0, KKey("WIN+o"), 0, Amarok::OSD::instance(), SLOT( forceToggleOSD() ), true, true );
488 action = new KAction( i18n( "Show OSD" ), m_pPlaylistWindow );
489 action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_O ) );
490 connect( action, SIGNAL( triggered() ), Amarok::OSD::instance(), SLOT( forceToggleOSD() ) );
491 #endif
492 #if 0
493 m_pGlobalAccel->insert( "mute", i18n( "Mute Volume" ), 0, KKey("WIN+m"), 0,
494 ec, SLOT( mute() ), true, true );
495 m_pGlobalAccel->insert( "rating1", i18n( "Rate Current Track: 1" ), 0, KKey("WIN+1"), 0,
496 this, SLOT( setRating1() ), true, true );
497 m_pGlobalAccel->insert( "rating2", i18n( "Rate Current Track: 2" ), 0, KKey("WIN+2"), 0,
498 this, SLOT( setRating2() ), true, true );
499 m_pGlobalAccel->insert( "rating3", i18n( "Rate Current Track: 3" ), 0, KKey("WIN+3"), 0,
500 this, SLOT( setRating3() ), true, true );
501 m_pGlobalAccel->insert( "rating4", i18n( "Rate Current Track: 4" ), 0, KKey("WIN+4"), 0,
502 this, SLOT( setRating4() ), true, true );
503 m_pGlobalAccel->insert( "rating5", i18n( "Rate Current Track: 5" ), 0, KKey("WIN+5"), 0,
504 this, SLOT( setRating5() ), true, true );
505 #endif
507 // KGlobalAccel::self()->setConfigGroup( "Shortcuts" );
508 // KGlobalAccel::self()->readSettings( KGlobal::config().data() );
511 // FIXME Is this still needed with KDE4?
512 #if 0
513 //TODO fix kde accel system so that kactions find appropriate global shortcuts
514 // and there is only one configure shortcuts dialog
516 KActionCollection* const ac = Amarok::actionCollection();
517 KAccelShortcutList list( m_pGlobalAccel );
519 for( uint i = 0; i < list.count(); ++i )
521 KAction *action = ac->action( list.name( i ).toLatin1() );
523 if( action )
525 //this is a hack really, also it means there may be two calls to the slot for the shortcut
526 action->setShortcutConfigurable( false );
527 action->setShortcut( list.shortcut( i ) );
530 #endif
534 /////////////////////////////////////////////////////////////////////////////////////
535 // METHODS
536 /////////////////////////////////////////////////////////////////////////////////////
538 #include <id3v1tag.h>
539 #include <tbytevector.h>
540 #include <QTextCodec>
541 #include <kglobal.h>
543 //this class is only used in this module, so I figured I may as well define it
544 //here and save creating another header/source file combination
546 class ID3v1StringHandler : public TagLib::ID3v1::StringHandler
548 QTextCodec *m_codec;
550 virtual TagLib::String parse( const TagLib::ByteVector &data ) const
552 return QStringToTString( m_codec->toUnicode( data.data(), data.size() ) );
555 virtual TagLib::ByteVector render( const TagLib::String &ts ) const
557 const QByteArray qcs = m_codec->fromUnicode( TStringToQString(ts) );
558 return TagLib::ByteVector( qcs, (uint) qcs.length() );
561 public:
562 ID3v1StringHandler( int codecIndex )
563 : m_codec( QTextCodec::codecForIndex( codecIndex ) )
565 debug() << "codec: " << m_codec << endl;
566 debug() << "codec-name: " << m_codec->name() << endl;
569 ID3v1StringHandler( QTextCodec *codec )
570 : m_codec( codec )
572 debug() << "codec: " << m_codec << endl;
573 debug() << "codec-name: " << m_codec->name() << endl;
577 //SLOT
578 void App::applySettings( bool firstTime )
580 ///Called when the configDialog is closed with OK or Apply
582 DEBUG_BLOCK
584 #ifdef Q_WS_X11
585 //probably needs to be done in TrayIcon when it receives a QEvent::ToolTip (see QSystemtrayIcon documentation)
586 //TrackToolTip::instance()->removeFromWidget( m_pTray );
587 #endif
588 playlistWindow()->applySettings();
589 Scrobbler::instance()->applySettings();
590 Amarok::OSD::instance()->applySettings();
591 CollectionDB::instance()->applySettings();
592 #ifdef Q_WS_X11
593 m_pTray->setVisible( AmarokConfig::showTrayIcon() );
594 //TrackToolTip::instance()->addToWidget( m_pTray );
595 #endif
598 //on startup we need to show the window, but only if it wasn't hidden on exit
599 //and always if the trayicon isn't showing
600 QWidget* main_window = mainWindow();
601 #ifdef Q_WS_X11
602 if( ( main_window && firstTime && !Amarok::config()->readEntry( "HiddenOnExit", false ) ) || ( main_window && !AmarokConfig::showTrayIcon() ) )
603 #endif
605 main_window->show();
607 //takes longer but feels shorter. Crazy eh? :)
608 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
612 { //<Engine>
613 EngineBase *engine = EngineController::engine();
615 if( firstTime || AmarokConfig::soundSystem() !=
616 PluginManager::getService( engine )->property( "X-KDE-Amarok-name" ).toString() )
618 //will unload engine for us first if necessary
619 engine = EngineController::instance()->loadEngine();
622 engine->setXfadeLength( AmarokConfig::crossfade() ? AmarokConfig::crossfadeLength() : 0 );
623 engine->setVolume( AmarokConfig::masterVolume() );
625 engine->setEqualizerEnabled( AmarokConfig::equalizerEnabled() );
626 if ( AmarokConfig::equalizerEnabled() )
627 engine->setEqualizerParameters( AmarokConfig::equalizerPreamp(), AmarokConfig::equalizerGains() );
629 Amarok::actionCollection()->action("play_audiocd")->setEnabled( EngineController::hasEngineProperty( "HasKIO" ) || EngineController::hasEngineProperty("HasCDDA"));
630 } //</Engine>
632 { //<Collection>
633 CollectionView::instance()->renderView(true);
634 } //</Collection>
635 { //<Context>
636 ContextBrowser::instance()->renderView();
637 } //</Context>
639 { // delete unneeded cover images from cache
640 const QString size = QString::number( AmarokConfig::coverPreviewSize() ) + '@';
641 const QDir cacheDir = Amarok::saveLocation( "albumcovers/cache/" );
642 const QStringList obsoleteCovers = cacheDir.entryList( QStringList("*") );
643 oldForeach( obsoleteCovers )
644 if ( !(*it).startsWith( size ) && !(*it).startsWith( "50@" ) )
645 QFile( cacheDir.filePath( *it ) ).remove();
648 //if ( !firstTime )
649 // Bizarrely and ironically calling this causes crashes for
650 // some people! FIXME
651 //AmarokConfig::writeConfig();
655 //SLOT
656 void
657 App::continueInit()
659 DEBUG_BLOCK
660 const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
661 bool restoreSession = args->count() == 0 || args->isSet( "append" ) || args->isSet( "enqueue" )
662 || Amarok::config()->readEntry( "AppendAsDefault", false );
664 // Make this instance so it can start receiving signals
665 MoodServer::instance();
667 // Remember old folder setup, so we can detect changes after the wizard was used
668 //const QStringList oldCollectionFolders = MountPointManager::instance()->collectionFolders();
671 if ( Amarok::config()->readEntry( "First Run", true ) || args->isSet( "wizard" ) ) {
672 std::cout << "STARTUP\n" << std::flush; //hide the splashscreen
673 firstRunWizard();
674 Amarok::config()->writeEntry( "First Run", false );
675 Amarok::config()->sync();
678 CollectionDB::instance()->checkDatabase();
680 m_pMediaDeviceManager = MediaDeviceManager::instance();
681 m_pPlaylistWindow = new PlaylistWindow();
682 #ifdef Q_WS_X11
683 m_pTray = new Amarok::TrayIcon( m_pPlaylistWindow );
684 #endif
685 m_pPlaylistWindow->init(); //creates the playlist, browsers, etc.
686 //init playlist window as soon as the database is guaranteed to be usable
687 //connect( CollectionDB::instance(), SIGNAL( databaseUpdateDone() ), m_pPlaylistWindow, SLOT( init() ) );
688 initGlobalShortcuts();
689 //load previous playlist in separate thread
690 if ( restoreSession && AmarokConfig::savePlaylist() )
692 Playlist::instance()->restoreSession();
693 //Debug::stamp();
694 //p->restoreSession();
696 if( args->isSet( "engine" ) ) {
697 // we correct some common errors (case issues, missing -engine off the end)
698 QString engine = args->getOption( "engine" ).toLower();
699 if( engine.startsWith( "gstreamer" ) ) engine = "gst-engine";
700 if( !engine.endsWith( "engine" ) ) engine += "-engine";
702 AmarokConfig::setSoundSystem( engine );
704 Debug::stamp();
705 //create engine, show TrayIcon etc.
706 applySettings( true );
707 Debug::stamp();
708 // Start ScriptManager. Must be created _after_ PlaylistWindow.
709 ScriptManager::instance();
710 Debug::stamp();
711 //notify loader application that we have started
712 std::cout << "STARTUP\n" << std::flush;
714 //do after applySettings(), or the OSD will flicker and other wierdness!
715 //do before restoreSession()!
716 EngineController::instance()->attach( this );
718 //set a default interface
719 engineStateChanged( Engine::Empty );
721 if ( AmarokConfig::resumePlayback() && restoreSession && !args->isSet( "stop" ) ) {
722 //restore session as long as the user didn't specify media to play etc.
723 //do this after applySettings() so OSD displays correctly
724 EngineController::instance()->restoreSession();
727 // Refetch covers every 80 days to comply with Amazon license
728 new RefreshImages();
730 CollectionDB *collDB = CollectionDB::instance();
731 //Collection scan is triggered in firstRunWizard if the colelction folder setup was changed in the wizard
733 // If database version is updated, the collection needs to be rescanned.
734 // Works also if the collection is empty for some other reason
735 // (e.g. deleted collection.db)
736 if ( CollectionDB::instance()->isEmpty() )
738 //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( startScan() ) );
739 collDB->startScan();
741 else if ( AmarokConfig::monitorChanges() )
742 //connect( collDB, SIGNAL( databaseUpdateDone() ), collDB, SLOT( scanModifiedDirs() ) );
743 collDB->scanModifiedDirs();
746 handleCliArgs();
750 bool Amarok::genericEventHandler( QWidget *recipient, QEvent *e )
752 //this is used as a generic event handler for widgets that want to handle
753 //typical events in an Amarok fashion
755 //to use it just pass the event eg:
757 // void Foo::barEvent( QBarEvent *e )
758 // {
759 // Amarok::genericEventHandler( this, e );
760 // }
762 switch( e->type() )
764 case QEvent::DragEnter:
765 #define e static_cast<QDropEvent*>(e)
766 e->setAccepted( KUrl::List::canDecode( e->mimeData() ) );
767 break;
769 case QEvent::Drop:
771 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
772 if( !list.isEmpty() )
774 Q3PopupMenu popup;
775 //FIXME this isn't a good way to determine if there is a currentTrack, need playlist() function
776 const bool b = EngineController::engine()->loaded();
778 popup.insertItem( KIcon( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ),
779 Playlist::Append );
780 popup.insertItem( KIcon( Amarok::icon( "add_playlist" ) ), i18n( "Append && &Play" ),
781 Playlist::DirectPlay | Playlist::Append );
782 if( b )
783 popup.insertItem( KIcon( Amarok::icon( "fast_forward" ) ), i18n( "&Queue Track" ),
784 Playlist::Queue );
785 popup.insertSeparator();
786 popup.insertItem( i18n( "&Cancel" ), 0 );
788 const int id = popup.exec( recipient->mapToGlobal( e->pos() ) );
790 if ( id > 0 )
791 Playlist::instance()->insertMedia( list, id );
793 else return false;
794 #undef e
796 break;
799 //this like every entry in the generic event handler is used by more than one widget
800 //please don't remove!
801 case QEvent::Wheel:
803 #define e static_cast<QWheelEvent*>(e)
805 //this behaviour happens for the systray
806 //to override one, override it in that class
808 switch( e->state() )
810 case Qt::ControlModifier:
812 const bool up = e->delta() > 0;
814 //if this seems strange to you, please bring it up on #amarok
815 //for discussion as we can't decide which way is best!
816 if( up ) EngineController::instance()->previous();
817 else EngineController::instance()->next();
818 break;
820 case Qt::ShiftModifier:
822 EngineController::instance()->seekRelative( ( e->delta() / 120 ) * 5000 ); //5 seconds for keyboard seeking.
823 break;
825 default:
826 EngineController::instance()->increaseVolume( e->delta() / Amarok::VOLUME_SENSITIVITY );
829 e->accept();
830 #undef e
832 break;
835 case QEvent::Close:
837 //KDE policy states we should hide to tray and not quit() when the
838 //close window button is pushed for the main widget
840 static_cast<QCloseEvent*>(e)->accept(); //if we don't do this the info box appears on quit()!
842 if( AmarokConfig::showTrayIcon() && !e->spontaneous() && !kapp->sessionSaving() )
844 KMessageBox::information( recipient,
845 i18n( "<qt>Closing the main-window will keep Amarok running in the System Tray. "
846 "Use <B>Quit</B> from the menu, or the Amarok tray-icon to exit the application.</qt>" ),
847 i18n( "Docking in System Tray" ), "hideOnCloseInfo" );
849 else pApp->quit();
851 break;
853 default:
854 return false;
857 return true;
861 void App::engineStateChanged( Engine::State state, Engine::State oldState )
863 const MetaBundle &bundle = EngineController::instance()->bundle();
864 switch( state )
866 case Engine::Empty:
867 m_pPlaylistWindow->setCaption( "Amarok" );
868 TrackToolTip::instance()->clear();
869 Amarok::OSD::instance()->setImage( QImage( KIconLoader().iconPath( "amarok", -K3Icon::SizeHuge ) ) );
870 break;
872 case Engine::Playing:
873 if ( oldState == Engine::Paused )
874 Amarok::OSD::instance()->OSDWidget::show( i18nc( "state, as in playing", "Play" ) );
875 if ( !bundle.prettyTitle().isEmpty() )
876 m_pPlaylistWindow->setCaption( i18n("Amarok - %1", bundle.veryNiceTitle() ) );
877 break;
879 case Engine::Paused:
880 Amarok::OSD::instance()->OSDWidget::show( i18n("Paused") );
881 break;
883 case Engine::Idle:
884 m_pPlaylistWindow->setCaption( "Amarok" );
885 break;
887 default:
892 void App::engineNewMetaData( const MetaBundle &bundle, bool /*trackChanged*/ )
894 Amarok::OSD::instance()->show( bundle );
895 if ( !bundle.prettyTitle().isEmpty() )
896 m_pPlaylistWindow->setCaption( i18n("Amarok - %1", bundle.veryNiceTitle() ) );
898 TrackToolTip::instance()->setTrack( bundle );
901 void App::engineTrackPositionChanged( long position, bool /*userSeek*/ )
903 TrackToolTip::instance()->setPos( position );
906 void App::engineVolumeChanged( int newVolume )
908 Amarok::OSD::instance()->OSDWidget::volChanged( newVolume );
911 void App::slotConfigEqualizer() //SLOT
913 EqualizerSetup::instance()->show();
914 EqualizerSetup::instance()->raise();
918 void App::slotConfigAmarok( const QByteArray& page )
920 DEBUG_THREAD_FUNC_INFO
922 AmarokConfigDialog* dialog = static_cast<AmarokConfigDialog*>( KConfigDialog::exists( "settings" ) );
924 if( !dialog )
926 //KConfigDialog didn't find an instance of this dialog, so lets create it :
927 dialog = new AmarokConfigDialog( m_pPlaylistWindow, "settings", AmarokConfig::self() );
929 connect( dialog, SIGNAL(settingsChanged(const QString&)), SLOT(applySettings()) );
932 //FIXME it seems that if the dialog is on a different desktop it gets lost
933 // what do to? detect and move it?
935 // if ( page.isNull() )
936 // FIXME
937 // dialog->showPage( AmarokConfigDialog::s_currentPage );
938 // else
939 dialog->showPageByName( page );
941 dialog->show();
942 dialog->raise();
943 dialog->activateWindow();
946 void App::slotConfigShortcuts()
948 KKeyDialog::configure( Amarok::actionCollection(), KKeyChooser::LetterShortcutsAllowed, m_pPlaylistWindow );
951 void App::slotConfigToolBars()
953 PlaylistWindow* const pw = playlistWindow();
954 KEditToolbar dialog( pw->actionCollection(), pw->xmlFile(), true, pw );
956 dialog.showButton( KEditToolbar::Apply, false );
958 if( dialog.exec() )
960 playlistWindow()->reloadXML();
961 playlistWindow()->createGUI();
965 void App::firstRunWizard()
967 #if 0
968 ///show firstRunWizard
969 DEBUG_BLOCK
971 FirstRunWizard wizard;
972 setTopWidget( &wizard );
973 KConfigDialogManager* config = new KConfigDialogManager(&wizard, AmarokConfig::self(), "wizardconfig");
974 config->updateWidgets();
975 // connect(config, SIGNAL(settingsChanged()), SLOT(updateSettings()));
976 wizard.setCaption( makeStdCaption( i18n( "First-Run Wizard" ) ) );
978 if( wizard.exec() != QDialog::Rejected )
980 //make sure that the DB config is stored in amarokrc before calling CollectionDB's ctor
981 AmarokConfig::setDatabaseEngine(
982 QString::number( Amarok::databaseTypeCode( wizard.dbSetup7->databaseEngine->currentText() ) ) );
983 config->updateSettings();
985 const QStringList oldCollectionFolders = MountPointManager::instance()->collectionFolders();
986 wizard.writeCollectionConfig();
988 // If wizard is invoked at runtime, rescan collection if folder setup has changed
989 if ( !Amarok::config()->readEntry( "First Run", true ) &&
990 oldCollectionFolders != MountPointManager::instance()->collectionFolders() )
991 CollectionDB::instance()->startScan();
994 #endif
997 void App::setUseScores( bool use )
999 AmarokConfig::setUseScores( use );
1000 emit useScores( use );
1003 void App::setUseRatings( bool use )
1005 AmarokConfig::setUseRatings( use );
1006 emit useRatings( use );
1009 void App::setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
1011 AmarokConfig::setShowMoodbar( show );
1012 AmarokConfig::setMakeMoodier( moodier );
1013 AmarokConfig::setAlterMood( alter );
1014 AmarokConfig::setMoodsWithMusic( withMusic );
1015 emit moodbarPrefs( show, moodier, alter, withMusic );
1018 KIO::Job *App::trashFiles( const KUrl::List &files )
1020 KIO::Job *job = KIO::trash( files, true /*show progress*/ );
1021 Amarok::StatusBar::instance()->newProgressOperation( job ).setDescription( i18n("Moving files to trash") );
1022 connect( job, SIGNAL( result( KJob* ) ), this, SLOT( slotTrashResult( KJob* ) ) );
1023 return job;
1026 void App::setRating( int n )
1028 if( !AmarokConfig::useRatings() ) return;
1030 n *= 2;
1032 const Engine::State s = EngineController::instance()->engine()->state();
1033 if( s == Engine::Playing || s == Engine::Paused || s == Engine::Idle )
1035 const QString path = EngineController::instance()->playingURL().path();
1036 CollectionDB::instance()->setSongRating( path, n, true );
1037 const int rating = CollectionDB::instance()->getSongRating( path );
1038 EngineController::instance()->updateBundleRating( rating );
1039 Amarok::OSD::instance()->OSDWidget::ratingChanged( rating );
1041 else if( PlaylistWindow::self()->isReallyShown() && Playlist::instance()->qscrollview()->hasFocus() )
1042 Playlist::instance()->setSelectedRatings( n );
1045 void App::slotTrashResult( KJob *job )
1047 if( job->error() )
1048 job->uiDelegate()->showErrorMessage();
1051 QWidget *App::mainWindow() const
1053 return static_cast<QWidget*>( m_pPlaylistWindow );
1056 void App::quit()
1058 emit prepareToQuit();
1059 if( MediaBrowser::instance() && MediaBrowser::instance()->blockQuit() )
1061 // don't quit yet, as some media devices still have to finish transferring data
1062 QTimer::singleShot( 100, this, SLOT( quit() ) );
1063 return;
1065 KApplication::quit();
1068 namespace Amarok
1070 /// @see amarok.h
1072 //TODO remove these, they suck, do a generic getImage
1074 QPixmap getPNG( const QString &filename )
1076 QString file = !filename.endsWith( ".png", Qt::CaseInsensitive ) ? "amarok/images/%1.png" : "amarok/images/%1";
1078 return QPixmap( KStandardDirs::locate( "data", file.arg( filename ) ), "PNG" );
1081 QPixmap getJPG( const QString &filename )
1083 QString file = !filename.endsWith( ".jpg", Qt::CaseInsensitive ) ? "amarok/images/%1.jpg" : "amarok/images/%1";
1085 return QPixmap( KStandardDirs::locate( "data", QString( "amarok/images/%1.jpg" ).arg( filename ) ), "JPEG" );
1088 QWidget *mainWindow()
1090 return pApp->playlistWindow();
1093 KActionCollection *actionCollection()
1095 return pApp->playlistWindow()->actionCollection();
1098 KSharedConfig::Ptr config( const QString &group )
1100 //Slightly more useful config() that allows setting the group simultaneously
1101 KGlobal::config()->setGroup( group );
1102 return KGlobal::config();
1105 bool invokeBrowser( const QString& url )
1107 //URL can be in whatever forms KUrl understands - ie most.
1108 const QString cmd = "%1 \"%2\"";
1109 return KRun::runCommand( cmd.arg( AmarokConfig::externalBrowser(), KUrl( url ).url() ) ) > 0;
1112 namespace ColorScheme
1114 QColor Base;
1115 QColor Text;
1116 QColor Background;
1117 QColor Foreground;
1118 QColor AltBase;
1121 OverrideCursor::OverrideCursor( Qt::CursorShape cursor )
1123 QApplication::setOverrideCursor( cursor == Qt::WaitCursor ? KCursor::waitCursor() : KCursor::workingCursor() );
1126 OverrideCursor::~OverrideCursor()
1128 QApplication::restoreOverrideCursor();
1131 QString saveLocation( const QString &directory )
1133 globalDirsMutex.lock();
1134 QString result = KGlobal::dirs()->saveLocation( "data", QString("amarok/") + directory, true );
1135 globalDirsMutex.unlock();
1136 return result;
1139 QString cleanPath( const QString &path )
1141 QString result = path;
1142 // german umlauts
1143 result.replace( QChar(0x00e4), "ae" ).replace( QChar(0x00c4), "Ae" );
1144 result.replace( QChar(0x00f6), "oe" ).replace( QChar(0x00d6), "Oe" );
1145 result.replace( QChar(0x00fc), "ue" ).replace( QChar(0x00dc), "Ue" );
1146 result.replace( QChar(0x00df), "ss" );
1148 // some strange accents
1149 result.replace( QChar(0x00e7), "c" ).replace( QChar(0x00c7), "C" );
1150 result.replace( QChar(0x00fd), "y" ).replace( QChar(0x00dd), "Y" );
1151 result.replace( QChar(0x00f1), "n" ).replace( QChar(0x00d1), "N" );
1153 // accented vowels
1154 QChar a[] = { 'a', 0xe0,0xe1,0xe2,0xe3,0xe5, 0 };
1155 QChar A[] = { 'A', 0xc0,0xc1,0xc2,0xc3,0xc5, 0 };
1156 QChar E[] = { 'e', 0xe8,0xe9,0xea,0xeb, 0 };
1157 QChar e[] = { 'E', 0xc8,0xc9,0xca,0xcb, 0 };
1158 QChar i[] = { 'i', 0xec,0xed,0xee,0xef, 0 };
1159 QChar I[] = { 'I', 0xcc,0xcd,0xce,0xcf, 0 };
1160 QChar o[] = { 'o', 0xf2,0xf3,0xf4,0xf5,0xf8, 0 };
1161 QChar O[] = { 'O', 0xd2,0xd3,0xd4,0xd5,0xd8, 0 };
1162 QChar u[] = { 'u', 0xf9,0xfa,0xfb, 0 };
1163 QChar U[] = { 'U', 0xd9,0xda,0xdb, 0 };
1164 QChar nul[] = { 0 };
1165 QChar *replacements[] = { a, A, e, E, i, I, o, O, u, U, nul };
1167 for( int i = 0; i < result.length(); i++ )
1169 QChar c = result[ i ];
1170 for( uint n = 0; replacements[n][0] != QChar(0); n++ )
1172 for( uint k=0; replacements[n][k] != QChar(0); k++ )
1174 if( replacements[n][k] == c )
1176 c = replacements[n][0];
1180 result[ i ] = c;
1182 return result;
1185 QString asciiPath( const QString &path )
1187 QString result = path;
1188 for( int i = 0; i < result.length(); i++ )
1190 QChar c = result[ i ];
1191 if( c > QChar(0x7f) || c == QChar(0) )
1193 c = '_';
1195 result[ i ] = c;
1197 return result;
1200 QString vfatPath( const QString &path )
1202 QString s = path;
1204 for( int i = 0; i < s.length(); i++ )
1206 QChar c = s[ i ];
1207 if( c < QChar(0x20)
1208 || c=='*' || c=='?' || c=='<' || c=='>'
1209 || c=='|' || c=='"' || c==':' || c=='/'
1210 || c=='\\' )
1211 c = '_';
1212 s[ i ] = c;
1215 uint len = s.length();
1216 if( len == 3 || (len > 3 && s[3] == '.') )
1218 QString l = s.left(3).toLower();
1219 if( l=="aux" || l=="con" || l=="nul" || l=="prn" )
1220 s = '_' + s;
1222 else if( len == 4 || (len > 4 && s[4] == '.') )
1224 QString l = s.left(3).toLower();
1225 QString d = s.mid(3,1);
1226 if( (l=="com" || l=="lpt") &&
1227 (d=="0" || d=="1" || d=="2" || d=="3" || d=="4" ||
1228 d=="5" || d=="6" || d=="7" || d=="8" || d=="9") )
1229 s = '_' + s;
1232 while( s.startsWith( "." ) )
1233 s = s.mid(1);
1235 while( s.endsWith( "." ) )
1236 s = s.left( s.length()-1 );
1238 s = s.left(255);
1239 len = s.length();
1240 if( s[len-1] == ' ' )
1241 s[len-1] = '_';
1243 return s;
1246 QString decapitateString( const QString &input, const QString &ref )
1248 QString t = ref.toUpper();
1249 int length = t.length();
1250 int commonLength = 0;
1251 while( length > 0 )
1253 if ( input.toUpper().startsWith( t ) )
1255 commonLength = t.length();
1256 t = ref.toUpper().left( t.length() + length/2 );
1257 length = length/2;
1259 else
1261 t = ref.toUpper().left( t.length() - length/2 );
1262 length = length/2;
1265 QString clean = input;
1266 if( t.endsWith( " " ) || !ref.at( t.length() ).isLetterOrNumber() ) // common part ends with a space or complete word
1267 clean = input.right( input.length() - commonLength ).trimmed();
1268 return clean;
1271 void setUseScores( bool use ) { App::instance()->setUseScores( use ); }
1272 void setUseRatings( bool use ) { App::instance()->setUseRatings( use ); }
1273 void setMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
1274 { App::instance()->setMoodbarPrefs( show, moodier, alter, withMusic ); }
1275 KIO::Job *trashFiles( const KUrl::List &files ) { return App::instance()->trashFiles( files ); }
1278 #include "app.moc"