File indexing completed on 2024-05-05 04:47:27

0001 /****************************************************************************************
0002  * Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org>                                *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "BrowserCategoryList"
0018 
0019 #include "BrowserCategoryList.h"
0020 
0021 #include "App.h"
0022 #include "core/support/Debug.h"
0023 #include "InfoProxy.h"
0024 #include "PaletteHandler.h"
0025 #include "widgets/PrettyTreeView.h"
0026 #include "widgets/PrettyTreeDelegate.h"
0027 #include "widgets/SearchWidget.h"
0028 
0029 #include <Qt>
0030 #include <QComboBox>
0031 #include <QFile>
0032 #include <QStackedWidget>
0033 #include <QStandardPaths>
0034 #include <QTreeView>
0035 #include <QVBoxLayout>
0036 
0037 #include <KLocalizedString>
0038 
0039 
0040 BrowserCategoryList::BrowserCategoryList( const QString &name, QWidget* parent, bool sort )
0041     : BrowserCategory( name, parent )
0042     , m_categoryListModel( new BrowserCategoryListModel( this ) )
0043     , m_sorting( sort )
0044 {
0045     // -- the widget stack
0046     m_widgetStack = new QStackedWidget( this );
0047 
0048     QWidget* mainWidget = new QWidget( m_widgetStack );
0049     QVBoxLayout* vLayout = new QVBoxLayout( mainWidget );
0050     mainWidget->setLayout( vLayout );
0051 
0052     // -- the search widget
0053     m_searchWidget = new SearchWidget( this, false );
0054     m_searchWidget->setClickMessage( i18n( "Filter Music Sources" ) );
0055     vLayout->addWidget( m_searchWidget );
0056 
0057     connect( m_searchWidget, &SearchWidget::filterChanged, this, &BrowserCategoryList::setFilter );
0058 
0059     // -- the main list view
0060     m_categoryListView = new Amarok::PrettyTreeView();
0061     m_categoryListView->setFrameShape( QFrame::NoFrame );
0062 
0063     m_proxyModel = new BrowserCategoryListSortFilterProxyModel( this );
0064     m_proxyModel->setSourceModel( m_categoryListModel );
0065 
0066     m_categoryListView->setItemDelegate( new PrettyTreeDelegate( m_categoryListView ) );
0067     m_categoryListView->setHeaderHidden( true );
0068     m_categoryListView->setRootIsDecorated( false );
0069     m_categoryListView->setModel( m_proxyModel );
0070     m_categoryListView->setMouseTracking ( true );
0071 
0072     if( sort )
0073     {
0074         m_proxyModel->setSortRole( Qt::DisplayRole );
0075         m_categoryListView->setSortingEnabled( true );
0076         m_categoryListView->sortByColumn( 0, Qt::AscendingOrder );
0077     }
0078 
0079     connect( m_categoryListView, &Amarok::PrettyTreeView::activated,
0080              this, &BrowserCategoryList::categoryActivated );
0081 
0082     connect( m_categoryListView, &Amarok::PrettyTreeView::entered,
0083              this, &BrowserCategoryList::categoryEntered );
0084 
0085     vLayout->addWidget( m_categoryListView );
0086     m_widgetStack->addWidget( mainWidget );
0087 }
0088 
0089 BrowserCategoryList::~BrowserCategoryList()
0090 { }
0091 
0092 
0093 void
0094 BrowserCategoryList::categoryActivated( const QModelIndex &index )
0095 {
0096     DEBUG_BLOCK
0097     BrowserCategory * category = nullptr;
0098 
0099     if( index.data( CustomCategoryRoles::CategoryRole ).canConvert<BrowserCategory *>() )
0100         category = index.data( CustomCategoryRoles::CategoryRole ).value<BrowserCategory *>();
0101     else
0102         return;
0103 
0104     if( category )
0105     {
0106         debug() << "Show service: " <<  category->name();
0107         setActiveCategory( category );
0108     }
0109 }
0110 
0111 void
0112 BrowserCategoryList::home()
0113 {
0114     DEBUG_BLOCK
0115     if( activeCategory() )
0116     {
0117         BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( activeCategory() );
0118         if( childList )
0119             childList->home();
0120 
0121         activeCategory()->clearAdditionalItems();
0122         m_widgetStack->setCurrentIndex( 0 );
0123 
0124         Q_EMIT( viewChanged() );
0125     }
0126 }
0127 
0128 
0129 QMap<QString, BrowserCategory*>
0130 BrowserCategoryList::categories()
0131 {
0132     return m_categories;
0133 }
0134 
0135 void
0136 BrowserCategoryList::addCategory( BrowserCategory *category )
0137 {
0138     Q_ASSERT( category );
0139 
0140     category->setParentList( this );
0141 
0142     //insert service into service map
0143     category->setParent( this );
0144     m_categories[category->name()] = category;
0145     m_categoryListModel->addCategory( category );
0146     m_widgetStack->addWidget( category );
0147 
0148     //if this is also a category list, watch it for changes as we need to report
0149     //these down the tree
0150 
0151     BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( category );
0152     if ( childList )
0153         connect( childList, &BrowserCategoryList::viewChanged, this, &BrowserCategoryList::childViewChanged );
0154 
0155     category->polish(); // service categories do an additional construction in polish
0156 
0157     if( m_sorting )
0158     {
0159         m_proxyModel->sort( 0 );
0160     }
0161     Q_EMIT( viewChanged() );
0162 }
0163 
0164 
0165 void
0166 BrowserCategoryList::removeCategory( BrowserCategory *category )
0167 {
0168     Q_ASSERT( category );
0169 
0170     if( m_widgetStack->indexOf( category ) == -1 )
0171         return; // no such category
0172 
0173     if( m_widgetStack->currentWidget() == category )
0174         home();
0175 
0176     m_categories.remove( category->name() );
0177     m_categoryListModel->removeCategory( category );
0178     m_widgetStack->removeWidget( category );
0179     delete category;
0180 
0181     m_categoryListView->reset();
0182 
0183     Q_EMIT( viewChanged() );
0184 }
0185 
0186 BrowserCategory*
0187 BrowserCategoryList::activeCategory() const
0188 {
0189     return qobject_cast<BrowserCategory*>(m_widgetStack->currentWidget());
0190 }
0191 
0192 void BrowserCategoryList::setActiveCategory( BrowserCategory* category )
0193 {
0194     DEBUG_BLOCK;
0195 
0196     if( m_widgetStack->indexOf( category ) == -1 )
0197         return; // no such category
0198 
0199     if( !category || activeCategory() == category )
0200         return; // nothing to do
0201 
0202     if( activeCategory() )
0203         activeCategory()->clearAdditionalItems();
0204     category->setupAddItems();
0205 
0206     m_widgetStack->setCurrentWidget( category );
0207 
0208     Q_EMIT( viewChanged() );
0209 }
0210 
0211 void BrowserCategoryList::back()
0212 {
0213     DEBUG_BLOCK
0214 
0215     BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( activeCategory() );
0216     if( childList )
0217     {
0218         if( childList->activeCategory() != nullptr )
0219         {
0220             childList->back();
0221             return;
0222         }
0223     }
0224 
0225     home();
0226 }
0227 
0228 void BrowserCategoryList::childViewChanged()
0229 {
0230     DEBUG_BLOCK
0231     Q_EMIT( viewChanged() );
0232 }
0233 
0234 QString BrowserCategoryList::navigate( const QString & target )
0235 {
0236     DEBUG_BLOCK
0237     debug() << "target: " << target;
0238     QStringList categories = target.split( QLatin1Char('/') );
0239     if ( categories.isEmpty() )
0240         return QString();
0241 
0242     //remove our own name if present, before passing on...
0243     if ( categories.at( 0 ) == name() )
0244     {
0245         debug() << "removing own name (" << categories.at( 0 ) << ") from path";
0246         categories.removeFirst();
0247 
0248         if ( categories.isEmpty() )
0249         {
0250             //nothing else left, make sure this category is visible
0251             home();
0252             return QString();
0253         }
0254     }
0255 
0256     QString childName = categories.at( 0 );
0257     debug() << "looking for child category " << childName;
0258     if ( !m_categories.contains( childName ) )
0259         return target;
0260 
0261 
0262     debug() << "got it!";
0263     setActiveCategory( m_categories[childName] );
0264 
0265     //check if this category is also BrowserCategoryList.target
0266     BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( activeCategory() );
0267 
0268     if ( childList == nullptr )
0269     {
0270         debug() << "child is not a list...";
0271         if ( categories.size() > 1 )
0272         {
0273             categories.removeFirst();
0274             QString leftover = categories.join( QLatin1Char('/') );
0275             return leftover;
0276         }
0277         return QString();
0278 
0279     }
0280 
0281     //check if there are more arguments in the navigate string.
0282     if ( categories.size() == 1 )
0283     {
0284         debug() << "Child is a list but path ends here...";
0285         //only one name, but since the category we switched to is also
0286         //a category list, make sure that it is reset to home
0287         childList->home();
0288         return QString();
0289     }
0290 
0291     categories.removeFirst();
0292     debug() << "passing remaining path to child: " << categories.join( QLatin1Char('/') );
0293     return childList->navigate( categories.join( QLatin1Char('/') ) );
0294 
0295 }
0296 
0297 QString BrowserCategoryList::path()
0298 {
0299     DEBUG_BLOCK
0300     QString pathString = name();
0301 
0302     BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( activeCategory() );
0303 
0304     if( childList )
0305         pathString += '/' + childList->path();
0306     else if( activeCategory() )
0307         pathString += '/' + activeCategory()->name();
0308 
0309     debug() << "path: " << pathString;
0310     return pathString;
0311 }
0312 
0313 void BrowserCategoryList::categoryEntered( const QModelIndex & index )
0314 {
0315     //get the long description for this item and pass it to info proxy.
0316 
0317     BrowserCategory *category = nullptr;
0318 
0319     if ( index.data( CustomCategoryRoles::CategoryRole ).canConvert<BrowserCategory *>() )
0320         category = index.data( CustomCategoryRoles::CategoryRole ).value<BrowserCategory *>();
0321     else
0322         return;
0323 
0324     if( category )
0325     {
0326 
0327         //instead of just throwing out raw text, let's format the long description and the
0328         //icon into a nice html page.
0329 
0330         if ( m_infoHtmlTemplate.isEmpty() )
0331         {
0332 
0333             QString dataPath = QStandardPaths::locate( QStandardPaths::GenericDataLocation, QStringLiteral("amarok/data/"), QStandardPaths::LocateDirectory );
0334 
0335             //load html
0336             QString htmlPath = dataPath + "/hover_info_template.html";
0337             QFile file( htmlPath );
0338             if ( !file.open( QIODevice::ReadOnly | QIODevice::Text) )
0339             {
0340                 debug() << "error opening file:" << file.fileName() << "Error: " << file.error();
0341                 return;
0342             }
0343             m_infoHtmlTemplate = file.readAll();
0344             file.close();
0345 
0346             m_infoHtmlTemplate.replace( QLatin1String("{background_color}"), The::paletteHandler()->highlightColor().lighter( 150 ).name() );
0347             m_infoHtmlTemplate.replace( QLatin1String("{border_color}"), The::paletteHandler()->highlightColor().lighter( 150 ).name() );
0348             m_infoHtmlTemplate.replace( QLatin1String("{text_color}"), pApp->palette().brush( QPalette::Text ).color().name() );
0349             QColor highlight( pApp->palette().highlight().color() );
0350             highlight.setHsvF( highlight.hueF(), 0.3, .95, highlight.alphaF() );
0351             m_infoHtmlTemplate.replace( QLatin1String("{header_background_color}"), highlight.name() );
0352 
0353         }
0354 
0355         QString currentHtml = m_infoHtmlTemplate;
0356 
0357         currentHtml.replace( QLatin1String("%%NAME%%"), category->prettyName() );
0358         currentHtml.replace( QLatin1String("%%DESCRIPTION%%"), category->longDescription() );
0359         currentHtml.replace( QLatin1String("%%IMAGE_PATH%%"), "file://" + category->imagePath() );
0360 
0361         QVariantMap variantMap;
0362         variantMap[QStringLiteral("main_info")] = QVariant( currentHtml );
0363         The::infoProxy()->setInfo( variantMap );
0364     }
0365 }
0366 
0367 QString BrowserCategoryList::css()
0368 {
0369     QString style =
0370             "<style type='text/css'>"
0371             "body"
0372             "{"
0373             "    text-align:center;"
0374             "    background-color: {background_color};"
0375             "}"
0376             "#main"
0377             "    {"
0378             "        text-align: center;"
0379             "    }"
0380             ""
0381             "#text-border"
0382             "    {"
0383             "        display: block;"
0384             "        margin-left: 0;"
0385             "        margin-right: 0;"
0386             "        padding: 4px;"
0387             "        border: 4px solid {border_color};"
0388             "        -webkit-border-radius: 4px;"
0389             "        -khtml-border-radius: 4px;"
0390             "        -moz-border-radius: 4px;"
0391             "        border-radius: 4px;"
0392             "        font-size: 94%;"
0393             "        text-align: center;"
0394             "        word-wrap: normal;"
0395             "        background-color: {content_background_color};"
0396             "        color: {text_color};"
0397             "    }"
0398             "</style>";
0399 
0400     return style;
0401 }
0402 
0403 BrowserCategory *BrowserCategoryList::activeCategoryRecursive()
0404 {
0405     BrowserCategory *category = activeCategory();
0406 
0407     if( !category )
0408         return this;
0409 
0410     BrowserCategoryList *childList = qobject_cast<BrowserCategoryList*>( category );
0411     if( childList )
0412         return childList->activeCategoryRecursive();
0413 
0414     return category;
0415 }
0416 
0417 void BrowserCategoryList::setFilter( const QString &filter )
0418 {
0419     m_proxyModel->setFilterFixedString( filter );
0420 }
0421