File indexing completed on 2024-04-28 08:46:43

0001 /*
0002     SPDX-FileCopyrightText: 2006 Koos Vriezen <koos.vriezen@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include <cstdio>
0008 
0009 #include "config-kmplayer.h"
0010 // include files for Qt
0011 #include <QApplication>
0012 #include <QClipboard>
0013 #include <QMenu>
0014 #include <QIcon>
0015 #include <qdrawutil.h>
0016 #include <QPainter>
0017 #include <QAbstractItemDelegate>
0018 #include <QDropEvent>
0019 #include <QStyle>
0020 #include <QDropEvent>
0021 #include <QPalette>
0022 #include <QRegExp>
0023 #include <QAbstractItemModel>
0024 #include <QList>
0025 #include <QItemSelectionModel>
0026 #include <QMimeData>
0027 
0028 #include <KIconLoader>
0029 #include <KStandardAction>
0030 #include <KFindDialog>
0031 #include <KFind>
0032 #include <KLocalizedString>
0033 #include <KActionCollection>
0034 
0035 #include "kmplayercommon_log.h"
0036 #include "playlistview.h"
0037 #include "playmodel.h"
0038 #include "kmplayerview.h"
0039 #include "kmplayercontrolpanel.h"
0040 
0041 using namespace KMPlayer;
0042 
0043 namespace {
0044 
0045 class ItemDelegate : public QAbstractItemDelegate
0046 {
0047     QAbstractItemDelegate *default_item_delegate;
0048     PlayListView *playlist_view;
0049 public:
0050     ItemDelegate (PlayListView *v, QAbstractItemDelegate *def)
0051         : QAbstractItemDelegate (v),
0052           default_item_delegate (def),
0053           playlist_view (v)
0054     {}
0055     QWidget *createEditor (QWidget *w, const QStyleOptionViewItem &o, const QModelIndex &i) const override
0056     {
0057         return default_item_delegate->createEditor (w, o, i);
0058     }
0059     bool editorEvent (QEvent *e, QAbstractItemModel *m, const QStyleOptionViewItem &o, const QModelIndex &i) override
0060     {
0061         return default_item_delegate->editorEvent (e, m, o, i);
0062     }
0063     bool eventFilter (QObject *editor, QEvent *event) override
0064     {
0065         return default_item_delegate->eventFilter (editor, event);
0066     }
0067     void paint (QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &i) const override
0068     {
0069         playlist_view->paintCell (default_item_delegate, p, o, i);
0070     }
0071     void setEditorData (QWidget *e, const QModelIndex &i) const override
0072     {
0073         default_item_delegate->setEditorData (e, i);
0074     }
0075     void setModelData (QWidget *e, QAbstractItemModel *m, const QModelIndex &i) const override
0076     {
0077         default_item_delegate->setModelData (e, m, i);
0078     }
0079     QSize sizeHint (const QStyleOptionViewItem &o, const QModelIndex &i) const override
0080     {
0081         QSize size = default_item_delegate->sizeHint (o, i);
0082         return QSize (size.width (), size.height () + 2);
0083     }
0084     void updateEditorGeometry (QWidget *e, const QStyleOptionViewItem &o, const QModelIndex &i) const override
0085     {
0086         default_item_delegate->updateEditorGeometry (e, o, i);
0087     }
0088 };
0089 
0090 }
0091 
0092 //-----------------------------------------------------------------------------
0093 
0094 PlayListView::PlayListView (QWidget *, View *view, KActionCollection * ac)
0095  : //QTreeView (parent),
0096    m_view (view),
0097    m_find_dialog (nullptr),
0098    m_active_color (30, 0, 255),
0099    last_drag_tree_id (0),
0100    m_ignore_expanded (false) {
0101     setHeaderHidden (true);
0102     setSortingEnabled (false);
0103     setAcceptDrops (true);
0104     setDragDropMode (DragDrop);
0105     setDropIndicatorShown (true);
0106     setDragDropOverwriteMode (false);
0107     setRootIsDecorated (false);
0108     setSelectionMode (SingleSelection);
0109     setSelectionBehavior (SelectItems);
0110     setIndentation (4);
0111     //setItemsExpandable (false);
0112     //setAnimated (true);
0113     setUniformRowHeights (true);
0114     setItemDelegateForColumn (0, new ItemDelegate (this, itemDelegate ()));
0115     setEditTriggers (EditKeyPressed);
0116     QPalette palette;
0117     palette.setColor (foregroundRole(), QColor (0, 0, 0));
0118     palette.setColor (backgroundRole(), QColor (0xB2, 0xB2, 0xB2));
0119     setPalette (palette);
0120     m_itemmenu = new QMenu (this);
0121     m_find = KStandardAction::find (this, &PlayListView::slotFind, this);
0122     m_find_next = KStandardAction::findNext (this, &PlayListView::slotFindNext, this);
0123     m_find_next->setEnabled (false);
0124     m_edit_playlist_item = ac->addAction ("edit_playlist_item");
0125     m_edit_playlist_item->setText (i18n ("Edit &item"));
0126     connect (m_edit_playlist_item, &QAction::triggered,
0127              this, &PlayListView::renameSelected);
0128     connect (this, &QTreeView::expanded,
0129              this, &PlayListView::slotItemExpanded);
0130 }
0131 
0132 PlayListView::~PlayListView () {
0133 }
0134 
0135 void PlayListView::paintCell (const QAbstractItemDelegate *def,
0136         QPainter *p, const QStyleOptionViewItem &o, const QModelIndex i)
0137 {
0138     PlayItem *item = playModel ()->itemFromIndex (i);
0139     if (item) {
0140         TopPlayItem *ritem = item->rootItem ();
0141         if (ritem == item) {
0142             QStyleOptionViewItem option (o);
0143             if (currentIndex () == i) {
0144                 // no highlighting for the top items
0145                 option.palette.setColor (QPalette::Highlight,
0146                         topLevelWidget()->palette ().color (QPalette::Background));
0147                 option.palette.setColor (QPalette::HighlightedText,
0148                         topLevelWidget()->palette ().color (QPalette::Foreground));
0149             } else {
0150                 p->fillRect(o.rect, QBrush (topLevelWidget()->palette ().color (QPalette::Background)));
0151                 option.palette.setColor (QPalette::Text,
0152                         topLevelWidget()->palette ().color (QPalette::Foreground));
0153             }
0154             option.font = topLevelWidget()->font ();
0155             def->paint (p, option, i);
0156             qDrawShadeRect (p, o.rect, option.palette, !isExpanded (i));
0157         } else {
0158             QStyleOptionViewItem option (o);
0159             option.palette.setColor (QPalette::Text,
0160                     item->node && item->node->state == Node::state_began
0161                     ? m_active_color
0162                     : palette ().color (foregroundRole ()));
0163             def->paint (p, option, i);
0164         }
0165     }
0166 }
0167 
0168 void PlayListView::modelUpdating (const QModelIndex &)
0169 {
0170     m_ignore_expanded = true;
0171     QModelIndex index = selectionModel()->currentIndex ();
0172     if (index.isValid ())
0173         closePersistentEditor(index);
0174 }
0175 
0176 void PlayListView::modelUpdated (const QModelIndex& r, const QModelIndex& i, bool sel, bool exp)
0177 {
0178     if (exp)
0179         setExpanded (r, true);
0180     if (i.isValid () && sel) {
0181         setCurrentIndex (i);
0182         scrollTo (i);
0183     }
0184     m_find_next->setEnabled (!!m_current_find_elm);
0185     TopPlayItem *ti = static_cast<TopPlayItem*>(playModel()->itemFromIndex(r));
0186     if (!ti->have_dark_nodes && ti->show_all_nodes && !m_view->editMode())
0187         toggleShowAllNodes (); // redo, because the user can't change it anymore
0188     m_ignore_expanded = false;
0189 }
0190 
0191 QModelIndex PlayListView::index (PlayItem *item) const
0192 {
0193     return playModel ()->indexFromItem (item);
0194 }
0195 
0196 void PlayListView::selectItem(const QString&) {
0197     /*QTreeWidgetItem * item = selectedItem ();
0198     if (item && item->text (0) == txt)
0199         return;
0200     item = findItem (txt, 0);
0201     if (item) {
0202         item->setSelected (true);
0203         //ensureItemVisible (item);
0204     }*/
0205 }
0206 
0207 /*Q3DragObject * PlayListView::dragObject () {
0208     PlayItem * item = static_cast <PlayItem *> (selectedItem ());
0209     if (item && item->node) {
0210         QString txt = item->node->isPlayable ()
0211             ? item->node->mrl ()->src : item->node->outerXML ();
0212         Q3TextDrag * drag = new Q3TextDrag (txt, this);
0213         last_drag_tree_id = rootItem (item)->id;
0214         m_last_drag = item->node;
0215         drag->setIcon (*item->pixmap (0));
0216         if (!item->node->isPlayable ())
0217             drag->setSubtype ("xml");
0218         return drag;
0219     }
0220     return 0;
0221 }*/
0222 
0223 void PlayListView::setFont (const QFont & fnt) {
0224     //setTreeStepSize (QFontMetrics (fnt).boundingRect ('m').width ());
0225     QTreeView::setFont (fnt);
0226 }
0227 
0228 void PlayListView::contextMenuEvent (QContextMenuEvent *event)
0229 {
0230     PlayItem *item = playModel ()->itemFromIndex (indexAt (event->pos ()));
0231     if (item) {
0232         if (item->node || item->attribute) {
0233             TopPlayItem *ritem = item->rootItem ();
0234             if (m_itemmenu->actions().count () > 0) {
0235                 m_find->setVisible (false);
0236                 m_find_next->setVisible (false);
0237                 m_itemmenu->clear ();
0238             }
0239             m_itemmenu->addAction (QIcon::fromTheme("edit-copy"),
0240                     i18n ("&Copy to Clipboard"),
0241                     this, &PlayListView::copyToClipboard);
0242             if (item->attribute ||
0243                     (item->node && (item->node->isPlayable () ||
0244                                     item->node->isDocument ()) &&
0245                      item->node->mrl ()->bookmarkable))
0246                 m_itemmenu->addAction (QIcon::fromTheme("bookmark-new"),
0247                         i18n ("&Add Bookmark"),
0248                         this, QOverload<>::of(&PlayListView::addBookMark));
0249             if (ritem->have_dark_nodes) {
0250                 QAction *act = m_itemmenu->addAction (i18n ("&Show all"),
0251                         this, &PlayListView::toggleShowAllNodes);
0252                 act->setCheckable (true);
0253                 act->setChecked (ritem->show_all_nodes);
0254             }
0255             if (item->item_flags & Qt::ItemIsEditable)
0256                 m_itemmenu->addAction (m_edit_playlist_item);
0257             m_itemmenu->addSeparator ();
0258             m_find->setVisible (true);
0259             m_find_next->setVisible (true);
0260             Q_EMIT prepareMenu (item, m_itemmenu);
0261             m_itemmenu->exec (event->globalPos ());
0262         }
0263     } else {
0264         m_view->controlPanel ()->popupMenu->exec (event->globalPos ());
0265     }
0266 }
0267 
0268 void PlayListView::slotItemExpanded (const QModelIndex &index) {
0269     int chlds = model ()->rowCount (index);
0270     if (chlds > 0) {
0271         if (!m_ignore_expanded && chlds == 1)
0272             setExpanded (model ()->index (0, 0, index), true);
0273         scrollTo (model ()->index (chlds - 1, 0, index));
0274         scrollTo (index);
0275     }
0276 }
0277 
0278 TopPlayItem * PlayListView::rootItem (int id) const {
0279     PlayItem *root_item = playModel ()->rootItem ();
0280     return static_cast<TopPlayItem*>(root_item->child (id));
0281 }
0282 
0283 PlayItem *PlayListView::selectedItem () const {
0284     return playModel ()->itemFromIndex (currentIndex ());
0285 }
0286 
0287 void PlayListView::copyToClipboard () {
0288     QModelIndex i = currentIndex ();
0289     if (i.isValid ()) {
0290         QString s;
0291 
0292         QVariant v = i.data (PlayModel::UrlRole);
0293         if (v.isValid ())
0294             s = v.toString ();
0295         if (s.isEmpty ())
0296             s = i.data ().toString ();
0297 
0298         if (!s.isEmpty ())
0299             QApplication::clipboard()->setText (s);
0300     }
0301 }
0302 
0303 void PlayListView::addBookMark () {
0304     PlayItem * item = selectedItem ();
0305     if (item->node) {
0306         Mrl * mrl = item->node->mrl ();
0307         const QUrl url = QUrl::fromUserInput(mrl ? mrl->src : QString (item->node->nodeName ()));
0308         Q_EMIT addBookMark (mrl->title.isEmpty () ? url.toDisplayString() : mrl->title, url.url ());
0309     }
0310 }
0311 
0312 void PlayListView::toggleShowAllNodes () {
0313     PlayItem * cur_item = selectedItem ();
0314     if (cur_item) {
0315         TopPlayItem *ritem = cur_item->rootItem ();
0316         showAllNodes (ritem, !ritem->show_all_nodes);
0317     }
0318 }
0319 
0320 void PlayListView::showAllNodes(TopPlayItem *ri, bool show) {
0321     if (ri && ri->show_all_nodes != show) {
0322         PlayItem * cur_item = selectedItem ();
0323         ri->show_all_nodes = show;
0324         playModel()->updateTree (ri->id, ri->node, cur_item->node, true, false);
0325         if (m_current_find_elm &&
0326                 ri->node->document() == m_current_find_elm->document() &&
0327                 !ri->show_all_nodes) {
0328             if (!m_current_find_elm->role (RolePlaylist))
0329                 m_current_find_elm = nullptr;
0330             m_current_find_attr = nullptr;
0331         }
0332     }
0333 }
0334 
0335 bool PlayListView::isDragValid (QDropEvent *event) {
0336     if (event->source() == this &&
0337             event->mimeData ()
0338                 ->hasFormat ("application/x-qabstractitemmodeldatalist"))
0339         return true;
0340     if (event->mimeData()->hasUrls()) {
0341         const QList<QUrl> uriList = event->mimeData()->urls();
0342         if (!uriList.isEmpty ())
0343             return true;
0344     } else {
0345         QString text = event->mimeData ()->text ();
0346         if (!text.isEmpty () && QUrl::fromUserInput(text).isValid ())
0347             return true;
0348     }
0349     return false;
0350 }
0351 
0352 void PlayListView::dragMoveEvent (QDragMoveEvent *event)
0353 {
0354     PlayItem *itm = playModel ()->itemFromIndex (indexAt (event->pos ()));
0355     if (itm) {
0356         TopPlayItem *ritem = itm->rootItem ();
0357         if (ritem->itemFlags() & PlayModel::AllowDrops && isDragValid (event))
0358             event->accept ();
0359         else
0360             event->ignore();
0361     }
0362 }
0363 
0364 void PlayListView::dragEnterEvent (QDragEnterEvent *event)
0365 {
0366     if (isDragValid (event))
0367         event->accept ();
0368     else
0369         event->ignore();
0370 }
0371 
0372 void PlayListView::dropEvent (QDropEvent *event) {
0373     PlayItem *itm = playModel ()->itemFromIndex (indexAt (event->pos ()));
0374     if (itm && itm->node) {
0375         TopPlayItem *ritem = itm->rootItem ();
0376         NodePtr n = itm->node;
0377         if (ritem->id > 0 || n->isDocument ()) {
0378             Q_EMIT dropped (event, itm);
0379         } else {
0380             QList<QUrl> uris = event->mimeData()->urls();
0381             if (uris.isEmpty ()) {
0382                 const QUrl url = QUrl::fromUserInput(event->mimeData ()->text ());
0383                 if (url.isValid ())
0384                     uris.push_back (url);
0385             }
0386             if (uris.size () > 0) {
0387                 bool as_child = itm->node->hasChildNodes ();
0388                 NodePtr d = n->document ();
0389                 for (int i = uris.size (); i > 0; i--) {
0390                     Node * ni = new KMPlayer::GenericURL (d, uris[i-1].url ());
0391                     if (as_child)
0392                         n->insertBefore (ni, n->firstChild ());
0393                     else
0394                         n->parentNode ()->insertBefore (ni, n->nextSibling ());
0395                 }
0396                 PlayItem * citem = selectedItem ();
0397                 NodePtr cn;
0398                 if (citem)
0399                     cn = citem->node;
0400                 m_ignore_expanded = true;
0401                 citem = playModel()->updateTree (ritem, cn);
0402                 modelUpdated (playModel()->indexFromItem(ritem), playModel()->indexFromItem(citem), true, false);
0403                 m_ignore_expanded = false;
0404             }
0405         }
0406     }
0407 }
0408 
0409 PlayModel *PlayListView::playModel () const
0410 {
0411     return static_cast <PlayModel *> (model());
0412 }
0413 
0414 
0415 void PlayListView::renameSelected () {
0416     QModelIndex i = currentIndex ();
0417     PlayItem *itm = playModel ()->itemFromIndex (i);
0418     if (itm && itm->item_flags & Qt::ItemIsEditable)
0419         edit (i);
0420 }
0421 
0422 void PlayListView::slotCurrentItemChanged (QModelIndex /*cur*/, QModelIndex)
0423 {
0424     //TopPlayItem * ri = rootItem (qitem);
0425     //setItemsRenameable (ri && (ri->item_flagsTreeEdit) && ri != qitem);
0426 }
0427 
0428 void PlayListView::slotFind () {
0429     /*m_current_find_elm = 0L;
0430     if (!m_find_dialog) {
0431         m_find_dialog = new KFindDialog (this, KFind::CaseSensitive);
0432         m_find_dialog->setHasSelection (false);
0433         connect(m_find_dialog, SIGNAL(okClicked ()), this, SLOT(slotFindOk ()));
0434     } else
0435         m_find_dialog->setPattern (QString ());
0436     m_find_dialog->show ();*/
0437 }
0438 
0439 /*static QTreeWidgetItem * findNodeInTree (NodePtr n, QTreeWidgetItem * item) {
0440     //qCDebug(LOG_KMPLAYER_COMMON) << "item:" << item->text (0) << " n:" << (n ? n->nodeName () : "null" );
0441     PlayItem * pi = static_cast <PlayItem *> (item);
0442     if (!n || !pi->node)
0443         return 0L;
0444     if (n == pi->node)
0445         return item;
0446     for (int i = 0; i < item->childCount (); ++i) {
0447         //qCDebug(LOG_KMPLAYER_COMMON) << "ci:" << ci->text (0) << " n:" << n->nodeName ();
0448         QTreeWidgetItem *vi = findNodeInTree (n, item->child (i));
0449         if (vi)
0450             return vi;
0451     }
0452     return 0L;
0453 
0454 }*/
0455 
0456 void PlayListView::slotFindOk () {
0457     /*if (!m_find_dialog)
0458         return;
0459     m_find_dialog->hide ();
0460     long opt = m_find_dialog->options ();
0461     current_find_tree_id = 0;
0462     if (opt & KFind::FromCursor && currentItem ()) {
0463         PlayItem * lvi = selectedItem ();
0464         if (lvi && lvi->node) {
0465              m_current_find_elm = lvi->node;
0466              current_find_tree_id = rootItem (lvi)->id;
0467         } else if (lvi && lvi->attribute) {
0468             PlayItem*pi=static_cast<PlayItem*>(currentItem()->parent());
0469             if (pi) {
0470                 m_current_find_attr = lvi->attribute;
0471                 m_current_find_elm = pi->node;
0472             }
0473         }
0474     } else if (!(opt & KFind::FindIncremental))
0475         m_current_find_elm = 0L;
0476     if (!m_current_find_elm && topLevelItemCount ())
0477         m_current_find_elm = static_cast <PlayItem*>(topLevelItem(0))->node;
0478     if (m_current_find_elm)
0479         slotFindNext ();*/
0480 }
0481 
0482 /* A bit tricky, but between the find's PlayItems might be gone, so
0483  * try to match on the generated tree following the source's document tree
0484  */
0485 void PlayListView::slotFindNext () {
0486     /*if (!m_find_dialog)
0487         return;
0488     QString str = m_find_dialog->pattern();
0489     if (!m_current_find_elm || str.isEmpty ())
0490         return;
0491     long opt = m_find_dialog->options ();
0492     QRegExp regexp;
0493     if (opt & KFind::RegularExpression)
0494         regexp = QRegExp (str);
0495     bool cs = (opt & KFind::CaseSensitive);
0496     bool found = false;
0497     Node *node = NULL, *n = m_current_find_elm.ptr ();
0498     TopPlayItem * ri = rootItem (current_find_tree_id);
0499     while (!found && n) {
0500         if (ri->show_all_nodes || n->role (RolePlaylist)) {
0501             bool elm = n->isElementNode ();
0502             QString val = n->nodeName ();
0503             if (elm && !ri->show_all_nodes) {
0504                 Mrl * mrl = n->mrl ();
0505                 if (mrl) {
0506                     if (mrl->title.isEmpty ()) {
0507                         if (!mrl->src.isEmpty())
0508                             val = KURL(mrl->src).prettyUrl();
0509                     } else
0510                         val = mrl->title;
0511                 }
0512             } else if (!elm)
0513                 val = n->nodeValue ();
0514             if (((opt & KFind::RegularExpression) &&
0515                     val.find (regexp, 0) > -1) ||
0516                     (!(opt & KFind::RegularExpression) &&
0517                      val.find (str, 0, cs) > -1)) {
0518                 node = n;
0519                 m_current_find_attr = 0L;
0520                 found = true;
0521             } else if (elm && ri->show_all_nodes) {
0522                 for (Attribute *a = static_cast <Element *> (n)->attributes ().first (); a; a = a->nextSibling ()) {
0523                     QString attr = a->name ().toString ();
0524                     if (((opt & KFind::RegularExpression) &&
0525                                 (attr.find (regexp, 0) || a->value ().find (regexp, 0) > -1)) ||
0526                                 (!(opt & KFind::RegularExpression) &&
0527                                  (attr.find (str, 0, cs) > -1 || a->value ().find (str, 0, cs) > -1))) {
0528                         node = n;
0529                         m_current_find_attr = a;
0530                         found = true;
0531                         break;
0532                     }
0533                 }
0534             }
0535         }
0536         if (n) { //set pointer to next
0537             if (opt & KFind::FindBackwards) {
0538                 if (n->lastChild ()) {
0539                     n = n->lastChild ();
0540                 } else if (n->previousSibling ()) {
0541                     n = n->previousSibling ();
0542                 } else {
0543                     for (n = n->parentNode (); n; n = n->parentNode ())
0544                         if (n->previousSibling ()) {
0545                             n = n->previousSibling ();
0546                             break;
0547                         }
0548                     while (!n && current_find_tree_id > 0) {
0549                         ri = rootItem (--current_find_tree_id);
0550                         if (ri)
0551                             n = ri->node;
0552                     }
0553                 }
0554             } else {
0555                 if (n->firstChild ()) {
0556                     n = n->firstChild ();
0557                 } else if (n->nextSibling ()) {
0558                     n = n->nextSibling ();
0559                 } else {
0560                     for (n = n->parentNode (); n; n = n->parentNode ())
0561                         if (n->nextSibling ()) {
0562                             n = n->nextSibling ();
0563                             break;
0564                         }
0565                     while (!n) {
0566                         ri = rootItem (++current_find_tree_id);
0567                         if (!ri)
0568                             break;
0569                         n = ri->node;
0570                     }
0571                 }
0572             }
0573         }
0574     }
0575     m_current_find_elm = n;
0576     qCDebug(LOG_KMPLAYER_COMMON) << " search for " << str << "=" << (node ? node->nodeName () : "not found") << " next:" << (n ? n->nodeName () : " not found");
0577     if (found) {
0578         QTreeWidgetItem *fc = findNodeInTree (node, ri);
0579         if (!fc) {
0580             m_current_find_elm = 0L;
0581             qCDebug(LOG_KMPLAYER_COMMON) << "node not found in tree tree:" << ri->id;
0582         } else {
0583             fc->setSelected (true);
0584             if (m_current_find_attr && fc->childCount () && fc->child (0)->childCount ())
0585                 scrollToItem (fc->child (0)->child (0));
0586             scrollToItem (fc);
0587         }
0588     }
0589     m_find_next->setEnabled (!!m_current_find_elm);*/
0590 }
0591 
0592 #include "moc_playlistview.cpp"