diff --git a/libk3b/projects/datacd/k3bdiritem.cpp b/libk3b/projects/datacd/k3bdiritem.cpp index 24ae5b869..2d3382ec8 100644 --- a/libk3b/projects/datacd/k3bdiritem.cpp +++ b/libk3b/projects/datacd/k3bdiritem.cpp @@ -1,528 +1,528 @@ /* * * Copyright (C) 2003 Sebastian Trueg * Copyright (C) 2011 Michal Malek * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include "k3bdiritem.h" #include "k3bdatadoc.h" #include "k3bsessionimportitem.h" #include "k3bfileitem.h" #include "k3bisooptions.h" #include "k3b_i18n.h" #include #include K3b::DirItem::DirItem(const QString& name, const ItemFlags& flags) : K3b::DataItem( flags | DIR ), m_size(0), m_followSymlinksSize(0), m_blocks(0), m_followSymlinksBlocks(0), m_files(0), m_dirs(0) { m_k3bName = name; } K3b::DirItem::DirItem( const K3b::DirItem& item ) : K3b::DataItem( item ), m_size(0), m_followSymlinksSize(0), m_blocks(0), m_followSymlinksBlocks(0), m_files(0), m_dirs(0), m_localPath( item.m_localPath ) { Q_FOREACH( K3b::DataItem* _item, item.children() ) { addDataItem( _item->copy() ); } } K3b::DirItem::~DirItem() { // delete all children // doing this by hand is much saver than using the // auto-delete feature since some of the items' destructors // may change the list while( !m_children.isEmpty() ) { // it is important to use takeDataItem here to be sure // the size gets updated properly K3b::DataItem* item = m_children.first(); takeDataItem( item ); delete item; } // this has to be done after deleting the children // because the directory itself has a size of 0 in K3b // and all it's files' sizes have already been subtracted take(); } K3b::DataItem* K3b::DirItem::copy() const { return new K3b::DirItem( *this ); } K3b::DirItem* K3b::DirItem::getDirItem() const { return const_cast(this); } K3b::DirItem* K3b::DirItem::addDataItem( K3b::DataItem* item ) { if( canAddDataItem( item ) ) { // Detach item from its parent in case it's moved from elsewhere. // It is essential to do this before calling getDoc()->aboutToAddItemToDir() // avoid situation when beginRemoveRows() is called after beginInsertRows() // in DataProjectModel item->take(); // inform the doc if( DataDoc* doc = getDoc() ) { doc->beginInsertItems( this, m_children.size(), m_children.size() ); } addDataItemImpl( item ); if( DataDoc* doc = getDoc() ) { doc->endInsertItems( this, m_children.size()-1, m_children.size()-1 ); } } return this; } void K3b::DirItem::addDataItems( const Children& items ) { Children newItems; newItems.reserve( items.size() ); Q_FOREACH( DataItem* item, items ) { if( canAddDataItem( item ) ) { // Detach item from its parent in case it's moved from elsewhere. // It is essential to do this before calling getDoc()->aboutToAddItemToDir() // avoid situation when beginRemoveRows() is called after beginInsertRows() // in DataProjectModel item->take(); newItems.push_back( item ); } } if( !newItems.empty() ) { const int start = m_children.size(); const int end = m_children.size() + newItems.size() - 1; // inform the doc if( DataDoc* doc = getDoc() ) { doc->beginInsertItems( this, start, end ); } // pre-alloc space for items m_children.reserve( m_children.size() + newItems.size() ); Q_FOREACH( DataItem* item, newItems ) { addDataItemImpl( item ); } if( DataDoc* doc = getDoc() ) { doc->endInsertItems( this, start, end ); } } } void K3b::DirItem::removeDataItems( int start, int count ) { Children takenItems = takeDataItems( start, count ); qDeleteAll( takenItems ); } K3b::DataItem* K3b::DirItem::takeDataItem( K3b::DataItem* item ) { int i = m_children.lastIndexOf( item ); if( i > -1 ) { takeDataItems( i, 1 ); return item; } else { return 0; } } K3b::DirItem::Children K3b::DirItem::takeDataItems( int start, int count ) { Children takenItems; if( start >= 0 && count > 0 ) { if( DataDoc* doc = getDoc() ) { doc->beginRemoveItems( this, start, start+count-1 ); } for( int i = 0; i < count; ++i ) { DataItem* item = m_children.at( start+i ); updateSize( item, true ); if( item->isDir() ) updateFiles( -1*((DirItem*)item)->numFiles(), -1*((DirItem*)item)->numDirs()-1 ); else updateFiles( -1, 0 ); item->setParentDir( 0 ); // unset OLD_SESSION flag if it was the last child from previous sessions updateOldSessionFlag(); takenItems.append( item ); } // filling the gap: move items from after removed range - qCopy( m_children.begin()+start+count, m_children.end(), + std::copy( m_children.begin()+start+count, m_children.end(), m_children.begin()+start ); // remove unused space for( int i = 0; i < count; ++i ) { m_children.pop_back(); } // inform the doc if( DataDoc* doc = getDoc() ) { doc->endRemoveItems( this, start, start+count-1 ); } Q_FOREACH( DataItem* item, takenItems ) { if( item->isFile() ) { // restore the item imported from an old session if( DataItem* replaceItem = static_cast(item)->replaceItemFromOldSession() ) addDataItem( replaceItem ); } } } return takenItems; } K3b::DataItem* K3b::DirItem::nextSibling() const { if( !m_children.isEmpty() ) return m_children.first(); else return K3b::DataItem::nextSibling(); } K3b::DataItem* K3b::DirItem::nextChild( K3b::DataItem* prev ) const { // search for prev in children int index = m_children.lastIndexOf( prev ); if( index < 0 || index+1 == m_children.count() ) { return 0; } else return m_children[index+1]; } bool K3b::DirItem::alreadyInDirectory( const QString& filename ) const { return (find( filename ) != 0); } K3b::DataItem* K3b::DirItem::find( const QString& filename ) const { Q_FOREACH( K3b::DataItem* item, m_children ) { if( item->k3bName() == filename ) return item; } return 0; } K3b::DataItem* K3b::DirItem::findByPath( const QString& p ) { if( p.isEmpty() || p == "/" ) return this; QString path = p; if( path.startsWith('/') ) path = path.mid(1); int pos = path.indexOf( "/" ); if( pos < 0 ) return find( path ); else { // do it recursivly K3b::DataItem* item = find( path.left(pos) ); if( item && item->isDir() ) return ((K3b::DirItem*)item)->findByPath( path.mid( pos+1 ) ); else return 0; } } bool K3b::DirItem::mkdir( const QString& dirPath ) { // // An absolut path always starts at the root item // if( dirPath[0] == '/' ) { if( parent() ) return parent()->mkdir( dirPath ); else return mkdir( dirPath.mid( 1 ) ); } if( findByPath( dirPath ) ) return false; QString restPath; QString dirName; int pos = dirPath.indexOf( '/' ); if( pos == -1 ) { dirName = dirPath; } else { dirName = dirPath.left( pos ); restPath = dirPath.mid( pos+1 ); } K3b::DataItem* dir = find( dirName ); if( !dir ) { dir = new K3b::DirItem( dirName ); addDataItem( dir ); } else if( !dir->isDir() ) { return false; } if( !restPath.isEmpty() ) return static_cast(dir)->mkdir( restPath ); return true; } KIO::filesize_t K3b::DirItem::itemSize( bool followsylinks ) const { if( followsylinks ) return m_followSymlinksSize; else return m_size; } K3b::Msf K3b::DirItem::itemBlocks( bool followSymlinks ) const { if( followSymlinks ) return m_followSymlinksBlocks; else return m_blocks; } bool K3b::DirItem::isSubItem( const DataItem* item ) const { for( const DirItem* dir = dynamic_cast(item); dir != 0; dir = dir->parent() ) { if( dir == this ) { return true; } } return false; } long K3b::DirItem::numFiles() const { return m_files; } long K3b::DirItem::numDirs() const { return m_dirs; } bool K3b::DirItem::isRemoveable() const { if( !K3b::DataItem::isRemoveable() ) return false; for( Children::const_iterator it = m_children.constBegin(), end = m_children.constEnd(); it != end; ++it ) { if( !( *it )->isRemoveable() ) return false; } return true; } void K3b::DirItem::updateSize( K3b::DataItem* item, bool removed ) { if ( !item->isFromOldSession() ) { if( removed ) { m_followSymlinksSize -= item->itemSize( true ); m_size -= item->itemSize( false ); m_followSymlinksBlocks -= item->itemBlocks( true ).lba(); m_blocks -= item->itemBlocks( false ).lba(); } else { m_followSymlinksSize += item->itemSize( true ); m_size += item->itemSize( false ); m_followSymlinksBlocks += item->itemBlocks( true ).lba(); m_blocks += item->itemBlocks( false ).lba(); } } if( parent() ) parent()->updateSize( item, removed ); } void K3b::DirItem::updateFiles( long files, long dirs ) { m_files += files; m_dirs += dirs; if( parent() ) parent()->updateFiles( files, dirs ); } void K3b::DirItem::updateOldSessionFlag() { if( flags().testFlag( OLD_SESSION ) ) { for( Children::const_iterator it = m_children.constBegin(), end = m_children.constEnd(); it != end; ++it ) { if( (*it)->isFromOldSession() ) { return; } } setFlags( flags() & ~OLD_SESSION ); } } bool K3b::DirItem::writeToCd() const { // check if this dir contains items to write Children::const_iterator end( m_children.constEnd() ); for( Children::const_iterator it = m_children.constBegin(); it != end; ++it ) { if( (*it)->writeToCd() ) return true; } return K3b::DataItem::writeToCd(); } QMimeType K3b::DirItem::mimeType() const { return QMimeDatabase().mimeTypeForName( "inode/directory" ); } bool K3b::DirItem::canAddDataItem( DataItem* item ) const { // check if we are a subdir of item DirItem* dirItem = dynamic_cast( item ); if( dirItem && dirItem->isSubItem( this ) ) { qDebug() << "(K3b::DirItem) trying to move a dir item down in it's own tree."; return false; } else if( !item || m_children.contains( item ) ) { return false; } else { return true; } } void K3b::DirItem::addDataItemImpl( DataItem* item ) { if( item->isFile() ) { // do we replace an old item? QString name = item->k3bName(); int cnt = 1; while( DataItem* oldItem = find( name ) ) { if( !oldItem->isDir() && oldItem->isFromOldSession() ) { // in this case we remove this item from it's parent and save it in the new one // to be able to recover it oldItem->take(); static_cast(oldItem)->setReplaceItem( static_cast(item) ); static_cast(item)->setReplacedItemFromOldSession( oldItem ); break; } else { // // add a counter to the filename // if( item->k3bName()[item->k3bName().length()-4] == '.' ) name = item->k3bName().left( item->k3bName().length()-4 ) + QString::number(cnt++) + item->k3bName().right(4); else name = item->k3bName() + QString::number(cnt++); } } item->setK3bName( name ); } m_children.append( item ); updateSize( item, false ); if( item->isDir() ) updateFiles( ((DirItem*)item)->numFiles(), ((DirItem*)item)->numDirs()+1 ); else updateFiles( 1, 0 ); item->setParentDir( this ); // If item is from previous session,flag this directory as such also if( !isFromOldSession() && item->isFromOldSession() ) { setFlags( flags() | OLD_SESSION ); } } K3b::RootItem::RootItem( K3b::DataDoc& doc ) : K3b::DirItem( "root", 0 ), m_doc( doc ) { } K3b::RootItem::~RootItem() { } K3b::DataDoc* K3b::RootItem::getDoc() const { return &m_doc; } QString K3b::RootItem::k3bName() const { return m_doc.isoOptions().volumeID(); } void K3b::RootItem::setK3bName( const QString& text ) { m_doc.setVolumeID( text ); } diff --git a/libk3b/tools/qprocess/k3bqprocess.cpp b/libk3b/tools/qprocess/k3bqprocess.cpp index d41294358..583c4c030 100644 --- a/libk3b/tools/qprocess/k3bqprocess.cpp +++ b/libk3b/tools/qprocess/k3bqprocess.cpp @@ -1,1944 +1,1944 @@ /**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ //#define QPROCESS_DEBUG #if defined QPROCESS_DEBUG #include #include #include #if !defined(Q_OS_WINCE) #include #endif //QT_BEGIN_NAMESPACE /* Returns a human readable representation of the first \a len characters in \a data. */ static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) { if (!data) return "(null)"; QByteArray out; for (int i = 0; i < len && i < maxSize; ++i) { char c = data[i]; if (isprint(c)) { out += c; } else switch (c) { case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: char buf[5]; qsnprintf(buf, sizeof(buf), "\\%3o", c); buf[4] = '\0'; out += QByteArray(buf); } } if (len < maxSize) out += "..."; return out; } //QT_END_NAMESPACE #endif #include "k3bqprocess.h" #include "k3bqprocess_p.h" #include #include #include #include #include #ifdef Q_WS_WIN #include #endif #ifndef QT_NO_PROCESS //QT_BEGIN_NAMESPACE void K3bQProcessPrivate::Channel::clear() { switch (type) { case PipeSource: Q_ASSERT(process); process->stdinChannel.type = Normal; process->stdinChannel.process = 0; break; case PipeSink: Q_ASSERT(process); process->stdoutChannel.type = Normal; process->stdoutChannel.process = 0; break; } type = Normal; file.clear(); process = 0; } /*! \class QProcess \brief The QProcess class is used to start external programs and to communicate with them. \ingroup io \ingroup misc \mainclass \reentrant To start a process, pass the name and command line arguments of the program you want to run as arguments to start(). For example: \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 0 \dots \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 1 \snippet doc/src/snippets/qprocess/qprocess-simpleexecution.cpp 2 QProcess then enters the \l Starting state, and when the program has started, QProcess enters the \l Running state and emits started(). QProcess allows you to treat a process as a sequential I/O device. You can write to and read from the process just as you would access a network connection using QTcpSocket. You can then write to the process's standard input by calling write(), and read the standard output by calling read(), readLine(), and getChar(). Because it inherits QIODevice, QProcess can also be used as an input source for QXmlReader, or for generating data to be uploaded using QFtp. \note On Windows CE, reading and writing to a process is not supported. When the process exits, QProcess reenters the \l NotRunning state (the initial state), and emits finished(). The finished() signal provides the exit code and exit status of the process as arguments, and you can also call exitCode() to obtain the exit code of the last process that finished, and exitStatus() to obtain its exit status. If an error occurs at any point in time, QProcess will emit the error() signal. You can also call error() to find the type of error that occurred last, and state() to find the current process state. \section1 Communicating via Channels Processes have two predefined output channels: The standard output channel (\c stdout) supplies regular console output, and the standard error channel (\c stderr) usually supplies the errors that are printed by the process. These channels represent two separate streams of data. You can toggle between them by calling setReadChannel(). QProcess emits readyRead() when data is available on the current read channel. It also emits readyReadStandardOutput() when new standard output data is available, and when new standard error data is available, readyReadStandardError() is emitted. Instead of calling read(), readLine(), or getChar(), you can explicitly read all data from either of the two channels by calling readAllStandardOutput() or readAllStandardError(). The terminology for the channels can be misleading. Be aware that the process's output channels correspond to QProcess's \e read channels, whereas the process's input channels correspond to QProcess's \e write channels. This is because what we read using QProcess is the process's output, and what we write becomes the process's input. QProcess can merge the two output channels, so that standard output and standard error data from the running process both use the standard output channel. Call setProcessChannelMode() with MergedChannels before starting the process to activative this feature. You also have the option of forwarding the output of the running process to the calling, main process, by passing ForwardedChannels as the argument. Certain processes need special environment settings in order to operate. You can set environment variables for your process by calling setEnvironment(). To set a working directory, call setWorkingDirectory(). By default, processes are run in the current working directory of the calling process. \section1 Synchronous Process API QProcess provides a set of functions which allow it to be used without an event loop, by suspending the calling thread until certain signals are emitted: \list \o waitForStarted() blocks until the process has started. \o waitForReadyRead() blocks until new data is available for reading on the current read channel. \o waitForBytesWritten() blocks until one payload of data has been written to the process. \o waitForFinished() blocks until the process has finished. \endlist Calling these functions from the main thread (the thread that calls QApplication::exec()) may cause your user interface to freeze. The following example runs \c gzip to compress the string "Qt rocks!", without an event loop: \snippet doc/src/snippets/process/process.cpp 0 \section1 Notes for Windows Users Some Windows commands (for example, \c dir) are not provided by separate applications, but by the command interpreter itself. If you attempt to use QProcess to execute these commands directly, it won't work. One possible solution is to execute the command interpreter itself (\c{cmd.exe} on some Windows systems), and ask the interpreter to execute the desired command. \sa QBuffer, QFile, QTcpSocket */ /*! \enum QProcess::ProcessChannel This enum describes the process channels used by the running process. Pass one of these values to setReadChannel() to set the current read channel of QProcess. \value StandardOutput The standard output (stdout) of the running process. \value StandardError The standard error (stderr) of the running process. \sa setReadChannel() */ /*! \enum QProcess::ProcessChannelMode This enum describes the process channel modes of QProcess. Pass one of these values to setProcessChannelMode() to set the current read channel mode. \value SeparateChannels QProcess manages the output of the running process, keeping standard output and standard error data in separate internal buffers. You can select the QProcess's current read channel by calling setReadChannel(). This is the default channel mode of QProcess. \value MergedChannels QProcess merges the output of the running process into the standard output channel (\c stdout). The standard error channel (\c stderr) will not receive any data. The standard output and standard error data of the running process are interleaved. \value ForwardedChannels QProcess forwards the output of the running process onto the main process. Anything the child process writes to its standard output and standard error will be written to the standard output and standard error of the main process. \sa setReadChannelMode() */ /*! \enum QProcess::ProcessError This enum describes the different types of errors that are reported by QProcess. \value FailedToStart The process failed to start. Either the invoked program is missing, or you may have insufficient permissions to invoke the program. \value Crashed The process crashed some time after starting successfully. \value Timedout The last waitFor...() function timed out. The state of QProcess is unchanged, and you can try calling waitFor...() again. \value WriteError An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. \value ReadError An error occurred when attempting to read from the process. For example, the process may not be running. \value UnknownError An unknown error occurred. This is the default return value of error(). \sa error() */ /*! \enum QProcess::ProcessState This enum describes the different states of QProcess. \value NotRunning The process is not running. \value Starting The process is starting, but the program has not yet been invoked. \value Running The process is running and is ready for reading and writing. \sa state() */ /*! \enum QProcess::ExitStatus This enum describes the different exit statuses of QProcess. \value NormalExit The process exited normally. \value CrashExit The process crashed. \sa exitStatus() */ /*! \fn void QProcess::error(QProcess::ProcessError error) This signal is emitted when an error occurs with the process. The specified \a error describes the type of error that occurred. */ /*! \fn void QProcess::started() This signal is emitted by QProcess when the process has started, and state() returns \l Running. */ /*! \fn void QProcess::stateChanged(QProcess::ProcessState newState) This signal is emitted whenever the state of QProcess changes. The \a newState argument is the state QProcess changed to. */ /*! \fn void QProcess::finished(int exitCode) \obsolete \overload Use finished(int exitCode, QProcess::ExitStatus status) instead. */ /*! \fn void QProcess::finished(int exitCode, QProcess::ExitStatus exitStatus) This signal is emitted when the process finishes. \a exitCode is the exit code of the process, and \a exitStatus is the exit status. After the process has finished, the buffers in QProcess are still intact. You can still read any data that the process may have written before it finished. \sa exitStatus() */ /*! \fn void QProcess::readyReadStandardOutput() This signal is emitted when the process has made new data available through its standard output channel (\c stdout). It is emitted regardless of the current \l{readChannel()}{read channel}. \sa readAllStandardOutput(), readChannel() */ /*! \fn void QProcess::readyReadStandardError() This signal is emitted when the process has made new data available through its standard error channel (\c stderr). It is emitted regardless of the current \l{readChannel()}{read channel}. \sa readAllStandardError(), readChannel() */ /*! \internal */ K3bQProcessPrivate::K3bQProcessPrivate() { processChannel = ::QProcess::StandardOutput; processChannelMode = ::QProcess::SeparateChannels; processError = ::QProcess::UnknownError; processState = ::QProcess::NotRunning; pid = 0; sequenceNumber = 0; exitCode = 0; exitStatus = ::QProcess::NormalExit; startupSocketNotifier = 0; deathNotifier = 0; notifier = 0; pipeWriter = 0; childStartedPipe[0] = INVALID_Q_PIPE; childStartedPipe[1] = INVALID_Q_PIPE; deathPipe[0] = INVALID_Q_PIPE; deathPipe[1] = INVALID_Q_PIPE; exitCode = 0; crashed = false; dying = false; emittedReadyRead = false; emittedBytesWritten = false; #ifdef Q_WS_WIN pipeWriter = 0; processFinishedNotifier = 0; #endif // Q_WS_WIN #ifdef Q_OS_UNIX serial = 0; #endif } /*! \internal */ K3bQProcessPrivate::~K3bQProcessPrivate() { if (stdinChannel.process) stdinChannel.process->stdoutChannel.clear(); if (stdoutChannel.process) stdoutChannel.process->stdinChannel.clear(); } /*! \internal */ void qDeleteInEventHandler(QObject *o); void K3bQProcessPrivate::cleanup() { q_func()->setProcessState(::QProcess::NotRunning); #ifdef Q_OS_WIN if (pid) { CloseHandle(pid->hThread); CloseHandle(pid->hProcess); delete pid; pid = 0; } if (processFinishedNotifier) { processFinishedNotifier->setEnabled(false); qDeleteInEventHandler(processFinishedNotifier); processFinishedNotifier = 0; } #endif pid = 0; sequenceNumber = 0; dying = false; if (stdoutChannel.notifier) { stdoutChannel.notifier->setEnabled(false); delete stdoutChannel.notifier; stdoutChannel.notifier = 0; } if (stderrChannel.notifier) { stderrChannel.notifier->setEnabled(false); delete stderrChannel.notifier; stderrChannel.notifier = 0; } if (stdinChannel.notifier) { stdinChannel.notifier->setEnabled(false); delete stdinChannel.notifier; stdinChannel.notifier = 0; } if (startupSocketNotifier) { startupSocketNotifier->setEnabled(false); delete startupSocketNotifier; startupSocketNotifier = 0; } if (deathNotifier) { deathNotifier->setEnabled(false); delete deathNotifier; deathNotifier = 0; } if (notifier) { delete notifier; notifier = 0; } destroyPipe(stdoutChannel.pipe); destroyPipe(stderrChannel.pipe); destroyPipe(stdinChannel.pipe); destroyPipe(childStartedPipe); destroyPipe(deathPipe); #ifdef Q_OS_UNIX serial = 0; #endif } /*! \internal */ bool K3bQProcessPrivate::_q_canReadStandardOutput() { Q_Q(K3bQProcess); qint64 available = bytesAvailableFromStdout(); if (available == 0) { if (stdoutChannel.notifier) stdoutChannel.notifier->setEnabled(false); destroyPipe(stdoutChannel.pipe); #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::canReadStandardOutput(), 0 bytes available"); #endif return false; } if (!(processFlags & K3bQProcess::RawStdout)) { char *ptr = outputReadBuffer.reserve(available); qint64 readBytes = readFromStdout(ptr, available); if (readBytes == -1) { processError = ::QProcess::ReadError; q->setErrorString(K3bQProcess::tr("Error reading from process")); emit q->error(processError); #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::canReadStandardOutput(), failed to read from the process"); #endif return false; } #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::canReadStandardOutput(), read %d bytes from the process' output", int(readBytes)); #endif if (stdoutChannel.closed) { outputReadBuffer.chop(readBytes); return false; } outputReadBuffer.chop(available - readBytes); bool didRead = false; if (readBytes == 0) { if (stdoutChannel.notifier) stdoutChannel.notifier->setEnabled(false); } else if (processChannel == ::QProcess::StandardOutput) { didRead = true; if (!emittedReadyRead) { emittedReadyRead = true; emit q->readyRead(); emittedReadyRead = false; } } emit q->readyReadStandardOutput(); return didRead; } else { if (!emittedReadyRead) { emittedReadyRead = true; emit q->readyRead(); emittedReadyRead = false; } emit q->readyReadStandardOutput(); return true; } } /*! \internal */ bool K3bQProcessPrivate::_q_canReadStandardError() { Q_Q(K3bQProcess); qint64 available = bytesAvailableFromStderr(); if (available == 0) { if (stderrChannel.notifier) stderrChannel.notifier->setEnabled(false); destroyPipe(stderrChannel.pipe); return false; } char *ptr = errorReadBuffer.reserve(available); qint64 readBytes = readFromStderr(ptr, available); if (readBytes == -1) { processError = ::QProcess::ReadError; q->setErrorString(K3bQProcess::tr("Error reading from process")); emit q->error(processError); return false; } if (stderrChannel.closed) { errorReadBuffer.chop(readBytes); return false; } errorReadBuffer.chop(available - readBytes); bool didRead = false; if (readBytes == 0) { if (stderrChannel.notifier) stderrChannel.notifier->setEnabled(false); } else if (processChannel == ::QProcess::StandardError) { didRead = true; if (!emittedReadyRead) { emittedReadyRead = true; emit q->readyRead(); emittedReadyRead = false; } } emit q->readyReadStandardError(); return didRead; } /*! \internal */ bool K3bQProcessPrivate::_q_canWrite() { Q_Q(K3bQProcess); if (processFlags & K3bQProcess::RawStdin) { if (stdinChannel.notifier) stdinChannel.notifier->setEnabled(false); isReadyWrite = true; emit q->readyWrite(); } else { if (stdinChannel.notifier) stdinChannel.notifier->setEnabled(false); if (writeBuffer.isEmpty()) { #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::canWrite(), not writing anything (empty write buffer)."); #endif return false; } qint64 written = writeToStdin(writeBuffer.readPointer(), writeBuffer.nextDataBlockSize()); if (written < 0) { destroyPipe(stdinChannel.pipe); processError = ::QProcess::WriteError; q->setErrorString(K3bQProcess::tr("Error writing to process")); #if defined(QPROCESS_DEBUG) && !defined(Q_OS_WINCE) qDebug("K3bQProcessPrivate::canWrite(), failed to write (%s)", strerror(errno)); #endif emit q->error(processError); return false; } #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::canWrite(), wrote %d bytes to the process input", int(written)); #endif writeBuffer.free(written); if (!emittedBytesWritten) { emittedBytesWritten = true; emit q->bytesWritten(written); emittedBytesWritten = false; } if (stdinChannel.notifier && !writeBuffer.isEmpty()) stdinChannel.notifier->setEnabled(true); if (writeBuffer.isEmpty() && stdinChannel.closed) closeWriteChannel(); } return true; } /*! \internal */ bool K3bQProcessPrivate::_q_processDied() { #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::_q_processDied()"); #endif #ifdef Q_OS_UNIX if (!waitForDeadChild()) return false; #endif #ifdef Q_OS_WIN if (processFinishedNotifier) processFinishedNotifier->setEnabled(false); #endif // the process may have died before it got a chance to report that it was // either running or stopped, so we will call _q_startupNotification() and // give it a chance to emit started() or error(FailedToStart). if (processState == ::QProcess::Starting) { if (!_q_startupNotification()) return true; } return _q_notifyProcessDied(); } bool K3bQProcessPrivate::_q_notifyProcessDied() { Q_Q(K3bQProcess); #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::_q_notifyProcessDied()"); #endif if ( processFlags&K3bQProcess::RawStdout ) { qint64 bytes = bytesAvailableFromStdout(); #if defined QPROCESS_DEBUG qDebug() << "bytesAvailableFromStdout:" << bytes; #endif // wait for all data to be read if ( bytes > 0 ) { QMetaObject::invokeMethod( q, "_q_notifyProcessDied", Qt::QueuedConnection ); return false; } } if (dying) { // at this point we know the process is dead. prevent // reentering this slot recursively by calling waitForFinished() // or opening a dialog inside slots connected to the readyRead // signals emitted below. return true; } dying = true; // in case there is data in the pipe line and this slot by chance // got called before the read notifications, call these two slots // so the data is made available before the process dies. if ( !processFlags.testFlag( K3bQProcess::RawStdout ) ) { _q_canReadStandardOutput(); } _q_canReadStandardError(); findExitCode(); if (crashed) { exitStatus = ::QProcess::CrashExit; processError = ::QProcess::Crashed; q->setErrorString(K3bQProcess::tr("Process crashed")); emit q->error(processError); } bool wasRunning = (processState == ::QProcess::Running); cleanup(); if (wasRunning) { // we received EOF now: emit q->readChannelFinished(); // in the future: //emit q->standardOutputClosed(); //emit q->standardErrorClosed(); emit q->finished(exitCode); emit q->finished(exitCode, exitStatus); } #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::_q_notifyProcessDied() process is dead"); #endif return true; } /*! \internal */ bool K3bQProcessPrivate::_q_startupNotification() { Q_Q(K3bQProcess); #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::startupNotification()"); #endif if (startupSocketNotifier) startupSocketNotifier->setEnabled(false); if (processStarted()) { q->setProcessState(::QProcess::Running); emit q->started(); return true; } q->setProcessState(::QProcess::NotRunning); processError = ::QProcess::FailedToStart; emit q->error(processError); #ifdef Q_OS_UNIX // make sure the process manager removes this entry waitForDeadChild(); findExitCode(); #endif cleanup(); return false; } /*! \internal */ void K3bQProcessPrivate::closeWriteChannel() { #if defined QPROCESS_DEBUG qDebug("K3bQProcessPrivate::closeWriteChannel()"); #endif if (stdinChannel.notifier) { stdinChannel.notifier->setEnabled(false); if (stdinChannel.notifier) { delete stdinChannel.notifier; stdinChannel.notifier = 0; } } #ifdef Q_OS_WIN // ### Find a better fix, feeding the process little by little // instead. flushPipeWriter(); #endif destroyPipe(stdinChannel.pipe); } qint64 K3bQProcessPrivate::readData( char *data, qint64 maxlen, QProcess::ProcessChannel channel ) { if (processFlags&K3bQProcess::RawStdout && channel == ::QProcess::StandardOutput) { return readFromStdout(data, maxlen); } else { QRingBuffer *readBuffer = (channel == ::QProcess::StandardError) ? &errorReadBuffer : &outputReadBuffer; if (maxlen == 1 && !readBuffer->isEmpty()) { int c = readBuffer->getChar(); if (c == -1) { #if defined QPROCESS_DEBUG qDebug("QProcess::readData(%p \"%s\", %d) == -1", data, qt_prettyDebug(data, 1, maxlen).constData(), 1); #endif return -1; } *data = (char) c; #if defined QPROCESS_DEBUG qDebug("QProcess::readData(%p \"%s\", %d) == 1", data, qt_prettyDebug(data, 1, maxlen).constData(), 1); #endif return 1; } qint64 bytesToRead = qint64(qMin(readBuffer->size(), (int)maxlen)); qint64 readSoFar = 0; while (readSoFar < bytesToRead) { const char *ptr = readBuffer->readPointer(); int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, readBuffer->nextDataBlockSize()); memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); readSoFar += bytesToReadFromThisBlock; readBuffer->free(bytesToReadFromThisBlock); } #if defined QPROCESS_DEBUG qDebug("QProcess::readData(%p \"%s\", %lld) == %lld", data, qt_prettyDebug(data, readSoFar, 16).constData(), maxlen, readSoFar); #endif if (!readSoFar && processState == ::QProcess::NotRunning) return -1; // EOF return readSoFar; } } /*! Constructs a QProcess object with the given \a parent. */ K3bQProcess::K3bQProcess(QObject *parent) : QIODevice(parent), d_ptr( new K3bQProcessPrivate ) { d_ptr->q_ptr = this; //#if defined QPROCESS_DEBUG qDebug("K3bQProcess::QProcess(%p)", parent); //#endif } /*! Destructs the QProcess object, i.e., killing the process. Note that this function will not return until the process is terminated. */ K3bQProcess::~K3bQProcess() { Q_D(K3bQProcess); if (d->processState != ::QProcess::NotRunning) { qWarning("QProcess: Destroyed while process is still running."); kill(); waitForFinished(); } #ifdef Q_OS_UNIX // make sure the process manager removes this entry d->findExitCode(); #endif d->cleanup(); delete d; } K3bQProcess::ProcessFlags K3bQProcess::flags() const { Q_D(const K3bQProcess); return d->processFlags; } void K3bQProcess::setFlags( K3bQProcess::ProcessFlags flags ) { Q_D(K3bQProcess); d->processFlags = flags; } /*! \obsolete Returns the read channel mode of the QProcess. This function is equivalent to processChannelMode() \sa processChannelMode() */ ::QProcess::ProcessChannelMode K3bQProcess::readChannelMode() const { return processChannelMode(); } /*! \obsolete Use setProcessChannelMode(\a mode) instead. \sa setProcessChannelMode() */ void K3bQProcess::setReadChannelMode(::QProcess::ProcessChannelMode mode) { setProcessChannelMode(mode); } /*! \since 4.2 Returns the channel mode of the QProcess standard output and standard error channels. \sa setReadChannelMode(), ProcessChannelMode, setReadChannel() */ ::QProcess::ProcessChannelMode K3bQProcess::processChannelMode() const { Q_D(const K3bQProcess); return d->processChannelMode; } /*! \since 4.2 Sets the channel mode of the QProcess standard output and standard error channels to the \a mode specified. This mode will be used the next time start() is called. For example: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 0 \sa readChannelMode(), ProcessChannelMode, setReadChannel() */ void K3bQProcess::setProcessChannelMode(::QProcess::ProcessChannelMode mode) { Q_D(K3bQProcess); d->processChannelMode = mode; } /*! Returns the current read channel of the QProcess. \sa setReadChannel() */ QProcess::ProcessChannel K3bQProcess::readChannel() const { Q_D(const K3bQProcess); return d->processChannel; } /*! Sets the current read channel of the QProcess to the given \a channel. The current input channel is used by the functions read(), readAll(), readLine(), and getChar(). It also determines which channel triggers QProcess to emit readyRead(). \sa readChannel() */ void K3bQProcess::setReadChannel(::QProcess::ProcessChannel channel) { Q_D(K3bQProcess); // if (d->processChannel != channel) { // QByteArray buf = d->buffer.readAll(); // if (d->processChannel == QProcess::StandardOutput) { // for (int i = buf.size() - 1; i >= 0; --i) // d->outputReadBuffer.ungetChar(buf.at(i)); // } else { // for (int i = buf.size() - 1; i >= 0; --i) // d->errorReadBuffer.ungetChar(buf.at(i)); // } // } d->processChannel = channel; } /*! Closes the read channel \a channel. After calling this function, QProcess will no longer receive data on the channel. Any data that has already been received is still available for reading. Call this function to save memory, if you are not interested in the output of the process. \sa closeWriteChannel(), setReadChannel() */ void K3bQProcess::closeReadChannel(::QProcess::ProcessChannel channel) { Q_D(K3bQProcess); if (channel == ::QProcess::StandardOutput) { d->stdoutChannel.closed = true; if ( d->processFlags&RawStdout ) d->destroyPipe(d->stdoutChannel.pipe); } else d->stderrChannel.closed = true; } /*! Schedules the write channel of QProcess to be closed. The channel will close once all data has been written to the process. After calling this function, any attempts to write to the process will fail. Closing the write channel is necessary for programs that read input data until the channel has been closed. For example, the program "more" is used to display text data in a console on both Unix and Windows. But it will not display the text data until QProcess's write channel has been closed. Example: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 1 The write channel is implicitly opened when start() is called. \sa closeReadChannel() */ void K3bQProcess::closeWriteChannel() { Q_D(K3bQProcess); d->stdinChannel.closed = true; // closing if (d->writeBuffer.isEmpty()) d->closeWriteChannel(); } /*! \since 4.2 Redirects the process' standard input to the file indicated by \a fileName. When an input redirection is in place, the QProcess object will be in read-only mode (calling write() will result in error). If the file \a fileName does not exist at the moment start() is called or is not readable, starting the process will fail. Calling setStandardInputFile() after the process has started has no effect. \sa setStandardOutputFile(), setStandardErrorFile(), setStandardOutputProcess() */ void K3bQProcess::setStandardInputFile(const QString &fileName) { Q_D(K3bQProcess); d->stdinChannel = fileName; } /*! \since 4.2 Redirects the process' standard output to the file \a fileName. When the redirection is in place, the standard output read channel is closed: reading from it using read() will always fail, as will readAllStandardOutput(). If the file \a fileName doesn't exist at the moment start() is called, it will be created. If it cannot be created, the starting will fail. If the file exists and \a mode is QIODevice::Truncate, the file will be truncated. Otherwise (if \a mode is QIODevice::Append), the file will be appended to. Calling setStandardOutputFile() after the process has started has no effect. \sa setStandardInputFile(), setStandardErrorFile(), setStandardOutputProcess() */ void K3bQProcess::setStandardOutputFile(const QString &fileName, OpenMode mode) { Q_ASSERT(mode == Append || mode == Truncate); Q_D(K3bQProcess); d->stdoutChannel = fileName; d->stdoutChannel.append = mode == Append; } /*! \since 4.2 Redirects the process' standard error to the file \a fileName. When the redirection is in place, the standard error read channel is closed: reading from it using read() will always fail, as will readAllStandardError(). The file will be appended to if \a mode is Append, otherwise, it will be truncated. See setStandardOutputFile() for more information on how the file is opened. Note: if setProcessChannelMode() was called with an argument of QProcess::MergedChannels, this function has no effect. \sa setStandardInputFile(), setStandardOutputFile(), setStandardOutputProcess() */ void K3bQProcess::setStandardErrorFile(const QString &fileName, OpenMode mode) { Q_ASSERT(mode == Append || mode == Truncate); Q_D(K3bQProcess); d->stderrChannel = fileName; d->stderrChannel.append = mode == Append; } /*! \since 4.2 Pipes the standard output stream of this process to the \a destination process' standard input. The following shell command: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 2 Can be accomplished with QProcesses with the following code: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 3 */ void K3bQProcess::setStandardOutputProcess(K3bQProcess *destination) { K3bQProcessPrivate *dfrom = d_func(); K3bQProcessPrivate *dto = destination->d_func(); dfrom->stdoutChannel.pipeTo(dto); dto->stdinChannel.pipeFrom(dfrom); } /*! If QProcess has been assigned a working directory, this function returns the working directory that the QProcess will enter before the program has started. Otherwise, (i.e., no directory has been assigned,) an empty string is returned, and QProcess will use the application's current working directory instead. \sa setWorkingDirectory() */ QString K3bQProcess::workingDirectory() const { Q_D(const K3bQProcess); return d->workingDirectory; } /*! Sets the working directory to \a dir. QProcess will start the process in this directory. The default behavior is to start the process in the working directory of the calling process. \sa workingDirectory(), start() */ void K3bQProcess::setWorkingDirectory(const QString &dir) { Q_D(K3bQProcess); d->workingDirectory = dir; } /*! Returns the native process identifier for the running process, if available. If no process is currently running, 0 is returned. */ Q_PID K3bQProcess::pid() const { Q_D(const K3bQProcess); return d->pid; } /*! \reimp This function operates on the current read channel. \sa readChannel(), setReadChannel() */ bool K3bQProcess::canReadLine() const { Q_D(const K3bQProcess); const QRingBuffer *readBuffer = (d->processChannel == ::QProcess::StandardError) ? &d->errorReadBuffer : &d->outputReadBuffer; return readBuffer->canReadLine() || QIODevice::canReadLine(); } /*! Closes all communication with the process and kills it. After calling this function, QProcess will no longer emit readyRead(), and data can no longer be read or written. */ void K3bQProcess::close() { emit aboutToClose(); while (waitForBytesWritten(-1)) ; kill(); waitForFinished(-1); QIODevice::close(); } /*! \reimp Returns true if the process is not running, and no more data is available for reading; otherwise returns false. */ bool K3bQProcess::atEnd() const { Q_D(const K3bQProcess); const QRingBuffer *readBuffer = (d->processChannel == ::QProcess::StandardError) ? &d->errorReadBuffer : &d->outputReadBuffer; return QIODevice::atEnd() && (!isOpen() || readBuffer->isEmpty()); } /*! \reimp */ bool K3bQProcess::isSequential() const { return true; } /*! \reimp */ qint64 K3bQProcess::bytesAvailable() const { Q_D(const K3bQProcess); const QRingBuffer *readBuffer = (d->processChannel == ::QProcess::StandardError) ? &d->errorReadBuffer : &d->outputReadBuffer; #if defined QPROCESS_DEBUG qDebug("QProcess::bytesAvailable() == %i (%s)", readBuffer->size(), (d->processChannel == ::QProcess::StandardError) ? "stderr" : "stdout"); #endif return readBuffer->size() + QIODevice::bytesAvailable(); } /*! \reimp */ qint64 K3bQProcess::bytesToWrite() const { Q_D(const K3bQProcess); qint64 size = d->writeBuffer.size(); #ifdef Q_OS_WIN size += d->pipeWriterBytesToWrite(); #endif return size; } /*! Returns the type of error that occurred last. \sa state() */ ::QProcess::ProcessError K3bQProcess::error() const { Q_D(const K3bQProcess); return d->processError; } /*! Returns the current state of the process. \sa stateChanged(), error() */ ::QProcess::ProcessState K3bQProcess::state() const { Q_D(const K3bQProcess); return d->processState; } /*! Sets the environment that QProcess will use when starting a process to the \a environment specified which consists of a list of key=value pairs. For example, the following code adds the \c{C:\\BIN} directory to the list of executable paths (\c{PATHS}) on Windows: \snippet doc/src/snippets/qprocess-environment/main.cpp 0 \sa environment(), systemEnvironment() */ void K3bQProcess::setEnvironment(const QStringList &environment) { Q_D(K3bQProcess); d->environment = environment; } /*! Returns the environment that QProcess will use when starting a process, or an empty QStringList if no environment has been set using setEnvironment(). If no environment has been set, the environment of the calling process will be used. \note The environment settings are ignored on Windows CE, as there is no concept of an environment. \sa setEnvironment(), systemEnvironment() */ QStringList K3bQProcess::environment() const { Q_D(const K3bQProcess); return d->environment; } /*! Blocks until the process has started and the started() signal has been emitted, or until \a msecs milliseconds have passed. Returns true if the process was started successfully; otherwise returns false (if the operation timed out or if an error occurred). This function can operate without an event loop. It is useful when writing non-GUI applications and when performing I/O operations in a non-GUI thread. \warning Calling this function from the main (GUI) thread might cause your user interface to freeze. If msecs is -1, this function will not time out. \sa started(), waitForReadyRead(), waitForBytesWritten(), waitForFinished() */ bool K3bQProcess::waitForStarted(int msecs) { Q_D(K3bQProcess); if (d->processState == ::QProcess::Starting) { if (!d->waitForStarted(msecs)) return false; setProcessState(::QProcess::Running); emit started(); } return d->processState == ::QProcess::Running; } /*! \reimp */ bool K3bQProcess::waitForReadyRead(int msecs) { Q_D(K3bQProcess); if (d->processState == ::QProcess::NotRunning) return false; if (d->processChannel == ::QProcess::StandardOutput && d->stdoutChannel.closed) return false; if (d->processChannel == ::QProcess::StandardError && d->stderrChannel.closed) return false; return d->waitForReadyRead(msecs); } /*! \reimp */ bool K3bQProcess::waitForBytesWritten(int msecs) { Q_D(K3bQProcess); if (d->processState == ::QProcess::NotRunning) return false; if (d->processState == ::QProcess::Starting) { QTime stopWatch; stopWatch.start(); bool started = waitForStarted(msecs); if (!started) return false; if (msecs != -1) msecs -= stopWatch.elapsed(); } return d->waitForBytesWritten(msecs); } /*! Blocks until the process has finished and the finished() signal has been emitted, or until \a msecs milliseconds have passed. Returns true if the process finished; otherwise returns false (if the operation timed out or if an error occurred). This function can operate without an event loop. It is useful when writing non-GUI applications and when performing I/O operations in a non-GUI thread. \warning Calling this function from the main (GUI) thread might cause your user interface to freeze. If msecs is -1, this function will not time out. \sa finished(), waitForStarted(), waitForReadyRead(), waitForBytesWritten() */ bool K3bQProcess::waitForFinished(int msecs) { Q_D(K3bQProcess); if (d->processState == ::QProcess::NotRunning) return false; if (d->processState == ::QProcess::Starting) { QTime stopWatch; stopWatch.start(); bool started = waitForStarted(msecs); if (!started) return false; if (msecs != -1) msecs -= stopWatch.elapsed(); } return d->waitForFinished(msecs); } /*! Sets the current state of the QProcess to the \a state specified. \sa state() */ void K3bQProcess::setProcessState(::QProcess::ProcessState state) { Q_D(K3bQProcess); if (d->processState == state) return; d->processState = state; emit stateChanged(state); } /*! This function is called in the child process context just before the program is executed on Unix or Mac OS X (i.e., after \e fork(), but before \e execve()). Reimplement this function to do last minute initialization of the child process. Example: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 4 You cannot exit the process (by calling exit(), for instance) from this function. If you need to stop the program before it starts execution, your workaround is to emit finished() and then call exit(). \warning This function is called by QProcess on Unix and Mac OS X only. On Windows, it is not called. */ void K3bQProcess::setupChildProcess() { } /*! \reimp */ qint64 K3bQProcess::readData(char *data, qint64 maxlen) { Q_D(K3bQProcess); return d->readData( data, maxlen, d->processChannel ); } /*! \reimp */ qint64 K3bQProcess::writeData(const char *data, qint64 len) { Q_D(K3bQProcess); #if defined(Q_OS_WINCE) Q_UNUSED(data); Q_UNUSED(len); d->processError = ::QProcess::WriteError; setErrorString(tr("Error writing to process")); emit error(d->processError); return -1; #endif if (d->stdinChannel.closed) { #if defined QPROCESS_DEBUG qDebug("QProcess::writeData(%p \"%s\", %lld) == 0 (write channel closing)", data, qt_prettyDebug(data, len, 16).constData(), len); #endif return 0; } if (d->processFlags & K3bQProcess::RawStdin) { d->waitForBytesWritten(); qint64 r = d->writeToStdin(data, len); if ( r > 0 ) emit bytesWritten(r); return r; } else { if (len == 1) { d->writeBuffer.putChar(*data); if (d->stdinChannel.notifier) d->stdinChannel.notifier->setEnabled(true); #if defined QPROCESS_DEBUG qDebug("QProcess::writeData(%p \"%s\", %lld) == 1 (written to buffer)", data, qt_prettyDebug(data, len, 16).constData(), len); #endif return 1; } char *dest = d->writeBuffer.reserve(len); memcpy(dest, data, len); if (d->stdinChannel.notifier) d->stdinChannel.notifier->setEnabled(true); #if defined QPROCESS_DEBUG qDebug("QProcess::writeData(%p \"%s\", %lld) == %lld (written to buffer)", data, qt_prettyDebug(data, len, 16).constData(), len, len); #endif return len; } } /*! Regardless of the current read channel, this function returns all data available from the standard output of the process as a QByteArray. \sa readyReadStandardOutput(), readAllStandardError(), readChannel(), setReadChannel() */ QByteArray K3bQProcess::readAllStandardOutput() { Q_D(K3bQProcess); if (!(d->processFlags&RawStdout)) { ::QProcess::ProcessChannel tmp = readChannel(); setReadChannel(::QProcess::StandardOutput); QByteArray data = readAll(); setReadChannel(tmp); return data; } else { return QByteArray(); } } /*! Regardless of the current read channel, this function returns all data available from the standard error of the process as a QByteArray. \sa readyReadStandardError(), readAllStandardOutput(), readChannel(), setReadChannel() */ QByteArray K3bQProcess::readAllStandardError() { Q_D(K3bQProcess); if (d->processFlags&RawStdout) { // // HACK: this is an ugly hack to get around the following problem: // K3b uses QProcess from different threads. This is no problem unless // the read channel is changed here while the other thread tries to read // from stdout. It will then result in two reads from stderr instead // (this one and the other thread which originally wanted to read from // stdout). // The "solution" atm is to reimplement QIODevice::readAll here, ignoring its // buffer (no real problem since K3b::Process is always opened Unbuffered) // QByteArray tmp; tmp.resize(int(d->errorReadBuffer.size())); qint64 readBytes = d->readData(tmp.data(), tmp.size(), QProcess::StandardError); tmp.resize(readBytes < 0 ? 0 : int(readBytes)); return tmp; } else { ::QProcess::ProcessChannel tmp = readChannel(); setReadChannel(::QProcess::StandardError); QByteArray data = readAll(); setReadChannel(tmp); return data; } } /*! Starts the program \a program in a new process, passing the command line arguments in \a arguments. The OpenMode is set to \a mode. QProcess will immediately enter the Starting state. If the process starts successfully, QProcess will emit started(); otherwise, error() will be emitted. Note that arguments that contain spaces are not passed to the process as separate arguments. \bold{Windows:} Arguments that contain spaces are wrapped in quotes. \note Processes are started asynchronously, which means the started() and error() signals may be delayed. Call waitForStarted() to make sure the process has started (or has failed to start) and those signals have been emitted. \sa pid(), started(), waitForStarted() */ void K3bQProcess::start(const QString &program, const QStringList &arguments, OpenMode mode) { Q_D(K3bQProcess); if (d->processState != ::QProcess::NotRunning) { qWarning("QProcess::start: Process is already running"); return; } #if defined QPROCESS_DEBUG qDebug() << "QProcess::start(" << program << "," << arguments << "," << mode << ")"; #endif d->outputReadBuffer.clear(); d->errorReadBuffer.clear(); d->isReadyWrite = false; if (d->stdinChannel.type != K3bQProcessPrivate::Channel::Normal) mode &= ~WriteOnly; // not open for writing if (d->stdoutChannel.type != K3bQProcessPrivate::Channel::Normal && (d->stderrChannel.type != K3bQProcessPrivate::Channel::Normal || d->processChannelMode == ::QProcess::MergedChannels)) mode &= ~ReadOnly; // not open for reading if (mode == 0) mode = Unbuffered; QIODevice::open(mode); d->stdinChannel.closed = false; d->stdoutChannel.closed = false; d->stderrChannel.closed = false; d->program = program; d->arguments = arguments; d->exitCode = 0; d->exitStatus = ::QProcess::NormalExit; d->processError = ::QProcess::UnknownError; setErrorString( QString() ); d->startProcess(); } static QStringList parseCombinedArgString(const QString &program) { QStringList args; QString tmp; int quoteCount = 0; bool inQuote = false; // handle quoting. tokens can be surrounded by double quotes // "hello world". three consecutive double quotes represent // the quote character itself. for (int i = 0; i < program.size(); ++i) { if (program.at(i) == QLatin1Char('"')) { ++quoteCount; if (quoteCount == 3) { // third consecutive quote quoteCount = 0; tmp += program.at(i); } continue; } if (quoteCount) { if (quoteCount == 1) inQuote = !inQuote; quoteCount = 0; } if (!inQuote && program.at(i).isSpace()) { if (!tmp.isEmpty()) { args += tmp; tmp.clear(); } } else { tmp += program.at(i); } } if (!tmp.isEmpty()) args += tmp; return args; } /*! \overload Starts the program \a program in a new process. \a program is a single string of text containing both the program name and its arguments. The arguments are separated by one or more spaces. For example: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 5 The \a program string can also contain quotes, to ensure that arguments containing spaces are correctly supplied to the new process. For example: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 6 Note that, on Windows, quotes need to be both escaped and quoted. For example, the above code would be specified in the following way to ensure that \c{"My Documents"} is used as the argument to the \c dir executable: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 7 The OpenMode is set to \a mode. */ void K3bQProcess::start(const QString &program, OpenMode mode) { QStringList args = parseCombinedArgString(program); QString prog = args.first(); args.removeFirst(); start(prog, args, mode); } /*! Attempts to terminate the process. The process may not exit as a result of calling this function (it is given the chance to prompt the user for any unsaved files, etc). On Windows, terminate() posts a WM_CLOSE message to all toplevel windows of the process and then to the main thread of the process itself. On Unix and Mac OS X the SIGTERM signal is sent. Console applications on Windows that do not run an event loop, or whose event loop does not handle the WM_CLOSE message, can only be terminated by calling kill(). \sa kill() */ void K3bQProcess::terminate() { Q_D(K3bQProcess); d->terminateProcess(); } /*! Kills the current process, causing it to exit immediately. On Windows, kill() uses TerminateProcess, and on Unix and Mac OS X, the SIGKILL signal is sent to the process. \sa terminate() */ void K3bQProcess::kill() { Q_D(K3bQProcess); d->killProcess(); } /*! Returns the exit code of the last process that finished. */ int K3bQProcess::exitCode() const { Q_D(const K3bQProcess); return d->exitCode; } /*! \since 4.1 Returns the exit status of the last process that finished. On Windows, if the process was terminated with TerminateProcess() from another application this function will still return NormalExit unless the exit code is less than 0. */ ::QProcess::ExitStatus K3bQProcess::exitStatus() const { Q_D(const K3bQProcess); return d->exitStatus; } /*! Starts the program \a program with the arguments \a arguments in a new process, waits for it to finish, and then returns the exit code of the process. Any data the new process writes to the console is forwarded to the calling process. The environment and working directory are inherited by the calling process. On Windows, arguments that contain spaces are wrapped in quotes. */ int K3bQProcess::execute(const QString &program, const QStringList &arguments) { QProcess process; - process.setReadChannelMode(::QProcess::ForwardedChannels); + process.setProcessChannelMode(::QProcess::ForwardedChannels); process.start(program, arguments); process.waitForFinished(-1); return process.exitCode(); } /*! \overload Starts the program \a program in a new process. \a program is a single string of text containing both the program name and its arguments. The arguments are separated by one or more spaces. */ int K3bQProcess::execute(const QString &program) { QProcess process; - process.setReadChannelMode(::QProcess::ForwardedChannels); + process.setProcessChannelMode(::QProcess::ForwardedChannels); process.start(program); process.waitForFinished(-1); return process.exitCode(); } /*! Starts the program \a program with the arguments \a arguments in a new process, and detaches from it. Returns true on success; otherwise returns false. If the calling process exits, the detached process will continue to live. Note that arguments that contain spaces are not passed to the process as separate arguments. \bold{Unix:} The started process will run in its own session and act like a daemon. \bold{Windows:} Arguments that contain spaces are wrapped in quotes. The started process will run as a regular standalone process. The process will be started in the directory \a workingDirectory. If the function is successful then *\a pid is set to the process identifier of the started process. */ bool K3bQProcess::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid) { return K3bQProcessPrivate::startDetached(program, arguments, workingDirectory, pid); } /*! Starts the program \a program with the given \a arguments in a new process, and detaches from it. Returns true on success; otherwise returns false. If the calling process exits, the detached process will continue to live. Note that arguments that contain spaces are not passed to the process as separate arguments. \bold{Unix:} The started process will run in its own session and act like a daemon. \bold{Windows:} Arguments that contain spaces are wrapped in quotes. The started process will run as a regular standalone process. */ bool K3bQProcess::startDetached(const QString &program, const QStringList &arguments) { return K3bQProcessPrivate::startDetached(program, arguments); } /*! \overload Starts the program \a program in a new process. \a program is a single string of text containing both the program name and its arguments. The arguments are separated by one or more spaces. The \a program string can also contain quotes, to ensure that arguments containing spaces are correctly supplied to the new process. */ bool K3bQProcess::startDetached(const QString &program) { QStringList args = parseCombinedArgString(program); QString prog = args.first(); args.removeFirst(); return K3bQProcessPrivate::startDetached(prog, args); } QT_BEGIN_INCLUDE_NAMESPACE #ifdef Q_OS_MAC # include # define environ (*_NSGetEnviron()) #elif defined(Q_OS_WINCE) static char *qt_wince_environ[] = { 0 }; #define environ qt_wince_environ #elif !defined(Q_OS_WIN) extern char **environ; #endif QT_END_INCLUDE_NAMESPACE /*! \since 4.1 Returns the environment of the calling process as a list of key=value pairs. Example: \snippet doc/src/snippets/code/src_corelib_io_qprocess.cpp 8 \sa environment(), setEnvironment() */ QStringList K3bQProcess::systemEnvironment() { QStringList tmp; // char *entry = 0; // int count = 0; // while ((entry = environ[count++])) // tmp << QString::fromLocal8Bit(entry); return tmp; } bool K3bQProcess::isReadyWrite() const { Q_D(const K3bQProcess); return d->isReadyWrite; } /*! \typedef Q_PID \relates QProcess Typedef for the identifiers used to represent processes on the underlying platform. On Unix, this corresponds to \l qint64; on Windows, it corresponds to \c{_PROCESS_INFORMATION*}. \sa QProcess::pid() */ //QT_END_NAMESPACE #include "moc_k3bqprocess.cpp" #endif // QT_NO_PROCESS diff --git a/src/k3b.cpp b/src/k3b.cpp index 0345649b7..ae3193f9c 100644 --- a/src/k3b.cpp +++ b/src/k3b.cpp @@ -1,1535 +1,1535 @@ /* * * Copyright (C) 1998-2009 Sebastian Trueg * (C) 2009-2011 Michal Malek * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include "k3b.h" #include "k3bappdevicemanager.h" #include "k3bapplication.h" #include "k3baudiodecoder.h" #include "k3baudiodoc.h" #include "k3baudiotrackdialog.h" #include "k3baudioview.h" #include "k3bcuefileparser.h" #include "k3bdatadoc.h" #include "k3bdataview.h" #include "k3bdeviceselectiondialog.h" #include "k3bdirview.h" #include "k3bexternalbinmanager.h" #include "k3bfiletreeview.h" #include "k3bglobals.h" #include "k3binterface.h" #include "k3biso9660.h" #include "k3bjob.h" #include "k3bmediacache.h" #include "k3bmediaselectiondialog.h" #include "k3bmedium.h" #include "k3bmixeddoc.h" #include "k3bmixedview.h" #include "k3bmovixdoc.h" #include "k3bmovixview.h" #include "k3bprojectburndialog.h" #include "k3bplugin.h" #include "k3bpluginmanager.h" #include "k3bprojectmanager.h" #include "k3bprojecttabwidget.h" #include "k3bsignalwaiter.h" #include "k3bstdguiitems.h" #include "k3bsystemproblemdialog.h" #include "k3bstatusbarmanager.h" #include "k3btempdirselectionwidget.h" #include "k3bthemedheader.h" #include "k3bthememanager.h" #include "k3burlnavigator.h" #include "k3bvcddoc.h" #include "k3bvcdview.h" #include "k3bvideodvddoc.h" #include "k3bvideodvdview.h" #include "k3bview.h" #include "k3bwelcomewidget.h" #include "misc/k3bimagewritingdialog.h" #include "misc/k3bmediacopydialog.h" #include "misc/k3bmediaformattingdialog.h" #include "option/k3boptiondialog.h" #include "projects/k3bdatamultisessionimportdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool isProjectFile( QMimeDatabase const& mimeDatabase, QUrl const& url ) { return mimeDatabase.mimeTypeForUrl( url ).inherits( "application/x-k3b" ); } bool isDiscImage( const QUrl& url ) { K3b::Iso9660 iso( url.toLocalFile() ); if( iso.open() ) { iso.close(); return true; } K3b::CueFileParser parser( url.toLocalFile() ); if( parser.isValid() && (parser.toc().contentType() == K3b::Device::DATA || parser.toc().contentType() == K3b::Device::MIXED) ) { return true; } return false; } bool areAudioFiles( const QList& urls ) { // check if the files are all audio we can handle. If so create an audio project bool audio = true; QList fl = k3bcore->pluginManager()->plugins( "AudioDecoder" ); for( QList::const_iterator it = urls.begin(); it != urls.end(); ++it ) { const QUrl& url = *it; if( QFileInfo(url.toLocalFile()).isDir() ) { audio = false; break; } bool a = false; Q_FOREACH( K3b::Plugin* plugin, fl ) { if( static_cast( plugin )->canDecode( url ) ) { a = true; break; } } if( !a ) { audio = a; break; } } if( !audio && urls.count() == 1 ) { // see if it's an audio cue file K3b::CueFileParser parser( urls.first().toLocalFile() ); if( parser.isValid() && parser.toc().contentType() == K3b::Device::AUDIO ) { audio = true; } } return audio; } void setEqualSizes( QSplitter* splitter ) { QList sizes = splitter->sizes(); int sum = 0; for( int i = 0; i < sizes.count(); ++i ) { sum += sizes.at( i ); } - qFill( sizes, sum / sizes.count() ); + std::fill( sizes.begin(), sizes.end(), sum / sizes.count() ); splitter->setSizes( sizes ); } } // namespace class K3b::MainWindow::Private { public: KRecentFilesAction* actionFileOpenRecent; QAction* actionFileSave; QAction* actionFileSaveAs; QAction* actionFileClose; KToggleAction* actionViewStatusBar; KToggleAction* actionViewDocumentHeader; /** The MDI-Interface is managed by this tabbed view */ ProjectTabWidget* documentTab; // project actions QList dataProjectActions; // The K3b-specific widgets DirView* dirView; OptionDialog* optionDialog; StatusBarManager* statusBarManager; bool initialized; // the funny header ThemedHeader* documentHeader; KFilePlacesModel* filePlacesModel; K3b::UrlNavigator* urlNavigator; K3b::Doc* lastDoc; QSplitter* mainSplitter; K3b::WelcomeWidget* welcomeWidget; QStackedWidget* documentStack; QWidget* documentHull; QMimeDatabase mimeDatabase; }; K3b::MainWindow::MainWindow() : KXmlGuiWindow(0), d( new Private ) { d->lastDoc = 0; setPlainCaption( i18n("K3b - The CD and DVD Kreator") ); // ///////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts initActions(); initView(); initStatusBar(); createGUI(); // ///////////////////////////////////////////////////////////////// // incorporate Device Manager into main window factory()->addClient( k3bappcore->appDeviceManager() ); connect( k3bappcore->appDeviceManager(), SIGNAL(detectingDiskInfo(K3b::Device::Device*)), this, SLOT(showDiskInfo(K3b::Device::Device*)) ); // we need the actions for the welcomewidget KConfigGroup grp( config(), "Welcome Widget" ); d->welcomeWidget->loadConfig( grp ); // fill the tabs action menu d->documentTab->addAction( d->actionFileSave ); d->documentTab->addAction( d->actionFileSaveAs ); d->documentTab->addAction( d->actionFileClose ); // ///////////////////////////////////////////////////////////////// // disable actions at startup slotStateChanged( "state_project_active", KXMLGUIClient::StateReverse ); connect( k3bappcore->projectManager(), SIGNAL(newProject(K3b::Doc*)), this, SLOT(createClient(K3b::Doc*)) ); connect( k3bcore->deviceManager(), SIGNAL(changed()), this, SLOT(slotCheckSystemTimed()) ); // FIXME: now make sure the welcome screen is displayed completely //resize( 780, 550 ); // getMainDockWidget()->resize( getMainDockWidget()->size().expandedTo( d->welcomeWidget->sizeHint() ) ); // d->dirTreeDock->resize( QSize( d->dirTreeDock->sizeHint().width(), d->dirTreeDock->height() ) ); readOptions(); new Interface( this ); } K3b::MainWindow::~MainWindow() { delete d; } KSharedConfig::Ptr K3b::MainWindow::config() const { return KSharedConfig::openConfig(); } void K3b::MainWindow::initActions() { // merge in the device actions from the device manager // operator+= is deprecated but I know no other way to do this. Why does the KDE app framework // need to have all actions in the mainwindow's actioncollection anyway (or am I just to stupid to // see the correct solution?) // clang-analyzer wrongly treat KF5's KStandardAction::open as Unix API Improper use of 'open' QAction* actionFileOpen = KStandardAction::open( this, SLOT(slotFileOpen()), actionCollection() ); actionFileOpen->setToolTip( i18n( "Opens an existing project" ) ); actionFileOpen->setStatusTip( actionFileOpen->toolTip() ); d->actionFileOpenRecent = KStandardAction::openRecent( this, SLOT(slotFileOpenRecent(QUrl)), actionCollection() ); d->actionFileOpenRecent->setToolTip( i18n( "Opens a recently used file" ) ); d->actionFileOpenRecent->setStatusTip( d->actionFileOpenRecent->toolTip() ); d->actionFileSave = KStandardAction::save( this, SLOT(slotFileSave()), actionCollection() ); d->actionFileSave->setToolTip( i18n( "Saves the current project" ) ); d->actionFileSave->setStatusTip( d->actionFileSave->toolTip() ); d->actionFileSaveAs = KStandardAction::saveAs( this, SLOT(slotFileSaveAs()), actionCollection() ); d->actionFileSaveAs->setToolTip( i18n( "Saves the current project to a new URL" ) ); d->actionFileSaveAs->setStatusTip( d->actionFileSaveAs->toolTip() ); QAction* actionFileSaveAll = new QAction( QIcon::fromTheme( "document-save-all" ), i18n("Save All"), this ); actionFileSaveAll->setToolTip( i18n( "Saves all open projects" ) ); actionFileSaveAll->setStatusTip( actionFileSaveAll->toolTip() ); actionCollection()->addAction( "file_save_all", actionFileSaveAll ); connect( actionFileSaveAll, SIGNAL(triggered(bool)), this, SLOT(slotFileSaveAll()) ); d->actionFileClose = KStandardAction::close( this, SLOT(slotFileClose()), actionCollection() ); d->actionFileClose->setToolTip(i18n("Closes the current project")); d->actionFileClose->setStatusTip( d->actionFileClose->toolTip() ); QAction* actionFileCloseAll = new QAction( i18n("Close All"), this ); actionFileCloseAll->setToolTip(i18n("Closes all open projects")); actionFileCloseAll->setStatusTip( actionFileCloseAll->toolTip() ); actionCollection()->addAction( "file_close_all", actionFileCloseAll ); connect( actionFileCloseAll, SIGNAL(triggered(bool)), this, SLOT(slotFileCloseAll()) ); QAction* actionFileQuit = KStandardAction::quit(this, SLOT(slotFileQuit()), actionCollection()); actionFileQuit->setToolTip(i18n("Quits the application")); actionFileQuit->setStatusTip( actionFileQuit->toolTip() ); QAction* actionFileNewAudio = new QAction( QIcon::fromTheme( "media-optical-audio" ), i18n("New &Audio CD Project"), this ); actionFileNewAudio->setToolTip( i18n("Creates a new audio CD project") ); actionFileNewAudio->setStatusTip( actionFileNewAudio->toolTip() ); actionCollection()->addAction( "file_new_audio", actionFileNewAudio ); connect( actionFileNewAudio, SIGNAL(triggered(bool)), this, SLOT(slotNewAudioDoc()) ); QAction* actionFileNewData = new QAction( QIcon::fromTheme( "media-optical-data" ), i18n("New &Data Project"), this ); actionFileNewData->setToolTip( i18n("Creates a new data project") ); actionFileNewData->setStatusTip( actionFileNewData->toolTip() ); actionCollection()->addAction( "file_new_data", actionFileNewData ); connect( actionFileNewData, SIGNAL(triggered(bool)), this, SLOT(slotNewDataDoc()) ); QAction* actionFileNewMixed = new QAction( QIcon::fromTheme( "media-optical-mixed-cd" ), i18n("New &Mixed Mode CD Project"), this ); actionFileNewMixed->setToolTip( i18n("Creates a new mixed audio/data CD project") ); actionFileNewMixed->setStatusTip( actionFileNewMixed->toolTip() ); actionCollection()->addAction( "file_new_mixed", actionFileNewMixed ); connect( actionFileNewMixed, SIGNAL(triggered(bool)), this, SLOT(slotNewMixedDoc()) ); QAction* actionFileNewVcd = new QAction( QIcon::fromTheme( "media-optical-video" ), i18n("New &Video CD Project"), this ); actionFileNewVcd->setToolTip( i18n("Creates a new Video CD project") ); actionFileNewVcd->setStatusTip( actionFileNewVcd->toolTip() ); actionCollection()->addAction( "file_new_vcd", actionFileNewVcd ); connect( actionFileNewVcd, SIGNAL(triggered(bool)), this, SLOT(slotNewVcdDoc()) ); QAction* actionFileNewMovix = new QAction( QIcon::fromTheme( "media-optical-video" ), i18n("New &eMovix Project"), this ); actionFileNewMovix->setToolTip( i18n("Creates a new eMovix project") ); actionFileNewMovix->setStatusTip( actionFileNewMovix->toolTip() ); actionCollection()->addAction( "file_new_movix", actionFileNewMovix ); connect( actionFileNewMovix, SIGNAL(triggered(bool)), this, SLOT(slotNewMovixDoc()) ); QAction* actionFileNewVideoDvd = new QAction( QIcon::fromTheme( "media-optical-video" ), i18n("New V&ideo DVD Project"), this ); actionFileNewVideoDvd->setToolTip( i18n("Creates a new Video DVD project") ); actionFileNewVideoDvd->setStatusTip( actionFileNewVideoDvd->toolTip() ); actionCollection()->addAction( "file_new_video_dvd", actionFileNewVideoDvd ); connect( actionFileNewVideoDvd, SIGNAL(triggered(bool)), this, SLOT(slotNewVideoDvdDoc()) ); QAction* actionFileContinueMultisession = new QAction( QIcon::fromTheme( "media-optical-data" ), i18n("Continue Multisession Project"), this ); actionFileContinueMultisession->setToolTip( i18n( "Continues multisession project" ) ); actionFileContinueMultisession->setStatusTip( actionFileContinueMultisession->toolTip() ); actionCollection()->addAction( "file_continue_multisession", actionFileContinueMultisession ); connect( actionFileContinueMultisession, SIGNAL(triggered(bool)), this, SLOT(slotContinueMultisession()) ); KActionMenu* actionFileNewMenu = new KActionMenu( i18n("&New Project"),this ); actionFileNewMenu->setIcon( QIcon::fromTheme( "document-new" ) ); actionFileNewMenu->setToolTip(i18n("Creates a new project")); actionFileNewMenu->setStatusTip( actionFileNewMenu->toolTip() ); actionFileNewMenu->setDelayed( false ); actionFileNewMenu->addAction( actionFileNewData ); actionFileNewMenu->addAction( actionFileContinueMultisession ); actionFileNewMenu->addSeparator(); actionFileNewMenu->addAction( actionFileNewAudio ); actionFileNewMenu->addAction( actionFileNewMixed ); actionFileNewMenu->addSeparator(); actionFileNewMenu->addAction( actionFileNewVcd ); actionFileNewMenu->addAction( actionFileNewVideoDvd ); actionFileNewMenu->addSeparator(); actionFileNewMenu->addAction( actionFileNewMovix ); actionCollection()->addAction( "file_new", actionFileNewMenu ); QAction* actionProjectAddFiles = new QAction( QIcon::fromTheme( "document-open" ), i18n("&Add Files..."), this ); actionProjectAddFiles->setToolTip( i18n("Add files to the current project") ); actionProjectAddFiles->setStatusTip( actionProjectAddFiles->toolTip() ); actionCollection()->addAction( "project_add_files", actionProjectAddFiles ); connect( actionProjectAddFiles, SIGNAL(triggered(bool)), this, SLOT(slotProjectAddFiles()) ); QAction* actionClearProject = new QAction( QIcon::fromTheme( QApplication::isRightToLeft() ? "edit-clear-locationbar-rtl" : "edit-clear-locationbar-ltr" ), i18n("&Clear Project"), this ); actionClearProject->setToolTip( i18n("Clear the current project") ); actionClearProject->setStatusTip( actionClearProject->toolTip() ); actionCollection()->addAction( "project_clear_project", actionClearProject ); connect( actionClearProject, SIGNAL(triggered(bool)), this, SLOT(slotClearProject()) ); QAction* actionToolsFormatMedium = new QAction( QIcon::fromTheme( "tools-media-optical-format" ), i18n("&Format/Erase rewritable disk..."), this ); actionToolsFormatMedium->setIconText( i18n( "Format" ) ); actionToolsFormatMedium->setToolTip( i18n("Open the rewritable disk formatting/erasing dialog") ); actionToolsFormatMedium->setStatusTip( actionToolsFormatMedium->toolTip() ); actionCollection()->addAction( "tools_format_medium", actionToolsFormatMedium ); connect( actionToolsFormatMedium, SIGNAL(triggered(bool)), this, SLOT(slotFormatMedium()) ); QAction* actionToolsWriteImage = new QAction( QIcon::fromTheme( "tools-media-optical-burn-image" ), i18n("&Burn Image..."), this ); actionToolsWriteImage->setToolTip( i18n("Write an ISO 9660, cue/bin, or cdrecord clone image to an optical disc") ); actionToolsWriteImage->setStatusTip( actionToolsWriteImage->toolTip() ); actionCollection()->addAction( "tools_write_image", actionToolsWriteImage ); connect( actionToolsWriteImage, SIGNAL(triggered(bool)), this, SLOT(slotWriteImage()) ); QAction* actionToolsMediaCopy = new QAction( QIcon::fromTheme( "tools-media-optical-copy" ), i18n("Copy &Medium..."), this ); actionToolsMediaCopy->setIconText( i18n( "Copy" ) ); actionToolsMediaCopy->setToolTip( i18n("Open the media copy dialog") ); actionToolsMediaCopy->setStatusTip( actionToolsMediaCopy->toolTip() ); actionCollection()->addAction( "tools_copy_medium", actionToolsMediaCopy ); connect( actionToolsMediaCopy, SIGNAL(triggered(bool)), this, SLOT(slotMediaCopy()) ); QAction* actionToolsCddaRip = new QAction( QIcon::fromTheme( "tools-rip-audio-cd" ), i18n("Rip Audio CD..."), this ); actionToolsCddaRip->setToolTip( i18n("Digitally extract tracks from an audio CD") ); actionToolsCddaRip->setStatusTip( actionToolsCddaRip->toolTip() ); actionCollection()->addAction( "tools_cdda_rip", actionToolsCddaRip ); connect( actionToolsCddaRip, SIGNAL(triggered(bool)), this, SLOT(slotCddaRip()) ); QAction* actionToolsVideoDvdRip = new QAction( QIcon::fromTheme( "tools-rip-video-dvd" ), i18n("Rip Video DVD..."), this ); actionToolsVideoDvdRip->setToolTip( i18n("Transcode Video DVD titles") ); actionToolsVideoDvdRip->setStatusTip( actionToolsVideoDvdRip->toolTip() ); connect( actionToolsVideoDvdRip, SIGNAL(triggered(bool)), this, SLOT(slotVideoDvdRip()) ); actionCollection()->addAction( "tools_videodvd_rip", actionToolsVideoDvdRip ); QAction* actionToolsVideoCdRip = new QAction( QIcon::fromTheme( "tools-rip-video-cd" ), i18n("Rip Video CD..."), this ); actionToolsVideoCdRip->setToolTip( i18n("Extract tracks from a Video CD") ); actionToolsVideoCdRip->setStatusTip( actionToolsVideoCdRip->toolTip() ); actionCollection()->addAction( "tools_videocd_rip", actionToolsVideoCdRip ); connect( actionToolsVideoCdRip, SIGNAL(triggered(bool)), this, SLOT(slotVideoCdRip()) ); d->actionViewDocumentHeader = new KToggleAction(i18n("Show Projects Header"),this); d->actionViewDocumentHeader->setToolTip( i18n("Shows/hides title header of projects panel") ); d->actionViewDocumentHeader->setStatusTip( d->actionViewDocumentHeader->toolTip() ); actionCollection()->addAction("view_document_header", d->actionViewDocumentHeader); d->actionViewStatusBar = KStandardAction::showStatusbar(this, SLOT(slotViewStatusBar()), actionCollection()); KStandardAction::showMenubar( this, SLOT(slotShowMenuBar()), actionCollection() ); KStandardAction::keyBindings( this, SLOT(slotConfigureKeys()), actionCollection() ); KStandardAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection()); setStandardToolBarMenuEnabled(true); QAction* actionSettingsConfigure = KStandardAction::preferences(this, SLOT(slotSettingsConfigure()), actionCollection() ); actionSettingsConfigure->setToolTip( i18n("Configure K3b settings") ); actionSettingsConfigure->setStatusTip( actionSettingsConfigure->toolTip() ); QAction* actionHelpSystemCheck = new QAction( i18n("System Check"), this ); actionHelpSystemCheck->setToolTip( i18n("Checks system configuration") ); actionHelpSystemCheck->setStatusTip( actionHelpSystemCheck->toolTip() ); actionCollection()->addAction( "help_check_system", actionHelpSystemCheck ); connect( actionHelpSystemCheck, SIGNAL(triggered(bool)), this, SLOT(slotManualCheckSystem()) ); } QList K3b::MainWindow::projects() const { return k3bappcore->projectManager()->projects(); } void K3b::MainWindow::slotConfigureKeys() { KShortcutsDialog::configure( actionCollection(),KShortcutsEditor::LetterShortcutsDisallowed, this ); } void K3b::MainWindow::initStatusBar() { d->statusBarManager = new K3b::StatusBarManager( this ); } void K3b::MainWindow::initView() { // setup main docking things d->mainSplitter = new QSplitter( Qt::Vertical, this ); QSplitter* upperSplitter = new QSplitter( Qt::Horizontal, d->mainSplitter ); d->mainSplitter->addWidget( upperSplitter ); // --- Document Dock ---------------------------------------------------------------------------- d->documentStack = new QStackedWidget( d->mainSplitter ); d->mainSplitter->addWidget( d->documentStack ); d->documentHull = new QWidget( d->documentStack ); QGridLayout* documentHullLayout = new QGridLayout( d->documentHull ); documentHullLayout->setContentsMargins( 0, 0, 0, 0 ); documentHullLayout->setSpacing( 0 ); setCentralWidget( d->mainSplitter ); setEqualSizes( d->mainSplitter ); d->documentHeader = new K3b::ThemedHeader( d->documentHull ); d->documentHeader->setTitle( i18n("Current Projects") ); d->documentHeader->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); d->documentHeader->setLeftPixmap( K3b::Theme::PROJECT_LEFT ); d->documentHeader->setRightPixmap( K3b::Theme::PROJECT_RIGHT ); connect( d->actionViewDocumentHeader, SIGNAL(toggled(bool)), d->documentHeader, SLOT(setVisible(bool)) ); // add the document tab to the styled document box d->documentTab = new K3b::ProjectTabWidget( d->documentHull ); documentHullLayout->addWidget( d->documentHeader, 0, 0 ); documentHullLayout->addWidget( d->documentTab, 1, 0 ); connect( d->documentTab, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentDocChanged()) ); connect( d->documentTab, SIGNAL(tabCloseRequested(Doc*)), this, SLOT(slotFileClose(Doc*)) ); d->welcomeWidget = new K3b::WelcomeWidget( this, d->documentStack ); d->documentStack->addWidget( d->welcomeWidget ); d->documentStack->addWidget( d->documentHull ); d->documentStack->setCurrentWidget( d->welcomeWidget ); // --------------------------------------------------------------------------------------------- // --- Directory Dock -------------------------------------------------------------------------- K3b::FileTreeView* fileTreeView = new K3b::FileTreeView( upperSplitter ); upperSplitter->addWidget( fileTreeView ); // --------------------------------------------------------------------------------------------- // --- Contents Dock --------------------------------------------------------------------------- d->dirView = new K3b::DirView( fileTreeView, upperSplitter ); upperSplitter->addWidget( d->dirView ); // --- filetreecombobox-toolbar ---------------------------------------------------------------- d->filePlacesModel = new KFilePlacesModel; d->urlNavigator = new K3b::UrlNavigator(d->filePlacesModel, this); connect( d->urlNavigator, SIGNAL(activated(QUrl)), d->dirView, SLOT(showUrl(QUrl)) ); connect( d->urlNavigator, SIGNAL(activated(K3b::Device::Device*)), d->dirView, SLOT(showDevice(K3b::Device::Device*)) ); connect( d->dirView, SIGNAL(urlEntered(QUrl)), d->urlNavigator, SLOT(setUrl(QUrl)) ); connect( d->dirView, SIGNAL(deviceSelected(K3b::Device::Device*)), d->urlNavigator, SLOT(setDevice(K3b::Device::Device*)) ); QWidgetAction * urlNavigatorAction = new QWidgetAction(this); urlNavigatorAction->setDefaultWidget(d->urlNavigator); urlNavigatorAction->setText(i18n("&Location Bar")); actionCollection()->addAction( "location_bar", urlNavigatorAction ); // --------------------------------------------------------------------------------------------- } void K3b::MainWindow::createClient( K3b::Doc* doc ) { qDebug(); // create the proper K3b::View (maybe we should put this into some other class like K3b::ProjectManager) K3b::View* view = 0; switch( doc->type() ) { case K3b::Doc::AudioProject: view = new K3b::AudioView( static_cast(doc), d->documentTab ); break; case K3b::Doc::DataProject: view = new K3b::DataView( static_cast(doc), d->documentTab ); break; case K3b::Doc::MixedProject: { K3b::MixedDoc* mixedDoc = static_cast(doc); view = new K3b::MixedView( mixedDoc, d->documentTab ); mixedDoc->dataDoc()->setView( view ); mixedDoc->audioDoc()->setView( view ); break; } case K3b::Doc::VcdProject: view = new K3b::VcdView( static_cast(doc), d->documentTab ); break; case K3b::Doc::MovixProject: view = new K3b::MovixView( static_cast(doc), d->documentTab ); break; case K3b::Doc::VideoDvdProject: view = new K3b::VideoDvdView( static_cast(doc), d->documentTab ); break; } if( view != 0 ) { doc->setView( view ); view->setWindowTitle( doc->URL().fileName() ); d->documentTab->addTab( doc ); d->documentTab->setCurrentTab( doc ); slotCurrentDocChanged(); } } K3b::View* K3b::MainWindow::activeView() const { if( Doc* doc = activeDoc() ) return qobject_cast( doc->view() ); else return 0; } K3b::Doc* K3b::MainWindow::activeDoc() const { return d->documentTab->currentTab(); } K3b::Doc* K3b::MainWindow::openDocument(const QUrl& url) { slotStatusMsg(i18n("Opening file...")); // // First we check if this is an iso image in case someone wants to open one this way // if( isDiscImage( url ) ) { slotWriteImage( url ); return 0; } else { // see if it's an audio cue file K3b::CueFileParser parser( url.toLocalFile() ); if( parser.isValid() && parser.toc().contentType() == K3b::Device::AUDIO ) { K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::AudioProject ); doc->addUrl( url ); return doc; } else { // check, if document already open. If yes, set the focus to the first view K3b::Doc* doc = k3bappcore->projectManager()->findByUrl( url ); if( doc ) { d->documentTab->setCurrentTab( doc ); return doc; } doc = k3bappcore->projectManager()->openProject( url ); if( doc == 0 ) { KMessageBox::error (this,i18n("Could not open document."), i18n("Error")); return 0; } d->actionFileOpenRecent->addUrl(url); return doc; } } } void K3b::MainWindow::saveOptions() { KConfigGroup recentGrp(config(),"Recent Files"); d->actionFileOpenRecent->saveEntries( recentGrp ); KConfigGroup grpFileView( config(), "file view" ); d->dirView->saveConfig( grpFileView ); KConfigGroup grpWindows(config(), "main_window_settings"); saveMainWindowSettings( grpWindows ); k3bcore->saveSettings( config() ); KConfigGroup grp(config(), "Welcome Widget" ); d->welcomeWidget->saveConfig( grp ); KConfigGroup grpOption( config(), "General Options" ); grpOption.writeEntry( "Show Document Header", d->actionViewDocumentHeader->isChecked() ); grpOption.writeEntry( "Navigator breadcrumb mode", !d->urlNavigator->isUrlEditable() ); config()->sync(); } void K3b::MainWindow::readOptions() { KConfigGroup grpWindow(config(), "main_window_settings"); applyMainWindowSettings( grpWindow ); KConfigGroup grp( config(), "General Options" ); d->actionViewDocumentHeader->setChecked( grp.readEntry("Show Document Header", true) ); d->urlNavigator->setUrlEditable( !grp.readEntry( "Navigator breadcrumb mode", true ) ); // initialize the recent file list KConfigGroup recentGrp(config(), "Recent Files"); d->actionFileOpenRecent->loadEntries( recentGrp ); KConfigGroup grpFileView( config(), "file view" ); d->dirView->readConfig( grpFileView ); d->documentHeader->setVisible( d->actionViewDocumentHeader->isChecked() ); } void K3b::MainWindow::saveProperties( KConfigGroup& grp ) { // 1. put saved projects in the config // 2. save every modified project in "~/.kde/share/apps/k3b/sessions/" + KApp->sessionId() // 3. save the url of the project (might be something like "AudioCD1") in the config // 4. save the status of every project (modified/saved) QString saveDir = QString( "%1/sessions/%2/" ).arg( QStandardPaths::writableLocation( QStandardPaths::DataLocation ), qApp->sessionId() ); QDir().mkpath(saveDir); // // FIXME: for some reason the config entries are not properly stored when using the default // // KMainWindow session config. Since I was not able to find the bug I use another config object // // ---------------------------------------------------------- // KConfig c( saveDir + "list", KConfig::SimpleConfig ); // KConfigGroup grp( &c, "Saved Session" ); // // ---------------------------------------------------------- QList docs = k3bappcore->projectManager()->projects(); grp.writeEntry( "Number of projects", docs.count() ); int cnt = 1; Q_FOREACH( K3b::Doc* doc, docs ) { // the "name" of the project (or the original url if isSaved()) grp.writePathEntry( QString("%1 url").arg(cnt), (doc)->URL().url() ); // is the doc modified grp.writeEntry( QString("%1 modified").arg(cnt), (doc)->isModified() ); // has the doc already been saved? grp.writeEntry( QString("%1 saved").arg(cnt), (doc)->isSaved() ); // where does the session management save it? If it's not modified and saved this is // the same as the url QUrl saveUrl = (doc)->URL(); if( !(doc)->isSaved() || (doc)->isModified() ) saveUrl = QUrl::fromLocalFile( saveDir + QString::number(cnt) ); grp.writePathEntry( QString("%1 saveurl").arg(cnt), saveUrl.url() ); // finally save it k3bappcore->projectManager()->saveProject( doc, saveUrl ); ++cnt; } // c.sync(); } // FIXME:move this to K3b::ProjectManager void K3b::MainWindow::readProperties( const KConfigGroup& grp ) { // FIXME: do not delete the files here. rather do it when the app is exited normally // since that's when we can be sure we never need the session stuff again. // 1. read all projects from the config // 2. simply open all of themg // 3. reset the saved urls and the modified state // 4. delete "~/.kde/share/apps/k3b/sessions/" + KApp->sessionId() QString saveDir = QString( "%1/sessions/%2/" ).arg( QStandardPaths::writableLocation( QStandardPaths::DataLocation ), qApp->sessionId() ); QDir().mkpath(saveDir); // // FIXME: for some reason the config entries are not properly stored when using the default // // KMainWindow session config. Since I was not able to find the bug I use another config object // // ---------------------------------------------------------- // KConfig c( saveDir + "list"/*, true*/ ); // KConfigGroup grp( &c, "Saved Session" ); // // ---------------------------------------------------------- int cnt = grp.readEntry( "Number of projects", 0 ); /* qDebug() << "(K3b::MainWindow::readProperties) number of projects from last session in " << saveDir << ": " << cnt << endl << " read from config group " << c->group() << endl; */ for( int i = 1; i <= cnt; ++i ) { // in this case the constructor works since we saved as url() QUrl url( grp.readPathEntry( QString("%1 url").arg(i),QString() ) ); bool modified = grp.readEntry( QString("%1 modified").arg(i),false ); bool saved = grp.readEntry( QString("%1 saved").arg(i),false ); QUrl saveUrl( grp.readPathEntry( QString("%1 saveurl").arg(i),QString() ) ); // now load the project if( K3b::Doc* doc = k3bappcore->projectManager()->openProject( saveUrl ) ) { // reset the url doc->setURL( url ); doc->setModified( modified ); doc->setSaved( saved ); } else qDebug() << "(K3b::MainWindow) could not open session saved doc " << url.toLocalFile(); // remove the temp file if( !saved || modified ) QFile::remove( saveUrl.toLocalFile() ); } // and now remove the temp dir KIO::del( QUrl::fromLocalFile(saveDir), KIO::HideProgressInfo ); } bool K3b::MainWindow::queryClose() { // // Check if a job is currently running // For now K3b only allows for one major job at a time which means that we only need to cancel // this one job. // if( k3bcore->jobsRunning() ) { // pitty, but I see no possibility to make this work. It always crashes because of the event // management thing mentioned below. So until I find a solution K3b simply will refuse to close // while a job i running return false; // qDebug() << "(K3b::MainWindow::queryClose) jobs running."; // K3b::Job* job = k3bcore->runningJobs().getFirst(); // // now search for the major job (to be on the safe side although for now no subjobs register with the k3bcore) // K3b::JobHandler* jh = job->jobHandler(); // while( jh->isJob() ) { // job = static_cast( jh ); // jh = job->jobHandler(); // } // qDebug() << "(K3b::MainWindow::queryClose) main job found: " << job->jobDescription(); // // now job is the major job and jh should be a widget // QWidget* progressDialog = dynamic_cast( jh ); // qDebug() << "(K3b::MainWindow::queryClose) job active: " << job->active(); // // now ask the user if he/she really wants to cancel this job // if( job->active() ) { // if( KMessageBox::questionYesNo( progressDialog ? progressDialog : this, // i18n("Do you really want to cancel?"), // i18n("Cancel") ) == KMessageBox::Yes ) { // // cancel the job // qDebug() << "(K3b::MainWindow::queryClose) canceling job."; // job->cancel(); // // wait for the job to finish // qDebug() << "(K3b::MainWindow::queryClose) waiting for job to finish."; // K3b::SignalWaiter::waitForJob( job ); // // close the progress dialog // if( progressDialog ) { // qDebug() << "(K3b::MainWindow::queryClose) closing progress dialog."; // progressDialog->close(); // // // // now here we have the problem that due to the whole Qt event thing the exec call (or // // in this case most likely the startJob call) does not return until we leave this method. // // That means that the progress dialog might be deleted by it's parent below (when we // // close docs) before it is deleted by the creator (most likely a projectburndialog). // // That would result in a double deletion and thus a crash. // // So we just reparent the dialog to 0 here so it's (former) parent won't delete it. // // // progressDialog->reparent( 0, QPoint(0,0) ); // } // qDebug() << "(K3b::MainWindow::queryClose) job cleanup done."; // } // else // return false; // } } saveOptions(); // // if we are closed by the session manager everything is fine since we store the // current state in saveProperties // if( qApp->isSavingSession() ) return true; // FIXME: do not close the docs here. Just ask for them to be saved and return false // if the user chose cancel for some doc // --------------------------------- // we need to manually close all the views to ensure that // each of them receives a close-event and // the user is asked for every modified doc to save the changes // --------------------------------- while( K3b::View* view = activeView() ) { if( !canCloseDocument(view->doc()) ) return false; closeProject(view->doc()); } return true; } bool K3b::MainWindow::canCloseDocument( K3b::Doc* doc ) { if( !doc->isModified() ) return true; if( !KConfigGroup( config(), "General Options" ).readEntry( "ask_for_saving_changes_on_exit", true ) ) return true; switch ( KMessageBox::warningYesNoCancel( this, i18n("%1 has unsaved data.", doc->URL().fileName() ), i18n("Closing Project"), KStandardGuiItem::save(), KStandardGuiItem::dontSave() ) ) { case KMessageBox::Yes: if ( !fileSave( doc ) ) return false; case KMessageBox::No: return true; default: return false; } } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void K3b::MainWindow::slotFileOpen() { slotStatusMsg(i18n("Opening file...")); QList urls = QFileDialog::getOpenFileUrls( this, i18n("Open Files"), QUrl(), i18n("K3b Projects (*.k3b)")); for( QList::iterator it = urls.begin(); it != urls.end(); ++it ) { openDocument( *it ); d->actionFileOpenRecent->addUrl( *it ); } } void K3b::MainWindow::slotFileOpenRecent(const QUrl& url) { slotStatusMsg(i18n("Opening file...")); openDocument(url); } void K3b::MainWindow::slotFileSaveAll() { Q_FOREACH( K3b::Doc* doc, k3bappcore->projectManager()->projects() ) { fileSave( doc ); } } void K3b::MainWindow::slotFileSave() { if( K3b::Doc* doc = activeDoc() ) { fileSave( doc ); } } bool K3b::MainWindow::fileSave( K3b::Doc* doc ) { slotStatusMsg(i18n("Saving file...")); if( doc == 0 ) { doc = activeDoc(); } if( doc != 0 ) { if( !doc->isSaved() ) return fileSaveAs( doc ); else if( !k3bappcore->projectManager()->saveProject( doc, doc->URL()) ) KMessageBox::error (this,i18n("Could not save the current document."), i18n("I/O Error")); } return false; } void K3b::MainWindow::slotFileSaveAs() { if( K3b::Doc* doc = activeDoc() ) { fileSaveAs( doc ); } } bool K3b::MainWindow::fileSaveAs( K3b::Doc* doc ) { slotStatusMsg(i18n("Saving file with a new filename...")); if( !doc ) { doc = activeDoc(); } if( doc ) { // we do not use the static QFileDialog method here to be able to specify a filename suggestion QFileDialog dlg( this, i18n("Save As"), QString(), i18n("K3b Projects (*.k3b)") ); dlg.setAcceptMode( QFileDialog::AcceptSave ); dlg.selectFile( doc->name() ); dlg.exec(); QList urls = dlg.selectedUrls(); if( !urls.isEmpty() ) { QUrl url = urls.front(); KRecentDocument::add( url ); KIO::StatJob* statJob = KIO::stat(url, KIO::StatJob::DestinationSide, KIO::HideProgressInfo); bool exists = statJob->exec(); if( !exists || KMessageBox::warningContinueCancel( this, i18n("Do you want to overwrite %1?", url.toDisplayString() ), i18n("File Exists"), KStandardGuiItem::overwrite() ) == KMessageBox::Continue ) { if( k3bappcore->projectManager()->saveProject( doc, url ) ) { d->actionFileOpenRecent->addUrl(url); return true; } else { KMessageBox::error (this,i18n("Could not save the current document."), i18n("I/O Error")); } } } } return false; } void K3b::MainWindow::slotFileClose() { if( K3b::View* view = activeView() ) { slotFileClose( view->doc() ); } } void K3b::MainWindow::slotFileClose( Doc* doc ) { slotStatusMsg(i18n("Closing file...")); if( doc && canCloseDocument(doc) ) { closeProject(doc); } slotCurrentDocChanged(); } void K3b::MainWindow::slotFileCloseAll() { while( K3b::View* view = activeView() ) { K3b::Doc* doc = view->doc(); if( canCloseDocument(doc) ) closeProject(doc); else break; } slotCurrentDocChanged(); } void K3b::MainWindow::closeProject( K3b::Doc* doc ) { // unplug the actions if( factory() ) { if( d->lastDoc == doc ) { factory()->removeClient( static_cast(d->lastDoc->view()) ); d->lastDoc = 0; } } // remove the doc from the project tab d->documentTab->removeTab( doc ); // remove the project from the manager k3bappcore->projectManager()->removeProject( doc ); // delete view and doc delete doc->view(); delete doc; } void K3b::MainWindow::slotFileQuit() { close(); } void K3b::MainWindow::slotViewStatusBar() { //turn Statusbar on or off if(d->actionViewStatusBar->isChecked()) { statusBar()->show(); } else { statusBar()->hide(); } } void K3b::MainWindow::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently // statusBar()->clear(); // statusBar()->setItemText(text,1); statusBar()->showMessage( text, 2000 ); } void K3b::MainWindow::slotSettingsConfigure() { K3b::OptionDialog d( this ); d.exec(); // emit a changed signal every time since we do not know if the user selected // "apply" and "cancel" or "ok" emit configChanged( config() ); } void K3b::MainWindow::showOptionDialog( K3b::OptionDialog::ConfigPage index ) { K3b::OptionDialog d( this); d.setCurrentPage( index ); d.exec(); // emit a changed signal every time since we do not know if the user selected // "apply" and "cancel" or "ok" emit configChanged( config() ); } K3b::Doc* K3b::MainWindow::slotNewAudioDoc() { slotStatusMsg(i18n("Creating new Audio CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::AudioProject ); return doc; } K3b::Doc* K3b::MainWindow::slotNewDataDoc() { slotStatusMsg(i18n("Creating new Data CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::DataProject ); return doc; } K3b::Doc* K3b::MainWindow::slotContinueMultisession() { return K3b::DataMultisessionImportDialog::importSession( 0, this ); } K3b::Doc* K3b::MainWindow::slotNewVideoDvdDoc() { slotStatusMsg(i18n("Creating new Video DVD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::VideoDvdProject ); return doc; } K3b::Doc* K3b::MainWindow::slotNewMixedDoc() { slotStatusMsg(i18n("Creating new Mixed Mode CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::MixedProject ); return doc; } K3b::Doc* K3b::MainWindow::slotNewVcdDoc() { slotStatusMsg(i18n("Creating new Video CD Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::VcdProject ); return doc; } K3b::Doc* K3b::MainWindow::slotNewMovixDoc() { slotStatusMsg(i18n("Creating new eMovix Project.")); K3b::Doc* doc = k3bappcore->projectManager()->createProject( K3b::Doc::MovixProject ); return doc; } void K3b::MainWindow::slotCurrentDocChanged() { // check the doctype K3b::View* v = activeView(); if( v ) { k3bappcore->projectManager()->setActive( v->doc() ); // // There are two possiblities to plug the project actions: // 1. Through KXMLGUIClient::plugActionList // This way we just ask the View for the actionCollection (which it should merge with // the doc's) and plug it into the project menu. // Advantage: easy and clear to handle // Disadvantage: we may only plug all actions at once into one menu // // 2. Through merging the doc as a KXMLGUIClient // This way every view is a KXMLGUIClient and it's GUI is just merged into the MainWindow's. // Advantage: flexible // Disadvantage: every view needs it's own XML file // // if( factory() ) { if( d->lastDoc ) factory()->removeClient( static_cast(d->lastDoc->view()) ); factory()->addClient( v ); d->lastDoc = v->doc(); } else qDebug() << "(K3b::MainWindow) ERROR: could not get KXMLGUIFactory instance."; } else k3bappcore->projectManager()->setActive( 0L ); if( k3bappcore->projectManager()->isEmpty() ) { slotStateChanged( "state_project_active", KXMLGUIClient::StateReverse ); } else { slotStateChanged( "state_project_active", KXMLGUIClient::StateNoReverse ); } if( k3bappcore->projectManager()->isEmpty() ) d->documentStack->setCurrentWidget( d->welcomeWidget ); else d->documentStack->setCurrentWidget( d->documentHull ); } void K3b::MainWindow::slotEditToolbars() { KConfigGroup grp( config(), "main_window_settings" ); saveMainWindowSettings( grp ); KEditToolBar dlg( factory() ); connect( &dlg, SIGNAL(newToolbarConfig()), SLOT(slotNewToolBarConfig()) ); dlg.exec(); } void K3b::MainWindow::slotNewToolBarConfig() { KConfigGroup grp(config(), "main_window_settings"); applyMainWindowSettings(grp); } bool K3b::MainWindow::eject() { KConfigGroup c( config(), "General Options" ); return !c.readEntry( "No cd eject", false ); } void K3b::MainWindow::slotErrorMessage(const QString& message) { KMessageBox::error( this, message ); } void K3b::MainWindow::slotWarningMessage(const QString& message) { KMessageBox::sorry( this, message ); } void K3b::MainWindow::slotWriteImage() { K3b::ImageWritingDialog d( this ); d.exec(); } void K3b::MainWindow::slotWriteImage( const QUrl& url ) { K3b::ImageWritingDialog d( this ); d.setImage( url ); d.exec(); } void K3b::MainWindow::slotProjectAddFiles() { K3b::View* view = activeView(); if( view ) { const QList urls = QFileDialog::getOpenFileUrls(this, i18n("Select Files to Add to Project"), QUrl(), i18n("All Files (*)") ); if( !urls.isEmpty() ) view->addUrls( urls ); } else KMessageBox::error( this, i18n("Please create a project before adding files"), i18n("No Active Project")); } void K3b::MainWindow::formatMedium( K3b::Device::Device* dev ) { K3b::MediaFormattingDialog d( this ); d.setDevice( dev ); d.exec(); } void K3b::MainWindow::slotFormatMedium() { formatMedium( 0 ); } void K3b::MainWindow::mediaCopy( K3b::Device::Device* dev ) { K3b::MediaCopyDialog d( this ); d.setReadingDevice( dev ); d.exec(); } void K3b::MainWindow::slotMediaCopy() { mediaCopy( 0 ); } // void K3b::MainWindow::slotVideoDvdCopy() // { // K3b::VideoDvdCopyDialog d( this ); // d.exec(); // } void K3b::MainWindow::slotShowMenuBar() { if( menuBar()->isVisible() ) menuBar()->hide(); else menuBar()->show(); } K3b::ExternalBinManager* K3b::MainWindow::externalBinManager() const { return k3bcore->externalBinManager(); } K3b::Device::DeviceManager* K3b::MainWindow::deviceManager() const { return k3bcore->deviceManager(); } void K3b::MainWindow::slotDataImportSession() { if( activeView() ) { if( K3b::DataView* view = qobject_cast(activeView()) ) { view->actionCollection()->action( "project_data_import_session" )->trigger(); } } } void K3b::MainWindow::slotDataClearImportedSession() { if( activeView() ) { if( K3b::DataView* view = qobject_cast(activeView()) ) { view->actionCollection()->action( "project_data_clear_imported_session" )->trigger(); } } } void K3b::MainWindow::slotEditBootImages() { if( activeView() ) { if( K3b::DataView* view = qobject_cast(activeView()) ) { view->actionCollection()->action( "project_data_edit_boot_images" )->trigger(); } } } void K3b::MainWindow::slotCheckSystemTimed() { // run the system check from the event queue so we do not // mess with the device state resetting throughout the app // when called from K3b::DeviceManager::changed QTimer::singleShot( 0, this, SLOT(slotCheckSystem()) ); } void K3b::MainWindow::slotCheckSystem() { K3b::SystemProblemDialog::checkSystem( this, K3b::SystemProblemDialog::NotifyOnlyErrors ); } void K3b::MainWindow::slotManualCheckSystem() { K3b::SystemProblemDialog::checkSystem(this, K3b::SystemProblemDialog::AlwaysNotify, true/* forceCheck */); } void K3b::MainWindow::addUrls( const QList& urls ) { if( urls.count() == 1 && isProjectFile( d->mimeDatabase, urls.first() ) ) { openDocument( urls.first() ); } else if( K3b::View* view = activeView() ) { view->addUrls( urls ); } else if( urls.count() == 1 && isDiscImage( urls.first() ) ) { slotWriteImage( urls.first() ); } else if( areAudioFiles( urls ) ) { static_cast(slotNewAudioDoc()->view())->addUrls( urls ); } else { static_cast(slotNewDataDoc()->view())->addUrls( urls ); } } void K3b::MainWindow::slotClearProject() { K3b::Doc* doc = k3bappcore->projectManager()->activeDoc(); if( doc ) { if( KMessageBox::warningContinueCancel( this, i18n("Do you really want to clear the current project?"), i18n("Clear Project"), KStandardGuiItem::clear(), KStandardGuiItem::cancel(), QString("clear_current_project_dontAskAgain") ) == KMessageBox::Continue ) { doc->clear(); } } } void K3b::MainWindow::slotCddaRip() { cddaRip( 0 ); } void K3b::MainWindow::cddaRip( K3b::Device::Device* dev ) { if( !dev || !(k3bappcore->mediaCache()->medium( dev ).content() & K3b::Medium::ContentAudio ) ) dev = K3b::MediaSelectionDialog::selectMedium( K3b::Device::MEDIA_CD_ALL, K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, K3b::Medium::ContentAudio, this, i18n("Audio CD Rip") ); if( dev ) d->dirView->showDevice( dev ); } void K3b::MainWindow::videoDvdRip( K3b::Device::Device* dev ) { if( !dev || !(k3bappcore->mediaCache()->medium( dev ).content() & K3b::Medium::ContentVideoDVD ) ) dev = K3b::MediaSelectionDialog::selectMedium( K3b::Device::MEDIA_DVD_ALL, K3b::Device::STATE_COMPLETE, K3b::Medium::ContentVideoDVD, this, i18n("Video DVD Rip") ); if( dev ) d->dirView->showDevice( dev ); } void K3b::MainWindow::slotVideoDvdRip() { videoDvdRip( 0 ); } void K3b::MainWindow::videoCdRip( K3b::Device::Device* dev ) { if( !dev || !(k3bappcore->mediaCache()->medium( dev ).content() & K3b::Medium::ContentVideoCD ) ) dev = K3b::MediaSelectionDialog::selectMedium( K3b::Device::MEDIA_CD_ALL, K3b::Device::STATE_COMPLETE, K3b::Medium::ContentVideoCD, this, i18n("Video CD Rip") ); if( dev ) d->dirView->showDevice( dev ); } void K3b::MainWindow::slotVideoCdRip() { videoCdRip( 0 ); } void K3b::MainWindow::showDiskInfo( K3b::Device::Device* dev ) { d->dirView->showDiskInfo( dev ); } diff --git a/src/k3bsplash.cpp b/src/k3bsplash.cpp index a9de2eb37..2d790eed3 100644 --- a/src/k3bsplash.cpp +++ b/src/k3bsplash.cpp @@ -1,105 +1,105 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include "k3bsplash.h" #include "k3bthememanager.h" #include "k3bapplication.h" #include #include #include #include #include #include #include -#include #include +#include #include K3b::Splash::Splash( QWidget* parent ) : QWidget( parent) { setAttribute( Qt::WA_DeleteOnClose ); setWindowFlags(Qt::FramelessWindowHint| Qt::SplashScreen| Qt::WindowStaysOnTopHint| Qt::X11BypassWindowManagerHint); QPalette pal( palette() ); pal.setColor( QPalette::Window, Qt::black ); pal.setColor( QPalette::WindowText, Qt::white ); setPalette( pal ); QLabel* copyrightLabel = new QLabel( KAboutData::applicationData().copyrightStatement(), this ); copyrightLabel->setContentsMargins( 5, 5, 5, 5 ); copyrightLabel->setAlignment( Qt::AlignRight ); QLabel* picLabel = new QLabel( this ); if( K3b::Theme* theme = k3bappcore->themeManager()->currentTheme() ) { picLabel->setPalette( theme->palette() ); picLabel->setPixmap( theme->pixmap( K3b::Theme::SPLASH ) ); } m_infoBox = new QLabel( this ); m_infoBox->setContentsMargins( 5, 5, 5, 5 ); QVBoxLayout* layout = new QVBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); layout->addWidget( copyrightLabel ); layout->addWidget( picLabel ); layout->addWidget( m_infoBox ); // Set geometry, with support for Xinerama systems QRect r; r.setSize(sizeHint()); - int ps = QApplication::desktop()->primaryScreen(); - r.moveCenter( QApplication::desktop()->screenGeometry(ps).center() ); + const QScreen *ps = QGuiApplication::primaryScreen(); + r.moveCenter( ps->geometry().center() ); setGeometry(r); } K3b::Splash::~Splash() { } void K3b::Splash::mousePressEvent( QMouseEvent* ) { close(); } void K3b::Splash::show() { QWidget::show(); // make sure the splash screen is shown immediately qApp->processEvents(); } void K3b::Splash::addInfo( const QString& s ) { m_infoBox->setText( s ); qApp->processEvents(); } void K3b::Splash::hide() { QWidget::hide(); qApp->processEvents(); } diff --git a/src/projects/k3baudioeditorwidget.cpp b/src/projects/k3baudioeditorwidget.cpp index c7d72ec39..4197111a2 100644 --- a/src/projects/k3baudioeditorwidget.cpp +++ b/src/projects/k3baudioeditorwidget.cpp @@ -1,880 +1,880 @@ /* * * Copyright (C) 2004-2008 Sebastian Trueg * Copyright (C) 2010-2011 Michal Malek * * This file is part of the K3b project. * Copyright (C) 1998-2008 Sebastian Trueg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include "k3baudioeditorwidget.h" #include #include #include #include #include #include #include #include #include #include #include class K3b::AudioEditorWidget::Range { public: Range( int i, const K3b::Msf& s, const K3b::Msf& e, bool sf, bool ef, const QString& t, const QBrush& b ) : id(i), start(s), end(e), startFixed(sf), endFixed(ef), brush(b), toolTip(t) { } int id; K3b::Msf start; K3b::Msf end; bool startFixed; bool endFixed; QBrush brush; QString toolTip; bool operator<( const K3b::AudioEditorWidget::Range& r ) const { return start < r.start; } bool operator>( const K3b::AudioEditorWidget::Range& r ) const { return start > r.start; } bool operator==( const K3b::AudioEditorWidget::Range& r ) const { return id == r.id; } typedef QLinkedList List; }; class K3b::AudioEditorWidget::Marker { public: Marker( int i, const K3b::Msf& msf, bool f, const QColor& c, const QString& t ) : id(i), pos(msf), fixed(f), color(c), toolTip(t) { } int id; K3b::Msf pos; bool fixed; QColor color; QString toolTip; operator K3b::Msf& () { return pos; } bool operator==( const Marker& r ) const { return id == r.id; } typedef QLinkedList List; }; struct K3b::AudioEditorWidget::SortByStart { bool operator()( Range const* lhs, Range const* rhs ) { return lhs->start < rhs->start; } }; class K3b::AudioEditorWidget::Private { public: Private() : allowOverlappingRanges(true), rangeSelectionEnabled(false), selectedRangeId(0), draggedRangeId(0), movedRangeId(0), maxMarkers(1), idCnt(1), mouseAt(true), draggingRangeEnd(false), draggedMarker(0), margin(5) { } QBrush selectedRangeBrush; bool allowOverlappingRanges; bool rangeSelectionEnabled; int selectedRangeId; int draggedRangeId; int movedRangeId; K3b::Msf lastMovePosition; Range::List ranges; Marker::List markers; int maxMarkers; K3b::Msf length; int idCnt; bool mouseAt; bool draggingRangeEnd; Marker* draggedMarker; /** * Margin around the timethingy */ int margin; }; K3b::AudioEditorWidget::AudioEditorWidget( QWidget* parent ) : QFrame( parent ) { d = new Private; d->selectedRangeBrush = palette().highlight(); setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum ); setFrameStyle( StyledPanel|Sunken ); setMouseTracking(true); setCursor( Qt::PointingHandCursor ); } K3b::AudioEditorWidget::~AudioEditorWidget() { delete d; } QSize K3b::AudioEditorWidget::minimumSizeHint() const { // some fixed height minimum and enough space for a tickmark every minute // But never exceed 2/3 of the screen width, otherwise it just looks ugly // FIXME: this is still bad for long sources and there might be 60 minutes sources! int maxWidth = QApplication::desktop()->width()*2/3; int wantedWidth = 2*d->margin + 2*frameWidth() + (d->length.totalFrames()/75/60 + 1) * fontMetrics().width( "000" ); return QSize( qMin( maxWidth, wantedWidth ), 2*d->margin + 12 + 6 /*12 for the tickmarks and 6 for the markers */ + fontMetrics().height() + 2*frameWidth() ); } QSize K3b::AudioEditorWidget::sizeHint() const { return minimumSizeHint(); } void K3b::AudioEditorWidget::setLength( const K3b::Msf& length ) { d->length = length; // TODO: remove markers beyond length // TODO: shorten ranges if nesseccary update(); } const K3b::Msf K3b::AudioEditorWidget::length() const { return d->length; } void K3b::AudioEditorWidget::setSelectedRangeBrush( const QBrush& b ) { d->selectedRangeBrush = b; } const QBrush& K3b::AudioEditorWidget::selectedRangeBrush() const { return d->selectedRangeBrush; } void K3b::AudioEditorWidget::setAllowOverlappingRanges( bool b ) { d->allowOverlappingRanges = b; } bool K3b::AudioEditorWidget::allowOverlappingRanges() const { return d->allowOverlappingRanges; } void K3b::AudioEditorWidget::enableRangeSelection( bool b ) { d->rangeSelectionEnabled = b; update(); } bool K3b::AudioEditorWidget::rangeSelectedEnabled() const { return d->selectedRangeId != 0; } void K3b::AudioEditorWidget::setSelectedRange( int id ) { d->selectedRangeId = id; if( rangeSelectedEnabled() ) { update(); emit selectedRangeChanged( d->selectedRangeId ); } } int K3b::AudioEditorWidget::selectedRange() const { return d->selectedRangeId; } int K3b::AudioEditorWidget::addRange( const K3b::Msf& start, const K3b::Msf& end, bool startFixed, bool endFixed, const QString& toolTip, const QBrush& brush ) { if( start > end || end > d->length-1 ) return -1; Range r( d->idCnt++, start, end, startFixed, endFixed, toolTip, - brush.style() != Qt::NoBrush ? brush : palette().background() ); + brush.style() != Qt::NoBrush ? brush : palette().window() ); d->ranges.append( r ); // only update the changed range QRect rect = contentsRect(); rect.setLeft( msfToPos( start ) ); rect.setRight( msfToPos( end ) ); update( rect ); return r.id; } int K3b::AudioEditorWidget::findRange( int pos ) const { Range* r = findRange( QPoint( pos, 0 ) ); if( r ) return r->id; else return 0; } int K3b::AudioEditorWidget::findRangeEdge( int pos, bool* end ) const { if( Range* r = findRangeEdge( QPoint( pos, 0 ), end ) ) return r->id; else return 0; } bool K3b::AudioEditorWidget::modifyRange( int identifier, const K3b::Msf& start, const K3b::Msf& end ) { if( Range* range = getRange( identifier ) ) { if( start > end ) return false; if( end > d->length ) return false; range->start = start; range->end = end; if( !d->allowOverlappingRanges ) fixupOverlappingRanges( range->id ); repaint(); return true; } else return false; } bool K3b::AudioEditorWidget::removeRange( int identifier ) { if( Range* range = getRange( identifier ) ) { emit rangeRemoved( identifier ); // repaint only the part of the range QRect rect = contentsRect(); rect.setLeft( msfToPos( range->start ) ); rect.setRight( msfToPos( range->end ) ); if( d->selectedRangeId == range->id ) setSelectedRange( 0 ); d->ranges.removeAll( *range ); update( rect ); return true; } else return false; } K3b::Msf K3b::AudioEditorWidget::rangeStart( int identifier ) const { if( Range* range = getRange( identifier ) ) return range->start; else return 0; } K3b::Msf K3b::AudioEditorWidget::rangeEnd( int identifier ) const { if( Range* range = getRange( identifier ) ) return range->end; else return 0; } QList K3b::AudioEditorWidget::allRanges() const { // First collect all ranges to one random-access container... QList ranges; Q_FOREACH( Range const& range, d->ranges ) { ranges.push_back( &range ); } // ...then sort it... std::sort(ranges.begin(), ranges.end(), SortByStart()); // ...finally collect its identifiers and return the collection. QList identifiers; Q_FOREACH( Range const* range, ranges ) { identifiers.push_back( range->id ); } return identifiers; } void K3b::AudioEditorWidget::setMaxNumberOfMarkers( int i ) { d->maxMarkers = i; // remove last markers while( d->markers.count() > qMax( 1, d->maxMarkers ) ) { removeMarker( d->markers.last().id ); } } int K3b::AudioEditorWidget::addMarker( const K3b::Msf& pos, bool fixed, const QString& toolTip, const QColor& color ) { if( pos < d->length ) { - Marker m( d->idCnt++, pos, fixed, color.isValid() ? color : palette().foreground().color(), toolTip ); + Marker m( d->idCnt++, pos, fixed, color.isValid() ? color : palette().windowText().color(), toolTip ); d->markers.append( m ); return m.id; } else return -1; } bool K3b::AudioEditorWidget::removeMarker( int identifier ) { if( Marker* m = getMarker( identifier ) ) { emit markerRemoved( identifier ); // TODO: in case a marker is bigger than one pixel this needs to be changed QRect rect = contentsRect(); rect.setLeft( msfToPos( m->pos ) ); rect.setRight( msfToPos( m->pos ) ); d->markers.removeAll( *m ); update( rect ); return true; } else return false; } bool K3b::AudioEditorWidget::moveMarker( int identifier, const K3b::Msf& pos ) { if( pos < d->length ) if( Marker* m = getMarker( identifier ) ) { QRect rect = contentsRect(); rect.setLeft( qMin( msfToPos( pos ), msfToPos( m->pos ) ) ); rect.setRight( qMax( msfToPos( pos ), msfToPos( m->pos ) ) ); m->pos = pos; // TODO: in case a marker is bigger than one pixel this needs to be changed update( rect ); return true; } return false; } void K3b::AudioEditorWidget::enableMouseAtSignal( bool b ) { d->mouseAt = b; } void K3b::AudioEditorWidget::paintEvent( QPaintEvent* e ) { Q_UNUSED( e ); QPainter p( this ); QRect drawRect( contentsRect() ); drawRect.setLeft( drawRect.left() + d->margin ); drawRect.setRight( drawRect.right() - d->margin ); // from minimumSizeHint() // int neededHeight = fontMetrics().height() + 12 + 6; // drawRect.setTop( drawRect.top() + (drawRect.height() - neededHeight)/2 ); // drawRect.setHeight( neededHeight ); drawRect.setTop( drawRect.top() + d->margin ); drawRect.setBottom( drawRect.bottom() - d->margin ); drawAll( &p, drawRect ); } void K3b::AudioEditorWidget::drawAll( QPainter* p, const QRect& drawRect ) { // we simply draw the ranges one after the other. for( Range::List::const_iterator it = d->ranges.constBegin(); it != d->ranges.constEnd(); ++it ) drawRange( p, drawRect, *it ); // Hack to make sure the currently selected range is always on top if( Range* selectedRange = getRange( d->selectedRangeId ) ) drawRange( p, drawRect, *selectedRange ); for( Marker::List::const_iterator it = d->markers.constBegin(); it != d->markers.constEnd(); ++it ) drawMarker( p, drawRect, *it ); // left vline p->drawLine( drawRect.left(), drawRect.top(), drawRect.left(), drawRect.bottom() ); // timeline p->drawLine( drawRect.left(), drawRect.bottom(), drawRect.right(), drawRect.bottom() ); // right vline p->drawLine( drawRect.right(), drawRect.top(), drawRect.right(), drawRect.bottom() ); // draw minute markers every minute int minute = 1; int minuteStep = 1; int markerVPos = drawRect.bottom(); int maxMarkerWidth = fontMetrics().width( QString::number(d->length.minutes()) ); int minNeededSpace = maxMarkerWidth + 1; int x = 0; while( minute*60*75 < d->length ) { int newX = msfToPos( minute*60*75 ); // only draw the mark if we have anough space if( newX - x >= minNeededSpace ) { p->drawLine( newX, markerVPos, newX, markerVPos-5 ); QRect txtRect( newX-(maxMarkerWidth/2), markerVPos - 6 - fontMetrics().height(), maxMarkerWidth, fontMetrics().height() ); p->drawText( txtRect, Qt::AlignCenter, QString::number(minute) ); // FIXME: draw second markers if we have enough space x = newX; } else { minute -= minuteStep; if( minuteStep == 1 ) minuteStep = 5; else minuteStep *= 2; } minute += minuteStep; } } void K3b::AudioEditorWidget::drawRange( QPainter* p, const QRect& drawRect, const K3b::AudioEditorWidget::Range& r ) { p->save(); int start = msfToPos( r.start ); int end = msfToPos( r.end ); if( rangeSelectedEnabled() && r.id == d->selectedRangeId ) p->setBrush( selectedRangeBrush() ); else p->setBrush( r.brush ); p->drawRect( start, drawRect.top()+6 , end-start+1-1, drawRect.height()-6-1 ); p->restore(); } void K3b::AudioEditorWidget::drawMarker( QPainter* p, const QRect& drawRect, const K3b::AudioEditorWidget::Marker& m ) { p->save(); p->setPen( m.color ); p->setBrush( m.color ); int x = msfToPos( m.pos ); p->drawLine( x, drawRect.bottom(), x, drawRect.top() ); QPolygon points( 3 ); points.setPoint( 0, x, drawRect.top() + 6 ); points.setPoint( 1, x-3, drawRect.top() ); points.setPoint( 2, x+3, drawRect.top() ); p->drawPolygon( points ); p->restore(); } void K3b::AudioEditorWidget::fixupOverlappingRanges( int rangeId ) { Range* r = getRange( rangeId ); Range::List::iterator range = d->ranges.begin(); while( r != 0 && range != d->ranges.end() ) { if( range->id != rangeId ) { // remove the range if it is covered completely if( range->start >= r->start && range->end <= r->end ) { if( d->selectedRangeId == range->id ) setSelectedRange( 0 ); range = d->ranges.erase( range ); emit rangeRemoved( rangeId ); // "r" may be invalid at this point, let's find it once again r = getRange( rangeId ); } else { // split the range if it contains r completely if( r->start >= range->start && r->end <= range->end ) { // create a new range that spans the part after r addRange( r->end+1, range->end, range->startFixed, range->endFixed, range->toolTip, range->brush ); // modify the old range to only span the part before r range->end = r->start-1; emit rangeChanged( range->id, range->start, range->end ); } else if( range->start >= r->start && range->start <= r->end ) { range->start = r->end+1; emit rangeChanged( range->id, range->start, range->end ); } else if( range->end >= r->start && range->end <= r->end ) { range->end = r->start-1; emit rangeChanged( range->id, range->start, range->end ); } ++range; } } else { ++range; } } } void K3b::AudioEditorWidget::mousePressEvent( QMouseEvent* e ) { d->draggedRangeId = 0; d->draggedMarker = 0; bool end; if( Range* r = findRangeEdge( e->pos(), &end ) ) { d->draggedRangeId = r->id; d->draggingRangeEnd = end; setSelectedRange( r->id ); } else if( Range* r = findRange( e->pos() ) ) { d->movedRangeId = r->id; d->lastMovePosition = posToMsf( e->pos().x() ); setSelectedRange( r->id ); d->draggedMarker = findMarker( e->pos() ); } QFrame::mousePressEvent(e); } void K3b::AudioEditorWidget::mouseReleaseEvent( QMouseEvent* e ) { if( !d->allowOverlappingRanges ) { // // modify and even delete ranges that we touched // if( d->draggedRangeId != 0 ) { fixupOverlappingRanges( d->draggedRangeId ); repaint(); } else if( d->movedRangeId != 0 ) { fixupOverlappingRanges( d->movedRangeId ); repaint(); } } d->draggedRangeId = 0; d->draggedMarker = 0; d->movedRangeId = 0; QFrame::mouseReleaseEvent(e); } void K3b::AudioEditorWidget::mouseDoubleClickEvent( QMouseEvent* e ) { QFrame::mouseDoubleClickEvent(e); } void K3b::AudioEditorWidget::mouseMoveEvent( QMouseEvent* e ) { if( d->mouseAt ) emit mouseAt( posToMsf( e->pos().x() ) ); if( e->buttons() & Qt::LeftButton ) { if( Range* draggedRange = getRange( d->draggedRangeId ) ) { // determine the position the range's end was dragged to and its other end K3b::Msf msfPos = qMax( K3b::Msf(), qMin( posToMsf( e->pos().x() ), d->length-1 ) ); K3b::Msf otherEnd = ( d->draggingRangeEnd ? draggedRange->start : draggedRange->end ); // move it to the new pos if( d->draggingRangeEnd ) draggedRange->end = msfPos; else draggedRange->start = msfPos; // if we pass the other end switch them if( draggedRange->start > draggedRange->end ) { K3b::Msf buf = draggedRange->start; draggedRange->start = draggedRange->end; draggedRange->end = buf; d->draggingRangeEnd = !d->draggingRangeEnd; } emit rangeChanged( draggedRange->id, draggedRange->start, draggedRange->end ); repaint(); } else if( d->draggedMarker ) { d->draggedMarker->pos = posToMsf( e->pos().x() ); emit markerMoved( d->draggedMarker->id, d->draggedMarker->pos ); repaint(); } else if( Range* movedRange = getRange( d->movedRangeId ) ) { int diff = posToMsf( e->pos().x() ).lba() - d->lastMovePosition.lba(); if( movedRange->end + diff >= d->length ) diff = d->length.lba() - movedRange->end.lba() - 1; else if( movedRange->start - diff < 0 ) diff = -1 * movedRange->start.lba(); movedRange->start += diff; movedRange->end += diff; // if( !d->allowOverlappingRanges ) // fixupOverlappingRanges( d->movedRangeId ); d->lastMovePosition = posToMsf( e->pos().x() ); emit rangeChanged( movedRange->id, movedRange->start, movedRange->end ); repaint(); } } else if( findRangeEdge( e->pos() ) || findMarker( e->pos() ) ) setCursor( Qt::SizeHorCursor ); else setCursor( Qt::PointingHandCursor ); QFrame::mouseMoveEvent(e); } bool K3b::AudioEditorWidget::event( QEvent* e ) { if( e->type() == QEvent::ToolTip ) { QHelpEvent* helpEvent = dynamic_cast( e ); const QPoint pos = mapFromGlobal( helpEvent->globalPos() ); if( Marker* m = findMarker( pos ) ) { QToolTip::showText( helpEvent->globalPos(), m->toolTip.isEmpty() ? m->pos.toString() : QString("%1 (%2)").arg(m->toolTip).arg(m->pos.toString()), this ); } else if( Range* range = findRange( pos ) ) { QToolTip::showText( helpEvent->globalPos(), range->toolTip.isEmpty() ? QString("%1 - %2").arg(range->start.toString()).arg(range->end.toString()) : QString("%1 (%2 - %3)").arg(range->toolTip).arg(range->start.toString()).arg(range->end.toString()), this ); } else { QToolTip::hideText(); } e->accept(); return true; } else { return QWidget::event( e ); } } K3b::AudioEditorWidget::Range* K3b::AudioEditorWidget::getRange( int i ) const { for( Range::List::iterator it = d->ranges.begin(); it != d->ranges.end(); ++it ) if( (*it).id == i ) return &( *it ); return 0; } K3b::AudioEditorWidget::Range* K3b::AudioEditorWidget::findRange( const QPoint& p ) const { // TODO: binary search; maybe store start and end positions in sorted lists for quick searching // this might be a stupid approach but we do not have many ranges anyway for( Range::List::iterator it = d->ranges.begin(); it != d->ranges.end(); ++it ) { Range& range = *it; int start = msfToPos( range.start ); int end = msfToPos( range.end ); if( p.x() >= start && p.x() <= end ) { return ⦥ } } return 0; } K3b::AudioEditorWidget::Range* K3b::AudioEditorWidget::findRangeEdge( const QPoint& p, bool* isEnd ) const { // TODO: binary search // this might be a stupid approach but we do not have many ranges anyway for( Range::List::iterator it = d->ranges.begin(); it != d->ranges.end(); ++it ) { Range& range = *it; int start = msfToPos( range.start ); int end = msfToPos( range.end ); // // In case two ranges meet at one point moving the mouse cursor deeper into one // range allows for grabbing that end // if( p.x() - 3 <= start && p.x() >= start && !range.startFixed ) { if( isEnd ) *isEnd = false; return ⦥ } else if( p.x() <= end && p.x() + 3 >= end && !range.endFixed ) { if( isEnd ) *isEnd = true; return ⦥ } } return 0; } K3b::AudioEditorWidget::Marker* K3b::AudioEditorWidget::getMarker( int i ) const { for( Marker::List::iterator it = d->markers.begin(); it != d->markers.end(); ++it ) if( (*it).id == i ) return &( *it ); return 0; } K3b::AudioEditorWidget::Marker* K3b::AudioEditorWidget::findMarker( const QPoint& p ) const { // TODO: binary search for( Marker::List::iterator it = d->markers.begin(); it != d->markers.end(); ++it ) { Marker& marker = *it; int start = msfToPos( marker.pos ); if( p.x() - 1 <= start && p.x() + 1 >= start && !marker.fixed ) return ▮ } return 0; } // p is in widget coordinates K3b::Msf K3b::AudioEditorWidget::posToMsf( int p ) const { int w = contentsRect().width() - 2*d->margin; int x = qMin( p-frameWidth()-d->margin, w ); return ( (int)((double)(d->length.lba()-1) / (double)w * (double)x) ); } // returns widget coordinates int K3b::AudioEditorWidget::msfToPos( const K3b::Msf& msf ) const { int w = contentsRect().width() - 2*d->margin; int pos = (int)((double)w / (double)(d->length.lba()-1) * (double)msf.lba()); return frameWidth() + d->margin + qMin( pos, w-1 ); }