<commit_before>/*
    This file is part of Akregator.

    Copyright (C) 2004 <NAME> <<EMAIL>>
                  2005 <NAME> <<EMAIL>>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "articleviewer.h"
#include "akregatorconfig.h"
#include "aboutdata.h"
#include "actions.h"
#include "article.h"
#include "articleformatter.h"
#include "articlematcher.h"
#include "feed.h"
#include "folder.h"
#include "treenode.h"
#include "utils.h"
#include "openurlrequest.h"

#include <kpimutils/kfileio.h>

#include <kaction.h>
#include <kactioncollection.h>
#include <kapplication.h>
#include <kfiledialog.h>
#include <khtmlview.h>
#include <kicon.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmenu.h>
#include <kmessagebox.h>
#include <krun.h>
#include <kshell.h>
#include <kstandarddirs.h>
#include <kstandardaction.h>
#include <ktoolinvocation.h>
#include <kurl.h>
#include <kglobalsettings.h>
#include <kparts/browserextension.h>
#include <kparts/browserrun.h>

#include <QClipboard>
#include <QKeySequence>
#include <QGridLayout>

#include <boost/bind.hpp>

#include <memory>
#include <cassert>

using namespace boost;
using namespace Akregator;
using namespace Akregator::Filters;

namespace Akregator {

ArticleViewer::ArticleViewer(QWidget *parent)
    : QWidget(parent), 
      m_url(0),
      m_htmlFooter(),
      m_currentText(),
      m_imageDir( KUrl::fromPath( KGlobal::dirs()->saveLocation("cache", "akregator/Media/" ) ) ),
      m_node(0),
      m_viewMode(NormalView),
      m_part( new ArticleViewerPart( this ) ), 
      m_normalViewFormatter( new DefaultNormalViewFormatter( m_imageDir, m_part->view() ) ), 
      m_combinedViewFormatter( new DefaultCombinedViewFormatter( m_imageDir, m_part->view() ) )
{
    QGridLayout* layout = new QGridLayout(this);
    layout->setMargin(0);
    layout->addWidget(m_part->widget(), 0, 0);

    setFocusProxy( m_part->widget() );

    m_part->setZoomFactor(100);
    m_part->setJScriptEnabled(false);
    m_part->setJavaEnabled(false);
    m_part->setMetaRefreshEnabled(false);
    m_part->setPluginsEnabled(false);
    m_part->setDNDEnabled(true);
    m_part->setAutoloadImages(true);
    m_part->setStatusMessagesEnabled(false);

    // change the cursor when loading stuff...
    connect( m_part, SIGNAL(started(KIO::Job *)),
             this, SLOT(slotStarted(KIO::Job *)));
    connect( m_part, SIGNAL(completed()),
             this, SLOT(slotCompleted()));

    KParts::BrowserExtension* ext = m_part->browserExtension();
    connect(ext, SIGNAL(popupMenu (QPoint, KUrl, mode_t, KParts::OpenUrlArguments, KParts::BrowserArguments, KParts::BrowserExtension::PopupFlags, KParts::BrowserExtension::ActionGroupMap)),
             this, SLOT(slotPopupMenu(QPoint, KUrl, mode_t, KParts::OpenUrlArguments, KParts::BrowserArguments, KParts::BrowserExtension::PopupFlags))); // ActionGroupMap argument removed, unused by slot

    connect( ext, SIGNAL(openUrlRequestDelayed(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)),
             this, SLOT(slotOpenUrlRequestDelayed(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)) );

    connect( ext, SIGNAL(createNewWindow(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)),
             this, SLOT(slotCreateNewWindow(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)) );

    connect(ext, SIGNAL(createNewWindow(KUrl,
            KParts::OpenUrlArguments,
            KParts::BrowserArguments,
            KParts::WindowArgs,
            KParts::ReadOnlyPart**)),
            this, SLOT(slotCreateNewWindow(KUrl,
                         KParts::OpenUrlArguments,
                         KParts::BrowserArguments,
                         KParts::WindowArgs,
                         KParts::ReadOnlyPart**)));

    QAction* action = 0;
    action = KStandardAction::print(this, SLOT(slotPrint()), m_part->actionCollection());
    m_part->actionCollection()->addAction("viewer_print", action);

    action = KStandardAction::copy(this, SLOT(slotCopy()), m_part->actionCollection());
    m_part->actionCollection()->addAction("viewer_copy", action);

    action = m_part->actionCollection()->addAction("incFontSizes");
    action->setIcon(KIcon("zoom-in"));
    action->setText(i18n("&Increase Font Sizes"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotZoomIn()));
    action->setShortcut( QKeySequence( Qt::CTRL+Qt::Key_Plus ) );

    action = m_part->actionCollection()->addAction("decFontSizes");
    action->setIcon(KIcon("zoom-out"));
    action->setText(i18n("&Decrease Font Sizes"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotZoomOut()));
    action->setShortcut( QKeySequence(  Qt::CTRL + Qt::Key_Minus ) );

    action = m_part->actionCollection()->addAction("copylinkaddress");
    action->setText(i18n("Copy &Link Address"));
    connect(action, SIGNAL(triggered(bool) ), SLOT(slotCopyLinkAddress()));

    action = m_part->actionCollection()->addAction("savelinkas");
    action->setText(i18n("&Save Link As..."));
    connect(action, SIGNAL(triggered(bool) ), SLOT(slotSaveLinkAs()));

    action = m_part->actionCollection()->addAction("articleviewer_scroll_up");
    action->setText(i18n("&Scroll Up"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotScrollUp()));
    action->setShortcuts(KShortcut( "Up" ));

    action = m_part->actionCollection()->addAction("articleviewer_scroll_down");
    action->setText(i18n("&Scroll Down"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotScrollDown()));
    action->setShortcuts(KShortcut( "Down" ));

    updateCss();

    connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));

    connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), this, SLOT(slotPaletteOrFontChanged()) );
    connect(KGlobalSettings::self(), SIGNAL(kdisplayFontChanged()), this, SLOT(slotPaletteOrFontChanged()) );

    m_htmlFooter = "</body></html>";
}

ArticleViewer::~ArticleViewer()
{
}

KParts::ReadOnlyPart* ArticleViewer::part() const
{
    return m_part;
}

int ArticleViewer::pointsToPixel(int pointSize) const
{
    return ( pointSize * m_part->view()->logicalDpiY() + 36 ) / 72 ;
}

void ArticleViewer::slotOpenUrlRequestDelayed(const KUrl& url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& browserArgs)
{
    OpenUrlRequest req(url);
    req.setArgs(args);
    req.setBrowserArgs(browserArgs);
    req.setOptions(OpenUrlRequest::NewTab);

    if (m_part->button() == Qt::LeftButton)
    {
        switch (Settings::lMBBehaviour())
        {
            case Settings::EnumLMBBehaviour::OpenInExternalBrowser:
                req.setOptions(OpenUrlRequest::ExternalBrowser);
                break;
            case Settings::EnumLMBBehaviour::OpenInBackground:
                req.setOpenInBackground(true);
                break;
            default:
                break;
        }
    }
    else if (m_part->button() == Qt::MidButton)
    {
        switch (Settings::mMBBehaviour())
        {
            case Settings::EnumMMBBehaviour::OpenInExternalBrowser:
                req.setOptions(OpenUrlRequest::ExternalBrowser);
                break;
            case Settings::EnumMMBBehaviour::OpenInBackground:
                req.setOpenInBackground(true);
                break;
            default:
                break;
        }
    }

    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotCreateNewWindow(const KUrl& url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& browserArgs)
{
    OpenUrlRequest req(url);
    req.setArgs(args);
    req.setBrowserArgs(browserArgs);
    req.setOptions(OpenUrlRequest::NewTab);

    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotCreateNewWindow(const KUrl& url,
                                       const KParts::OpenUrlArguments& args,
                                       const KParts::BrowserArguments& browserArgs,
                                       const KParts::WindowArgs& /*windowArgs*/,
                                       KParts::ReadOnlyPart** part)
{
    OpenUrlRequest req;
    req.setUrl(url);
    req.setArgs(args);
    req.setBrowserArgs(browserArgs);
    req.setOptions(OpenUrlRequest::NewTab);

    emit signalOpenUrlRequest(req);
    if ( part )
        *part = req.part();
}

void ArticleViewer::slotPopupMenu(const QPoint& p, const KUrl& kurl, mode_t, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&, KParts::BrowserExtension::PopupFlags kpf)
{
    const bool isLink = (kpf & KParts::BrowserExtension::ShowNavigationItems) == 0; // ## why not use kpf & IsLink ?
    const bool isSelection = (kpf & KParts::BrowserExtension::ShowTextSelectionItems) != 0;

    QString url = kurl.url();

    m_url = url;
    KMenu popup;

    if (isLink && !isSelection)
    {
        popup.addAction( createOpenLinkInNewTabAction( kurl, this, SLOT( slotOpenLinkInForegroundTab() ), &popup ) );
        popup.addAction( createOpenLinkInExternalBrowserAction( kurl, this, SLOT( slotOpenLinkInBrowser() ), &popup ) );
        popup.addSeparator();
        popup.addAction( m_part->action("savelinkas") );
        popup.addAction( m_part->action("copylinkaddress") );
    }
    else
    {
        if (isSelection)
        {
            popup.addAction( m_part->action("viewer_copy") );
            popup.addSeparator();
        }
        popup.addAction( m_part->action("viewer_print") );
       //KAction *ac = action("setEncoding");
       //if (ac)
       //     ac->plug(&popup);
    }
    popup.exec(p);
}

// taken from KDevelop
void ArticleViewer::slotCopy()
{
    QString text = m_part->selectedText();
    text.replace( QChar( 0xa0 ), ' ' );
    QApplication::clipboard()->disconnect( this );
}

void ArticleViewer::slotCopyLinkAddress()
{
    if(m_url.isEmpty()) return;
    QClipboard *cb = QApplication::clipboard();
    cb->setText(m_url.prettyUrl(), QClipboard::Clipboard);
    // don't set url to selection as it's a no-no according to a fd.o spec
    //cb->setText(m_url.prettyUrl(), QClipboard::Selection);
}

void ArticleViewer::slotSelectionChanged()
{
    m_part->action("viewer_copy")->setEnabled(!m_part->selectedText().isEmpty());
}

void ArticleViewer::slotOpenLinkInternal()
{
    openUrl(m_url);
}

void ArticleViewer::slotOpenLinkInForegroundTab()
{
    OpenUrlRequest req(m_url);
    req.setOptions(OpenUrlRequest::NewTab);
    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotOpenLinkInBackgroundTab()
{
    OpenUrlRequest req(m_url);
    req.setOptions(OpenUrlRequest::NewTab);
    req.setOpenInBackground(true);
    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotOpenLinkInBrowser()
{
    OpenUrlRequest req(m_url);
    req.setOptions(OpenUrlRequest::ExternalBrowser);
    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotSaveLinkAs()
{
    KUrl tmp( m_url );

    if ( tmp.fileName(KUrl::ObeyTrailingSlash).isEmpty() )
        tmp.setFileName( "index.html" );
    KParts::BrowserRun::simpleSave(tmp, tmp.fileName());
}

void ArticleViewer::slotStarted(KIO::Job* job)
{
    m_part->widget()->setCursor( Qt::WaitCursor );
    emit started(job);
}

void ArticleViewer::slotCompleted()
{
    m_part->widget()->unsetCursor();
    emit completed();
}

void ArticleViewer::slotScrollUp()
{
    m_part->view()->scrollBy(0,-10);
}

void ArticleViewer::slotScrollDown()
{
    m_part->view()->scrollBy(0,10);
}

void ArticleViewer::slotZoomIn()
{
    int zf = m_part->zoomFactor();
    if (zf < 100)
    {
        zf = zf - (zf % 20) + 20;
        m_part->setZoomFactor(zf);
    }
    else
    {
        zf = zf - (zf % 50) + 50;
        m_part->setZoomFactor(zf < 300 ? zf : 300);
    }
}

void ArticleViewer::slotZoomOut()
{
    int zf = m_part->zoomFactor();
    if (zf <= 100)
    {
        zf = zf - (zf % 20) - 20;
        m_part->setZoomFactor(zf > 20 ? zf : 20);
    }
    else
    {
        zf = zf - (zf % 50) - 50;
        m_part->setZoomFactor(zf);
    }
}

void ArticleViewer::slotSetZoomFactor(int percent)
{
    m_part->setZoomFactor(percent);
}

// some code taken from KDevelop (lib/widgets/kdevhtmlpart.cpp)
void ArticleViewer::slotPrint( )
{
    m_part->view()->print();
}



void ArticleViewer::connectToNode(TreeNode* node)
{
    if (node)
    {
        if (m_viewMode == CombinedView)
        {
            connect( node, SIGNAL(signalChanged(Akregator::TreeNode*)), this, SLOT(slotUpdateCombinedView() ) );
            connect( node, SIGNAL(signalArticlesAdded(Akregator::TreeNode*, QList<Akregator::Article>)), this, SLOT(slotArticlesAdded(Akregator::TreeNode*, QList<Akregator::Article>)));
            connect( node, SIGNAL(signalArticlesRemoved(Akregator::TreeNode*, QList<Akregator::Article>)), this, SLOT(slotArticlesRemoved(Akregator::TreeNode*, QList<Akregator::Article>)));
            connect( node, SIGNAL(signalArticlesUpdated(Akregator::TreeNode*, QList<Akregator::Article>)), this, SLOT(slotArticlesUpdated(Akregator::TreeNode*, QList<Akregator::Article>)));
        }
        if (m_viewMode == SummaryView)
            connect( node, SIGNAL(signalChanged(Akregator::TreeNode*)), this, SLOT(slotShowSummary(Akregator::TreeNode*) ) );

        connect( node, SIGNAL(signalDestroyed(Akregator::TreeNode*)), this, SLOT(slotClear() ) );
    }
}

void ArticleViewer::disconnectFromNode(TreeNode* node)
{
    if (node)
        node->disconnect( this );
}

void ArticleViewer::renderContent(const QString& text)
{
    m_part->closeUrl();
    m_currentText = text;
    beginWriting();
    //kDebug() << text;
    m_part->write(text);
    endWriting();
}

void ArticleViewer::beginWriting()
{
    QString head = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n <html><head><title>.</title>");

    if (m_viewMode == CombinedView)
        head += m_combinedModeCSS;
    else
        head += m_normalModeCSS;

    head += "</style></head><body>";
    m_part->view()->setContentsPos(0,0);
    m_part->begin(m_link);
    m_part->write(head);
}

void ArticleViewer::endWriting()
{
    m_part->write(m_htmlFooter);
    //kDebug() << m_htmlFooter;
    m_part->end();
}


void ArticleViewer::slotShowSummary(TreeNode* node)
{
    m_viewMode = SummaryView;

    if (!node)
    {
        slotClear();
        return;
    }

    if (node != m_node)
    {
        disconnectFromNode(m_node);
        connectToNode(node);
        m_node = node;
    }

    QString summary = m_normalViewFormatter->formatSummary(node);
    m_link = QString();
    renderContent(summary);
}

void ArticleViewer::showArticle( const Akregator::Article& article )
{
    if ( article.isNull() )
    {
        slotClear();
        return;
    }
        
    m_viewMode = NormalView;
    disconnectFromNode(m_node);
    m_article = article;
    m_node = 0;
    m_link = article.link();
    if (article.feed()->loadLinkedWebsite())
    {
        openUrl(article.link());
    }
    else
    {
        renderContent( m_normalViewFormatter->formatArticle(article, ArticleFormatter::ShowIcon) );
    }
}

bool ArticleViewer::openUrl(const KUrl& url)
{
    if (!m_article.isNull() && m_article.feed()->loadLinkedWebsite())
    {
        return m_part->openUrl(url);
    }
    else
    {
        reload();
        return true;
    }
}

void ArticleViewer::setFilters(const std::vector< shared_ptr<const AbstractMatcher> >& filters )
{
    if ( filters == m_filters )
        return;

    m_filters = filters;

    slotUpdateCombinedView();
}

void ArticleViewer::slotUpdateCombinedView()
{
    if (m_viewMode != CombinedView)
        return;

    if (!m_node)
        return slotClear();

    QList<Article> articles = m_node->articles();
    qSort(articles);

    QString text;

    int num = 0;
    QTime spent;
    spent.start();

    const std::vector< shared_ptr<const AbstractMatcher> >::const_iterator filterEnd = m_filters.end();
    
    Q_FOREACH( const Article& i, articles )
    {
        if ( i.isDeleted() )
            continue;
        
        if ( std::find_if( m_filters.begin(), m_filters.end(), !bind( &AbstractMatcher::matches, _1, i ) ) != filterEnd )
            continue;

        text += "<p><div class=\"article\">"+m_combinedViewFormatter->formatArticle( i, ArticleFormatter::NoIcon)+"</div><p>";
        ++num;
    }
    
    kDebug() <<"Combined view rendering: (" << num <<" articles):" <<"generating HTML:" << spent.elapsed() <<"ms";
    renderContent(text);
    kDebug() <<"HTML rendering:" << spent.elapsed() <<"ms";
}

void ArticleViewer::slotArticlesUpdated(TreeNode* /*node*/, const QList<Article>& /*list*/)
{
    if (m_viewMode == CombinedView)
        slotUpdateCombinedView();
}

void ArticleViewer::slotArticlesAdded(TreeNode* /*node*/, const QList<Article>& /*list*/)
{
}

void ArticleViewer::slotArticlesRemoved(TreeNode* /*node*/, const QList<Article>& /*list*/)
{
}

void ArticleViewer::slotClear()
{
    disconnectFromNode(m_node);
    m_node = 0;
    m_article = Article();

    renderContent(QString());
}
<commit_msg>backport: Don't crash when delete multiple articles while "show linked website" is used CCBUG:172851<commit_after>/*
    This file is part of Akregator.

    Copyright (C) 2004 <NAME> <<EMAIL>>
                  2005 <NAME> <<EMAIL>>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "articleviewer.h"
#include "akregatorconfig.h"
#include "aboutdata.h"
#include "actions.h"
#include "article.h"
#include "articleformatter.h"
#include "articlematcher.h"
#include "feed.h"
#include "folder.h"
#include "treenode.h"
#include "utils.h"
#include "openurlrequest.h"

#include <kpimutils/kfileio.h>

#include <kaction.h>
#include <kactioncollection.h>
#include <kapplication.h>
#include <kfiledialog.h>
#include <khtmlview.h>
#include <kicon.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmenu.h>
#include <kmessagebox.h>
#include <krun.h>
#include <kshell.h>
#include <kstandarddirs.h>
#include <kstandardaction.h>
#include <ktoolinvocation.h>
#include <kurl.h>
#include <kglobalsettings.h>
#include <kparts/browserextension.h>
#include <kparts/browserrun.h>

#include <QClipboard>
#include <QKeySequence>
#include <QGridLayout>

#include <boost/bind.hpp>

#include <memory>
#include <cassert>

using namespace boost;
using namespace Akregator;
using namespace Akregator::Filters;

namespace Akregator {

ArticleViewer::ArticleViewer(QWidget *parent)
    : QWidget(parent),
      m_url(0),
      m_htmlFooter(),
      m_currentText(),
      m_imageDir( KUrl::fromPath( KGlobal::dirs()->saveLocation("cache", "akregator/Media/" ) ) ),
      m_node(0),
      m_viewMode(NormalView),
      m_part( new ArticleViewerPart( this ) ),
      m_normalViewFormatter( new DefaultNormalViewFormatter( m_imageDir, m_part->view() ) ),
      m_combinedViewFormatter( new DefaultCombinedViewFormatter( m_imageDir, m_part->view() ) )
{
    QGridLayout* layout = new QGridLayout(this);
    layout->setMargin(0);
    layout->addWidget(m_part->widget(), 0, 0);

    setFocusProxy( m_part->widget() );

    m_part->setZoomFactor(100);
    m_part->setJScriptEnabled(false);
    m_part->setJavaEnabled(false);
    m_part->setMetaRefreshEnabled(false);
    m_part->setPluginsEnabled(false);
    m_part->setDNDEnabled(true);
    m_part->setAutoloadImages(true);
    m_part->setStatusMessagesEnabled(false);

    // change the cursor when loading stuff...
    connect( m_part, SIGNAL(started(KIO::Job *)),
             this, SLOT(slotStarted(KIO::Job *)));
    connect( m_part, SIGNAL(completed()),
             this, SLOT(slotCompleted()));

    KParts::BrowserExtension* ext = m_part->browserExtension();
    connect(ext, SIGNAL(popupMenu (QPoint, KUrl, mode_t, KParts::OpenUrlArguments, KParts::BrowserArguments, KParts::BrowserExtension::PopupFlags, KParts::BrowserExtension::ActionGroupMap)),
             this, SLOT(slotPopupMenu(QPoint, KUrl, mode_t, KParts::OpenUrlArguments, KParts::BrowserArguments, KParts::BrowserExtension::PopupFlags))); // ActionGroupMap argument removed, unused by slot

    connect( ext, SIGNAL(openUrlRequestDelayed(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)),
             this, SLOT(slotOpenUrlRequestDelayed(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)) );

    connect( ext, SIGNAL(createNewWindow(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)),
             this, SLOT(slotCreateNewWindow(KUrl, KParts::OpenUrlArguments, KParts::BrowserArguments)) );

    connect(ext, SIGNAL(createNewWindow(KUrl,
            KParts::OpenUrlArguments,
            KParts::BrowserArguments,
            KParts::WindowArgs,
            KParts::ReadOnlyPart**)),
            this, SLOT(slotCreateNewWindow(KUrl,
                         KParts::OpenUrlArguments,
                         KParts::BrowserArguments,
                         KParts::WindowArgs,
                         KParts::ReadOnlyPart**)));

    QAction* action = 0;
    action = KStandardAction::print(this, SLOT(slotPrint()), m_part->actionCollection());
    m_part->actionCollection()->addAction("viewer_print", action);

    action = KStandardAction::copy(this, SLOT(slotCopy()), m_part->actionCollection());
    m_part->actionCollection()->addAction("viewer_copy", action);

    action = m_part->actionCollection()->addAction("incFontSizes");
    action->setIcon(KIcon("zoom-in"));
    action->setText(i18n("&Increase Font Sizes"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotZoomIn()));
    action->setShortcut( QKeySequence( Qt::CTRL+Qt::Key_Plus ) );

    action = m_part->actionCollection()->addAction("decFontSizes");
    action->setIcon(KIcon("zoom-out"));
    action->setText(i18n("&Decrease Font Sizes"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotZoomOut()));
    action->setShortcut( QKeySequence(  Qt::CTRL + Qt::Key_Minus ) );

    action = m_part->actionCollection()->addAction("copylinkaddress");
    action->setText(i18n("Copy &Link Address"));
    connect(action, SIGNAL(triggered(bool) ), SLOT(slotCopyLinkAddress()));

    action = m_part->actionCollection()->addAction("savelinkas");
    action->setText(i18n("&Save Link As..."));
    connect(action, SIGNAL(triggered(bool) ), SLOT(slotSaveLinkAs()));

    action = m_part->actionCollection()->addAction("articleviewer_scroll_up");
    action->setText(i18n("&Scroll Up"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotScrollUp()));
    action->setShortcuts(KShortcut( "Up" ));

    action = m_part->actionCollection()->addAction("articleviewer_scroll_down");
    action->setText(i18n("&Scroll Down"));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotScrollDown()));
    action->setShortcuts(KShortcut( "Down" ));

    updateCss();

    connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));

    connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), this, SLOT(slotPaletteOrFontChanged()) );
    connect(KGlobalSettings::self(), SIGNAL(kdisplayFontChanged()), this, SLOT(slotPaletteOrFontChanged()) );

    m_htmlFooter = "</body></html>";
}

ArticleViewer::~ArticleViewer()
{
}

KParts::ReadOnlyPart* ArticleViewer::part() const
{
    return m_part;
}

int ArticleViewer::pointsToPixel(int pointSize) const
{
    return ( pointSize * m_part->view()->logicalDpiY() + 36 ) / 72 ;
}

void ArticleViewer::slotOpenUrlRequestDelayed(const KUrl& url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& browserArgs)
{
    OpenUrlRequest req(url);
    req.setArgs(args);
    req.setBrowserArgs(browserArgs);
    req.setOptions(OpenUrlRequest::NewTab);

    if (m_part->button() == Qt::LeftButton)
    {
        switch (Settings::lMBBehaviour())
        {
            case Settings::EnumLMBBehaviour::OpenInExternalBrowser:
                req.setOptions(OpenUrlRequest::ExternalBrowser);
                break;
            case Settings::EnumLMBBehaviour::OpenInBackground:
                req.setOpenInBackground(true);
                break;
            default:
                break;
        }
    }
    else if (m_part->button() == Qt::MidButton)
    {
        switch (Settings::mMBBehaviour())
        {
            case Settings::EnumMMBBehaviour::OpenInExternalBrowser:
                req.setOptions(OpenUrlRequest::ExternalBrowser);
                break;
            case Settings::EnumMMBBehaviour::OpenInBackground:
                req.setOpenInBackground(true);
                break;
            default:
                break;
        }
    }

    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotCreateNewWindow(const KUrl& url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& browserArgs)
{
    OpenUrlRequest req(url);
    req.setArgs(args);
    req.setBrowserArgs(browserArgs);
    req.setOptions(OpenUrlRequest::NewTab);

    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotCreateNewWindow(const KUrl& url,
                                       const KParts::OpenUrlArguments& args,
                                       const KParts::BrowserArguments& browserArgs,
                                       const KParts::WindowArgs& /*windowArgs*/,
                                       KParts::ReadOnlyPart** part)
{
    OpenUrlRequest req;
    req.setUrl(url);
    req.setArgs(args);
    req.setBrowserArgs(browserArgs);
    req.setOptions(OpenUrlRequest::NewTab);

    emit signalOpenUrlRequest(req);
    if ( part )
        *part = req.part();
}

void ArticleViewer::slotPopupMenu(const QPoint& p, const KUrl& kurl, mode_t, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&, KParts::BrowserExtension::PopupFlags kpf)
{
    const bool isLink = (kpf & KParts::BrowserExtension::ShowNavigationItems) == 0; // ## why not use kpf & IsLink ?
    const bool isSelection = (kpf & KParts::BrowserExtension::ShowTextSelectionItems) != 0;

    QString url = kurl.url();

    m_url = url;
    KMenu popup;

    if (isLink && !isSelection)
    {
        popup.addAction( createOpenLinkInNewTabAction( kurl, this, SLOT( slotOpenLinkInForegroundTab() ), &popup ) );
        popup.addAction( createOpenLinkInExternalBrowserAction( kurl, this, SLOT( slotOpenLinkInBrowser() ), &popup ) );
        popup.addSeparator();
        popup.addAction( m_part->action("savelinkas") );
        popup.addAction( m_part->action("copylinkaddress") );
    }
    else
    {
        if (isSelection)
        {
            popup.addAction( m_part->action("viewer_copy") );
            popup.addSeparator();
        }
        popup.addAction( m_part->action("viewer_print") );
       //KAction *ac = action("setEncoding");
       //if (ac)
       //     ac->plug(&popup);
    }
    popup.exec(p);
}

// taken from KDevelop
void ArticleViewer::slotCopy()
{
    QString text = m_part->selectedText();
    text.replace( QChar( 0xa0 ), ' ' );
    QApplication::clipboard()->disconnect( this );
}

void ArticleViewer::slotCopyLinkAddress()
{
    if(m_url.isEmpty()) return;
    QClipboard *cb = QApplication::clipboard();
    cb->setText(m_url.prettyUrl(), QClipboard::Clipboard);
    // don't set url to selection as it's a no-no according to a fd.o spec
    //cb->setText(m_url.prettyUrl(), QClipboard::Selection);
}

void ArticleViewer::slotSelectionChanged()
{
    m_part->action("viewer_copy")->setEnabled(!m_part->selectedText().isEmpty());
}

void ArticleViewer::slotOpenLinkInternal()
{
    openUrl(m_url);
}

void ArticleViewer::slotOpenLinkInForegroundTab()
{
    OpenUrlRequest req(m_url);
    req.setOptions(OpenUrlRequest::NewTab);
    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotOpenLinkInBackgroundTab()
{
    OpenUrlRequest req(m_url);
    req.setOptions(OpenUrlRequest::NewTab);
    req.setOpenInBackground(true);
    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotOpenLinkInBrowser()
{
    OpenUrlRequest req(m_url);
    req.setOptions(OpenUrlRequest::ExternalBrowser);
    emit signalOpenUrlRequest(req);
}

void ArticleViewer::slotSaveLinkAs()
{
    KUrl tmp( m_url );

    if ( tmp.fileName(KUrl::ObeyTrailingSlash).isEmpty() )
        tmp.setFileName( "index.html" );
    KParts::BrowserRun::simpleSave(tmp, tmp.fileName());
}

void ArticleViewer::slotStarted(KIO::Job* job)
{
    m_part->widget()->setCursor( Qt::WaitCursor );
    emit started(job);
}

void ArticleViewer::slotCompleted()
{
    m_part->widget()->unsetCursor();
    emit completed();
}

void ArticleViewer::slotScrollUp()
{
    m_part->view()->scrollBy(0,-10);
}

void ArticleViewer::slotScrollDown()
{
    m_part->view()->scrollBy(0,10);
}

void ArticleViewer::slotZoomIn()
{
    int zf = m_part->zoomFactor();
    if (zf < 100)
    {
        zf = zf - (zf % 20) + 20;
        m_part->setZoomFactor(zf);
    }
    else
    {
        zf = zf - (zf % 50) + 50;
        m_part->setZoomFactor(zf < 300 ? zf : 300);
    }
}

void ArticleViewer::slotZoomOut()
{
    int zf = m_part->zoomFactor();
    if (zf <= 100)
    {
        zf = zf - (zf % 20) - 20;
        m_part->setZoomFactor(zf > 20 ? zf : 20);
    }
    else
    {
        zf = zf - (zf % 50) - 50;
        m_part->setZoomFactor(zf);
    }
}

void ArticleViewer::slotSetZoomFactor(int percent)
{
    m_part->setZoomFactor(percent);
}

// some code taken from KDevelop (lib/widgets/kdevhtmlpart.cpp)
void ArticleViewer::slotPrint( )
{
    m_part->view()->print();
}



void ArticleViewer::connectToNode(TreeNode* node)
{
    if (node)
    {
        if (m_viewMode == CombinedView)
        {
            connect( node, SIGNAL(signalChanged(Akregator::TreeNode*)), this, SLOT(slotUpdateCombinedView() ) );
            connect( node, SIGNAL(signalArticlesAdded(Akregator::TreeNode*, QList<Akregator::Article>)), this, SLOT(slotArticlesAdded(Akregator::TreeNode*, QList<Akregator::Article>)));
            connect( node, SIGNAL(signalArticlesRemoved(Akregator::TreeNode*, QList<Akregator::Article>)), this, SLOT(slotArticlesRemoved(Akregator::TreeNode*, QList<Akregator::Article>)));
            connect( node, SIGNAL(signalArticlesUpdated(Akregator::TreeNode*, QList<Akregator::Article>)), this, SLOT(slotArticlesUpdated(Akregator::TreeNode*, QList<Akregator::Article>)));
        }
        if (m_viewMode == SummaryView)
            connect( node, SIGNAL(signalChanged(Akregator::TreeNode*)), this, SLOT(slotShowSummary(Akregator::TreeNode*) ) );

        connect( node, SIGNAL(signalDestroyed(Akregator::TreeNode*)), this, SLOT(slotClear() ) );
    }
}

void ArticleViewer::disconnectFromNode(TreeNode* node)
{
    if (node)
        node->disconnect( this );
}

void ArticleViewer::renderContent(const QString& text)
{
    m_part->closeUrl();
    m_currentText = text;
    beginWriting();
    //kDebug() << text;
    m_part->write(text);
    endWriting();
}

void ArticleViewer::beginWriting()
{
    QString head = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n <html><head><title>.</title>");

    if (m_viewMode == CombinedView)
        head += m_combinedModeCSS;
    else
        head += m_normalModeCSS;

    head += "</style></head><body>";
    m_part->view()->setContentsPos(0,0);
    m_part->begin(m_link);
    m_part->write(head);
}

void ArticleViewer::endWriting()
{
    m_part->write(m_htmlFooter);
    //kDebug() << m_htmlFooter;
    m_part->end();
}


void ArticleViewer::slotShowSummary(TreeNode* node)
{
    m_viewMode = SummaryView;

    if (!node)
    {
        slotClear();
        return;
    }

    if (node != m_node)
    {
        disconnectFromNode(m_node);
        connectToNode(node);
        m_node = node;
    }

    QString summary = m_normalViewFormatter->formatSummary(node);
    m_link = QString();
    renderContent(summary);
}

void ArticleViewer::showArticle( const Akregator::Article& article )
{
    if ( article.isNull() || article.isDeleted() )
    {
        slotClear();
        return;
    }

    m_viewMode = NormalView;
    disconnectFromNode(m_node);
    m_article = article;
    m_node = 0;
    m_link = article.link();
    if (article.feed()->loadLinkedWebsite())
    {
        openUrl(article.link());
    }
    else
    {
        renderContent( m_normalViewFormatter->formatArticle(article, ArticleFormatter::ShowIcon) );
    }
}

bool ArticleViewer::openUrl(const KUrl& url)
{
    if (!m_article.isNull() && m_article.feed()->loadLinkedWebsite())
    {
        return m_part->openUrl(url);
    }
    else
    {
        reload();
        return true;
    }
}

void ArticleViewer::setFilters(const std::vector< shared_ptr<const AbstractMatcher> >& filters )
{
    if ( filters == m_filters )
        return;

    m_filters = filters;

    slotUpdateCombinedView();
}

void ArticleViewer::slotUpdateCombinedView()
{
    if (m_viewMode != CombinedView)
        return;

    if (!m_node)
        return slotClear();

    QList<Article> articles = m_node->articles();
    qSort(articles);

    QString text;

    int num = 0;
    QTime spent;
    spent.start();

    const std::vector< shared_ptr<const AbstractMatcher> >::const_iterator filterEnd = m_filters.end();

    Q_FOREACH( const Article& i, articles )
    {
        if ( i.isDeleted() )
            continue;

        if ( std::find_if( m_filters.begin(), m_filters.end(), !bind( &AbstractMatcher::matches, _1, i ) ) != filterEnd )
            continue;

        text += "<p><div class=\"article\">"+m_combinedViewFormatter->formatArticle( i, ArticleFormatter::NoIcon)+"</div><p>";
        ++num;
    }

    kDebug() <<"Combined view rendering: (" << num <<" articles):" <<"generating HTML:" << spent.elapsed() <<"ms";
    renderContent(text);
    kDebug() <<"HTML rendering:" << spent.elapsed() <<"ms";
}

void ArticleViewer::slotArticlesUpdated(TreeNode* /*node*/, const QList<Article>& /*list*/)
{
    if (m_viewMode == CombinedView)
        slotUpdateCombinedView();
}

void ArticleViewer::slotArticlesAdded(TreeNode* /*node*/, const QList<Article>& /*list*/)
{
}

void ArticleViewer::slotArticlesRemoved(TreeNode* /*node*/, const QList<Article>& /*list*/)
{
}

void ArticleViewer::slotClear()
{
    disconnectFromNode(m_node);
    m_node = 0;
    m_article = Article();

    renderContent(QString());
}

{'fraction_non_alphanumeric': 0.14022532218836486, 'fraction_numerical': 0.00559112129937659, 'mean_word_length': 3.5873300846370864, 'pattern_counts': {'":': 0, '<': 173, '<?xml version=': 0, '>': 315, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '339119', 'n_tokens_mistral': 11591, 'n_tokens_neox': 11144, 'n_words': 2130}