You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/collectionbrowser.cpp

4719 lines
166 KiB

// (c) 2004 Mark Kretschmann <markey@web.de>
// (c) 2004 Christian Muehlhaeuser <chris@chris.de>
// (c) 2005 GÁbor Lehel <illissius@gmail.com>
// (c) 2005 Alexandre Pereira de Oliveira <aleprj@gmail.com>
// (c) 2005 Christan Baumgart <christianbaumgart@web.de>
// (c) 2006 Joe Rabinoff <bobqwatson@yahoo.com>
// See COPYING file for licensing information.
#include <config.h>
#include "amarok.h"
#include "amarokconfig.h"
#include "browserbar.h"
#include "browserToolBar.h"
#include "clicklineedit.h"
#include "collectionbrowser.h"
#include "collectiondb.h"
#include "covermanager.h"
#include "debug.h"
#include "deletedialog.h"
#include "directorylist.h"
#include "editfilterdialog.h"
#include "k3bexporter.h"
#include "mediabrowser.h"
#include "metabundle.h"
#include "mountpointmanager.h"
#include "organizecollectiondialog.h"
#include "playlist.h" //insertMedia()
#include "playlistbrowser.h"
#include "starmanager.h"
#include "statusbar.h"
#include "tagdialog.h"
#include "threadmanager.h"
#include "qstringx.h"
#include <taglib/tfile.h> //TagLib::File::isWritable
#include <unistd.h> //CollectionView ctor
#include <tqapplication.h>
#include <tqcstring.h>
#include <tqdragobject.h>
#include <tqlayout.h> //infobox
#include <tqmap.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqptrlist.h>
#include <tqpushbutton.h>
#include <tqsimplerichtext.h>
#include <tqtimer.h>
#include <tqtooltip.h> //TQToolTip::add()
#include <tqheader.h>
#include <tqregexp.h>
#include <kactioncollection.h>
#include <kapplication.h> //kapp
#include <kconfig.h>
#include <kcombobox.h>
#include <kcursor.h>
#include <kdialogbase.h>
#include <kglobal.h>
#include <kiconloader.h> //renderView()
#include <klocale.h>
#include <kmessagebox.h>
#include <kpopupmenu.h>
#include <ktoolbarbutton.h> //ctor
#include <kurldrag.h> //dragObject()
#include <kio/job.h>
#include <kpushbutton.h>
extern "C"
{
#if KDE_VERSION < KDE_MAKE_VERSION(3,3,91)
#include <X11/Xlib.h> //ControlMask in contentsDragMoveEvent()
#endif
}
using namespace CollectionBrowserIds;
namespace Amarok { extern KConfig *config( const TQString& ); }
class CoverFetcher;
CollectionBrowser *CollectionBrowser::s_instance = 0;
CollectionBrowser::CollectionBrowser( const char* name )
: TQVBox( 0, name )
, m_cat1Menu( new KPopupMenu( this ) )
, m_cat2Menu( new KPopupMenu( this ) )
, m_cat3Menu( new KPopupMenu( this ) )
, m_timer( new TQTimer( this ) )
, m_returnPressed( false )
{
s_instance = this;
setSpacing( 4 );
m_toolbar = new Browser::ToolBar( this );
{ //<Search LineEdit>
KToolBarButton *button;
KToolBar* searchToolBar = new Browser::ToolBar( this );
button = new KToolBarButton( "locationbar_erase", 0, searchToolBar );
m_searchEdit = new ClickLineEdit( i18n( "Enter search terms here" ), searchToolBar );
m_searchEdit->installEventFilter( this );
KPushButton *filterButton = new KPushButton("...", searchToolBar, "filter");
searchToolBar->setStretchableWidget( m_searchEdit );
m_searchEdit->setFrame( TQFrame::Sunken );
connect( button, TQT_SIGNAL( clicked() ), TQT_SLOT( slotClearFilter() ) );
connect( filterButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotEditFilter() ) );
TQToolTip::add( button, i18n( "Clear search field" ) );
TQToolTip::add( m_searchEdit, i18n( "Enter space-separated terms to search in the collection" ) );
TQToolTip::add( filterButton, i18n( "Click to edit collection filter" ) );
} //</Search LineEdit>
// We put a little toolbar for the forward/back buttons for iPod
// navigation to the right of m_timeFilter. This toolbar is
// hidden when not in iPod browsing mode; it is shown and hidden
// in CollectionView::setViewMode(). m_ipodHbox holds m_timeFilter
// and m_ipodToolbar
m_ipodHbox = new TQHBox( this );
m_ipodHbox->setSpacing( 7 ); // looks better
m_timeFilter = new KComboBox( m_ipodHbox, "timeFilter" );
m_ipodHbox->setStretchFactor( m_timeFilter, 1 );
// Allow the combobox to shrink so the iPod buttons are still visible
m_timeFilter->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Fixed );
m_timeFilter->insertItem( i18n( "Entire Collection" ) );
m_timeFilter->insertItem( i18n( "Added Today" ) );
m_timeFilter->insertItem( i18n( "Added Within One Week" ) );
m_timeFilter->insertItem( i18n( "Added Within One Month" ) );
m_timeFilter->insertItem( i18n( "Added Within Three Months" ) );
m_timeFilter->insertItem( i18n( "Added Within One Year" ) );
// m_ipodToolbar just holds the forward and back buttons, which are
// plugged below
m_ipodToolbar = new Browser::ToolBar( m_ipodHbox );
m_ipodHbox->setStretchFactor( m_ipodToolbar, 0 );
m_ipodToolbar->setIconText( KToolBar::IconOnly, false );
KActionCollection* ac = new KActionCollection( this );
m_view = new CollectionView( this );
m_view->installEventFilter( this );
m_configureAction = new KAction( i18n( "Configure Folders" ), Amarok::icon( "configure" ), 0, TQT_TQOBJECT(this), TQT_SLOT( setupDirs() ), ac, "Configure" );
m_treeViewAction = new KRadioAction( i18n( "Tree View" ), "view_tree", 0, TQT_TQOBJECT(m_view), TQT_SLOT( setTreeMode() ), ac, "Tree View" );
m_flatViewAction = new KRadioAction( i18n( "Flat View" ), "view_detailed", 0, TQT_TQOBJECT(m_view), TQT_SLOT( setFlatMode() ), ac, "Flat View" );
m_ipodViewAction = new KRadioAction( i18n( "iPod View" ), Amarok::icon("device"), 0, TQT_TQOBJECT(m_view), TQT_SLOT( setIpodMode() ), ac, "iPod View" );
m_treeViewAction->setExclusiveGroup("view mode");
m_flatViewAction->setExclusiveGroup("view mode");
m_ipodViewAction->setExclusiveGroup("view mode");
switch( m_view->m_viewMode )
{
case CollectionView::modeTreeView:
m_treeViewAction->setChecked( true );
break;
case CollectionView::modeFlatView:
m_flatViewAction->setChecked( true );
break;
case CollectionView::modeIpodView:
m_ipodViewAction->setChecked( true );
break;
}
m_showDividerAction = new KToggleAction( i18n( "Show Divider" ), "leftjust", 0, TQT_TQOBJECT(this), TQT_SLOT( toggleDivider() ), ac, "Show Divider" );
m_showDividerAction->setChecked(m_view->m_showDivider);
// m_ipodIncrement and m_ipodDecrement are the actions that
// correspond to moving forward / backward in the iPod collection
// browser window; see the "For iPod-style navigation" comments below.
m_ipodDecrement = new KAction( i18n( "Browse backward" ),
TQIconSet( m_view->ipodDecrementIcon(), TQIconSet::Small ),
0, TQT_TQOBJECT(m_view), TQT_SLOT( decrementDepth() ), ac,
"iPod Decrement" );
m_ipodIncrement = new KAction( i18n( "Browse forward" ),
TQIconSet( m_view->ipodIncrementIcon(), TQIconSet::Small ),
0, TQT_TQOBJECT(m_view), TQT_SLOT( incrementDepth() ), ac,
"iPod Increment" );
m_ipodDecrement->plug( m_ipodToolbar );
m_ipodIncrement->plug( m_ipodToolbar );
// Show / hide m_ipodToolbar based on the view mode
ipodToolbar( m_view->m_viewMode == CollectionView::modeIpodView );
m_tagfilterMenuButton = new KActionMenu( i18n( "Group By" ), "filter", ac );
m_tagfilterMenuButton->setDelayed( false );
// FIXME: either both or nothing
//m_tagfilterMenuButton->setEnabled( m_view->m_viewMode == CollectionView::modeTreeView );
//connect ( m_treeViewAction, TQT_SIGNAL ( toggled(bool) ), m_tagfilterMenuButton, TQT_SLOT( setEnabled (bool) ) );
layoutToolbar();
m_categoryMenu = m_tagfilterMenuButton->popupMenu();
m_categoryMenu->insertItem( i18n( "Artist" ), m_view, TQT_SLOT( presetMenu( int ) ), 0, IdArtist );
m_categoryMenu->insertItem( i18n( "Artist / Album" ), m_view, TQT_SLOT( presetMenu( int ) ), 0, IdArtistAlbum );
m_categoryMenu->insertItem( i18n( "Artist" )+" / "+ i18n( "Year" ) + i18n( " - " ) + i18n( "Album" ), m_view, TQT_SLOT( presetMenu( int ) ), 0, IdArtistVisYearAlbum );
m_categoryMenu->insertItem( i18n( "Album" ), m_view, TQT_SLOT( presetMenu( int ) ), 0, IdAlbum );
m_categoryMenu->insertItem( i18n( "Genre / Artist" ), m_view, TQT_SLOT( presetMenu( int ) ), 0, IdGenreArtist );
m_categoryMenu->insertItem( i18n( "Genre / Artist / Album" ), m_view, TQT_SLOT( presetMenu( int ) ), 0, IdGenreArtistAlbum );
m_categoryMenu->insertSeparator();
m_categoryMenu->insertItem( i18n( "&First Level" ), m_cat1Menu );
m_categoryMenu->insertItem( i18n( "&Second Level"), m_cat2Menu );
m_categoryMenu->insertItem( i18n( "&Third Level" ), m_cat3Menu );
m_cat1Menu ->insertItem( i18n( "&Album" ), m_view, TQT_SLOT( cat1Menu( int ) ), 0, IdAlbum );
m_cat1Menu ->insertItem( i18n( "(Y&ear) - Album" ), m_view, TQT_SLOT( cat1Menu( int ) ), 0, IdVisYearAlbum);
m_cat1Menu ->insertItem( i18n( "A&rtist"), m_view, TQT_SLOT( cat1Menu( int ) ), 0, IdArtist );
m_cat1Menu ->insertItem( i18n( "&Composer"), m_view, TQT_SLOT( cat1Menu( int ) ), 0, IdComposer );
m_cat1Menu ->insertItem( i18n( "&Genre" ), m_view, TQT_SLOT( cat1Menu( int ) ), 0, IdGenre );
m_cat1Menu ->insertItem( i18n( "&Year" ), m_view, TQT_SLOT( cat1Menu( int ) ), 0, IdYear );
m_cat1Menu ->insertItem( i18n( "&Label" ), m_view, TQT_SLOT( cat1Menu( int ) ), 0, IdLabel );
m_cat2Menu ->insertItem( i18n( "&None" ), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdNone );
m_cat2Menu ->insertSeparator();
m_cat2Menu ->insertItem( i18n( "&Album" ), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdAlbum );
m_cat2Menu ->insertItem( i18n( "(Y&ear) - Album" ), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdVisYearAlbum);
m_cat2Menu ->insertItem( i18n( "A&rtist" ), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdArtist );
m_cat2Menu ->insertItem( i18n( "&Composer"), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdComposer );
m_cat2Menu ->insertItem( i18n( "&Genre" ), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdGenre );
m_cat2Menu ->insertItem( i18n( "&Year" ), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdYear );
m_cat2Menu ->insertItem( i18n( "&Label" ), m_view, TQT_SLOT( cat2Menu( int ) ), 0, IdLabel );
m_cat3Menu ->insertItem( i18n( "&None" ), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdNone );
m_cat3Menu ->insertSeparator();
m_cat3Menu ->insertItem( i18n( "A&lbum" ), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdAlbum );
m_cat3Menu ->insertItem( i18n( "(Y&ear) - Album" ), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdVisYearAlbum);
m_cat3Menu ->insertItem( i18n( "A&rtist" ), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdArtist );
m_cat3Menu ->insertItem( i18n( "&Composer"), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdComposer );
m_cat3Menu ->insertItem( i18n( "&Genre" ), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdGenre );
m_cat3Menu ->insertItem( i18n( "&Year" ), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdYear );
m_cat3Menu ->insertItem( i18n( "&Label" ), m_view, TQT_SLOT( cat3Menu( int ) ), 0, IdLabel );
m_view->cat1Menu( m_view->m_cat1, false );
m_view->cat2Menu( m_view->m_cat2, false );
m_view->cat3Menu( m_view->m_cat3, false );
m_view->setViewMode( m_view->m_viewMode );
connect( m_timer, TQT_SIGNAL( timeout() ), TQT_SLOT( slotSetFilter() ) );
connect( m_searchEdit, TQT_SIGNAL( textChanged( const TQString& ) ), TQT_SLOT( slotSetFilterTimeout() ) );
connect( m_timeFilter, TQT_SIGNAL( activated( int ) ), TQT_SLOT( slotSetFilter() ) );
setFocusProxy( m_view ); //default object to get focus
}
void
CollectionBrowser::slotClearFilter() //SLOT
{
m_searchEdit->clear();
kapp->processEvents(); //Let the search bar redraw fully.
TQTimer::singleShot( 0, this, TQT_SLOT( slotSetFilter() ) ); //Filter instantly
TQTimer::singleShot( 0, m_view, TQT_SLOT( slotEnsureSelectedItemVisible() ) );
}
void
CollectionBrowser::slotSetFilterTimeout() //SLOT
{
m_returnPressed = false;
m_timer->start( 280, true ); //stops the timer for us first
}
void
CollectionBrowser::slotSetFilter() //SLOT
{
m_timer->stop();
m_view->m_dirty = true;
m_view->setFilter( m_searchEdit->text() );
m_view->setTimeFilter( m_timeFilter->currentItem() );
m_view->renderView();
if ( m_returnPressed )
appendSearchResults();
m_returnPressed = false;
}
void
CollectionBrowser::slotSetFilter( const TQString &filter ) //SLOT
{
m_searchEdit->setText( filter );
kapp->processEvents(); //Let the search bar redraw fully.
TQTimer::singleShot( 0, this, TQT_SLOT( slotSetFilter() ) ); //Filter instantly
TQTimer::singleShot( 0, m_view, TQT_SLOT( slotEnsureSelectedItemVisible() ) );
}
void
CollectionBrowser::slotEditFilter() //SLOT
{
EditFilterDialog *cod = new EditFilterDialog( this, false, m_searchEdit->text() );
connect( cod, TQT_SIGNAL(filterChanged(const TQString &)), TQT_SLOT(slotSetFilter(const TQString &)) );
if( cod->exec() )
m_searchEdit->setText( cod->filter() );
delete cod;
}
void
CollectionBrowser::setupDirs() //SLOT
{
m_view->setupDirs();
}
void
CollectionBrowser::toggleDivider() //SLOT
{
m_view->setShowDivider( m_showDividerAction->isChecked() );
}
void
CollectionBrowser::appendSearchResults()
{
//If we are not filtering, or the search string has changed recently, do nothing
if ( m_searchEdit->text().stripWhiteSpace().isEmpty() || m_timer->isActive() )
return;
m_view->selectAll();
Playlist::instance()->insertMedia( m_view->listSelected(), Playlist::Unique | Playlist::Append );
m_view->clearSelection();
slotClearFilter();
}
bool
CollectionBrowser::eventFilter( TQObject *o, TQEvent *e )
{
switch( e->type() )
{
case 6/*TQEvent::KeyPress*/:
//there are a few keypresses that we intercept
#define e TQT_TQKEYEVENT(e)
if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(m_searchEdit) ) //the search lineedit
{
switch( e->key() )
{
case Key_Up:
case Key_Down:
case Key_PageDown:
case Key_PageUp:
m_view->setFocus();
TQApplication::sendEvent( m_view, e );
return true;
case Key_Escape:
slotClearFilter();
return true;
case Key_Return:
case Key_Enter:
if ( m_timer->isActive() )
{
//Immediately filter and add results
m_timer->stop();
m_returnPressed = true;
TQTimer::singleShot( 0, this, TQT_SLOT( slotSetFilter() ) );
}
else
{
//Add current results
appendSearchResults();
}
return true;
default:
return false;
}
}
// (Joe Rabinoff) the code that was here which dealt with wrapping
// the selection around when Key_Up or Key_Down was pressed was
// moved to CollectionView::keyPressEvent(). That code also
// skips dividers.
if( ( e->key() >= Key_0 && e->key() <= Key_Z ) || e->key() == Key_Backspace || e->key() == Key_Escape )
{
m_searchEdit->setFocus();
TQApplication::sendEvent( m_searchEdit, e );
return true;
}
#undef e
break;
default:
break;
}
return TQVBox::eventFilter( o, e );
}
void
CollectionBrowser::layoutToolbar()
{
if ( !m_toolbar ) return;
m_toolbar->clear();
m_toolbar->setIconText( KToolBar::IconTextRight, false );
m_tagfilterMenuButton->plug( m_toolbar );
m_toolbar->setIconText( KToolBar::IconOnly, false );
m_toolbar->insertLineSeparator();
m_treeViewAction->plug( m_toolbar );
m_flatViewAction->plug( m_toolbar );
m_ipodViewAction->plug( m_toolbar );
m_toolbar->insertLineSeparator();
m_showDividerAction->plug( m_toolbar );
m_configureAction->plug( m_toolbar );
//This would break things if the toolbar is too big, see bug #121915
//setMinimumWidth( m_toolbar->sizeHint().width() + 2 ); //set a reasonable minWidth
}
// (De)activate the iPod toolbar when switching into and out of
// iPod browsing mode
void
CollectionBrowser::ipodToolbar( bool activate )
{
if( activate )
m_ipodToolbar->show();
else
m_ipodToolbar->hide();
}
//////////////////////////////////////////////////////////////////////////////////////////
// CLASS CollectionView
//////////////////////////////////////////////////////////////////////////////////////////
CollectionView* CollectionView::m_instance = 0;
CollectionView::CollectionView( CollectionBrowser* parent )
: KListView( parent )
, m_parent( parent )
, m_timeFilter( 0 )
, m_currentDepth( 0 )
, m_ipodIncremented ( 1 )
, m_dirty( true )
, m_organizingFileCancelled( false )
{
DEBUG_FUNC_INFO
m_instance = this;
setSelectionMode( TQListView::Extended );
setItemsMovable( false );
setSorting( 0 );
setShowSortIndicator( true );
setAcceptDrops( true );
setAllColumnsShowFocus( true );
//<READ CONFIG>
KConfig* config = Amarok::config( "Collection Browser" );
m_cat1 = config->readNumEntry( "Category1", IdArtist );
m_cat2 = config->readNumEntry( "Category2", IdAlbum );
m_cat3 = config->readNumEntry( "Category3", IdNone );
#define saneCat(x) (x==IdAlbum||x==IdArtist||x==IdComposer||x==IdGenre||x==IdYear \
||x==IdNone \
||x==IdArtistAlbum||x==IdGenreArtist||x==IdGenreArtistAlbum||x==IdVisYearAlbum||x==IdArtistVisYearAlbum)
if( !saneCat(m_cat1) )
{
m_cat1 = IdArtist;
m_cat2 = IdAlbum;
m_cat2 = IdNone;
}
if( !saneCat(m_cat2) || !saneCat(m_cat3) )
{
m_cat2 = m_cat3 = IdNone;
}
#undef saneCat
m_viewMode = config->readNumEntry( "ViewMode", modeTreeView );
m_showDivider = config->readBoolEntry( "ShowDivider", true);
updateTrackDepth();
m_flatColumnWidths.clear();
TQStringList flatWidths = config->readListEntry( "FlatColumnWidths" );
for( TQStringList::iterator it = flatWidths.begin();
it != flatWidths.end();
it++ )
m_flatColumnWidths.push_back( (*it).toInt() );
//</READ CONFIG>
KActionCollection* ac = new KActionCollection( this );
KStdAction::selectAll( TQT_TQOBJECT(this), TQT_SLOT( selectAll() ), ac, "collectionview_select_all" );
connect( CollectionDB::instance(), TQT_SIGNAL( scanStarted() ),
this, TQT_SLOT( scanStarted() ) );
connect( CollectionDB::instance(), TQT_SIGNAL( scanDone( bool ) ),
this, TQT_SLOT( scanDone( bool ) ) );
connect( BrowserBar::instance(), TQT_SIGNAL( browserActivated( int ) ),
this, TQT_SLOT( renderView() ) ); // renderView() checks if current tab is this
connect( CollectionDB::instance(), TQT_SIGNAL( ratingChanged( const TQString&, int ) ),
this, TQT_SLOT( ratingChanged( const TQString&, int ) ) );
connect( this, TQT_SIGNAL( expanded( TQListViewItem* ) ),
this, TQT_SLOT( slotExpand( TQListViewItem* ) ) );
connect( this, TQT_SIGNAL( collapsed( TQListViewItem* ) ),
this, TQT_SLOT( slotCollapse( TQListViewItem* ) ) );
connect( this, TQT_SIGNAL( returnPressed( TQListViewItem* ) ),
this, TQT_SLOT( invokeItem( TQListViewItem* ) ) );
connect( this, TQT_SIGNAL( doubleClicked( TQListViewItem*, const TQPoint&, int ) ),
this, TQT_SLOT( invokeItem( TQListViewItem*, const TQPoint&, int ) ) );
connect( this, TQT_SIGNAL( clicked( TQListViewItem*, const TQPoint&, int ) ),
this, TQT_SLOT( ipodItemClicked( TQListViewItem*, const TQPoint&, int ) ) );
connect( this, TQT_SIGNAL( contextMenuRequested( TQListViewItem*, const TQPoint&, int ) ),
this, TQT_SLOT( rmbPressed( TQListViewItem*, const TQPoint&, int ) ) );
connect( header(), TQT_SIGNAL( sizeChange( int, int, int ) ),
this, TQT_SLOT( triggerUpdate() ) );
connect( MountPointManager::instance(), TQT_SIGNAL( mediumConnected( int ) ),
this, TQT_SLOT( databaseChanged() ) );
connect( MountPointManager::instance(), TQT_SIGNAL( mediumRemoved( int ) ),
this, TQT_SLOT( databaseChanged() ) );
}
CollectionView::~CollectionView() {
DEBUG_FUNC_INFO
KConfig* const config = Amarok::config( "Collection Browser" );
config->writeEntry( "Category1", m_cat1 );
config->writeEntry( "Category2", m_cat2 );
config->writeEntry( "Category3", m_cat3 );
config->writeEntry( "ViewMode", m_viewMode );
config->writeEntry( "ShowDivider", m_showDivider );
TQStringList flatWidths;
for( TQValueList<int>::iterator it = m_flatColumnWidths.begin();
it != m_flatColumnWidths.end();
it++ )
flatWidths.push_back( TQString::number( (*it) ) );
config->writeEntry( "FlatColumnWidths", flatWidths );
}
void
CollectionView::setShowDivider( bool show )
{
if (show != m_showDivider) {
m_showDivider = show;
renderView(true);
}
}
// Reimplemented for iPod-style navigation, and to skip dividers
// Specifically, this method traps the Key_Up/Down/Left/Right events.
// When Up or Down is pressed, it skips dividers and wraps around when
// necessary. When Left or Right is pressed and we are viewing in
// iPod mode, the iPod "move forward / backward" actions are activated.
void
CollectionView::keyPressEvent( TQKeyEvent *e )
{
typedef TQListViewItemIterator It;
// Reimplement up and down to skip dividers and to loop around.
// Some of this code used to be in CollectionBrowser::eventFilter.
// This rewritten code is more faithful to the ordinary moving
// behavior, even when looping around. (For instance, it behaves
// correctly if control-up is pressed at the top of the screen.)
// It sends fake keypress events to the parent instead of programatically
// selecting items.
if( (e->key() == Key_Up || e->key() == Key_Down ) && currentItem() )
{
// Handle both up and down at once to avoid code duplication (it's
// a delicate piece of logic, and was hard to get right)
TQListViewItem *cur = currentItem();
#define nextItem (e->key() == Key_Up ? cur->itemAbove() : cur->itemBelow())
bool wraparound = true;
// First skip any dividers directly above / below
do
{
KListView::keyPressEvent( e );
if( currentItem() == cur ) // Prevent infinite loops
{
if( nextItem != 0 )
wraparound = false;
break;
}
cur = currentItem();
if( cur && dynamic_cast<DividerItem*>( cur ) == 0 )
wraparound = false; // Found an item above / below
} while( cur != NULL
&& dynamic_cast<DividerItem*>(cur) != 0
&& nextItem != 0 );
if( cur == 0 ) return; // Shouldn't happen
// Wrap around if necessary, by sending a Key_Home/Key_End event.
if( wraparound )
{
TQKeyEvent e2 ( e->type(),
(e->key() == Key_Up ? Key_End : Key_Home),
0, e->state(),
TQString(), e->isAutoRepeat(), e->count() );
TQApplication::sendEvent( this, &e2 );
cur = currentItem();
// The first item may also be a divider, so keep moving
// until it's not
while ( cur != 0
&& dynamic_cast<DividerItem*>(cur) != 0
&& nextItem != 0 )
{
KListView::keyPressEvent( e );
if( currentItem() == cur ) // Prevent infinite loops
break;
cur = currentItem();
}
}
#undef nextItem
}
// When Right/Left is pressed in iPod view mode, activate the iPod
// "move forward/backward" action.
else if( (e->key() == Key_Left || e->key() == Key_Right)
&& m_viewMode == modeIpodView )
{
if( e->key() == Key_Right )
m_parent->m_ipodIncrement->activate();
else if( e->key() == Key_Left )
m_parent->m_ipodDecrement->activate();
}
else // we don't want the event
KListView::keyPressEvent( e );
}
//////////////////////////////////////////////////////////////////////////////////////////
// public slots
//////////////////////////////////////////////////////////////////////////////////////////
void
CollectionView::renderView(bool force /* = false */) //SLOT
{
SHOULD_BE_GUI
if(!force && !m_dirty )
return;
if( BrowserBar::instance()->currentBrowser() != m_parent )
{
// the collectionbrowser is intensive for sql, so we only renderView() if the tab
// is currently active. else, wait until user focuses it.
// debug() << "current browser is not collection, aborting renderView()" << endl;
m_dirty = true;
return;
}
m_dirty = false;
// Don't cache / restore view if we're in ipod mode and we've
// just incremented or decremented, since we'll run selectIpodItems()
// below anyway.
if( childCount() &&
!(m_viewMode == modeIpodView && m_ipodIncremented > 0) )
cacheView();
//clear();
safeClear();
if ( m_viewMode == modeFlatView )
{
renderFlatModeView( force );
}
if( m_viewMode == modeIpodView )
{
renderIpodModeView( force );
}
if( m_viewMode == modeTreeView )
{
renderTreeModeView( force );
}
// Don't cache or restore view when we're just going to run
// selectIpodItems() below anyway.
if( !(m_viewMode == modeIpodView && m_ipodIncremented > 0) )
restoreView();
else
selectIpodItems();
}
//////////////////////////////////////////////////////////////////////////////////////////
// private slots
//////////////////////////////////////////////////////////////////////////////////////////
void
CollectionView::setupDirs() //SLOT
{
KDialogBase dialog( this, 0, false );
kapp->setTopWidget( &dialog );
dialog.setCaption( kapp->makeStdCaption( i18n("Configure Collection") ) );
CollectionSetup *setup = new CollectionSetup( &dialog );
dialog.setMainWidget( setup );
dialog.showButtonApply( false );
dialog.adjustSize();
// Make the dialog a bit bigger, default is too small to be useful
dialog.resize( dialog.width() + 50, dialog.height() + 150 );
if ( dialog.exec() != TQDialog::Rejected )
{
const bool rescan = ( MountPointManager::instance()->collectionFolders() != setup->dirs() );
setup->writeConfig();
if ( rescan )
CollectionDB::instance()->startScan();
}
}
void
CollectionView::scanStarted() // SLOT
{
Amarok::actionCollection()->action("update_collection")->setEnabled( false );
}
void
CollectionView::scanDone( bool changed ) //SLOT
{
if( changed )
{
renderView(true);
}
Amarok::actionCollection()->action("update_collection")->setEnabled( true );
}
void
CollectionView::slotEnsureSelectedItemVisible() //SLOT
{
//Scroll to make sure the first selected item is visible
//Find the first selected item
TQListViewItem *r=0;
for ( TQListViewItem *i = firstChild(); i && !r; i=i->nextSibling() )
{
if ( i->isSelected() )
r = i;
for ( TQListViewItem *j = i->firstChild(); j && !r; j=j->nextSibling() )
{
if ( j->isSelected() )
r = j;
for ( TQListViewItem *k = j->firstChild(); k && !r; k=k->nextSibling() )
{
if ( k->isSelected() )
r = k;
}
}
}
if ( r )
{
//We've found the selected item. Now let's refocus on it.
//An elaborate agorithm to try to make as much as possible of the vicinity visible
//It looks better if things end up consistently in one place.
//So, scroll to the end so that we come at items from the bottom.
if ( lastChild() )
ensureItemVisible( lastChild() );
//Create a reverse list of parents, grandparents etc.
//Later we try to make the grandparents in view, then their children etc.
//This means that the selected item has the most priority as it is done last.
TQValueStack<TQListViewItem*> parents;
while ( r )
{
parents.push( r );
r = r->parent();
}
while ( !parents.isEmpty() )
{
//We would prefer the next item to be visible.
if ( parents.top()->nextSibling() )
ensureItemVisible( parents.top()->nextSibling() );
//It's even more important the actual item is visible than the next one.
ensureItemVisible( parents.top() );
parents.pop();
}
}
}
void
CollectionView::slotExpand( TQListViewItem* item ) //SLOT
{
if ( !item || !item->isExpandable() ) return;
int category = 0;
TQStringList values;
QueryBuilder qb;
bool c = false;
bool SortbyTrackFirst = false;
//Sort by track number first if album is in one of the categories, otherwise by track name first
if ( m_cat1 == IdAlbum ||
m_cat2 == IdAlbum ||
m_cat3 == IdAlbum )
SortbyTrackFirst = true;
// initialization for year - album mode
TQString tmptext;
int VisYearAlbum = -1;
int VisLabel = -1;
int q_cat1=m_cat1;
int q_cat2=m_cat2;
int q_cat3=m_cat3;
if( m_cat1 == IdVisYearAlbum ||
m_cat2 == IdVisYearAlbum ||
m_cat3 == IdVisYearAlbum )
{
SortbyTrackFirst = true;
if( m_cat1 == IdVisYearAlbum )
{
VisYearAlbum = 1;
q_cat1 = IdAlbum;
}
if( m_cat2 == IdVisYearAlbum )
{
VisYearAlbum = 2;
q_cat2 = IdAlbum;
}
if( m_cat3 == IdVisYearAlbum )
{
VisYearAlbum = 3;
q_cat3 = IdAlbum;
}
}
if( m_cat1 == IdLabel ||
m_cat2 == IdLabel ||
m_cat3 == IdLabel )
{
if( m_cat1 == IdLabel )
VisLabel = 1;
if( m_cat2 == IdLabel )
VisLabel = 2;
if ( m_cat3 == IdLabel )
VisLabel = 3;
}
if ( translateTimeFilter( timeFilter() ) > 0 )
qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, TQString().setNum( TQDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater );
TQString itemText;
bool isUnknown;
if ( dynamic_cast<CollectionItem*>( item ) )
{
itemText = static_cast<CollectionItem*>( item )->getSQLText( 0 );
}
else
{
debug() << "slotExpand in CollectionView of a non-CollectionItem" << endl;
itemText = item->text( 0 );
}
switch ( item->depth() )
{
case 0:
tmptext = itemText;
isUnknown = tmptext.isEmpty();
if ( !static_cast<CollectionItem*>( item )->isSampler() )
{
if ( m_cat1 == IdArtist )
qb.setOptions( QueryBuilder::optNoCompilations );
if( VisYearAlbum == 1 )
{
tmptext = item->text( 0 );
TQString year = tmptext.left( tmptext.find( i18n(" - ") ) );
yearAlbumCalc( year, tmptext );
qb.addMatch( QueryBuilder::tabYear, year, false, true );
if ( isUnknown )
tmptext = "";
}
qb.addMatch( q_cat1, tmptext, false, true );
}
else
{
qb.setOptions( QueryBuilder::optOnlyCompilations );
c = true;
}
if ( m_cat2 == QueryBuilder::tabSong )
{
qb.addReturnValue( q_cat2, QueryBuilder::valTitle, true );
qb.addReturnValue( q_cat2, QueryBuilder::valURL );
if ( c ) qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName, true );
if ( SortbyTrackFirst ) {
qb.sortBy( q_cat2, QueryBuilder::valDiscNumber );
qb.sortBy( q_cat2, QueryBuilder::valTrack );
}
if ( c ) qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.sortBy( q_cat2, QueryBuilder::valTitle );
if ( !SortbyTrackFirst ) {
qb.sortBy( q_cat2, QueryBuilder::valDiscNumber );
qb.sortBy( q_cat2, QueryBuilder::valTrack );
}
qb.sortBy( q_cat2, QueryBuilder::valURL );
}
else
{
c = false;
qb.addReturnValue( q_cat2, QueryBuilder::valName, true );
if( VisYearAlbum == 2 )
{
qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName, true );
qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName );
}
qb.sortBy( q_cat2, QueryBuilder::valName );
}
category = m_cat2;
break;
case 1:
tmptext = dynamic_cast<CollectionItem*>( item->parent() ) ?
static_cast<CollectionItem*>( item->parent() )->getSQLText( 0 ) :
item->parent()->text( 0 );
isUnknown = tmptext.isEmpty();
if( !static_cast<CollectionItem*>( item->parent() )->isSampler() )
{
if ( m_cat1 == IdArtist )
qb.setOptions( QueryBuilder::optNoCompilations );
if( VisYearAlbum == 1 )
{
tmptext = item->parent()->text( 0 );
TQString year = tmptext.left( tmptext.find( i18n(" - ") ) );
yearAlbumCalc( year, tmptext );
qb.addMatch( QueryBuilder::tabYear, year, false, true );
if ( isUnknown )
tmptext = "";
}
qb.addMatch( q_cat1, tmptext, false, true );
}
else
{
qb.setOptions( QueryBuilder::optOnlyCompilations );
c = true;
}
tmptext = itemText;
isUnknown = tmptext.isEmpty();
if( VisYearAlbum == 2 )
{
tmptext = item->text( 0 );
TQString year = tmptext.left( tmptext.find( i18n(" - ") ) );
yearAlbumCalc( year, tmptext );
qb.addMatch( QueryBuilder::tabYear, year, false, true );
if ( isUnknown )
tmptext = "";
}
qb.addMatch( q_cat2, tmptext, false, true );
if( m_cat3 == QueryBuilder::tabSong )
{
qb.addReturnValue( q_cat3, QueryBuilder::valTitle, true );
qb.addReturnValue( q_cat3, QueryBuilder::valURL );
if ( c ) qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName, true );
if ( SortbyTrackFirst ) {
qb.sortBy( q_cat3, QueryBuilder::valDiscNumber );
qb.sortBy( q_cat3, QueryBuilder::valTrack );
}
if ( c ) qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.sortBy( q_cat3, QueryBuilder::valTitle );
if ( !SortbyTrackFirst ) {
qb.sortBy( q_cat3, QueryBuilder::valDiscNumber );
qb.sortBy( q_cat3, QueryBuilder::valTrack );
}
qb.sortBy( q_cat3, QueryBuilder::valURL );
}
else
{
c = false;
qb.addReturnValue( q_cat3, QueryBuilder::valName, true );
if( VisYearAlbum == 3 )
{
qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName );
}
qb.sortBy( q_cat3, QueryBuilder::valName );
}
category = m_cat3;
break;
case 2:
tmptext = dynamic_cast<CollectionItem*> ( item->parent()->parent() ) ?
static_cast<CollectionItem*>( item->parent()->parent() )->getSQLText( 0 ) :
item->parent()->parent()->text( 0 );
isUnknown = tmptext.isEmpty();
if ( !static_cast<CollectionItem*>( item->parent()->parent() )->isSampler() )
{
if ( m_cat1 == IdArtist )
qb.setOptions( QueryBuilder::optNoCompilations );
if( VisYearAlbum == 1 )
{
tmptext = item->parent()->parent()->text( 0 );
TQString year = tmptext.left( tmptext.find( i18n(" - ") ) );
yearAlbumCalc( year, tmptext );
qb.addMatch( QueryBuilder::tabYear, year, false, true );
if ( isUnknown )
tmptext = "";
}
qb.addMatch( q_cat1, tmptext, false, true );
}
else
{
qb.setOptions( QueryBuilder::optOnlyCompilations );
c = true;
}
tmptext = dynamic_cast<CollectionItem*>( item->parent() ) ?
static_cast<CollectionItem*>( item->parent() )->getSQLText( 0 ) :
item->parent()->text( 0 );
isUnknown = tmptext.isEmpty();
if( VisYearAlbum == 2 )
{
tmptext = item->parent()->text( 0 );
TQString year = tmptext.left( tmptext.find( i18n(" - ") ) );
yearAlbumCalc( year, tmptext );
qb.addMatch( QueryBuilder::tabYear, year, false, true );
if ( isUnknown )
tmptext = "";
}
qb.addMatch( q_cat2, tmptext, false, true );
tmptext = itemText;
isUnknown = tmptext.isEmpty();
if( VisYearAlbum == 3 )
{
tmptext = item->text( 0 );
TQString year = tmptext.left( tmptext.find( i18n(" - ") ) );
yearAlbumCalc( year, tmptext );
qb.addMatch( QueryBuilder::tabYear, year, false, true );
if ( isUnknown )
tmptext = "";
}
qb.addMatch( q_cat3, tmptext, false, true );
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle, true );
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
if( c )
qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName, true );
if ( SortbyTrackFirst ) {
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
}
if ( c ) qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle );
if ( !SortbyTrackFirst ) {
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
}
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valURL );
category = IdNone;
break;
}
qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter );
qb.setOptions( QueryBuilder::optRemoveDuplicates );
values = qb.run();
uint countReturnValues = qb.countReturnValues();
TQPixmap pixmap;
bool expandable = category != IdNone;
if ( expandable )
pixmap = iconForCategory( category );
//this check avoid possible problems on database errors. FIXME: Should we add some real error handling here,
//like calling a collection update or something?
if ( values.isEmpty() ) { return; }
for ( int i = values.count() - countReturnValues; i >= 0; i -= countReturnValues )
{
TQString text;
bool unknown=false;
if ( category == IdVisYearAlbum )
text += ( values[ i+1 ].isEmpty() ? "?" : values[ i+1 ] ) + i18n( " - " );
//show "artist - title" for compilations
if ( c )
{
if ( values[ i + 2 ].stripWhiteSpace().isEmpty() )
{
text += i18n( "Unknown" ) + i18n( " - " );
unknown = true;
}
else
text = values[ i + 2 ] + i18n( " - " );
}
if ( values[ i ].stripWhiteSpace().isEmpty() )
{
if ( category == IdLabel )
text += i18n( "No Label" );
else
text += i18n( "Unknown" );
unknown = true;
}
else
text += values[ i ];
CollectionItem* child = new CollectionItem( item, category, unknown );
child->setDragEnabled( true );
child->setDropEnabled( false );
child->setText( 0, text );
if ( expandable )
child->setPixmap( 0, pixmap );
else
child->setUrl( values[ i + 1 ] );
child->setExpandable( expandable );
}
//Display the album cover for the parent item now it is expanded
if ( dynamic_cast<CollectionItem*>( item ) )
{
CollectionItem *i = static_cast<CollectionItem*>( item );
if ( i->m_cat == IdAlbum || i->m_cat == IdVisYearAlbum )
i->setPixmap( 0, TQPixmap() ); //The pixmap given is unimportant. The cover is used.
}
}
void
CollectionView::slotCollapse( TQListViewItem* item ) //SLOT
{
//On collapse, go back from showing the cover to showing the icon for albums
if ( dynamic_cast<CollectionItem*>( item ) )
{
CollectionItem *i = static_cast<CollectionItem*>( item );
if ( i->m_cat == IdAlbum || i->m_cat == IdVisYearAlbum )
i->setPixmap( 0, iconForCategory( i->m_cat ) );
}
TQListViewItem* child = item->firstChild();
TQListViewItem* childTmp;
//delete all children
while ( child )
{
childTmp = child;
child = child->nextSibling();
delete childTmp;
}
}
void
CollectionView::ratingChanged( const TQString&, int )
{
m_dirty = true;
TQTimer::singleShot( 0, CollectionView::instance(), TQT_SLOT( renderView() ) );
}
void
CollectionView::presetMenu( int id ) //SLOT
{
switch ( id )
{
case IdArtist:
cat1Menu( IdArtist, false );
cat2Menu( IdNone, false );
cat3Menu( IdNone, false );
break;
case IdAlbum:
cat1Menu( IdAlbum, false );
cat2Menu( IdNone, false );
cat3Menu( IdNone, false );
break;
case IdArtistAlbum:
cat1Menu( IdArtist, false );
cat2Menu( IdAlbum, false );
cat3Menu( IdNone, false );
break;
case IdArtistVisYearAlbum:
cat1Menu( IdArtist, false );
cat2Menu( IdVisYearAlbum, false );
cat3Menu( IdNone, false );
break;
case IdGenreArtist:
cat1Menu( IdGenre, false );
cat2Menu( IdArtist, false );
cat3Menu( IdNone, false );
break;
case IdGenreArtistAlbum:
cat1Menu( IdGenre, false );
cat2Menu( IdArtist, false );
cat3Menu( IdAlbum, false );
break;
}
renderView(true);
}
void
CollectionView::cat1Menu( int id, bool rerender ) //SLOT
{
m_parent->m_cat1Menu->setItemChecked( m_cat1, false ); //uncheck old item
m_parent->m_cat2Menu->setItemEnabled( m_cat1, true ); //enable old items
m_parent->m_cat3Menu->setItemEnabled( m_cat1, true );
m_cat1 = id;
updateColumnHeader();
resetIpodDepth();
m_parent->m_cat1Menu->setItemChecked( m_cat1, true );
//prevent choosing the same category in both menus
m_parent->m_cat2Menu->setItemEnabled( id , false );
m_parent->m_cat3Menu->setItemEnabled( id , false );
//if this item is checked in second menu, uncheck it
if ( m_parent->m_cat2Menu->isItemChecked( id ) ) {
m_parent->m_cat2Menu->setItemChecked( id, false );
m_parent->m_cat2Menu->setItemChecked( IdNone, true );
m_cat2 = IdNone;
enableCat3Menu( false );
}
//if this item is checked in third menu, uncheck it
if ( m_parent->m_cat3Menu->isItemChecked( id ) ) {
m_parent->m_cat3Menu->setItemChecked( id, false );
m_parent->m_cat3Menu->setItemChecked( IdNone, true );
m_cat3 = IdNone;
}
updateTrackDepth();
if ( rerender )
{
renderView(true);
}
}
void
CollectionView::cat2Menu( int id, bool rerender ) //SLOT
{
m_parent->m_cat2Menu->setItemChecked( m_cat2, false ); //uncheck old item
m_parent->m_cat3Menu->setItemEnabled( m_cat3, true ); //enable old item
m_cat2 = id;
m_parent->m_cat2Menu->setItemChecked( m_cat2, true );
updateColumnHeader();
resetIpodDepth();
enableCat3Menu( id != IdNone );
//prevent choosing the same category in both menus
m_parent->m_cat3Menu->setItemEnabled( m_cat1 , false );
if( id != IdNone )
m_parent->m_cat3Menu->setItemEnabled( id , false );
//if this item is checked in third menu, uncheck it
if ( m_parent->m_cat3Menu->isItemChecked( id ) ) {
m_parent->m_cat3Menu->setItemChecked( id, false );
enableCat3Menu( false );
}
updateTrackDepth();
if ( rerender )
{
renderView(true);
}
}
void
CollectionView::cat3Menu( int id, bool rerender ) //SLOT
{
m_parent->m_cat3Menu->setItemChecked( m_cat3, false ); //uncheck old item
m_cat3 = id;
m_parent->m_cat3Menu->setItemChecked( m_cat3, true );
updateColumnHeader();
resetIpodDepth();
updateTrackDepth();
if ( rerender )
{
renderView(true);
}
}
void
CollectionView::enableCat3Menu( bool enable )
{
m_parent->m_cat3Menu->setItemEnabled( IdAlbum, enable );
m_parent->m_cat3Menu->setItemEnabled( IdVisYearAlbum, enable );
m_parent->m_cat3Menu->setItemEnabled( IdArtist, enable );
m_parent->m_cat3Menu->setItemEnabled( IdComposer, enable );
m_parent->m_cat3Menu->setItemEnabled( IdGenre, enable );
m_parent->m_cat3Menu->setItemEnabled( IdYear, enable );
m_parent->m_cat3Menu->setItemEnabled( IdLabel, enable );
if( !enable ) {
m_parent->m_cat3Menu->setItemChecked( m_cat3, false );
m_parent->m_cat3Menu->setItemChecked( IdNone, true );
m_cat3 = IdNone;
}
updateTrackDepth();
}
void
CollectionView::invokeItem( TQListViewItem* i, const TQPoint& point, int column ) //SLOT
{
if( column == -1 )
return;
TQPoint p = mapFromGlobal( point );
if ( p.x() > header()->sectionPos( header()->mapToIndex( 0 ) ) + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin()
|| p.x() < header()->sectionPos( header()->mapToIndex( 0 ) ) )
invokeItem( i );
}
void
CollectionView::invokeItem( TQListViewItem* item ) //SLOT
{
if ( !item || dynamic_cast<DividerItem*>(item) )
return;
item->setSelected( true );
setCurrentItem( item );
//append and prevent doubles in playlist
if( item->isExpandable() || m_viewMode == modeIpodView )
Playlist::instance()->insertMedia( listSelected(), Playlist::DefaultOptions );
else
Playlist::instance()->insertMedia( static_cast<CollectionItem*>( item )->url(), Playlist::DefaultOptions );
}
// This slot is here to handle clicks on the right-arrow buttons
// in iPod browsing mode
void
CollectionView::ipodItemClicked( TQListViewItem *item, const TQPoint&, int c )
{
if( item == 0 || c == 0 )
return;
if( m_viewMode != modeIpodView )
return;
// The TQt manual says NOT to delete items from within this slot
TQTimer::singleShot( 0, m_parent->m_ipodIncrement, TQT_SLOT( activate() ) );
}
void
CollectionView::rmbPressed( TQListViewItem* item, const TQPoint& point, int ) //SLOT
{
if ( dynamic_cast<DividerItem*>( item ) ) return;
int artistLevel = -1;
if ( item ) {
KPopupMenu menu( this );
int cat = 0;
if ( m_viewMode == modeTreeView ) {
switch ( item->depth() )
{
case 0:
cat = m_cat1;
break;
case 1:
if( m_cat1 == IdArtist )
artistLevel = 0;
cat = m_cat2;
break;
case 2:
if( m_cat1 == IdArtist )
artistLevel = 0;
else if( m_cat2 == IdArtist )
artistLevel = 1;
cat = m_cat3;
break;
}
}
else if ( m_viewMode == modeIpodView ) {
int catArr[3] = {m_cat1, m_cat2, m_cat3};
if ( m_currentDepth < trackDepth() )
cat = catArr[m_currentDepth];
}
enum Actions { APPEND, QUEUE, MAKE, SAVE, MEDIA_DEVICE, BURN_ARTIST,
BURN_COMPOSER, BURN_ALBUM, BURN_CD, FETCH, INFO,
COMPILATION_SET, COMPILATION_UNSET, ORGANIZE, DELETE, TRASH,
FILE_MENU };
TQString trueItemText = getTrueItemText( cat, item );
KURL::List selection = listSelected();
TQStringList siblingSelection = listSelectedSiblingsOf( cat, item );
menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE );
menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), selection.count() == 1 ? i18n( "&Queue Track" )
: i18n( "&Queue Tracks" ), QUEUE );
if( selection.count() > 1 || item->isExpandable() )
menu.insertItem( SmallIconSet( Amarok::icon( "save" ) ), i18n( "&Save as Playlist..." ), SAVE );
menu.insertSeparator();
if( MediaBrowser::isAvailable() )
menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), i18n( "&Transfer to Media Device" ), MEDIA_DEVICE );
if( cat == IdArtist )
{
menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "&Burn All Tracks by This Artist" ), BURN_ARTIST );
menu.setItemEnabled( BURN_ARTIST, K3bExporter::isAvailable() && siblingSelection.count() == 1 );
}
else if( cat == IdComposer )
{
menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "&Burn All Tracks by This Composer" ), BURN_COMPOSER );
menu.setItemEnabled( BURN_COMPOSER, K3bExporter::isAvailable() && siblingSelection.count() == 1 );
}
else if( (cat == IdAlbum || cat == IdVisYearAlbum) )
{
menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "&Burn This Album" ), BURN_ALBUM );
menu.setItemEnabled( BURN_ALBUM, K3bExporter::isAvailable() && siblingSelection.count() == 1 );
}
// !item->isExpandable() in tree mode corresponds to
// showing tracks in iPod mode
else if( !item->isExpandable() &&
(m_viewMode != modeIpodView || m_currentDepth == trackDepth()) )
{
menu.insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n( "B&urn to CD" ), BURN_CD );
menu.setItemEnabled( BURN_CD, K3bExporter::isAvailable() );
}
menu.insertSeparator();
KPopupMenu fileMenu;
fileMenu.insertItem( SmallIconSet( "filesaveas" ), i18n( "&Organize File..." , "&Organize %n Files..." , selection.count() ) , ORGANIZE );
fileMenu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Delete File..." , "&Delete %n Files..." , selection.count() ) , DELETE );
menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Manage &Files" ), &fileMenu, FILE_MENU );
if( (cat == IdAlbum || cat == IdVisYearAlbum) && siblingSelection.count() == 1 ) // cover fetch isn't multiselection capable
{
menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch Cover From amazon.%1" ).arg( CoverManager::amazonTld() ), this, TQT_SLOT( fetchCover() ), 0, FETCH );
#ifndef AMAZON_SUPPORT
menu.setItemEnabled( FETCH, false );
#endif
if( trueItemText.isEmpty() ) // disable cover fetch for unknown albums
menu.setItemEnabled( FETCH, false );
}
if ( ( (cat == IdAlbum || cat == IdVisYearAlbum) && siblingSelection.count() > 0 ) //album
|| ( !item->isExpandable() && m_viewMode == modeTreeView ) ) //or track
{
menu.insertSeparator();
menu.insertItem( SmallIconSet( "ok" ), i18n( "Show under &Various Artists" ), COMPILATION_SET );
menu.insertItem( SmallIconSet( "cancel" ), i18n( "&Do not Show under Various Artists" ), COMPILATION_UNSET );
}
menu.insertSeparator();
menu.insertItem( SmallIconSet( Amarok::icon( "info" ) )
, i18n( "Edit Track &Information...", "Edit &Information for %n Tracks...", selection.count())
, this, TQT_SLOT( showTrackInfo() ), 0, INFO );
switch( menu.exec( point ) )
{
case APPEND:
Playlist::instance()->insertMedia( selection, Playlist::Append );
break;
case MAKE:
Playlist::instance()->insertMedia( selection, Playlist::Replace );
break;
case SAVE:
playlistFromURLs( selection );
break;
case QUEUE:
Playlist::instance()->insertMedia( selection, Playlist::Queue );
break;
case MEDIA_DEVICE:
MediaBrowser::queue()->addURLs( selection );
break;
case BURN_COMPOSER:
K3bExporter::instance()->exportComposer( trueItemText );
break;
case BURN_ARTIST:
K3bExporter::instance()->exportArtist( trueItemText );
break;
case BURN_ALBUM:
if( artistLevel == -1 || static_cast<CollectionItem *>(item)->isSampler() )
{
K3bExporter::instance()->exportAlbum( trueItemText );
}
else
{
TQString artist;
if( item->depth() - artistLevel == 1 )
artist = item->parent()->text( 0 );
else if( item->depth() - artistLevel == 2 )
artist = item->parent()->parent()->text( 0 );
else if( item->depth() - artistLevel == 3 )
artist = item->parent()->parent()->parent()->text( 0 );
K3bExporter::instance()->exportAlbum( artist, trueItemText );
}
break;
case BURN_CD:
K3bExporter::instance()->exportTracks( selection );
break;
case COMPILATION_SET:
setCompilation( selection, true );
break;
case COMPILATION_UNSET:
setCompilation( selection, false );
break;
case ORGANIZE:
organizeFiles( selection, i18n( "Organize Collection Files" ), false /* do not add to collection, just move */ );
break;
case DELETE:
if ( DeleteDialog::showTrashDialog(this, selection) )
{
CollectionDB::instance()->removeSongs( selection );
foreachType( KURL::List, selection )
CollectionDB::instance()->emitFileDeleted( (*it).path() );
}
m_dirty = true;
TQTimer::singleShot( 0, CollectionView::instance(), TQT_SLOT( renderView() ) );
break;
}
}
}
void
CollectionView::setViewMode( int mode, bool rerender /*=true*/ )
{
if( m_viewMode == modeFlatView )
{
m_flatColumnWidths.clear();
for ( int c = 0; c < columns(); ++c )
m_flatColumnWidths.push_back( columnWidth( c ) );
}
m_viewMode = mode;
clear();
updateColumnHeader();
if( m_viewMode == modeIpodView )
{
#if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0)
setShadeSortColumn( false );
#endif
m_parent->m_ipodDecrement->setEnabled( m_currentDepth > 0 );
m_parent->ipodToolbar( true );
}
else
{
#if KDE_VERSION >= KDE_MAKE_VERSION(3,4,0)
setShadeSortColumn( true );
#endif
m_parent->ipodToolbar( false );
}
if ( rerender )
{
// Pretend we just incremented the view depth so that
// renderView() will call selectIpodItems()
if( m_viewMode == modeIpodView )
m_ipodIncremented = 1;
renderView( true );
}
}
void
CollectionItem::setPixmap(int column, const TQPixmap & pix)
{
//Don't show the cover if the album isn't expanded (for speed)
if ( !isOpen() )
{
TQListViewItem::setPixmap( column, pix );
return;
}
//Generate Album name
TQString album( text( 0 ) ), artist;
if ( m_cat == IdVisYearAlbum )
{
TQString pointlessString;
CollectionView::yearAlbumCalc( pointlessString, album );
}
else if ( m_cat != IdAlbum )
{
TQListViewItem::setPixmap( column, pix );
return;
}
//Now m_cat is either IdAlbum or IdVisYearAlbum, and so this is an album as required.
//Now work out the artist
CollectionItem *p = this;
while ( p->parent() && dynamic_cast<CollectionItem*>( p->parent() ) )
{
p = static_cast<CollectionItem*>( p->parent() );
if ( IdArtist == p->m_cat )
{
artist = p->text( 0 );
break;
}
}
if ( artist.isNull() )
{
//Try to guess artist - this will only happen if you don't have an Artist category
//above the Album category in the tree
QueryBuilder qb;
qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.addMatch( QueryBuilder::tabAlbum, QueryBuilder::valName, album );
TQStringList values( qb.run() );
if ( !values.isEmpty() )
artist = values[ 0 ];
else
{
//Don't bother trying to create a shadow because it won't work anyway. The
//nocover image has intial transparency, so adding the shadow doesn't work.
TQListViewItem::setPixmap( column, TQPixmap( CollectionDB::instance()->notAvailCover( false, 50 ) ) );
return;
}
}
TQListViewItem::setPixmap( column, TQPixmap( CollectionDB::instance()->albumImage( artist, album, true, 50 ) ) );
}
void
CollectionView::fetchCover() //SLOT
{
#ifdef AMAZON_SUPPORT
CollectionItem* item = static_cast<CollectionItem*>( currentItem() );
if ( !item ) return;
int cat = 0;
switch ( item->depth() )
{
case 0:
cat = m_cat1;
break;
case 1:
cat = m_cat2;
break;
case 2:
cat = m_cat3;
break;
}
TQString album = item->text(0);
if( cat == IdVisYearAlbum )
{
// we can't use findRev since an album may have " - " within it.
TQString sep = i18n(" - ");
album = album.right( album.length() - sep.length() - album.find( sep ) );
}
// find the first artist's name
TQStringList values =
CollectionDB::instance()->query( TQString (
"SELECT DISTINCT artist.name FROM artist, album, tags "
"WHERE artist.id = tags.artist AND tags.album = album.id "
"AND album.name = '%1';" )
.arg( CollectionDB::instance()->escapeString( album ) ) );
if ( !values.isEmpty() )
CollectionDB::instance()->fetchCover( this, values[0], album, false, static_cast<TQListViewItem*>(item) );
#endif
}
void
CollectionView::showTrackInfo() //SLOT
{
DEBUG_BLOCK
KURL::List urls = listSelected();
int selectedTracksNumber = urls.count();
//If we have only one, call the full dialog. Otherwise, the multiple tracks one.
if ( selectedTracksNumber == 1 )
{
TagDialog* dialog = new TagDialog( urls.first(), instance() );
dialog->show();
}
else if ( selectedTracksNumber )
{
TagDialog* dialog = new TagDialog( urls, instance() );
dialog->show();
}
}
bool
CollectionView::isOrganizingFiles() const
{
return m_organizeURLs.count() > 0;
}
void CollectionView::cancelOrganizingFiles()
{
// Set the indicator
m_organizingFileCancelled = true;
// Cancel the current underlying CollectionDB::instance()->moveFile operation
CollectionDB::instance()->cancelMovingFileJob();
}
void
CollectionView::organizeFiles( const KURL::List &urls, const TQString &caption, bool copy ) //SLOT
{
if( m_organizingFileCancelled )
{
TQString shortMsg = i18n( "Cannot start organize operation until jobs are aborted." );
Amarok::StatusBar::instance()->shortMessage( shortMsg, KDE::StatusBar::Sorry );
return;
}
if( m_organizeURLs.count() )
{
if( copy != m_organizeCopyMode )
{
TQString shortMsg = i18n( "Cannot start organize operation of different kind while another is in progress." );
Amarok::StatusBar::instance()->shortMessage( shortMsg, KDE::StatusBar::Sorry );
return;
}
else
{
m_organizeURLs += Amarok::recursiveUrlExpand( urls );
Amarok::StatusBar::instance()->incrementProgressTotalSteps( TQT_TQOBJECT(this), urls.count() );
return;
}
}
TQStringList folders = MountPointManager::instance()->collectionFolders();
if( folders.isEmpty() )
{
TQString longMsg = i18n( "You need to configure at least one folder for your collection for organizing your files." );
Amarok::StatusBar::instance()->longMessage( longMsg, KDE::StatusBar::Sorry );
return;
}
OrganizeCollectionDialogBase base( m_parent, "OrganizeFiles", true, caption,
KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::Details );
TQVBox* page = base.makeVBoxMainWidget();
OrganizeCollectionDialog dialog( page );
dialog.folderCombo->insertStringList( folders, 0 );
dialog.folderCombo->setCurrentItem( AmarokConfig::organizeDirectory() );
dialog.overwriteCheck->setChecked( AmarokConfig::overwriteFiles() );
dialog.filetypeCheck->setChecked( AmarokConfig::groupByFiletype() );
dialog.initialCheck->setChecked( AmarokConfig::groupArtists() );
dialog.spaceCheck->setChecked( AmarokConfig::replaceSpace() );
dialog.coverCheck->setChecked( AmarokConfig::coverIcons() );
dialog.ignoreTheCheck->setChecked( AmarokConfig::ignoreThe() );
dialog.vfatCheck->setChecked( AmarokConfig::vfatCompatible() );
dialog.asciiCheck->setChecked( AmarokConfig::asciiOnly() );
dialog.customschemeCheck->setChecked( AmarokConfig::useCustomScheme() );
dialog.formatEdit->setText( AmarokConfig::customScheme() );
dialog.regexpEdit->setText( AmarokConfig::replacementRegexp() );
dialog.replaceEdit->setText( AmarokConfig::replacementString() );
connect( &base, TQT_SIGNAL(detailsClicked()), &dialog, TQT_SLOT(slotDetails()) );
if( dialog.customschemeCheck->isChecked() )
{
base.setDetails( true );
}
else
{
dialog.slotDetails();
}
KURL::List previewURLs = Amarok::recursiveUrlExpand( urls.first(), 1 );
if( previewURLs.count() )
{
dialog.setPreviewBundle( MetaBundle( previewURLs.first() ) );
dialog.update( 0 );
}
base.setInitialSize( TQSize( 450, 350 ) );
if( base.exec() == KDialogBase::Accepted )
{
AmarokConfig::setOrganizeDirectory( dialog.folderCombo->currentItem() );
AmarokConfig::setOverwriteFiles( dialog.overwriteCheck->isChecked() );
AmarokConfig::setGroupByFiletype( dialog.filetypeCheck->isChecked() );
AmarokConfig::setGroupArtists( dialog.initialCheck->isChecked() );
AmarokConfig::setIgnoreThe( dialog.ignoreTheCheck->isChecked() );
AmarokConfig::setReplaceSpace( dialog.spaceCheck->isChecked() );
AmarokConfig::setCoverIcons( dialog.coverCheck->isChecked() );
AmarokConfig::setVfatCompatible( dialog.vfatCheck->isChecked() );
AmarokConfig::setAsciiOnly( dialog.asciiCheck->isChecked() );
AmarokConfig::setUseCustomScheme( dialog.customschemeCheck->isChecked() );
AmarokConfig::setCustomScheme( dialog.formatEdit->text() );
AmarokConfig::setReplacementRegexp( dialog.regexpEdit->text() );
AmarokConfig::setReplacementString( dialog.replaceEdit->text() );
KURL::List skipped;
m_organizeURLs = Amarok::recursiveUrlExpand( urls );
m_organizeCopyMode = copy;
CollectionDB::instance()->createTables( true ); // create temp tables
Amarok::StatusBar::instance()->newProgressOperation( TQT_TQOBJECT(this) )
.setDescription( caption )
.setAbortSlot( TQT_TQOBJECT(this), TQT_SLOT( cancelOrganizingFiles() ) )
.setTotalSteps( m_organizeURLs.count() );
while( !m_organizeURLs.empty() && !m_organizingFileCancelled )
{
KURL &src = m_organizeURLs.first();
if( !CollectionDB::instance()->organizeFile( src, dialog, copy ) )
{
skipped += src;
}
m_organizeURLs.pop_front();
Amarok::StatusBar::instance()->incrementProgress( TQT_TQOBJECT(this) );
if( m_organizingFileCancelled ) m_organizeURLs.clear();
}
CollectionDB::instance()->sanitizeCompilations(); //queryBuilder doesn't handle unknownCompilations
CollectionDB::instance()->copyTempTables(); // copy temp table contents to permanent tables
CollectionDB::instance()->dropTables( true ); // and drop them
// and now do an incremental scan since this was disabled while organizing files
TQTimer::singleShot( 0, CollectionDB::instance(), TQT_SLOT( scanMonitor() ) );
if( !m_organizingFileCancelled && skipped.count() > 0 )
{
TQString longMsg = i18n( "The following file could not be organized: ",
"The following %n files could not be organized: ", skipped.count() );
bool first = true;
for( KURL::List::iterator it = skipped.begin();
it != skipped.end();
it++ )
{
if( !first )
longMsg += i18n( ", " );
else
first = false;
longMsg += (*it).path();
}
longMsg += i18n( "." );
TQString shortMsg = i18n( "Sorry, one file could not be organized.",
"Sorry, %n files could not be organized.", skipped.count() );
Amarok::StatusBar::instance()->shortLongMessage( shortMsg, longMsg, KDE::StatusBar::Sorry );
}
else if ( m_organizingFileCancelled )
{
Amarok::StatusBar::instance()->shortMessage( i18n( "Aborting jobs..." ) );
m_organizingFileCancelled = false;
}
m_dirty = true;
TQTimer::singleShot( 0, CollectionView::instance(), TQT_SLOT( renderView() ) );
Amarok::StatusBar::instance()->endProgressOperation( TQT_TQOBJECT(this) );
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// private
//////////////////////////////////////////////////////////////////////////////////////////
void
CollectionView::contentsDragEnterEvent( TQDragEnterEvent *e )
{
e->accept( e->source() != viewport() && e->source() != this && KURLDrag::canDecode( e ) );
}
void
CollectionView::contentsDragMoveEvent( TQDragMoveEvent *e )
{
e->accept( e->source() != viewport() && e->source() != this && KURLDrag::canDecode( e ) );
}
void
CollectionView::contentsDropEvent( TQDropEvent *e )
{
KURL::List list;
if( KURLDrag::decode( e, list ) )
{
KURL::List expandedList;
int dropped = 0;
int invalid = 0;
for( KURL::List::iterator it = list.begin();
it != list.end();
++it )
{
if( (*it).isLocalFile() && TQFileInfo( (*it).path() ).isDir() )
expandedList += Amarok::recursiveUrlExpand( *it );
else
expandedList += *it;
}
KURL::List cleanList;
for( KURL::List::iterator it = expandedList.begin();
it != expandedList.end();
++it )
{
TQString proto = (*it).protocol();
if( !MetaBundle::isKioUrl( *it ) )
invalid++;
else if( (*it).isLocalFile() && CollectionDB::instance()->isFileInCollection( (*it).path() ) )
dropped++;
else
cleanList += *it;
}
TQString msg;
if( dropped > 0 )
msg += i18n( "One file already in collection",
"%n files already in collection", dropped );
if( invalid > 0 )
if( msg.isEmpty() )
msg += i18n( "One dropped file is invalid",
"%n dropped files are invalid", invalid );
else
msg += i18n( ", one dropped file is invalid",
", %n dropped files are invalid", invalid );
if( !msg.isEmpty() )
Amarok::StatusBar::instance()->shortMessage( msg );
if( cleanList.count() > 0 )
organizeFiles( list, i18n( "Copy Files To Collection" ), true /* copy */ );
}
}
void
CollectionView::dropProxyEvent( TQDropEvent *e )
{
contentsDropEvent( e );
}
void
CollectionView::safeClear()
{
bool block = signalsBlocked();
blockSignals( true );
clearSelection();
TQMap<TQListViewItem*, CoverFetcher*> *itemCoverMap = CollectionDB::instance()->getItemCoverMap();
TQMutex* itemCoverMapMutex = CollectionDB::instance()->getItemCoverMapMutex();
TQListViewItem *c = firstChild();
TQListViewItem *n;
itemCoverMapMutex->lock();
while( c ) {
if( itemCoverMap->contains( c ) )
itemCoverMap->erase( c );
n = c->nextSibling();
delete c;
c = n;
}
itemCoverMapMutex->unlock();
blockSignals( block );
triggerUpdate();
}
void
CollectionView::updateColumnHeader()
{
// remove all columns
for ( int i = columns() - 1; i >= 0 ; --i )
removeColumn( i );
if ( m_viewMode == modeFlatView )
{
setResizeMode( TQListView::NoColumn );
if( m_flatColumnWidths.size() == 0 )
{
addColumn( captionForTag( Title ) );
#define includesArtist(cat) (((cat)&IdArtist) \
||((cat)&IdArtistAlbum) \
||((cat)&IdGenreArtist) \
||((cat)&IdGenreArtistAlbum) \
||((cat)&IdArtistVisYearAlbum))
if( includesArtist(m_cat1)||includesArtist(m_cat2)||includesArtist(m_cat3) )
addColumn( captionForTag( Artist ) );
else
addColumn( captionForTag( Artist ), 0 );
#undef includesArtist
if( m_cat1&IdComposer || m_cat2&IdComposer || m_cat3&IdComposer )
addColumn( captionForTag( Composer ) );
else
addColumn( captionForTag( Composer ), 0 );
#define includesAlbum(cat) (((cat)&IdAlbum) \
||((cat)&IdArtistAlbum) \
||((cat)&IdGenreArtistAlbum) \
||((cat)&IdVisYearAlbum) \
||((cat)&IdArtistVisYearAlbum))
if( includesAlbum(m_cat1)||includesAlbum(m_cat2)||includesAlbum(m_cat3) )
addColumn( captionForTag( Album ) );
else
addColumn( captionForTag( Album ), 0 );
#undef includesAlbum
#define includesGenre(cat) (((cat)&IdGenre) \
||((cat)&IdGenreArtist) \
||((cat)&IdGenreArtistAlbum))
if( includesGenre(m_cat1)||includesGenre(m_cat2)||includesGenre(m_cat3) )
addColumn( captionForTag( Genre ) );
else
addColumn( captionForTag( Genre ), 0 );
#undef includesGenre
addColumn( captionForTag( Length ),0 );
addColumn( captionForTag( DiscNumber ), 0 );
addColumn( captionForTag( Track ), 0 );
#define includesYear(cat) (((cat)&IdYear) \
||((cat)&IdVisYearAlbum) \
||((cat)&IdArtistVisYearAlbum))
if( includesYear(m_cat1)||includesYear(m_cat2)||includesYear(m_cat3) )
addColumn( captionForTag( Year ) );
else
addColumn( captionForTag( Year ), 0 );
#undef includesYear
addColumn( captionForTag( Comment ), 0 );
addColumn( captionForTag( Playcount ), 0 );
addColumn( captionForTag( Score ), 0 );
addColumn( captionForTag( Rating ), 0 );
addColumn( captionForTag( Filename ), 0 );
addColumn( captionForTag( Firstplay ), 0 );
addColumn( captionForTag( Lastplay ), 0 );
addColumn( captionForTag( Modified ), 0 );
addColumn( captionForTag( Bitrate ), 0 );
addColumn( captionForTag( Filesize ), 0 );
addColumn( captionForTag( BPM ), 0 );
}
else
{
for( uint tag = 0; tag < NUM_TAGS; ++tag ) {
if( tag < m_flatColumnWidths.size() )
addColumn( captionForTag( static_cast<Tag>( tag ) ), m_flatColumnWidths[tag] );
else
addColumn( captionForTag( static_cast<Tag>( tag ) ), 0 );
}
}
setColumnAlignment( Track, TQt::AlignCenter );
setColumnAlignment( DiscNumber, TQt::AlignCenter );
setColumnAlignment( Length, TQt::AlignRight );
setColumnAlignment( Bitrate, TQt::AlignCenter );
setColumnAlignment( Score, TQt::AlignCenter );
setColumnAlignment( Playcount, TQt::AlignCenter );
setColumnAlignment( BPM, TQt::AlignRight );
//TQListView allows invisible columns to be resized, so we disable resizing for them
for ( int i = 0; i < columns(); ++i ) {
setColumnWidthMode ( i, TQListView::Manual );
if ( columnWidth( i ) == 0 )
header()->setResizeEnabled( false, i );
}
setRootIsDecorated( false );
}
else if ( m_viewMode == modeTreeView )
{
setResizeMode( TQListView::LastColumn );
TQString caption = captionForCategory( m_cat1 );
int catArr[2] = {m_cat2, m_cat3};
for(int i = 0; i < 2; i++) {
if (catArr[i] != IdNone ) {
caption += " / " + captionForCategory( catArr[i] );
}
}
addColumn( caption );
setRootIsDecorated( true );
}
// The iPod columns
// When not in track mode, there are two columns: the first
// contains the text + pixmap, and the second just has the
// right-arrow pixmap. In any case we're not in tree mode.
else if ( m_viewMode == modeIpodView )
{
TQString caption;
if( m_currentDepth == trackDepth() )
caption = i18n( "Tracks" );
else
{
int catArr[3] = {m_cat1, m_cat2, m_cat3};
caption = captionForCategory( catArr[m_currentDepth] );
}
addColumn( caption );
if( m_currentDepth != trackDepth() )
{
TQPixmap pixmap = ipodDecrementIcon();
// This column is for the "->" buttons. The width is just
// a guess, and will be changed once an item is added.
addColumn( "", pixmap.width() + 10 );
}
setRootIsDecorated( false );
// Neither column should automatically stretch, since this tends
// to add a scrollbar when it's not needed, and anyway we manually
// stretch it in viewportResizeEvent()
header()->setStretchEnabled( false, 0 );
if( m_currentDepth != trackDepth() )
header()->setStretchEnabled( false, 1 );
}
//manage column widths
TQResizeEvent rev( size(), TQSize() );
viewportResizeEvent( &rev );
m_parent->m_categoryMenu->setItemChecked( IdArtist, m_cat1 == IdArtist && m_cat2 == IdNone );
m_parent->m_categoryMenu->setItemChecked( IdAlbum, m_cat1 == IdAlbum && m_cat2 == IdNone );
m_parent->m_categoryMenu->setItemChecked( IdArtistAlbum, m_cat1 == IdArtist && m_cat2 == IdAlbum && m_cat3 == IdNone );
m_parent->m_categoryMenu->setItemChecked( IdArtistVisYearAlbum, m_cat1 == IdArtist && m_cat2 == IdVisYearAlbum && m_cat3 == IdNone );
m_parent->m_categoryMenu->setItemChecked( IdGenreArtist, m_cat1 == IdGenre && m_cat2 == IdArtist && m_cat3 == IdNone );
m_parent->m_categoryMenu->setItemChecked( IdGenreArtistAlbum, m_cat1 == IdGenre && m_cat2 == IdArtist && m_cat3 == IdAlbum );
}
void
CollectionView::startDrag()
{
KURL::List urls = listSelected();
KURLDrag* d = new KURLDrag( urls, this );
d->setPixmap( CollectionDB::createDragPixmap(urls),
TQPoint( CollectionDB::DRAGPIXMAP_OFFSET_X,
CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
d->dragCopy();
}
TQString
CollectionView::getTrueItemText( int cat, TQListViewItem* item ) const
{
//Work out the true name of the album ( where Unknown is "" ) , and the
TQString trueItemText;
if ( item == 0 )
{
warning() << "getTrueItemText() called for empty CollectionItem" << endl;
return TQString();
}
if ( dynamic_cast<CollectionItem*>( item ) )
{
CollectionItem* collectItem = static_cast<CollectionItem*>( item );
trueItemText = collectItem->getSQLText( 0 );
if ( cat == IdVisYearAlbum && !collectItem->isUnknown() )
trueItemText = trueItemText.right( trueItemText.length() - trueItemText.find( i18n( " - " ) ) - i18n( " - " ).length() );
}
else
{
trueItemText = item->text( 0 );
warning() << "getTrueItemText() called for non-CollectionItem with text '" << trueItemText << '\'' << endl;
}
return trueItemText;
}
TQStringList
CollectionView::listSelectedSiblingsOf( int cat, TQListViewItem* item )
{
// notice that using the nextSibling()-axis does not work in this case as this
// would only select items below the specified item.
TQStringList list;
TQString trueItemText;
int depth = item->depth();
// go to top most item
while( item && item->itemAbove() )
{
item = item->itemAbove();
//debug() << "walked up to item: " << getTrueItemText( cat, item ) << endl;
}
// walk down to get all selected items in same depth
while( item )
{
if ( item->isSelected() && item->depth() == depth )
{
trueItemText = getTrueItemText( cat, item );
//debug() << "selected item: " << trueItemText << endl;
list << trueItemText;
}
item = item->itemBelow();
}
return list;
}
KURL::List
CollectionView::listSelected()
{
//Here we determine the URLs of all selected items. We use two passes, one for the parent items,
//and another one for the children.
KURL::List list;
TQListViewItem* item;
TQStringList values;
QueryBuilder qb;
// initialization for year - album mode
TQString tmptext;
bool unknownText;
int VisYearAlbum = -1;
int q_cat1=m_cat1;
int q_cat2=m_cat2;
int q_cat3=m_cat3;
if (m_cat1 == IdVisYearAlbum || m_cat2 == IdVisYearAlbum || m_cat3 == IdVisYearAlbum)
{
if (m_cat1==IdVisYearAlbum)
{
VisYearAlbum = 1;
q_cat1 = IdAlbum;
}
if (m_cat2==IdVisYearAlbum)
{
VisYearAlbum = 2;
q_cat2 = IdAlbum;
}
if (m_cat3==IdVisYearAlbum)
{
VisYearAlbum = 3;
q_cat3 = IdAlbum;
}
}
if ( m_viewMode == modeFlatView )
{
for ( item = firstChild(); item; item = item->nextSibling() )
if ( item->isSelected() )
list << static_cast<CollectionItem*>( item ) ->url();
return list;
}
// The iPod selection code is written to resemble the tree mode
// selection logic, as well as what happens on an actual iPod. If
// we're in track mode, just return the list of tracks selected.
// Otherwise select all children of all currently selected items,
// sorting first by m_cat1, then m_cat2, then m_cat3. Sort by
// track first if one of the categories is Id(VisYear)Album.
// There is a difficulty with compilation albums -- if we're
// sorting by track first then we want to group compilation albums
// separately, and not with the individual artists, even if that's
// one of the categories (e.g. if the user just adds one
// compilation album, we can't sort by artist first). In other
// words, when one of the categories is Id(VisYear)Album,
// compilation albums should behave as if the artist is Various
// Artists, for sorting purposes. This is handled by making two
// separate queries, the first for the compilations, and the
// second for everything else.
// Note that if "All" is currently selected then the other
// selections are ignored.
if ( m_viewMode == modeIpodView )
{
// If we're already displaying tracks, just return the selected ones
if( m_currentDepth == trackDepth() )
{
TQPtrList<TQListViewItem> selected = selectedItems();
TQPtrList<TQListViewItem>::iterator it = selected.begin();
while( it != selected.end() )
{
if( dynamic_cast<CollectionItem*>(*it) != 0 )
list << dynamic_cast<CollectionItem*>(*it)->url();
++it;
}
return list;
}
// We're not displaying tracks.
QueryBuilder qb;
// Do a "fake" depth incrementation to figure out the
// correct filters at the current depth
incrementDepth( false );
// Copy the filter list before calling decrementDepth() below
TQStringList filters[3];
for( int i = 0; i < m_currentDepth; ++i )
filters[i] = m_ipodFilters[i];
TQStringList filterYear = m_ipodFilterYear;
// Undo the fake depth incrementation
decrementDepth( false );
int catArr[3] = {m_cat1, m_cat2, m_cat3};
int tables = 0;
bool sortByTrackFirst = false;
for(int i = 0; i < trackDepth(); ++i)
tables |= (catArr[i] == IdVisYearAlbum
? IdAlbum
: catArr[i]);
// Figure out if the results will be sorted by track first
// (i.e., if one of the filters is by album). If so, we need
// to search compilations first.
if( tables & IdAlbum )
sortByTrackFirst = true;
if( sortByTrackFirst )
{
// Build the query, recursively sorted. First get compilation
// albums so they'll be first, not sorted by artist
buildIpodQuery( qb, trackDepth(), filters, filterYear, true, true );
if ( translateTimeFilter( timeFilter() ) > 0 )
qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, TQString().setNum( TQDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater );
qb.setOptions( QueryBuilder::optOnlyCompilations );
qb.setGoogleFilter( tables | QueryBuilder::tabSong, m_filter );
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
values = qb.run();
}
// Now build the non-compilation album query
qb.clear();
buildIpodQuery( qb, trackDepth(), filters, filterYear, true, false );
if ( translateTimeFilter( timeFilter() ) > 0 )
qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, TQString().setNum( TQDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater );
if( sortByTrackFirst )
qb.setOptions( QueryBuilder::optNoCompilations );
qb.setGoogleFilter( tables | QueryBuilder::tabSong, m_filter );
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
values += qb.run();
int total = values.count() / qb.countReturnValues();
// This shouldn't happen
if( total == 0 )
return list;
TQStringList::Iterator it = values.begin();
KURL tmp;
while ( it != values.end() )
{
tmp.setPath( (*(it++)) );
list << tmp;
}
return list;
}
//first pass: parents
for ( item = firstChild(); item; item = item->nextSibling() )
if ( item->isSelected() )
{
const bool sampler = static_cast<CollectionItem*>( item )->isSampler();
qb.clear();
if ( translateTimeFilter( timeFilter() ) > 0 )
qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, TQString().setNum( TQDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater );
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
tmptext = static_cast<CollectionItem*>( item )->getSQLText( 0 );
unknownText = tmptext.isEmpty();
if ( !sampler )
{
if ( q_cat1 == IdArtist )
qb.setOptions( QueryBuilder::optNoCompilations );
if( VisYearAlbum == 1 )
{
tmptext = item->text( 0 );
TQString year = tmptext.left( tmptext.find( i18n(" - ") ) );
yearAlbumCalc( year, tmptext );
qb.addMatch( QueryBuilder::tabYear, year, false, true );
if ( unknownText )
tmptext = "";
}
qb.addMatch( q_cat1, tmptext, false, true );
}
else
qb.setOptions( QueryBuilder::optOnlyCompilations );
qb.setGoogleFilter( q_cat1 | q_cat2 | q_cat3 | QueryBuilder::tabSong, m_filter );
if( VisYearAlbum == 1 )
qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName);
if( !sampler ) qb.sortBy( q_cat1, QueryBuilder::valName );
if( VisYearAlbum == 2 )
qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName);
if( q_cat2 != QueryBuilder::tabSong )
qb.sortBy( q_cat2, QueryBuilder::valName );
if( VisYearAlbum == 3 )
qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName);
if( q_cat3 != QueryBuilder::tabSong )
qb.sortBy( q_cat3, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valURL );
qb.setOptions( QueryBuilder::optRemoveDuplicates );
values = qb.run();
for ( uint i = 0; i < values.count(); i++ )
{
KURL tmp;
tmp.setPath( values[i] );
list << tmp;
}
}
//second pass: category 1
if ( m_cat2 == IdNone )
{
for ( item = firstChild(); item; item = item->nextSibling() )
for ( TQListViewItem* child = item->firstChild(); child; child = child->nextSibling() )
if ( child->isSelected() && !child->parent()->isSelected() )
list << static_cast<CollectionItem*>( child ) ->url();
}
else {
for ( item = firstChild(); item; item = item->nextSibling() )
for ( TQListViewItem* child = item->firstChild(); child; child = child->nextSibling() )
if ( child->isSelected() && !child->parent()->isSelected() )
{
const bool sampler = static_cast<CollectionItem*>( item )->isSampler();
qb.clear();
if ( translateTimeFilter( timeFilter() ) > 0 )
qb.addFilter( QueryBuilder::tabSong, QueryBuilder::valCreateDate, TQString().setNum( TQDateTime::currentDateTime().toTime_t() - translateTimeFilter( timeFilter() ) ), QueryBuilder::modeGreater );