| 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 |
***************************************************************************/ |
| 8 |
|
| 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 |
|
| 18 |
#include "App.h" |
| 19 |
|
| 20 |
#include "Amarok.h" |
| 21 |
#include "amarokconfig.h" |
| 22 |
#include "amarokurls/AmarokUrl.h" |
| 23 |
#include "CollectionManager.h" |
| 24 |
#include "ConfigDialog.h" |
| 25 |
#include "covermanager/CoverFetcher.h" |
| 26 |
#include "dbus/CollectionDBusHandler.h" |
| 27 |
#include "Debug.h" |
| 28 |
#include "EngineController.h" |
| 29 |
#include "firstruntutorial/FirstRunTutorial.h" |
| 30 |
#include "MainWindow.h" |
| 31 |
#include "Meta.h" |
| 32 |
#include "meta/MetaConstants.h" |
| 33 |
#include "MountPointManager.h" |
| 34 |
#include "Osd.h" |
| 35 |
#include "PlayerDBusHandler.h" |
| 36 |
#include "Playlist.h" |
| 37 |
#include "PlaylistFileSupport.h" |
| 38 |
#include "playlist/PlaylistActions.h" |
| 39 |
#include "playlist/PlaylistModel.h" |
| 40 |
#include "playlist/PlaylistController.h" |
| 41 |
#include "playlistmanager/PlaylistManager.h" |
| 42 |
#include "PluginManager.h" |
| 43 |
#include "RootDBusHandler.h" |
| 44 |
#include "ScriptManager.h" |
| 45 |
#include "statusbar/StatusBar.h" |
| 46 |
#include "Systray.h" |
| 47 |
#include "TracklistDBusHandler.h" |
| 48 |
|
| 49 |
#include <iostream> |
| 50 |
|
| 51 |
#include <KAboutData> |
| 52 |
#include <KAction> |
| 53 |
#include <KCalendarSystem> |
| 54 |
#include <KCmdLineArgs> //initCliArgs() |
| 55 |
#include <KEditToolBar> //slotConfigToolbars() |
| 56 |
#include <KGlobalSettings> |
| 57 |
#include <KIO/CopyJob> |
| 58 |
#include <KJob> |
| 59 |
#include <KJobUiDelegate> |
| 60 |
#include <KLocale> |
| 61 |
#include <KShortcutsDialog> //slotConfigShortcuts() |
| 62 |
#include <KSplashScreen> |
| 63 |
#include <KStandardDirs> |
| 64 |
|
| 65 |
#include <QByteArray> |
| 66 |
#include <QFile> |
| 67 |
#include <KPixmapCache> |
| 68 |
#include <QStringList> |
| 69 |
#include <QTimer> //showHyperThreadingWarning() |
| 70 |
#include <QtDBus/QtDBus> |
| 71 |
|
| 72 |
#ifdef TAGLIB_EXTRAS_FOUND |
| 73 |
#include <audiblefiletyperesolver.h> |
| 74 |
#include <asffiletyperesolver.h> |
| 75 |
#include <wavfiletyperesolver.h> |
| 76 |
#include <realmediafiletyperesolver.h> |
| 77 |
#include <mp4filetyperesolver.h> |
| 78 |
#endif |
| 79 |
|
| 80 |
QMutex Debug::mutex; |
| 81 |
QMutex Amarok::globalDirsMutex; |
| 82 |
QPointer<KActionCollection> Amarok::actionCollectionObject; |
| 83 |
|
| 84 |
int App::mainThreadId = 0; |
| 85 |
|
| 86 |
#ifdef Q_WS_MAC |
| 87 |
#include <CoreFoundation/CoreFoundation.h> |
| 88 |
extern void setupEventHandler_mac(long); |
| 89 |
#endif |
| 90 |
|
| 91 |
|
| 92 |
AMAROK_EXPORT KAboutData aboutData( "amarok", 0, |
| 93 |
ki18n( "Amarok" ), APP_VERSION, |
| 94 |
ki18n( "The audio player for KDE" ), KAboutData::License_GPL, |
| 95 |
ki18n( "(C) 2002-2003, Mark Kretschmann\n(C) 2003-2009, The Amarok Development Squad" ), |
| 96 |
ki18n( "IRC:\nirc.freenode.net - #amarok, #amarok.de, #amarok.es, #amarok.fr\n\nFeedback:\namarok@kde.org\n\n(Build Date: %1)" ).subs( __DATE__ ), |
| 97 |
( "http://amarok.kde.org" ) ); |
| 98 |
|
| 99 |
App::App() |
| 100 |
: KUniqueApplication() |
| 101 |
, m_splash( 0 ) |
| 102 |
{ |
| 103 |
DEBUG_BLOCK |
| 104 |
PERF_LOG( "Begin Application ctor" ) |
| 105 |
|
| 106 |
// required for last.fm plugin to grab app version |
| 107 |
setApplicationVersion( APP_VERSION ); |
| 108 |
|
| 109 |
if( AmarokConfig::showSplashscreen() && !isSessionRestored() ) |
| 110 |
{ |
| 111 |
PERF_LOG( "Init KStandardDirs cache" ) |
| 112 |
KStandardDirs *stdDirs = KGlobal::dirs(); |
| 113 |
PERF_LOG( "Finding image" ) |
| 114 |
QString img = stdDirs->findResource( "data", "amarok/images/splash_screen.jpg" ); |
| 115 |
PERF_LOG( "Creating pixmap" ) |
| 116 |
QPixmap splashpix( img ); |
| 117 |
PERF_LOG( "Creating splashscreen" ) |
| 118 |
m_splash = new KSplashScreen( splashpix, Qt::WindowStaysOnTopHint ); |
| 119 |
PERF_LOG( "showing splashscreen" ) |
| 120 |
m_splash->show(); |
| 121 |
} |
| 122 |
|
| 123 |
#ifdef TAGLIB_EXTRAS_FOUND |
| 124 |
PERF_LOG( "Registering taglib plugins" ) |
| 125 |
TagLib::FileRef::addFileTypeResolver(new MP4FileTypeResolver); |
| 126 |
TagLib::FileRef::addFileTypeResolver(new ASFFileTypeResolver); |
| 127 |
TagLib::FileRef::addFileTypeResolver(new RealMediaFileTypeResolver); |
| 128 |
TagLib::FileRef::addFileTypeResolver(new AudibleFileTypeResolver); |
| 129 |
TagLib::FileRef::addFileTypeResolver(new WavFileTypeResolver); |
| 130 |
PERF_LOG( "Done Registering taglib plugins" ) |
| 131 |
#endif |
| 132 |
|
| 133 |
qRegisterMetaType<Meta::DataPtr>(); |
| 134 |
qRegisterMetaType<Meta::DataList>(); |
| 135 |
qRegisterMetaType<Meta::TrackPtr>(); |
| 136 |
qRegisterMetaType<Meta::TrackList>(); |
| 137 |
qRegisterMetaType<Meta::AlbumPtr>(); |
| 138 |
qRegisterMetaType<Meta::AlbumList>(); |
| 139 |
qRegisterMetaType<Meta::ArtistPtr>(); |
| 140 |
qRegisterMetaType<Meta::ArtistList>(); |
| 141 |
qRegisterMetaType<Meta::GenrePtr>(); |
| 142 |
qRegisterMetaType<Meta::GenreList>(); |
| 143 |
qRegisterMetaType<Meta::ComposerPtr>(); |
| 144 |
qRegisterMetaType<Meta::ComposerList>(); |
| 145 |
qRegisterMetaType<Meta::YearPtr>(); |
| 146 |
qRegisterMetaType<Meta::YearList>(); |
| 147 |
|
| 148 |
|
| 149 |
//make sure we have enough cache space for all our crazy svg stuff |
| 150 |
KPixmapCache cache( "Amarok-pixmaps" ); |
| 151 |
cache.setCacheLimit ( 20 * 1024 ); |
| 152 |
|
| 153 |
#ifdef Q_WS_MAC |
| 154 |
// this is inspired by OpenSceneGraph: osgDB/FilePath.cpp |
| 155 |
|
| 156 |
// Start with the the Bundle PlugIns directory. |
| 157 |
|
| 158 |
// Get the main bundle first. No need to retain or release it since |
| 159 |
// we are not keeping a reference |
| 160 |
CFBundleRef myBundle = CFBundleGetMainBundle(); |
| 161 |
if( myBundle ) |
| 162 |
{ |
| 163 |
// CFBundleGetMainBundle will return a bundle ref even if |
| 164 |
// the application isn't part of a bundle, so we need to |
| 165 |
// check |
| 166 |
// if the path to the bundle ends in ".app" to see if it is |
| 167 |
// a |
| 168 |
// proper application bundle. If it is, the plugins path is |
| 169 |
// added |
| 170 |
CFURLRef urlRef = CFBundleCopyBundleURL(myBundle); |
| 171 |
if(urlRef) |
| 172 |
{ |
| 173 |
char bundlePath[1024]; |
| 174 |
if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) ) |
| 175 |
{ |
| 176 |
QByteArray bp( bundlePath ); |
| 177 |
size_t len = bp.length(); |
| 178 |
if( len > 4 && bp.right( 4 ) == ".app" ) |
| 179 |
{ |
| 180 |
bp.append( "/Contents/MacOS" ); |
| 181 |
QByteArray path = qgetenv( "PATH" ); |
| 182 |
if( path.length() > 0 ) |
| 183 |
{ |
| 184 |
path.prepend( ":" ); |
| 185 |
} |
| 186 |
path.prepend( bp ); |
| 187 |
debug() << "setting PATH=" << path; |
| 188 |
setenv("PATH", path, 1); |
| 189 |
} |
| 190 |
} |
| 191 |
// docs say we are responsible for releasing CFURLRef |
| 192 |
CFRelease(urlRef); |
| 193 |
} |
| 194 |
} |
| 195 |
|
| 196 |
setupEventHandler_mac((long)this); |
| 197 |
#endif |
| 198 |
|
| 199 |
PERF_LOG( "Done App ctor" ) |
| 200 |
|
| 201 |
continueInit(); |
| 202 |
} |
| 203 |
|
| 204 |
App::~App() |
| 205 |
{ |
| 206 |
DEBUG_BLOCK |
| 207 |
|
| 208 |
delete m_splash; |
| 209 |
m_splash = 0; |
| 210 |
|
| 211 |
CollectionManager::instance()->stopScan(); |
| 212 |
|
| 213 |
// Hiding the OSD before exit prevents crash |
| 214 |
Amarok::OSD::instance()->hide(); |
| 215 |
|
| 216 |
if ( AmarokConfig::resumePlayback() ) |
| 217 |
{ |
| 218 |
if ( The::engineController()->state() != Phonon::StoppedState ) |
| 219 |
{ |
| 220 |
Meta::TrackPtr track = The::engineController()->currentTrack(); |
| 221 |
if( track ) |
| 222 |
{ |
| 223 |
AmarokConfig::setResumeTrack( track->playableUrl().prettyUrl() ); |
| 224 |
AmarokConfig::setResumeTime( The::engineController()->trackPosition() * 1000 ); |
| 225 |
AmarokConfig::setLastPlaying( Playlist::Model::instance()->activeRow() ); |
| 226 |
} |
| 227 |
} |
| 228 |
else |
| 229 |
{ |
| 230 |
AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time! |
| 231 |
AmarokConfig::setLastPlaying( -1 ); |
| 232 |
} |
| 233 |
} |
| 234 |
|
| 235 |
The::engineController()->endSession(); //records final statistics |
| 236 |
|
| 237 |
#ifndef Q_WS_MAC |
| 238 |
// do even if trayicon is not shown, it is safe |
| 239 |
Amarok::config().writeEntry( "HiddenOnExit", mainWindow()->isHidden() ); |
| 240 |
AmarokConfig::self()->writeConfig(); |
| 241 |
#else |
| 242 |
// for some reason on OS X the main window always reports being hidden |
| 243 |
// this means if you have the tray icon enabled, amarok will always open minimized |
| 244 |
Amarok::config().writeEntry( "HiddenOnExit", false ); |
| 245 |
AmarokConfig::self()->writeConfig(); |
| 246 |
#endif |
| 247 |
|
| 248 |
ScriptManager::destroy(); |
| 249 |
|
| 250 |
// this must be deleted before the connection to the Xserver is |
| 251 |
// severed, or we risk a crash when the QApplication is exited, |
| 252 |
// I asked Trolltech! *smug* |
| 253 |
Amarok::OSD::destroy(); |
| 254 |
|
| 255 |
// I tried this in the destructor for the Model but the object is destroyed after the |
| 256 |
// Config is written. Go figure! |
| 257 |
AmarokConfig::setLastPlaying( Playlist::Model::instance()->rowForTrack( Playlist::Model::instance()->activeTrack() ) ); |
| 258 |
|
| 259 |
AmarokConfig::self()->writeConfig(); |
| 260 |
|
| 261 |
//mainWindow()->deleteBrowsers(); |
| 262 |
delete mainWindow(); |
| 263 |
|
| 264 |
CollectionManager::destroy(); |
| 265 |
MountPointManager::destroy(); |
| 266 |
Playlist::Actions::destroy(); |
| 267 |
Playlist::Model::destroy(); |
| 268 |
PlaylistManager::destroy(); |
| 269 |
EngineController::destroy(); |
| 270 |
CoverFetcher::destroy(); |
| 271 |
|
| 272 |
|
| 273 |
#ifdef Q_WS_WIN |
| 274 |
// work around for KUniqueApplication being not completely implemented on windows |
| 275 |
QDBusConnectionInterface* dbusService; |
| 276 |
if (QDBusConnection::sessionBus().isConnected() && (dbusService = QDBusConnection::sessionBus().interface())) |
| 277 |
{ |
| 278 |
dbusService->unregisterService("org.mpris.amarok"); |
| 279 |
} |
| 280 |
#endif |
| 281 |
} |
| 282 |
|
| 283 |
void |
| 284 |
App::handleCliArgs() //static |
| 285 |
{ |
| 286 |
DEBUG_BLOCK |
| 287 |
|
| 288 |
KCmdLineArgs* const args = KCmdLineArgs::parsedArgs(); |
| 289 |
|
| 290 |
if( args->isSet( "cwd" ) ) |
| 291 |
KCmdLineArgs::setCwd( args->getOption( "cwd" ).toLocal8Bit() ); |
| 292 |
|
| 293 |
bool haveArgs = false; |
| 294 |
if( args->count() > 0 ) |
| 295 |
{ |
| 296 |
haveArgs = true; |
| 297 |
|
| 298 |
KUrl::List list; |
| 299 |
for( int i = 0; i < args->count(); i++ ) |
| 300 |
{ |
| 301 |
KUrl url = args->url( i ); |
| 302 |
//TODO:PORTME |
| 303 |
// if( url.protocol() == "itpc" || url.protocol() == "pcast" ) |
| 304 |
// PlaylistBrowserNS::instance()->addPodcast( url ); |
| 305 |
// else |
| 306 |
|
| 307 |
if ( url.protocol() == "amarok" ) { |
| 308 |
|
| 309 |
AmarokUrl aUrl( url.url() ); |
| 310 |
aUrl.run(); |
| 311 |
|
| 312 |
} else { |
| 313 |
list << url; |
| 314 |
DEBUG_LINE_INFO |
| 315 |
} |
| 316 |
} |
| 317 |
|
| 318 |
int options = Playlist::AppendAndPlay; |
| 319 |
if( args->isSet( "queue" ) ) |
| 320 |
options = Playlist::Queue; |
| 321 |
else if( args->isSet( "append" ) ) |
| 322 |
options = Playlist::Append; |
| 323 |
else if( args->isSet( "load" ) ) |
| 324 |
options = Playlist::Replace; |
| 325 |
|
| 326 |
if( args->isSet( "play" ) ) |
| 327 |
options |= Playlist::DirectPlay; |
| 328 |
|
| 329 |
The::playlistController()->insertOptioned( list, options ); |
| 330 |
} |
| 331 |
|
| 332 |
//we shouldn't let the user specify two of these since it is pointless! |
| 333 |
//so we prioritise, pause > stop > play > next > prev |
| 334 |
//thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!) |
| 335 |
//then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log |
| 336 |
//I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it |
| 337 |
else if ( args->isSet( "pause" ) ) |
| 338 |
{ |
| 339 |
haveArgs = true; |
| 340 |
The::engineController()->pause(); |
| 341 |
} |
| 342 |
else if ( args->isSet( "stop" ) ) |
| 343 |
{ |
| 344 |
haveArgs = true; |
| 345 |
The::engineController()->stop(); |
| 346 |
} |
| 347 |
else if ( args->isSet( "play-pause" ) ) |
| 348 |
{ |
| 349 |
haveArgs = true; |
| 350 |
The::engineController()->playPause(); |
| 351 |
} |
| 352 |
else if ( args->isSet( "play" ) ) //will restart if we are playing |
| 353 |
{ |
| 354 |
haveArgs = true; |
| 355 |
The::engineController()->play(); |
| 356 |
} |
| 357 |
else if ( args->isSet( "next" ) ) |
| 358 |
{ |
| 359 |
haveArgs = true; |
| 360 |
The::playlistActions()->next(); |
| 361 |
} |
| 362 |
else if ( args->isSet( "previous" ) ) |
| 363 |
{ |
| 364 |
haveArgs = true; |
| 365 |
The::playlistActions()->back(); |
| 366 |
} |
| 367 |
/* |
| 368 |
else if (args->isSet("cdplay")) |
| 369 |
{ |
| 370 |
haveArgs = true; |
| 371 |
QString device = args->getOption("cdplay"); |
| 372 |
KUrl::List urls; |
| 373 |
if (The::engineController()->getAudioCDContents(device, urls)) |
| 374 |
{ |
| 375 |
Meta::TrackList tracks = CollectionManager::instance()->tracksForUrls( urls ); |
| 376 |
The::playlistController()->insertOptioned( tracks, Playlist::Replace|Playlist::DirectPlay ); |
| 377 |
} |
| 378 |
else |
| 379 |
{ // Default behaviour |
| 380 |
debug() << "Sorry, the engine does not support direct play from AudioCD..."; |
| 381 |
} |
| 382 |
} |
| 383 |
*/ |
| 384 |
|
| 385 |
static bool firstTime = true; |
| 386 |
const bool debugWasJustEnabled = !Amarok::config().readEntry( "Debug Enabled", false ) && args->isSet( "debug" ); |
| 387 |
const bool debugIsDisabled = !args->isSet( "debug" ); |
| 388 |
//allows debugging on OS X. Bundles have to be started with "open". Therefore it is not possible to pass an argument |
| 389 |
const bool forceDebug = Amarok::config().readEntry( "Force Debug", false ); |
| 390 |
|
| 391 |
|
| 392 |
Amarok::config().writeEntry( "Debug Enabled", forceDebug ? true : args->isSet( "debug" ) ); |
| 393 |
|
| 394 |
// Debug output will only work from this point on. If Amarok was run without debug output before, |
| 395 |
// then a part of the output (until this point) will be missing. Inform the user about this: |
| 396 |
if( debugWasJustEnabled || forceDebug ) |
| 397 |
{ |
| 398 |
debug() << "************************************************************************************************************"; |
| 399 |
debug() << "** DEBUGGING OUTPUT IS NOW ENABLED. PLEASE NOTE THAT YOU WILL ONLY SEE THE FULL OUTPUT ON THE NEXT START. **"; |
| 400 |
debug() << "************************************************************************************************************"; |
| 401 |
} |
| 402 |
else if( firstTime && debugIsDisabled ) |
| 403 |
{ |
| 404 |
Amarok::config().writeEntry( "Debug Enabled", true ); |
| 405 |
debug() << "**********************************************************************************************"; |
| 406 |
debug() << "** AMAROK WAS STARTED IN NORMAL MODE. IF YOU WANT TO SEE DEBUGGING INFORMATION, PLEASE USE: **"; |
| 407 |
debug() << "** amarok --debug **"; |
| 408 |
debug() << "**********************************************************************************************"; |
| 409 |
Amarok::config().writeEntry( "Debug Enabled", false ); |
| 410 |
} |
| 411 |
|
| 412 |
if( !firstTime && !haveArgs ) |
| 413 |
pApp->mainWindow()->activate(); |
| 414 |
firstTime = false; |
| 415 |
|
| 416 |
args->clear(); //free up memory |
| 417 |
} |
| 418 |
|
| 419 |
|
| 420 |
///////////////////////////////////////////////////////////////////////////////////// |
| 421 |
// INIT |
| 422 |
///////////////////////////////////////////////////////////////////////////////////// |
| 423 |
|
| 424 |
void |
| 425 |
App::initCliArgs( int argc, char *argv[] ) |
| 426 |
{ |
| 427 |
KCmdLineArgs::reset(); |
| 428 |
KCmdLineArgs::init( argc, argv, &::aboutData ); //calls KCmdLineArgs::addStdCmdLineOptions() |
| 429 |
initCliArgs(); |
| 430 |
} |
| 431 |
|
| 432 |
void |
| 433 |
App::initCliArgs() //static |
| 434 |
{ |
| 435 |
KCmdLineOptions options; |
| 436 |
|
| 437 |
options.add("+[URL(s)]", ki18n( "Files/URLs to open" )); |
| 438 |
options.add("r"); |
| 439 |
options.add("previous", ki18n( "Skip backwards in playlist" )); |
| 440 |
options.add("p"); |
| 441 |
options.add("play", ki18n( "Start playing current playlist" )); |
| 442 |
options.add("t"); |
| 443 |
options.add("play-pause", ki18n( "Play if stopped, pause if playing" )); |
| 444 |
options.add("pause", ki18n( "Pause playback" )); |
| 445 |
options.add("s"); |
| 446 |
options.add("stop", ki18n( "Stop playback" )); |
| 447 |
options.add("f"); |
| 448 |
options.add("next", ki18n( "Skip forwards in playlist" )); |
| 449 |
options.add(":", ki18n("Additional options:")); |
| 450 |
options.add("a"); |
| 451 |
options.add("append", ki18n( "Append files/URLs to playlist" )); |
| 452 |
options.add("queue", ki18n("Queue URLs after the currently playing track")); |
| 453 |
options.add("l"); |
| 454 |
options.add("load", ki18n("Load URLs, replacing current playlist")); |
| 455 |
options.add("d"); |
| 456 |
options.add("debug", ki18n("Print verbose debugging information")); |
| 457 |
options.add("m"); |
| 458 |
options.add("multipleinstances", ki18n("Allow running multiple Amarok instances")); |
| 459 |
options.add("cwd <directory>", ki18n( "Base for relative filenames/URLs" )); |
| 460 |
//options.add("cdplay <device>", ki18n("Play an AudioCD from <device> or system:/media/<device>")); |
| 461 |
|
| 462 |
KCmdLineArgs::addCmdLineOptions( options ); //add our own options |
| 463 |
} |
| 464 |
|
| 465 |
|
| 466 |
///////////////////////////////////////////////////////////////////////////////////// |
| 467 |
// METHODS |
| 468 |
///////////////////////////////////////////////////////////////////////////////////// |
| 469 |
|
| 470 |
#include <id3v1tag.h> |
| 471 |
#include <tbytevector.h> |
| 472 |
#include <QTextCodec> |
| 473 |
#include <KGlobal> |
| 474 |
|
| 475 |
//this class is only used in this module, so I figured I may as well define it |
| 476 |
//here and save creating another header/source file combination |
| 477 |
|
| 478 |
// Local version of taglib's QStringToTString macro. It is here, because taglib's one is |
| 479 |
// not Qt3Support clean (uses QString::utf8()). Once taglib will be clean of qt3support |
| 480 |
// it is safe to use QStringToTString again |
| 481 |
#define Qt4QStringToTString(s) TagLib::String(s.toUtf8().data(), TagLib::String::UTF8) |
| 482 |
|
| 483 |
class ID3v1StringHandler : public TagLib::ID3v1::StringHandler |
| 484 |
{ |
| 485 |
QTextCodec *m_codec; |
| 486 |
|
| 487 |
virtual TagLib::String parse( const TagLib::ByteVector &data ) const |
| 488 |
{ |
| 489 |
return Qt4QStringToTString( m_codec->toUnicode( data.data(), data.size() ) ); |
| 490 |
} |
| 491 |
|
| 492 |
virtual TagLib::ByteVector render( const TagLib::String &ts ) const |
| 493 |
{ |
| 494 |
const QByteArray qcs = m_codec->fromUnicode( TStringToQString(ts) ); |
| 495 |
return TagLib::ByteVector( qcs, (uint) qcs.length() ); |
| 496 |
} |
| 497 |
|
| 498 |
public: |
| 499 |
ID3v1StringHandler( int codecIndex ) |
| 500 |
: m_codec( QTextCodec::codecForName( QTextCodec::availableCodecs().at( codecIndex ) ) ) |
| 501 |
{ |
| 502 |
debug() << "codec: " << m_codec; |
| 503 |
debug() << "codec-name: " << m_codec->name(); |
| 504 |
} |
| 505 |
|
| 506 |
ID3v1StringHandler( QTextCodec *codec ) |
| 507 |
: m_codec( codec ) |
| 508 |
{ |
| 509 |
debug() << "codec: " << m_codec; |
| 510 |
debug() << "codec-name: " << m_codec->name(); |
| 511 |
} |
| 512 |
|
| 513 |
virtual ~ID3v1StringHandler() |
| 514 |
{} |
| 515 |
}; |
| 516 |
|
| 517 |
#undef Qt4QStringToTString |
| 518 |
|
| 519 |
//SLOT |
| 520 |
void App::applySettings( bool firstTime ) |
| 521 |
{ |
| 522 |
///Called when the configDialog is closed with OK or Apply |
| 523 |
|
| 524 |
DEBUG_BLOCK |
| 525 |
|
| 526 |
Amarok::OSD::instance()->applySettings(); |
| 527 |
m_tray->setVisible( AmarokConfig::showTrayIcon() ); |
| 528 |
|
| 529 |
//on startup we need to show the window, but only if it wasn't hidden on exit |
| 530 |
//and always if the trayicon isn't showing |
| 531 |
QWidget* main_window = mainWindow(); |
| 532 |
|
| 533 |
if( ( main_window && firstTime && !Amarok::config().readEntry( "HiddenOnExit", false ) ) || ( main_window && !AmarokConfig::showTrayIcon() ) ) |
| 534 |
{ |
| 535 |
PERF_LOG( "showing main window again" ) |
| 536 |
main_window->show(); |
| 537 |
PERF_LOG( "after showing mainWindow" ) |
| 538 |
} |
| 539 |
|
| 540 |
{ //<Engine> |
| 541 |
if( The::engineController()->volume() != AmarokConfig::masterVolume() ) |
| 542 |
{ |
| 543 |
// block signals to make sure that the OSD isn't shown |
| 544 |
const bool osdEnabled = Amarok::OSD::instance()->isEnabled(); |
| 545 |
Amarok::OSD::instance()->setEnabled( false ); |
| 546 |
The::engineController()->setVolume( AmarokConfig::masterVolume() ); |
| 547 |
Amarok::OSD::instance()->setEnabled( osdEnabled ); |
| 548 |
} |
| 549 |
|
| 550 |
if( The::engineController()->isMuted() != AmarokConfig::muteState() ) |
| 551 |
The::engineController()->setMuted( AmarokConfig::muteState() ); |
| 552 |
|
| 553 |
#if 0 |
| 554 |
// Audio CD is not currently supported |
| 555 |
Amarok::actionCollection()->action( "play_audiocd" )->setEnabled( false ); |
| 556 |
#endif |
| 557 |
|
| 558 |
} //</Engine> |
| 559 |
|
| 560 |
{ // delete unneeded cover images from cache |
| 561 |
PERF_LOG( "Begin cover handling" ) |
| 562 |
const QString size = QString::number( 100 ) + '@'; |
| 563 |
const QDir cacheDir = Amarok::saveLocation( "albumcovers/cache/" ); |
| 564 |
const QStringList obsoleteCovers = cacheDir.entryList( QStringList("*") ); |
| 565 |
foreach( const QString &it, obsoleteCovers ) |
| 566 |
//34@ is for playlist album cover images |
| 567 |
if ( !it.startsWith( size ) && !it.startsWith( "50@" ) && !it.startsWith( "32@" ) && !it.startsWith( "34@" ) ) |
| 568 |
QFile( cacheDir.filePath( it ) ).remove(); |
| 569 |
|
| 570 |
PERF_LOG( "done cover handling" ) |
| 571 |
} |
| 572 |
|
| 573 |
// show or hide CV |
| 574 |
if( mainWindow() ) |
| 575 |
mainWindow()->hideContextView( AmarokConfig::hideContextView() ); |
| 576 |
//if ( !firstTime ) |
| 577 |
// Bizarrely and ironically calling this causes crashes for |
| 578 |
// some people! FIXME |
| 579 |
//AmarokConfig::self()->writeConfig(); |
| 580 |
} |
| 581 |
|
| 582 |
//SLOT |
| 583 |
void |
| 584 |
App::continueInit() |
| 585 |
{ |
| 586 |
DEBUG_BLOCK |
| 587 |
|
| 588 |
PERF_LOG( "Begin App::continueInit" ) |
| 589 |
const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs(); |
| 590 |
const bool restoreSession = args->count() == 0 || args->isSet( "append" ) || args->isSet( "queue" ) |
| 591 |
|| Amarok::config().readEntry( "AppendAsDefault", false ); |
| 592 |
|
| 593 |
QTextCodec* utf8codec = QTextCodec::codecForName( "UTF-8" ); |
| 594 |
QTextCodec::setCodecForCStrings( utf8codec ); //We need this to make CollectionViewItem showing the right charecters. |
| 595 |
|
| 596 |
PERF_LOG( "Creating MainWindow" ) |
| 597 |
m_mainWindow = new MainWindow(); |
| 598 |
PERF_LOG( "Done creating MainWindow" ) |
| 599 |
|
| 600 |
m_tray = new Amarok::TrayIcon( mainWindow() ); |
| 601 |
|
| 602 |
PERF_LOG( "Creating DBus handlers" ) |
| 603 |
new Amarok::RootDBusHandler(); |
| 604 |
new Amarok::PlayerDBusHandler(); |
| 605 |
new Amarok::TracklistDBusHandler(); |
| 606 |
new CollectionDBusHandler( this ); |
| 607 |
QDBusConnection::sessionBus().registerService("org.mpris.amarok"); |
| 608 |
PERF_LOG( "Done creating DBus handlers" ) |
| 609 |
//DON'T DELETE THIS NEXT LINE or the app crashes when you click the X (unless we reimplement closeEvent) |
| 610 |
//Reason: in ~App we have to call the deleteBrowsers method or else we run afoul of refcount foobar in KHTMLPart |
| 611 |
//But if you click the X (not Action->Quit) it automatically kills MainWindow because KMainWindow sets this |
| 612 |
//for us as default (bad KMainWindow) |
| 613 |
mainWindow()->setAttribute( Qt::WA_DeleteOnClose, false ); |
| 614 |
//init playlist window as soon as the database is guaranteed to be usable |
| 615 |
|
| 616 |
//create engine, show TrayIcon etc. |
| 617 |
applySettings( true ); |
| 618 |
// Start ScriptManager. Must be created _after_ MainWindow. |
| 619 |
PERF_LOG( "Starting ScriptManager" ) |
| 620 |
ScriptManager::instance(); |
| 621 |
PERF_LOG( "ScriptManager started" ) |
| 622 |
|
| 623 |
if ( AmarokConfig::resumePlayback() && restoreSession && !args->isSet( "stop" ) ) { |
| 624 |
//restore session as long as the user didn't specify media to play etc. |
| 625 |
//do this after applySettings() so OSD displays correctly |
| 626 |
The::engineController()->restoreSession(); |
| 627 |
} |
| 628 |
|
| 629 |
if( AmarokConfig::monitorChanges() ) |
| 630 |
CollectionManager::instance()->checkCollectionChanges(); |
| 631 |
|
| 632 |
// Restore keyboard shortcuts etc from config |
| 633 |
Amarok::actionCollection()->readSettings(); |
| 634 |
|
| 635 |
delete m_splash; |
| 636 |
m_splash = 0; |
| 637 |
PERF_LOG( "App init done" ) |
| 638 |
KConfigGroup config = KGlobal::config()->group( "General" ); |
| 639 |
|
| 640 |
// NOTE: First Run Tutorial disabled for 2.1-beta1 release (too buggy / unfinished) |
| 641 |
#if 0 |
| 642 |
const bool firstruntut = config.readEntry( "FirstRunTutorial", true ); |
| 643 |
debug() << "Checking whether to run first run tutorial..." << firstruntut; |
| 644 |
if( firstruntut ) |
| 645 |
{ |
| 646 |
config.writeEntry( "FirstRunTutorial", false ); |
| 647 |
debug() << "Starting first run tutorial"; |
| 648 |
FirstRunTutorial *frt = new FirstRunTutorial( mainWindow() ); |
| 649 |
QTimer::singleShot( 1000, frt, SLOT( initOverlay() ) ); |
| 650 |
} |
| 651 |
|
| 652 |
if( config.readEntry( "First Run", true ) ) |
| 653 |
{ |
| 654 |
slotConfigAmarok( "CollectionConfig" ); |
| 655 |
config.writeEntry( "First Run", false ); |
| 656 |
} |
| 657 |
#endif |
| 658 |
} |
| 659 |
|
| 660 |
void App::slotConfigEqualizer() //SLOT |
| 661 |
{ |
| 662 |
// PORT 2.0 |
| 663 |
// EqualizerSetup::instance()->show(); |
| 664 |
// EqualizerSetup::instance()->raise(); |
| 665 |
} |
| 666 |
|
| 667 |
void App::slotConfigAmarok( const QString& page ) |
| 668 |
{ |
| 669 |
Amarok2ConfigDialog* dialog = static_cast<Amarok2ConfigDialog*>( KConfigDialog::exists( "settings" ) ); |
| 670 |
|
| 671 |
if( !dialog ) |
| 672 |
{ |
| 673 |
//KConfigDialog didn't find an instance of this dialog, so lets create it : |
| 674 |
dialog = new Amarok2ConfigDialog( mainWindow(), "settings", AmarokConfig::self() ); |
| 675 |
|
| 676 |
connect( dialog, SIGNAL( settingsChanged( const QString& ) ), SLOT( applySettings() ) ); |
| 677 |
} |
| 678 |
|
| 679 |
dialog->show( page ); |
| 680 |
} |
| 681 |
|
| 682 |
void App::slotConfigShortcuts() |
| 683 |
{ |
| 684 |
KShortcutsDialog::configure( Amarok::actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, mainWindow() ); |
| 685 |
AmarokConfig::self()->writeConfig(); |
| 686 |
} |
| 687 |
|
| 688 |
KIO::Job *App::trashFiles( const KUrl::List &files ) |
| 689 |
{ |
| 690 |
KIO::Job *job = KIO::trash( files ); |
| 691 |
The::statusBar()->newProgressOperation( job, i18n("Moving files to trash") ); |
| 692 |
connect( job, SIGNAL( result( KJob* ) ), this, SLOT( slotTrashResult( KJob* ) ) ); |
| 693 |
return job; |
| 694 |
} |
| 695 |
|
| 696 |
void App::slotTrashResult( KJob *job ) |
| 697 |
{ |
| 698 |
if( job->error() ) |
| 699 |
job->uiDelegate()->showErrorMessage(); |
| 700 |
} |
| 701 |
|
| 702 |
void App::quit() |
| 703 |
{ |
| 704 |
emit prepareToQuit(); |
| 705 |
/* |
| 706 |
if( MediaBrowser::instance() && MediaBrowser::instance()->blockQuit() ) |
| 707 |
{ |
| 708 |
// don't quit yet, as some media devices still have to finish transferring data |
| 709 |
QTimer::singleShot( 100, this, SLOT( quit() ) ); |
| 710 |
return; |
| 711 |
} |
| 712 |
*/ |
| 713 |
KApplication::quit(); |
| 714 |
} |
| 715 |
|
| 716 |
bool App::event( QEvent *event ) |
| 717 |
{ |
| 718 |
|
| 719 |
switch( event->type() ) |
| 720 |
{ |
| 721 |
//allows Amarok to open files from the finder on OS X |
| 722 |
case QEvent::FileOpen: |
| 723 |
{ |
| 724 |
QString file = static_cast<QFileOpenEvent*>( event )->file(); |
| 725 |
//we are only going to receive local files here |
| 726 |
KUrl url( file ); |
| 727 |
if( PlaylistManager::instance()->isPlaylist( url ) ) |
| 728 |
{ |
| 729 |
Meta::PlaylistPtr playlist = Meta::loadPlaylist( url ); |
| 730 |
The::playlistController()->insertOptioned( playlist, Playlist::AppendAndPlay ); |
| 731 |
} |
| 732 |
else |
| 733 |
{ |
| 734 |
Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url ); |
| 735 |
The::playlistController()->insertOptioned( track, Playlist::AppendAndPlay ); |
| 736 |
} |
| 737 |
return true; |
| 738 |
} |
| 739 |
default: |
| 740 |
return KUniqueApplication::event( event ); |
| 741 |
} |
| 742 |
} |
| 743 |
|
| 744 |
namespace Amarok |
| 745 |
{ |
| 746 |
/// @see amarok.h |
| 747 |
|
| 748 |
/* |
| 749 |
* Transform to be usable within HTML/HTML attributes |
| 750 |
*/ |
| 751 |
QString escapeHTMLAttr( const QString &s ) |
| 752 |
{ |
| 753 |
return QString(s).replace( '%', "%25" ).replace( '\'', "%27" ).replace( '"', "%22" ). |
| 754 |
replace( '#', "%23" ).replace( '?', "%3F" ); |
| 755 |
} |
| 756 |
QString unescapeHTMLAttr( const QString &s ) |
| 757 |
{ |
| 758 |
return QString(s).replace( "%3F", "?" ).replace( "%23", "#" ).replace( "%22", "\"" ). |
| 759 |
replace( "%27", "'" ).replace( "%25", "%" ); |
| 760 |
} |
| 761 |
|
| 762 |
/** |
| 763 |
* Function that must be used when separating contextBrowser escaped urls |
| 764 |
* detail can contain track/discnumber |
| 765 |
*/ |
| 766 |
void albumArtistTrackFromUrl( QString url, QString &artist, QString &album, QString &detail ) |
| 767 |
{ |
| 768 |
if ( !url.contains("@@@") ) return; |
| 769 |
//KHTML removes the trailing space! |
| 770 |
if ( url.endsWith( " @@@" ) ) |
| 771 |
url += ' '; |
| 772 |
|
| 773 |
const QStringList list = url.split( " @@@ ", QString::KeepEmptyParts ); |
| 774 |
|
| 775 |
int size = list.count(); |
| 776 |
|
| 777 |
if( size<=0 ) |
| 778 |
error() << "size<=0"; |
| 779 |
|
| 780 |
artist = size > 0 ? unescapeHTMLAttr( list[0] ) : ""; |
| 781 |
album = size > 1 ? unescapeHTMLAttr( list[1] ) : ""; |
| 782 |
detail = size > 2 ? unescapeHTMLAttr( list[2] ) : ""; |
| 783 |
} |
| 784 |
|
| 785 |
QString verboseTimeSince( const QDateTime &datetime ) |
| 786 |
{ |
| 787 |
const QDateTime now = QDateTime::currentDateTime(); |
| 788 |
const int datediff = datetime.daysTo( now ); |
| 789 |
|
| 790 |
if( datediff >= 6*7 /*six weeks*/ ) { // return absolute month/year |
| 791 |
const KCalendarSystem *cal = KGlobal::locale()->calendar(); |
| 792 |
const QDate date = datetime.date(); |
| 793 |
return i18nc( "monthname year", "%1 %2", cal->monthName(date), |
| 794 |
cal->yearString(date, KCalendarSystem::LongFormat) ); |
| 795 |
} |
| 796 |
|
| 797 |
//TODO "last week" = maybe within 7 days, but prolly before last sunday |
| 798 |
|
| 799 |
if( datediff >= 7 ) // return difference in weeks |
| 800 |
return i18np( "One week ago", "%1 weeks ago", (datediff+3)/7 ); |
| 801 |
|
| 802 |
if( datediff == -1 ) |
| 803 |
return i18nc( "When this track was last played", "Tomorrow" ); |
| 804 |
|
| 805 |
const int timediff = datetime.secsTo( now ); |
| 806 |
|
| 807 |
if( timediff >= 24*60*60 /*24 hours*/ ) // return difference in days |
| 808 |
return datediff == 1 ? |
| 809 |
i18n( "Yesterday" ) : |
| 810 |
i18np( "One day ago", "%1 days ago", (timediff+12*60*60)/(24*60*60) ); |
| 811 |
|
| 812 |
if( timediff >= 90*60 /*90 minutes*/ ) // return difference in hours |
| 813 |
return i18np( "One hour ago", "%1 hours ago", (timediff+30*60)/(60*60) ); |
| 814 |
|
| 815 |
//TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently" |
| 816 |
|
| 817 |
if( timediff >= 0 ) // return difference in minutes |
| 818 |
return timediff/60 ? |
| 819 |
i18np( "One minute ago", "%1 minutes ago", (timediff+30)/60 ) : |
| 820 |
i18n( "Within the last minute" ); |
| 821 |
|
| 822 |
return i18n( "The future" ); |
| 823 |
} |
| 824 |
|
| 825 |
QString verboseTimeSince( uint time_t ) |
| 826 |
{ |
| 827 |
if( !time_t ) |
| 828 |
return i18nc( "The amount of time since last played", "Never" ); |
| 829 |
|
| 830 |
QDateTime dt; |
| 831 |
dt.setTime_t( time_t ); |
| 832 |
return verboseTimeSince( dt ); |
| 833 |
} |
| 834 |
|
| 835 |
QString conciseTimeSince( uint time_t ) |
| 836 |
{ |
| 837 |
if( !time_t ) |
| 838 |
return i18nc( "The amount of time since last played", "0" ); |
| 839 |
|
| 840 |
QDateTime datetime; |
| 841 |
datetime.setTime_t( time_t ); |
| 842 |
|
| 843 |
const QDateTime now = QDateTime::currentDateTime(); |
| 844 |
const int datediff = datetime.daysTo( now ); |
| 845 |
|
| 846 |
if( datediff >= 6*7 /*six weeks*/ ) { // return difference in months |
| 847 |
return i18nc( "number of months ago", "%1M", datediff/7/4 ); |
| 848 |
} |
| 849 |
|
| 850 |
if( datediff >= 7 ) // return difference in weeks |
| 851 |
return i18nc( "w for weeks", "%1w", (datediff+3)/7 ); |
| 852 |
|
| 853 |
if( datediff == -1 ) |
| 854 |
return i18nc( "When this track was last played", "Tomorrow" ); |
| 855 |
|
| 856 |
const int timediff = datetime.secsTo( now ); |
| 857 |
|
| 858 |
if( timediff >= 24*60*60 /*24 hours*/ ) // return difference in days |
| 859 |
// xgettext: no-c-format |
| 860 |
return i18nc( "d for days", "%1d", (timediff+12*60*60)/(24*60*60) ); |
| 861 |
|
| 862 |
if( timediff >= 90*60 /*90 minutes*/ ) // return difference in hours |
| 863 |
return i18nc( "h for hours", "%1h", (timediff+30*60)/(60*60) ); |
| 864 |
|
| 865 |
//TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently" |
| 866 |
|
| 867 |
if( timediff >= 60 ) // return difference in minutes |
| 868 |
return QString("%1'").arg( ( timediff + 30 )/60 ); |
| 869 |
if( timediff >= 0 ) // return difference in seconds |
| 870 |
return QString("%1\"").arg( ( timediff + 1 )/60 ); |
| 871 |
|
| 872 |
return i18n( "0" ); |
| 873 |
|
| 874 |
} |
| 875 |
|
| 876 |
QWidget *mainWindow() |
| 877 |
{ |
| 878 |
return pApp->mainWindow(); |
| 879 |
} |
| 880 |
|
| 881 |
KActionCollection* actionCollection() // TODO: constify? |
| 882 |
{ |
| 883 |
if( !actionCollectionObject ) |
| 884 |
{ |
| 885 |
actionCollectionObject = new KActionCollection( pApp ); |
| 886 |
actionCollectionObject->setObjectName( "Amarok-KActionCollection" ); |
| 887 |
} |
| 888 |
|
| 889 |
return actionCollectionObject; |
| 890 |
} |
| 891 |
|
| 892 |
KConfigGroup config( const QString &group ) |
| 893 |
{ |
| 894 |
//Slightly more useful config() that allows setting the group simultaneously |
| 895 |
return KGlobal::config()->group( group ); |
| 896 |
} |
| 897 |
|
| 898 |
namespace ColorScheme |
| 899 |
{ |
| 900 |
QColor Base; |
| 901 |
QColor Text; |
| 902 |
QColor Background; |
| 903 |
QColor Foreground; |
| 904 |
QColor AltBase; |
| 905 |
} |
| 906 |
|
| 907 |
OverrideCursor::OverrideCursor( Qt::CursorShape cursor ) |
| 908 |
{ |
| 909 |
QApplication::setOverrideCursor( cursor == Qt::WaitCursor ? |
| 910 |
Qt::WaitCursor : |
| 911 |
Qt::BusyCursor ); |
| 912 |
} |
| 913 |
|
| 914 |
OverrideCursor::~OverrideCursor() |
| 915 |
{ |
| 916 |
QApplication::restoreOverrideCursor(); |
| 917 |
} |
| 918 |
|
| 919 |
QString saveLocation( const QString &directory ) |
| 920 |
{ |
| 921 |
globalDirsMutex.lock(); |
| 922 |
QString result = KGlobal::dirs()->saveLocation( "data", QString("amarok/") + directory, true ); |
| 923 |
globalDirsMutex.unlock(); |
| 924 |
return result; |
| 925 |
} |
| 926 |
|
| 927 |
QString cleanPath( const QString &path ) |
| 928 |
{ |
| 929 |
QString result = path; |
| 930 |
// german umlauts |
| 931 |
result.replace( QChar(0x00e4), "ae" ).replace( QChar(0x00c4), "Ae" ); |
| 932 |
result.replace( QChar(0x00f6), "oe" ).replace( QChar(0x00d6), "Oe" ); |
| 933 |
result.replace( QChar(0x00fc), "ue" ).replace( QChar(0x00dc), "Ue" ); |
| 934 |
result.replace( QChar(0x00df), "ss" ); |
| 935 |
|
| 936 |
// some strange accents |
| 937 |
result.replace( QChar(0x00e7), "c" ).replace( QChar(0x00c7), "C" ); |
| 938 |
result.replace( QChar(0x00fd), "y" ).replace( QChar(0x00dd), "Y" ); |
| 939 |
result.replace( QChar(0x00f1), "n" ).replace( QChar(0x00d1), "N" ); |
| 940 |
|
| 941 |
// czech letters with carons |
| 942 |
result.replace( QChar(0x0161), "s" ).replace( QChar(0x0160), "S" ); |
| 943 |
result.replace( QChar(0x010d), "c" ).replace( QChar(0x010c), "C" ); |
| 944 |
result.replace( QChar(0x0159), "r" ).replace( QChar(0x0158), "R" ); |
| 945 |
result.replace( QChar(0x017e), "z" ).replace( QChar(0x017d), "Z" ); |
| 946 |
result.replace( QChar(0x0165), "t" ).replace( QChar(0x0164), "T" ); |
| 947 |
result.replace( QChar(0x0148), "n" ).replace( QChar(0x0147), "N" ); |
| 948 |
result.replace( QChar(0x010f), "d" ).replace( QChar(0x010e), "D" ); |
| 949 |
|
| 950 |
// accented vowels |
| 951 |
QChar a[] = { 'a', 0xe0,0xe1,0xe2,0xe3,0xe5, 0 }; |
| 952 |
QChar A[] = { 'A', 0xc0,0xc1,0xc2,0xc3,0xc5, 0 }; |
| 953 |
QChar E[] = { 'e', 0xe8,0xe9,0xea,0xeb,0x11a, 0 }; |
| 954 |
QChar e[] = { 'E', 0xc8,0xc9,0xca,0xcb,0x11b, 0 }; |
| 955 |
QChar i[] = { 'i', 0xec,0xed,0xee,0xef, 0 }; |
| 956 |
QChar I[] = { 'I', 0xcc,0xcd,0xce,0xcf, 0 }; |
| 957 |
QChar o[] = { 'o', 0xf2,0xf3,0xf4,0xf5,0xf8, 0 }; |
| 958 |
QChar O[] = { 'O', 0xd2,0xd3,0xd4,0xd5,0xd8, 0 }; |
| 959 |
QChar u[] = { 'u', 0xf9,0xfa,0xfb,0x16e, 0 }; |
| 960 |
QChar U[] = { 'U', 0xd9,0xda,0xdb,0x16f, 0 }; |
| 961 |
QChar nul[] = { 0 }; |
| 962 |
QChar *replacements[] = { a, A, e, E, i, I, o, O, u, U, nul }; |
| 963 |
|
| 964 |
for( int i = 0; i < result.length(); i++ ) |
| 965 |
{ |
| 966 |
QChar c = result[ i ]; |
| 967 |
for( uint n = 0; replacements[n][0] != QChar(0); n++ ) |
| 968 |
{ |
| 969 |
for( uint k=0; replacements[n][k] != QChar(0); k++ ) |
| 970 |
{ |
| 971 |
if( replacements[n][k] == c ) |
| 972 |
{ |
| 973 |
c = replacements[n][0]; |
| 974 |
} |
| 975 |
} |
| 976 |
} |
| 977 |
result[ i ] = c; |
| 978 |
} |
| 979 |
return result; |
| 980 |
} |
| 981 |
|
| 982 |
QString asciiPath( const QString &path ) |
| 983 |
{ |
| 984 |
QString result = path; |
| 985 |
for( int i = 0; i < result.length(); i++ ) |
| 986 |
{ |
| 987 |
QChar c = result[ i ]; |
| 988 |
if( c > QChar(0x7f) || c == QChar(0) ) |
| 989 |
{ |
| 990 |
c = '_'; |
| 991 |
} |
| 992 |
result[ i ] = c; |
| 993 |
} |
| 994 |
return result; |
| 995 |
} |
| 996 |
|
| 997 |
QString vfatPath( const QString &path ) |
| 998 |
{ |
| 999 |
QString s = path; |
| 1000 |
|
| 1001 |
for( int i = 0; i < s.length(); i++ ) |
| 1002 |
{ |
| 1003 |
QChar c = s[ i ]; |
| 1004 |
if( c < QChar(0x20) |
| 1005 |
|| c=='*' || c=='?' || c=='<' || c=='>' |
| 1006 |
|| c=='|' || c=='"' || c==':' || c=='/' |
| 1007 |
|| c=='\\' ) |
| 1008 |
c = '_'; |
| 1009 |
s[ i ] = c; |
| 1010 |
} |
| 1011 |
|
| 1012 |
uint len = s.length(); |
| 1013 |
if( len == 3 || (len > 3 && s[3] == '.') ) |
| 1014 |
{ |
| 1015 |
QString l = s.left(3).toLower(); |
| 1016 |
if( l=="aux" || l=="con" || l=="nul" || l=="prn" ) |
| 1017 |
s = '_' + s; |
| 1018 |
} |
| 1019 |
else if( len == 4 || (len > 4 && s[4] == '.') ) |
| 1020 |
{ |
| 1021 |
QString l = s.left(3).toLower(); |
| 1022 |
QString d = s.mid(3,1); |
| 1023 |
if( (l=="com" || l=="lpt") && |
| 1024 |
(d=="0" || d=="1" || d=="2" || d=="3" || d=="4" || |
| 1025 |
d=="5" || d=="6" || d=="7" || d=="8" || d=="9") ) |
| 1026 |
s = '_' + s; |
| 1027 |
} |
| 1028 |
|
| 1029 |
while( s.startsWith( '.' ) ) |
| 1030 |
s = s.mid(1); |
| 1031 |
|
| 1032 |
while( s.endsWith( '.' ) ) |
| 1033 |
s = s.left( s.length()-1 ); |
| 1034 |
|
| 1035 |
s = s.left(255); |
| 1036 |
len = s.length(); |
| 1037 |
if( s[len-1] == ' ' ) |
| 1038 |
s[len-1] = '_'; |
| 1039 |
|
| 1040 |
return s; |
| 1041 |
} |
| 1042 |
|
| 1043 |
/* Strip the common prefix of two strings from the first one and trim |
| 1044 |
* whitespaces from the beginning of the resultant string. |
| 1045 |
* Case-insensitive. |
| 1046 |
* |
| 1047 |
* @param input the string being processed |
| 1048 |
* @param ref the string used to determine prefix |
| 1049 |
*/ |
| 1050 |
QString decapitateString( const QString &input, const QString &ref ) |
| 1051 |
{ |
| 1052 |
int len; //the length of common prefix calculated so far |
| 1053 |
for ( len = 0; len < input.length() && len < ref.length(); len++ ) |
| 1054 |
{ |
| 1055 |
if ( input.at( len ).toUpper() != ref.at( len ).toUpper() ) |
| 1056 |
break; |
| 1057 |
} |
| 1058 |
|
| 1059 |
return input.right( input.length() - len ).trimmed(); |
| 1060 |
} |
| 1061 |
|
| 1062 |
KIO::Job *trashFiles( const KUrl::List &files ) { return App::instance()->trashFiles( files ); } |
| 1063 |
} |
| 1064 |
|
| 1065 |
int App::newInstance() |
| 1066 |
{ |
| 1067 |
DEBUG_BLOCK |
| 1068 |
|
| 1069 |
static bool first = true; |
| 1070 |
if ( isSessionRestored() && first ) |
| 1071 |
{ |
| 1072 |
first = false; |
| 1073 |
return 0; |
| 1074 |
} |
| 1075 |
|
| 1076 |
first = false; |
| 1077 |
|
| 1078 |
handleCliArgs(); |
| 1079 |
return 0; |
| 1080 |
} |
| 1081 |
|
| 1082 |
|
| 1083 |
#include "App.moc" |