/* * newsscroller.cpp * * Copyright (c) 2000, 2001 Frerich Raabe * Copyright (c) 2001 Malte Starostik * * 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. For licensing and distribution details, check the * accompanying file 'COPYING'. */ #include #include #include #include #include #include #include #include #include "configaccess.h" #include "newsscroller.h" #include "newsengine.h" #include class Headline { public: Headline(NewsScroller *scroller, const Article::Ptr &article) : m_scroller(scroller), m_article(article), m_normal(0), m_highlighted(0) { }; virtual ~Headline() { reset(); } Article::Ptr article() const { return m_article; } int width() { return pixmap()->width(); } int height() { return pixmap()->height(); } TQPixmap *pixmap(bool highlighted = false, bool underlineHighlighted = true) { TQPixmap *result = highlighted ? m_highlighted : m_normal; if (!result) { const TQFontMetrics &metrics = m_scroller->fontMetrics(); int w, h; if (m_scroller->m_cfg->showIcons()) { w = m_article->newsSource()->icon().width() + 4 + metrics.width(m_article->headline()); h = TQMAX(metrics.height(), m_article->newsSource()->icon().height()); } else { w = metrics.width(m_article->headline()); h = metrics.height(); } if (ConfigAccess::rotated(static_cast(m_scroller->m_cfg->scrollingDirection()))) result = new TQPixmap(h, w); else result = new TQPixmap(w, h); result->fill(m_scroller->m_cfg->backgroundColor()); TQPainter p(result); TQFont f = m_scroller->font(); if (highlighted) f.setUnderline(underlineHighlighted); p.setFont(f); p.setPen(highlighted ? m_scroller->m_cfg->highlightedColor() : m_scroller->m_cfg->foregroundColor()); if (ConfigAccess::rotated(static_cast(m_scroller->m_cfg->scrollingDirection()))) { if (m_scroller->m_cfg->scrollingDirection() == ConfigAccess::UpRotated) { // Note that rotation also // changes the coordinate space // p.rotate(90.0); if (m_scroller->m_cfg->showIcons()) { p.drawPixmap(0, -m_article->newsSource()->icon().height(), m_article->newsSource()->icon()); p.drawText(m_article->newsSource()->icon().width() + 4, -metrics.descent(), m_article->headline()); } else p.drawText(0, -metrics.descent(), m_article->headline()); } else { p.rotate(-90.0); if (m_scroller->m_cfg->showIcons()) { p.drawPixmap(-w, h - m_article->newsSource()->icon().height(), m_article->newsSource()->icon()); p.drawText(-w + m_article->newsSource()->icon().width() + 4, h - metrics.descent(), m_article->headline()); } else p.drawText(-w, h - metrics.descent(), m_article->headline()); } } else { if (m_scroller->m_cfg->showIcons()) { p.drawPixmap(0, (result->height() - m_article->newsSource()->icon().height()) / 2, m_article->newsSource()->icon()); p.drawText(m_article->newsSource()->icon().width() + 4, result->height() - metrics.descent(), m_article->headline()); } else p.drawText(0, result->height() - metrics.descent(), m_article->headline()); } if (highlighted) m_highlighted = result; else m_normal = result; } return result; } void reset() { delete m_normal; m_normal = 0; delete m_highlighted; m_highlighted = 0; } private: NewsScroller *m_scroller; Article::Ptr m_article; TQPixmap *m_normal; TQPixmap *m_highlighted; }; NewsScroller::NewsScroller(TQWidget *parent, ConfigAccess *cfg, const char *name) : TQFrame(parent, name, WNoAutoErase), m_cfg(cfg), m_scrollTimer(new TQTimer(this)), m_activeHeadline(0), m_mouseDrag(false), m_totalStepping(0.0) { if (!kapp->dcopClient()->isAttached()) kapp->dcopClient()->attach(); setFrameStyle(StyledPanel | Sunken); m_headlines.setAutoDelete(true); connect(m_scrollTimer, TQT_SIGNAL(timeout()), TQT_SLOT(slotTimeout())); setAcceptDrops(true); reset(); } TQSize NewsScroller::sizeHint() const { return TQSize(fontMetrics().width(TQString::fromLatin1("X")) * 20, fontMetrics().height() * 2); } TQSizePolicy NewsScroller::sizePolicy() const { return TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding); } void NewsScroller::clear() { m_headlines.clear(); reset(); } void NewsScroller::dragEnterEvent(TQDragEnterEvent* event) { event->accept(TQTextDrag::canDecode(event)); } void NewsScroller::dropEvent(TQDropEvent* event) { TQString newSourceUrl; if ( TQTextDrag::decode(event, newSourceUrl) ) { // // This is just for http://www.webreference.com/services/news/ newSourceUrl = newSourceUrl.replace(TQRegExp( TQString::fromLatin1("^view-source:http%3A//")), TQString::fromLatin1("http://")); // newSourceUrl = newSourceUrl.stripWhiteSpace(); if (!isHeadline(newSourceUrl) && KMessageBox::questionYesNo(this, i18n("

Do you really want to add '%1' to" " the list of news sources?

") .arg(newSourceUrl), TQString(), i18n("Add"), KStdGuiItem::cancel()) == KMessageBox::Yes) { TDEConfig cfg(TQString::fromLatin1("knewsticker_panelappletrc"), false, false); ConfigAccess configFrontend(&cfg); TQStringList newsSources = configFrontend.newsSources(); TQString name = i18n("Unknown"); if (newsSources.contains(name)) for (unsigned int i = 0; ; i++) if (!newsSources.contains(i18n("Unknown %1").arg(i))) { name = i18n("Unknown %1").arg(i); break; } newsSources += name; configFrontend.setNewsSource(NewsSourceBase::Data(name, newSourceUrl)); configFrontend.setNewsSources(newsSources); TQByteArray data; kapp->dcopClient()->send("knewsticker", "KNewsTicker", "reparseConfig()", data); } } } bool NewsScroller::isHeadline(const TQString &location) const { for (Headline *h = m_headlines.first(); h; h = m_headlines.next()) if (h->article()->address() == location) return true; return false; } void NewsScroller::addHeadline(Article::Ptr article) { for (unsigned int i = 0; i < m_cfg->filters().count(); i++) if (m_cfg->filter(i).matches(article)) return; m_headlines.append(new Headline(this, article)); } void NewsScroller::scroll(int distance, bool interpret_directions) { unsigned int t_dir; if ( interpret_directions ) t_dir = m_cfg->scrollingDirection(); else t_dir = m_cfg->horizontalScrolling() ? ConfigAccess::Left : ConfigAccess::Up; switch (t_dir) { case ConfigAccess::Left: m_offset -= distance; if (m_offset <= - scrollWidth()) m_offset = m_offset + scrollWidth() - m_separator.width(); break; case ConfigAccess::Right: m_offset += distance; if (m_offset >= contentsRect().width()) m_offset = m_offset + m_separator.width() - scrollWidth(); break; case ConfigAccess::Up: case ConfigAccess::UpRotated: m_offset -= distance; if (m_offset <= - scrollHeight()) m_offset = m_offset + scrollHeight() - m_separator.height(); break; case ConfigAccess::Down: case ConfigAccess::DownRotated: m_offset += distance; if (m_offset >= contentsRect().height()) m_offset = m_offset + m_separator.height() - scrollHeight(); } TQPoint pt = mapFromGlobal(TQCursor::pos()); if (contentsRect().contains(pt)) updateActive(pt); update(); } void NewsScroller::enterEvent(TQEvent *) { if (m_cfg->slowedScrolling() && m_cfg->scrollingSpeed() > 1) m_scrollTimer->changeInterval(speedAsInterval(m_cfg->scrollingSpeed() / 2)); } void NewsScroller::mousePressEvent(TQMouseEvent *e) { if (e->button() == Qt::LeftButton || e->button() == Qt::MidButton) { m_dragPos = e->pos(); if (m_activeHeadline) m_tempHeadline = m_activeHeadline->article()->headline(); } } void NewsScroller::mouseReleaseEvent(TQMouseEvent *e) { if ((e->button() == Qt::LeftButton || e->button() == Qt::MidButton) && m_activeHeadline && m_activeHeadline->article()->headline() == m_tempHeadline && !m_mouseDrag) { m_activeHeadline->article()->open(); m_tempHeadline = TQString(); } if (e->button() == Qt::RightButton) emit(contextMenu()); if (m_mouseDrag) { m_mouseDrag = false; if (m_cfg->scrollingSpeed()) m_scrollTimer->start(speedAsInterval(m_cfg->scrollingSpeed())); } } void NewsScroller::mouseMoveEvent(TQMouseEvent *e) { // Are we in a drag phase? if (!m_mouseDrag) { // If not, check whether we need to start a drag. int dragDistance = 0; if (m_cfg->horizontalScrolling()) dragDistance = TQABS(e->x() - m_dragPos.x()); else dragDistance = TQABS(e->y() - m_dragPos.y()); m_mouseDrag = (e->state() & Qt::LeftButton != 0) && dragDistance >= TDEGlobal::config()->readNumEntry("StartDragDist", TDEApplication::startDragDistance()); if (m_mouseDrag) // Stop the scroller if we just started a drag. m_scrollTimer->stop(); } else { // If yes, move the scroller accordingly. bool createDrag; if (m_cfg->horizontalScrolling()) { scroll(m_dragPos.x() - e->x(), false); m_dragPos = e->pos(); createDrag = e->y() < 0 || e->y() > height(); } else { scroll(m_dragPos.y() - e->y(), false); m_dragPos = e->pos(); createDrag = e->x() < 0 || e->x() > width(); } m_dragPos = e->pos(); if (createDrag && m_activeHeadline) { KURL::List url; url.append(m_activeHeadline->article()->address()); TQDragObject *drag = new KURLDrag(url, this); drag->setPixmap(m_activeHeadline->article()->newsSource()->icon()); drag->drag(); m_mouseDrag = false; if (m_cfg->scrollingSpeed()) m_scrollTimer->start(speedAsInterval(m_cfg->scrollingSpeed())); } } if (updateActive(e->pos())) update(); } void NewsScroller::wheelEvent(TQWheelEvent *e) { // ### This 11 - m_cfg->mouseWheelSpeed() could be eliminated by swapping // the labels of the TQSlider. :] int distance = tqRound(TQABS(e->delta()) / (11 - m_cfg->mouseWheelSpeed())); int direction = e->delta() > 0 ? -1 : 1; for (int i = 0; i < distance; i++) scroll(direction); TQFrame::wheelEvent(e); } void NewsScroller::leaveEvent(TQEvent *) { if (m_cfg->slowedScrolling() && m_cfg->scrollingSpeed() > 1) m_scrollTimer->changeInterval(speedAsInterval(m_cfg->scrollingSpeed())); if (m_activeHeadline) { m_activeHeadline = 0; update(); } } void NewsScroller::drawContents(TQPainter *p) { if (!scrollWidth() || // No news and no "No News Available": uninitialized m_headlines.isEmpty()) // Happens when we're currently fetching new news return; TQPixmap buffer(contentsRect().width(), contentsRect().height()); buffer.fill(m_cfg->backgroundColor()); int pos = m_offset; // Paste in all the separator bitmaps (" +++ ") if (horizontal()) { while (pos > 0) pos -= scrollWidth() - (m_headlines.isEmpty() ? m_separator.width() : 0); do { bitBlt(&buffer, pos, (contentsRect().height() - m_separator.height()) / 2, &m_separator); pos += m_separator.width(); } while (m_headlines.isEmpty() && pos < contentsRect().width()); } else { while (pos > 0) pos -= scrollHeight() - (m_headlines.isEmpty() ? 0 : m_separator.height()); do { bitBlt(&buffer, (contentsRect().width() - m_separator.width()) / 2, pos, &m_separator); pos += m_separator.height(); } while (m_headlines.isEmpty() && pos < contentsRect().height()); } // Now do the headlines themselves do { TQPtrListIterator it(m_headlines); for(; *it; ++it) { if (horizontal()) { if (pos + (*it)->width() >= 0) bitBlt(&buffer, pos, (contentsRect().height() - (*it)->height()) / 2, (*it)->pixmap(*it == m_activeHeadline, m_cfg->underlineHighlighted())); pos += (*it)->width(); if (pos + m_separator.width() >= 0) bitBlt(&buffer, pos, (contentsRect().height() - m_separator.height()) / 2, &m_separator); pos += m_separator.width(); if (pos >= contentsRect().width()) break; } else { if (pos + (*it)->height() >= 0) bitBlt(&buffer, (contentsRect().width() - (*it)->width()) / 2, pos, (*it)->pixmap(*it == m_activeHeadline, m_cfg->underlineHighlighted())); pos += (*it)->height(); if (pos + m_separator.height() >= 0) bitBlt(&buffer, (contentsRect().width() - m_separator.width()) / 2, pos, &m_separator); pos += m_separator.height(); if (pos > contentsRect().height()) break; } } /* * Break out if we reached the bottom of the window before the end of the * list of headlines. */ if (*it) break; } while ((m_cfg->horizontalScrolling() && pos < contentsRect().width()) || pos < contentsRect().height()); p->drawPixmap(0, 0, buffer); } void NewsScroller::reset(bool bSeparatorOnly) { setFont(m_cfg->font()); m_scrollTimer->stop(); if (m_cfg->scrollingSpeed()) m_scrollTimer->start(speedAsInterval(m_cfg->scrollingSpeed())); TQString sep = m_headlines.isEmpty() ? i18n(" +++ No News Available +++") : TQString::fromLatin1(" +++ "); int w = fontMetrics().width(sep); int h = fontMetrics().height(); if (rotated()) m_separator.resize(h, w); else m_separator.resize(w, h); m_separator.fill(m_cfg->backgroundColor()); TQPainter p(&m_separator); p.setFont(font()); p.setPen(m_cfg->foregroundColor()); if(rotated()) { if (m_cfg->scrollingDirection() == ConfigAccess::UpRotated) { p.rotate(90.0); p.drawText(0, -fontMetrics().descent(),sep); } else { p.rotate(-90.0); p.drawText(-w, h-fontMetrics().descent(),sep); } } else p.drawText(0, m_separator.height() - fontMetrics().descent(), sep); p.end(); if (!bSeparatorOnly) for (TQPtrListIterator it(m_headlines); *it; ++it) (*it)->reset(); switch (m_cfg->scrollingDirection()) { case ConfigAccess::Left: m_offset = contentsRect().width(); break; case ConfigAccess::Right: m_offset = - scrollWidth(); break; case ConfigAccess::Up: case ConfigAccess::UpRotated: m_offset = contentsRect().height(); break; case ConfigAccess::Down: case ConfigAccess::DownRotated: m_offset = - scrollHeight(); } update(); } int NewsScroller::scrollWidth() const { int result = (m_headlines.count() + 1) * m_separator.width(); for (TQPtrListIterator it(m_headlines); *it; ++it) result += (*it)->width(); return result; } int NewsScroller::scrollHeight() const { int result = (m_headlines.count() + 1) * m_separator.height(); for (TQPtrListIterator it(m_headlines); *it; ++it) result += (*it)->height(); return result; } bool NewsScroller::updateActive(const TQPoint &pt) { int pos = m_offset; Headline *headline = 0; if (!m_headlines.isEmpty()) { while (pos > 0) if (horizontal()) pos -= scrollWidth() - m_separator.width(); else pos -= scrollHeight() - m_separator.height(); do { TQPtrListIterator it(m_headlines); for (; (headline = *it); ++it) { TQRect rect; if (horizontal()) { pos += m_separator.width(); rect.moveTopLeft(TQPoint(pos, (contentsRect().height() - (*it)->height()) / 2)); pos += (*it)->width(); } else { pos += m_separator.height(); rect.moveTopLeft(TQPoint((contentsRect().width() - (*it)->width()) / 2, pos)); pos += (*it)->height(); } rect.setSize(TQSize((*it)->width(), (*it)->height())); if (m_mouseDrag) if (horizontal()) { rect.setTop(0); rect.setHeight(height()); } else { rect.setLeft(0); rect.setWidth(width()); } if (rect.contains(pt)) break; } if (*it) break; } while ((m_cfg->horizontalScrolling() && pos < contentsRect().width()) || pos < contentsRect().height()); } if (m_activeHeadline == headline) return false; if ((m_activeHeadline = headline)) setCursor(KCursor::handCursor()); else unsetCursor(); return true; } void NewsScroller::slotTimeout() { m_totalStepping += m_stepping; if ( m_totalStepping >= 1.0 ) { const int distance = static_cast( m_totalStepping ); m_totalStepping -= distance; scroll( distance ); } } int NewsScroller::speedAsInterval( int speed ) { Q_ASSERT( speed > 0 ); static const int MaxScreenUpdates = 25; if ( speed <= MaxScreenUpdates ) { m_stepping = 1.0; return 1000 / speed; } m_stepping = speed / MaxScreenUpdates; return 1000 / MaxScreenUpdates; } #include "newsscroller.moc"