File indexing completed on 2024-04-28 17:05:55

0001 /*
0002     SPDX-FileCopyrightText: 2002 Shie Erlich <erlich@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002 Rafi Yanai <yanai@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "popularurls.h"
0010 
0011 #include <stdio.h>
0012 
0013 // QtCore
0014 #include <QList>
0015 // QtWidgets
0016 #include <QDialogButtonBox>
0017 #include <QGridLayout>
0018 #include <QHeaderView>
0019 #include <QLabel>
0020 #include <QLayout>
0021 #include <QPushButton>
0022 #include <QToolButton>
0023 
0024 #include <KConfigCore/KSharedConfig>
0025 #include <KI18n/KLocalizedString>
0026 #include <KItemViews/KTreeWidgetSearchLine>
0027 #include <KWidgetsAddons/KMessageBox>
0028 
0029 #include "../GUI/krtreewidget.h"
0030 #include "../icon.h"
0031 #include "../krglobal.h"
0032 #include "../krslots.h"
0033 
0034 #define STARTING_RANK 20
0035 #define INCREASE 2
0036 #define DECREASE 1
0037 
0038 PopularUrls::PopularUrls(QObject *parent)
0039     : QObject(parent)
0040     , head(nullptr)
0041     , tail(nullptr)
0042     , count(0)
0043 {
0044     dlg = new PopularUrlsDlg();
0045 }
0046 
0047 PopularUrls::~PopularUrls()
0048 {
0049     clearList();
0050     delete dlg;
0051 }
0052 
0053 void PopularUrls::clearList()
0054 {
0055     if (head) {
0056         UrlNodeP p = head, tmp;
0057         while (p) {
0058             tmp = p;
0059             p = p->next;
0060             delete tmp;
0061         }
0062     }
0063     ranks.clear();
0064     head = tail = nullptr;
0065 }
0066 
0067 void PopularUrls::save()
0068 {
0069     KConfigGroup svr(krConfig, "Private");
0070     // prepare the string list containing urls and int list with ranks
0071     QStringList urlList;
0072     QList<int> rankList;
0073     UrlNodeP p = head;
0074     while (p) {
0075         urlList << p->url.toDisplayString();
0076         rankList << p->rank;
0077         p = p->next;
0078     }
0079     svr.writeEntry("PopularUrls", urlList);
0080     svr.writeEntry("PopularUrlsRank", rankList);
0081 }
0082 
0083 void PopularUrls::load()
0084 {
0085     KConfigGroup svr(krConfig, "Private");
0086     QStringList urlList = svr.readEntry("PopularUrls", QStringList());
0087     QList<int> rankList = svr.readEntry("PopularUrlsRank", QList<int>());
0088     if (urlList.count() != rankList.count()) {
0089         KMessageBox::error(krMainWindow, i18n("The saved 'Popular URLs' are invalid. The list will be cleared."));
0090         return;
0091     }
0092     clearList();
0093     count = 0;
0094     // iterate through both lists and
0095     QStringList::Iterator uit;
0096     QList<int>::Iterator rit;
0097     for (uit = urlList.begin(), rit = rankList.begin(); uit != urlList.end() && rit != rankList.end(); ++uit, ++rit) {
0098         auto node = new UrlNode;
0099         node->url = QUrl(*uit);
0100         node->rank = *rit;
0101         appendNode(node);
0102         ranks.insert(*uit, node);
0103     }
0104 }
0105 
0106 // returns a url list with the 'max' top popular urls
0107 QList<QUrl> PopularUrls::getMostPopularUrls(int max)
0108 {
0109     // get at most 'max' urls
0110     QList<QUrl> list;
0111     UrlNodeP p = head;
0112     int tmp = 0;
0113     if (maxUrls < max)
0114         max = maxUrls; // don't give more than maxUrls
0115     while (p && tmp < max) {
0116         list << p->url;
0117         p = p->next;
0118         ++tmp;
0119     }
0120 
0121     return list;
0122 }
0123 
0124 // adds a url to the list, or increase rank of an existing url, making
0125 // sure to bump it up the list if needed
0126 void PopularUrls::addUrl(const QUrl &url)
0127 {
0128     QUrl tmpurl = url;
0129     tmpurl.setPassword(QString()); // make sure no passwords are permanently stored
0130     if (!tmpurl.path().endsWith('/')) // make a uniform trailing slash policy
0131         tmpurl.setPath(tmpurl.path() + '/');
0132     UrlNodeP pnode;
0133 
0134     decreaseRanks();
0135     if (!head) { // if the list is empty ... (assumes dict to be empty as well)
0136         pnode = new UrlNode;
0137         pnode->rank = STARTING_RANK;
0138         pnode->url = tmpurl;
0139         appendNode(pnode);
0140         ranks.insert(tmpurl.url(), head);
0141     } else {
0142         if (ranks.find(tmpurl.url()) == ranks.end()) { // is the added url new? if so, append it
0143             pnode = new UrlNode;
0144             pnode->rank = STARTING_RANK;
0145             pnode->url = tmpurl;
0146             appendNode(pnode);
0147             ranks.insert(tmpurl.url(), pnode);
0148         } else {
0149             pnode = ranks[tmpurl.url()];
0150             pnode->rank += INCREASE;
0151         }
0152     }
0153 
0154     // do we need to change location for this one?
0155     relocateIfNeeded(pnode);
0156 
0157     // too many urls?
0158     if (count > maxUrls)
0159         removeNode(tail);
0160 
0161     // dumpList();
0162 }
0163 
0164 // checks if 'node' needs to be bumped-up the ranking list and does it if needed
0165 void PopularUrls::relocateIfNeeded(UrlNodeP node)
0166 {
0167     if (node->prev && (node->prev->rank < node->rank)) {
0168         // iterate until we find the correct place to put it
0169         UrlNodeP tmp = node->prev->prev;
0170         while (tmp) {
0171             if (tmp->rank >= node->rank)
0172                 break; // found it!
0173             else
0174                 tmp = tmp->prev;
0175         }
0176         // now, if tmp isn't null, we need to move node to tmp->next
0177         // else move it to become head
0178         removeNode(node);
0179         insertNode(node, tmp);
0180     }
0181 }
0182 
0183 // iterate over the list, decreasing each url's rank
0184 // this is very naive, but a 1..30 for loop is acceptable (i hope)
0185 void PopularUrls::decreaseRanks()
0186 {
0187     if (head) {
0188         UrlNodeP p = head;
0189         while (p) {
0190             if (p->rank - DECREASE >= 0)
0191                 p->rank -= DECREASE;
0192             else
0193                 p->rank = 0;
0194             p = p->next;
0195         }
0196     }
0197 }
0198 
0199 // removes a node from the list, but doesn't free memory!
0200 // note: this will be buggy in case the list becomes empty (which should never happen)
0201 void PopularUrls::removeNode(UrlNodeP node)
0202 {
0203     if (node->prev) {
0204         if (tail == node)
0205             tail = node->prev;
0206         node->prev->next = node->next;
0207     }
0208     if (node->next) {
0209         if (head == node)
0210             head = node->next;
0211         node->next->prev = node->prev;
0212     }
0213     --count;
0214 }
0215 
0216 void PopularUrls::insertNode(UrlNodeP node, UrlNodeP after)
0217 {
0218     if (!after) { // make node head
0219         node->next = head;
0220         node->prev = nullptr;
0221         head->prev = node;
0222         head = node;
0223     } else {
0224         if (tail == after)
0225             tail = node;
0226         node->prev = after;
0227         node->next = after->next;
0228         if (node->next) {
0229             after->next->prev = node;
0230             after->next = node;
0231         }
0232     }
0233     ++count;
0234 }
0235 
0236 // appends 'node' to the end of the list, collecting garbage if needed
0237 void PopularUrls::appendNode(UrlNodeP node)
0238 {
0239     if (!tail) { // creating the first element
0240         head = tail = node;
0241         node->prev = node->next = nullptr;
0242     } else {
0243         node->next = nullptr;
0244         node->prev = tail;
0245         tail->next = node;
0246         tail = node;
0247     }
0248     ++count;
0249 }
0250 
0251 void PopularUrls::dumpList()
0252 {
0253     UrlNodeP p = head;
0254     printf("====start %d====\n", count);
0255     while (p) {
0256         printf("%d : %s\n", p->rank, p->url.url().toLatin1().data());
0257         p = p->next;
0258     }
0259     fflush(stdout);
0260 }
0261 
0262 void PopularUrls::showDialog()
0263 {
0264     QList<QUrl> list = getMostPopularUrls(maxUrls);
0265     dlg->run(list);
0266     if (dlg->result() == -1)
0267         return;
0268     SLOTS->refresh(list[dlg->result()]);
0269     // printf("running %s\n", list[dlg->result()].url().toLatin1());fflush(stdout);
0270 }
0271 
0272 // ===================================== PopularUrlsDlg ======================================
0273 PopularUrlsDlg::PopularUrlsDlg()
0274     : QDialog(krMainWindow)
0275 {
0276     setWindowTitle(i18n("Popular URLs"));
0277     setWindowModality(Qt::WindowModal);
0278 
0279     auto *mainLayout = new QVBoxLayout;
0280     setLayout(mainLayout);
0281 
0282     auto *layout = new QGridLayout;
0283     layout->setContentsMargins(0, 0, 0, 0);
0284 
0285     // listview to contain the urls
0286     urls = new KrTreeWidget(this);
0287     urls->header()->hide();
0288     urls->setSortingEnabled(false);
0289     urls->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0290 
0291     // quick search
0292     search = new KTreeWidgetSearchLine(this, urls);
0293     QLabel *lbl = new QLabel(i18n("&Search:"), this);
0294     lbl->setBuddy(search);
0295 
0296     layout->addWidget(lbl, 0, 0);
0297     layout->addWidget(search, 0, 1);
0298     layout->addWidget(urls, 1, 0, 1, 2);
0299 
0300     mainLayout->addLayout(layout);
0301 
0302     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0303     mainLayout->addWidget(buttonBox);
0304 
0305     setTabOrder(search, urls);
0306     setTabOrder((QWidget *)urls, buttonBox->button(QDialogButtonBox::Close));
0307 
0308     connect(buttonBox, &QDialogButtonBox::rejected, this, &PopularUrlsDlg::reject);
0309     connect(urls, &KrTreeWidget::activated, this, &PopularUrlsDlg::slotItemSelected);
0310     connect(search, &KTreeWidgetSearchLine::hiddenChanged, this, &PopularUrlsDlg::slotVisibilityChanged);
0311 }
0312 
0313 void PopularUrlsDlg::slotItemSelected(const QModelIndex &ndx)
0314 {
0315     selection = ndx.row();
0316     accept();
0317 }
0318 
0319 void PopularUrlsDlg::slotVisibilityChanged()
0320 {
0321     // select the first visible item
0322     QList<QTreeWidgetItem *> list = urls->selectedItems();
0323     if (list.count() > 0 && !list[0]->isHidden())
0324         return;
0325 
0326     urls->clearSelection();
0327     urls->setCurrentItem(nullptr);
0328 
0329     QTreeWidgetItemIterator it(urls);
0330     while (*it) {
0331         if (!(*it)->isHidden()) {
0332             (*it)->setSelected(true);
0333             urls->setCurrentItem(*it);
0334             break;
0335         }
0336         it++;
0337     }
0338 }
0339 
0340 PopularUrlsDlg::~PopularUrlsDlg()
0341 {
0342     delete search;
0343     delete urls;
0344 }
0345 
0346 void PopularUrlsDlg::run(QList<QUrl> list)
0347 {
0348     // populate the listview
0349     urls->clear();
0350     QList<QUrl>::Iterator it;
0351 
0352     QTreeWidgetItem *lastItem = nullptr;
0353 
0354     for (it = list.begin(); it != list.end(); ++it) {
0355         auto *item = new QTreeWidgetItem(urls, lastItem);
0356         lastItem = item;
0357         item->setText(0, (*it).isLocalFile() ? (*it).path() : (*it).toDisplayString());
0358         item->setIcon(0, (*it).isLocalFile() ? Icon("folder") : Icon("folder-html"));
0359     }
0360 
0361     setMinimumSize(urls->sizeHint().width() + 45, 400);
0362 
0363     search->clear();
0364     search->setFocus();
0365     selection = -1;
0366     slotVisibilityChanged();
0367     exec();
0368 }