/*************************************************************************** * Copyright (C) 2005 by Max Howell * * Copyright (C) 2005 by Ian Monroe * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #define DEBUG_PREFIX "StatusBar" #include "amarok.h" #include "debug.h" #include "squeezedtextlabel.h" #include "statusBarBase.h" #include "threadmanager.h" #include "enginecontroller.h" #include #include #include #include #include #include //writeLogFile() #include //writeLogFile() #include #include #include #include #include //polish() #include #include #include #include //class CloseButton #include #include #include //TQToolTip::palette() #include //segregated classes #include "popupMessage.h" #include "progressBar.h" namespace KDE { namespace SingleShotPool { static void startTimer( int timeout, TQObject *receiver, const char *slot ) { TQTimer *timer = static_cast( receiver->child( slot ) ); if( !timer ) { timer = new TQTimer( receiver, slot ); receiver->connect( timer, TQT_SIGNAL(timeout()), slot ); } timer->start( timeout, true ); } static inline bool isActive( TQObject *parent, const char *slot ) { TQTimer *timer = static_cast( parent->child( slot ) ); return timer && timer->isA( TQTIMER_OBJECT_NAME_STRING ) && timer->isActive(); } } //TODO allow for uncertain progress periods StatusBar::StatusBar( TQWidget *parent, const char *name ) : TQWidget( parent, name ) , m_logCounter( -1 ) { TQBoxLayout *mainlayout = new TQHBoxLayout( this, 2, /*spacing*/5 ); //we need extra spacing due to the way we paint the surrounding boxes TQBoxLayout *layout = new TQHBoxLayout( mainlayout, /*spacing*/5 ); TQHBox *statusBarTextBox = new TQHBox( this, "statusBarTextBox" ); m_mainTextLabel = new KDE::SqueezedTextLabel( statusBarTextBox, "mainTextLabel" ); TQToolButton *shortLongButton = new TQToolButton( statusBarTextBox, "shortLongButton" ); shortLongButton->hide(); TQHBox *mainProgressBarBox = new TQHBox( this, "progressBox" ); TQToolButton *b1 = new TQToolButton( mainProgressBarBox, "cancelButton" ); m_mainProgressBar = new TQProgressBar( mainProgressBarBox, "mainProgressBar" ); TQToolButton *b2 = new TQToolButton( mainProgressBarBox, "showAllProgressDetails" ); mainProgressBarBox->setSpacing( 2 ); mainProgressBarBox->hide(); layout->add( statusBarTextBox ); layout->add( mainProgressBarBox ); layout->setStretchFactor( statusBarTextBox, 3 ); layout->setStretchFactor( mainProgressBarBox, 1 ); m_otherWidgetLayout = new TQHBoxLayout( mainlayout, /*spacing*/5 ); mainlayout->setStretchFactor( layout, 6 ); mainlayout->setStretchFactor( m_otherWidgetLayout, 4 ); shortLongButton->setIconSet( SmallIconSet( "edit_add" ) ); TQToolTip::add( shortLongButton, i18n( "Show details" ) ); connect( shortLongButton, TQT_SIGNAL(clicked()), TQT_SLOT(showShortLongDetails()) ); b1->setIconSet( SmallIconSet( "cancel" ) ); b2->setIconSet( SmallIconSet( "2uparrow") ); b2->setToggleButton( true ); TQToolTip::add( b1, i18n( "Abort all background-operations" ) ); TQToolTip::add( b2, i18n( "Show progress detail" ) ); connect( b1, TQT_SIGNAL(clicked()), TQT_SLOT(abortAllProgressOperations()) ); connect( b2, TQT_SIGNAL(toggled( bool )), TQT_SLOT(toggleProgressWindow( bool )) ); m_popupProgress = new OverlayWidget( this, mainProgressBarBox, "popupProgress" ); m_popupProgress->setMargin( 1 ); m_popupProgress->setFrameStyle( TQFrame::Panel | TQFrame::Raised ); m_popupProgress->setFrameShape( TQFrame::StyledPanel ); m_popupProgress->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum ); (new TQGridLayout( m_popupProgress, 1 /*rows*/, 3 /*cols*/, 6, 3 ))->setAutoAdd( true ); } void StatusBar::addWidget( TQWidget *widget ) { m_otherWidgetLayout->add( widget ); } /// reimplemented functions void StatusBar::polish() { TQWidget::polish(); int h = 0; TQObjectList *list = queryList( TQWIDGET_OBJECT_NAME_STRING, 0, false, false ); for( TQObject * o = list->first(); o; o = list->next() ) { int _h = TQT_TQWIDGET( o ) ->minimumSizeHint().height(); if ( _h > h ) h = _h; // debug() << o->className() << ", " << o->name() << ": " << _h << ": " << TQT_TQWIDGET(o)->minimumHeight() << endl; if ( o->inherits( TQLABEL_OBJECT_NAME_STRING ) ) static_cast(TQT_TQWIDGET(o))->setIndent( 4 ); } h -= 4; // it's too big usually for ( TQObject * o = list->first(); o; o = list->next() ) TQT_TQWIDGET(o)->setFixedHeight( h ); delete list; } void StatusBar::paintEvent( TQPaintEvent* ) { TQObjectList *list = queryList( TQWIDGET_OBJECT_NAME_STRING, 0, false, false ); TQPainter p( this ); for( TQObject * o = list->first(); o; o = list->next() ) { TQWidget *w = TQT_TQWIDGET( o ); if ( !w->isVisible() ) continue; style().tqdrawPrimitive( TQStyle::PE_StatusBarSection, &p, TQRect( w->x() - 1, w->y() - 1, w->width() + 2, w->height() + 2 ), colorGroup(), TQStyle::Style_Default, TQStyleOption( w ) ); } delete list; } bool StatusBar::event( TQEvent *e ) { if ( e->type() == TQEvent::LayoutHint ) update(); return TQWidget::event( e ); } /// Messaging system void StatusBar::setMainText( const TQString &text ) { SHOULD_BE_GUI m_mainText = text; // it may not be appropriate for us to set the mainText yet resetMainText(); } void StatusBar::shortMessage( const TQString &text, bool longShort ) { SHOULD_BE_GUI m_mainTextLabel->setText( text ); m_mainTextLabel->setPalette( TQToolTip::palette() ); SingleShotPool::startTimer( longShort ? 8000 : 5000, TQT_TQOBJECT(this), TQT_SLOT(resetMainText()) ); writeLogFile( text ); } void StatusBar::resetMainText() { // if( sender() ) // debug() << sender()->name() << endl; // don't reset if we are showing a shortMessage if( SingleShotPool::isActive( TQT_TQOBJECT(this), TQT_SLOT(resetMainText()) ) ) return; m_mainTextLabel->unsetPalette(); shortLongButton()->hide(); if( allDone() ) m_mainTextLabel->setText( m_mainText ); else { ProgressBar *bar = 0; uint count = 0; foreachType( ProgressMap, m_progressMap ) if( !(*it)->m_done ) { bar = *it; count++; } if( count == 1 ) m_mainTextLabel->setText( bar->description() + i18n("...") ); else m_mainTextLabel->setText( i18n("Multiple background-tasks running") ); } } void StatusBar::shortLongMessage( const TQString &_short, const TQString &_long, int type ) { SHOULD_BE_GUI m_shortLongType = type; if( !_short.isEmpty() ) shortMessage( _short, true ); if ( !_long.isEmpty() ) { m_shortLongText = _long; shortLongButton()->show(); writeLogFile( _long ); } } void StatusBar::longMessage( const TQString &text, int type ) { SHOULD_BE_GUI if( text.isEmpty() ) return; PopupMessage *message; message = new PopupMessage( this, m_mainTextLabel ); connect( message, TQT_SIGNAL(destroyed(TQObject *)), this, TQT_SLOT(popupDeleted(TQObject *)) ); message->setText( text ); TQString image; switch( type ) { case Information: case Question: image = TDEGlobal::iconLoader()->iconPath( "messagebox_info", -KIcon::SizeHuge ); break; case Sorry: case Warning: image = TDEGlobal::iconLoader()->iconPath( "messagebox_warning", -KIcon::SizeHuge ); break; case Error: image = TDEGlobal::iconLoader()->iconPath( "messagebox_critical", -KIcon::SizeHuge ); // don't hide error messages. // message->setTimeout( 0 ); break; } if( !image.isEmpty() ) message->setImage( image ); if ( !m_messageQueue.isEmpty() ) message->stackUnder( m_messageQueue.last() ); message->display(); raise(); m_messageQueue += message; writeLogFile( text ); } void StatusBar::popupDeleted( TQObject *obj ) { m_messageQueue.remove( TQT_TQWIDGET( obj ) ); } void StatusBar::longMessageThreadSafe( const TQString &text, int /*type*/ ) { TQCustomEvent * e = new TQCustomEvent( 1000 ); e->setData( new TQString( text ) ); TQApplication::postEvent( this, e ); } void StatusBar::customEvent( TQCustomEvent *e ) { if(e->type() == 1000 ){ TQString *s = static_cast( e->data() ); longMessage( *s ); delete s; }else if(e->type() == 2000 ){ EngineController::instance()->unplayableNotification(); } } /// application wide progress monitor inline bool StatusBar::allDone() { for( ProgressMap::Iterator it = m_progressMap.begin(), end = m_progressMap.end(); it != end; ++it ) if( (*it)->m_done == false ) return false; return true; } ProgressBar& StatusBar::newProgressOperation( TQObject *owner ) { SHOULD_BE_GUI if ( m_progressMap.contains( owner ) ) return *m_progressMap[owner]; if( allDone() ) // if we're allDone then we need to remove the old progressBars before // we start anything new or the total progress will not be accurate pruneProgressBars(); else toggleProgressWindowButton()->show(); TQLabel *label = new TQLabel( m_popupProgress ); m_progressMap.insert( owner, new ProgressBar( m_popupProgress, label ) ); m_popupProgress->reposition(); connect( owner, TQT_SIGNAL(destroyed( TQObject* )), TQT_SLOT(endProgressOperation( TQObject* )) ); // so we can show the correct progress information // after the ProgressBar is setup SingleShotPool::startTimer( 0, TQT_TQOBJECT(this), TQT_SLOT(updateProgressAppearance()) ); progressBox()->show(); cancelButton()->setEnabled( true ); return *m_progressMap[ owner ]; } ProgressBar& StatusBar::newProgressOperation( TDEIO::Job *job ) { SHOULD_BE_GUI ProgressBar & bar = newProgressOperation( static_cast( job ) ); bar.setTotalSteps( 100 ); if(!allDone()) toggleProgressWindowButton()->show(); connect( job, TQT_SIGNAL(result( TDEIO::Job* )), TQT_SLOT(endProgressOperation()) ); //TODO connect( job, TQT_SIGNAL(infoMessage( TDEIO::Job *job, const TQString& )), TQT_SLOT() ); connect( job, TQT_SIGNAL(percent( TDEIO::Job*, unsigned long )), TQT_SLOT(setProgress( TDEIO::Job*, unsigned long )) ); return bar; } void StatusBar::endProgressOperation() { TQObject *owner = TQT_TQOBJECT(const_cast( sender() )); //HACK deconsting it TDEIO::Job *job = dynamic_cast( owner ); //FIXME doesn't seem to work for TDEIO::DeleteJob, it has it's own error handler and returns no error too // if you try to delete http urls for instance <- KDE SUCKS! if( job && job->error() ) shortLongMessage( TQString(), job->errorString(), Error ); endProgressOperation( owner ); } void StatusBar::endProgressOperation( TQObject *owner ) { //the owner of this progress operation has been deleted //we need to stop listening for progress from it //NOTE we don't delete it yet, as this upsets some //things, we just call setDone(). if ( !m_progressMap.contains( owner ) ) { SingleShotPool::startTimer( 2000, TQT_TQOBJECT(this), TQT_SLOT(hideMainProgressBar()) ); return ; } m_progressMap[owner]->setDone(); if( allDone() && !m_popupProgress->isShown() ) { cancelButton()->setEnabled( false ); SingleShotPool::startTimer( 2000, TQT_TQOBJECT(this), TQT_SLOT(hideMainProgressBar()) ); } updateTotalProgress(); } void StatusBar::abortAllProgressOperations() //slot { for( ProgressMap::Iterator it = m_progressMap.begin(), end = m_progressMap.end(); it != end; ++it ) (*it)->m_abort->animateClick(); m_mainTextLabel->setText( i18n("Aborting all jobs...") ); cancelButton()->setEnabled( false ); } void StatusBar::toggleProgressWindow( bool show ) //slot { m_popupProgress->reposition(); //FIXME shouldn't be needed, adding bars doesn't seem to do this m_popupProgress->setShown( show ); if( !show ) SingleShotPool::startTimer( 2000, TQT_TQOBJECT(this), TQT_SLOT(hideMainProgressBar()) ); } void StatusBar::showShortLongDetails() { if( !m_shortLongText.isEmpty() ) longMessage( m_shortLongText, m_shortLongType ); m_shortLongType = Information; m_shortLongText = TQString(); shortLongButton()->hide(); } void StatusBar::showMainProgressBar() { if( !allDone() ) progressBox()->show(); } void StatusBar::hideMainProgressBar() { if( allDone() && !m_popupProgress->isShown() ) { pruneProgressBars(); resetMainText(); m_mainProgressBar->setProgress( 0 ); progressBox()->hide(); } } void StatusBar::setProgress( int steps ) { setProgress( TQT_TQOBJECT(const_cast(sender())), steps ); } void StatusBar::setProgress( TDEIO::Job *job, unsigned long percent ) { setProgress( static_cast( job ), percent ); } void StatusBar::setProgress( const TQObject *owner, int steps ) { if ( !m_progressMap.contains( owner ) ) return ; m_progressMap[ owner ] ->setProgress( steps ); updateTotalProgress(); } void StatusBar::incrementProgressTotalSteps( const TQObject *owner, int inc ) { if ( !m_progressMap.contains( owner ) ) return ; m_progressMap[ owner ] ->setTotalSteps( m_progressMap[ owner ] ->totalSteps() + inc ); updateTotalProgress(); } void StatusBar::setProgressStatus( const TQObject *owner, const TQString &text ) { if ( !m_progressMap.contains( owner ) ) return ; m_progressMap[owner]->setStatus( text ); } void StatusBar::incrementProgress() { incrementProgress( TQT_TQOBJECT(const_cast(sender()) )); } void StatusBar::incrementProgress( const TQObject *owner ) { if ( !m_progressMap.contains( owner ) ) return; m_progressMap[owner]->setProgress( m_progressMap[ owner ] ->progress() + 1 ); updateTotalProgress(); } void StatusBar::updateTotalProgress() { uint totalSteps = 0; uint progress = 0; foreachType( ProgressMap, m_progressMap ) { totalSteps += (*it)->totalSteps(); progress += (*it)->progress(); } if( totalSteps == 0 && progress == 0 ) return; m_mainProgressBar->setTotalSteps( totalSteps ); m_mainProgressBar->setProgress( progress ); pruneProgressBars(); } void StatusBar::updateProgressAppearance() { toggleProgressWindowButton()->setShown( m_progressMap.count() > 1 ); resetMainText(); updateTotalProgress(); } void StatusBar::pruneProgressBars() { ProgressMap::Iterator it = m_progressMap.begin(); const ProgressMap::Iterator end = m_progressMap.end(); int count = 0; bool removedBar = false; while( it != end ) if( (*it)->m_done == true ) { delete (*it)->m_label; delete (*it)->m_abort; delete (*it); ProgressMap::Iterator jt = it; ++it; m_progressMap.erase( jt ); removedBar = true; } else { ++it; ++count; } if(count==1 && removedBar) //if its gone from 2 or more bars to one bar... { resetMainText(); toggleProgressWindowButton()->hide(); m_popupProgress->setShown(false); } } /// Method which writes to a rotating log file. void StatusBar::writeLogFile( const TQString &text ) { if( text.isEmpty() ) return; const int counter = 4; // number of logs to keep const uint maxSize = 30000; // approximately 1000 lines per log file int c = counter; TQString logBase = Amarok::saveLocation() + "statusbar.log."; TQFile file; if( m_logCounter < 0 ) //find which log to write to { for( ; c > 0; c-- ) { TQString log = logBase + TQString::number(c); file.setName( log ); if( TQFile::exists( log ) && file.size() <= maxSize ) break; } if( c == 0 ) file.setName( logBase + '0' ); m_logCounter = c; } else { file.setName( logBase + TQString::number(m_logCounter) ); } if( file.size() > maxSize ) { m_logCounter++; m_logCounter = m_logCounter % counter; file.setName( logBase + TQString::number(m_logCounter) ); // if we have overflown the log, then we want to overwrite the previous content if( !file.open( IO_WriteOnly ) ) return; } else if( !file.open( IO_WriteOnly|IO_Append ) ) return; TQTextStream stream( &file ); stream.setEncoding( TQTextStream::UnicodeUTF8 ); stream << "[" << TDEGlobal::locale()->formatDateTime( TQDateTime::currentDateTime() ) << "] " << text << endl; } } //namespace KDE #include "statusBarBase.moc"