You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bibletime/bibletime/frontend/display/chtmlreaddisplay.cpp

508 lines
16 KiB

/*********
*
* This file is part of BibleTime's source code, http://www.bibletime.info/.
*
* Copyright 1999-2006 by the BibleTime developers.
* The BibleTime source code is licensed under the GNU General Public License version 2.0.
*
**********/
#include "chtmlreaddisplay.h"
#include "frontend/displaywindow/cdisplaywindow.h"
#include "frontend/displaywindow/creadwindow.h"
#include "backend/creferencemanager.h"
#include "backend/cswordkey.h"
#include "frontend/cbtconfig.h"
#include "frontend/cdragdropmgr.h"
#include "frontend/cinfodisplay.h"
#include "util/ctoolclass.h"
#include "util/cpointers.h"
#include "util/scoped_resource.h"
//We will need to reference this in the TQt includes
#include <kdeversion.h>
//TQt includes
#include <tqcursor.h>
#include <tqscrollview.h>
#include <tqwidget.h>
#include <tqdragobject.h>
#include <tqpopupmenu.h>
#include <tqlayout.h>
#include <tqtimer.h>
#if KDE_VERSION < 0x030300
//We will need to show the error message.
#include <tqmessagebox.h>
#endif
//KDE includes
#include <kapplication.h>
#include <khtmlview.h>
#include <kglobalsettings.h>
#include <khtml_events.h>
#include <dom/dom2_range.h>
#include <dom/html_element.h>
#include <dom/dom2_traversal.h>
using namespace InfoDisplay;
CHTMLReadDisplay::CHTMLReadDisplay(CReadWindow* readWindow, TQWidget* tqparentWidget)
: KHTMLPart((m_view = new CHTMLReadDisplayView(this, tqparentWidget ? tqparentWidget : readWindow)), TQT_TQOBJECT(readWindow ? readWindow : tqparentWidget)),
CReadDisplay(readWindow),
m_currentAnchorCache(TQString()) {
setDNDEnabled(false);
setJavaEnabled(false);
setJScriptEnabled(false);
setPluginsEnabled(false);
m_view->setDragAutoScroll(false);
}
CHTMLReadDisplay::~CHTMLReadDisplay() {}
const TQString CHTMLReadDisplay::text( const CDisplay::TextType format, const CDisplay::TextPart part) {
switch (part) {
case Document: {
if (format == HTMLText) {
return document().toHTML();
}
else {
CDisplayWindow* window = tqparentWindow();
CSwordKey* const key = window->key();
CSwordModuleInfo* module = key->module();
//return htmlDocument().body().innerText().string().latin1();
//This function is never used for Bibles, so it is not
//implemented. If it should be, see CReadDisplay::print() for
//example code.
Q_ASSERT(module->type() == CSwordModuleInfo::Lexicon || module->type() == CSwordModuleInfo::Commentary || module->type() == CSwordModuleInfo::GenericBook);
if (module->type() == CSwordModuleInfo::Lexicon || module->type() == CSwordModuleInfo::Commentary || module->type() == CSwordModuleInfo::GenericBook) {
//TODO: This is a BAD HACK, we have to fnd a better solution to manage the settings now
CSwordBackend::FilterOptions filterOptions;
filterOptions.footnotes = false;
filterOptions.strongNumbers = false;
filterOptions.morphTags = false;
filterOptions.lemmas = false;
filterOptions.scriptureReferences = false;
filterOptions.textualVariants = false;
CPointers::backend()->setFilterOptions(filterOptions);
return TQString(key->strippedText()).append("\n(")
.append(key->key())
.append(", ")
.append(key->module()->name())
.append(")");
}
}
}
case SelectedText: {
if (!hasSelection()) {
return TQString();
}
else if (format == HTMLText) {
DOM::Range range = selection();
return range.toHTML().string();
}
else { //plain text requested
return selectedText();
}
}
case AnchorOnly: {
TQString moduleName;
TQString keyName;
CReferenceManager::Type type;
CReferenceManager::decodeHyperlink(activeAnchor(), moduleName, keyName, type);
return keyName;
}
case AnchorTextOnly: {
TQString moduleName;
TQString keyName;
CReferenceManager::Type type;
CReferenceManager::decodeHyperlink(activeAnchor(), moduleName, keyName, type);
if (CSwordModuleInfo* module = backend()->findModuleByName(moduleName)) {
util::scoped_ptr<CSwordKey> key( CSwordKey::createInstance(module) );
key->key( keyName );
return key->strippedText();
}
return TQString();
}
case AnchorWithText: {
TQString moduleName;
TQString keyName;
CReferenceManager::Type type;
CReferenceManager::decodeHyperlink(activeAnchor(), moduleName, keyName, type);
if (CSwordModuleInfo* module = backend()->findModuleByName(moduleName)) {
util::scoped_ptr<CSwordKey> key( CSwordKey::createInstance(module) );
key->key( keyName );
//TODO: This is a BAD HACK, we have to fnd a better solution to manage the settings now
CSwordBackend::FilterOptions filterOptions;
filterOptions.footnotes = false;
filterOptions.strongNumbers = false;
filterOptions.morphTags = false;
filterOptions.lemmas = false;
filterOptions.scriptureReferences = false;
filterOptions.textualVariants = false;
CPointers::backend()->setFilterOptions(filterOptions);
return TQString(key->strippedText()).append("\n(")
.append(key->key())
.append(", ")
.append(key->module()->name())
.append(")");
/* ("%1\n(%2, %3)")
.tqarg()
.tqarg(key->key())
.tqarg(key->module()->name());*/
}
return TQString();
}
default:
return TQString();
}
}
void CHTMLReadDisplay::setText( const TQString& newText ) {
begin();
write(newText);
end();
}
/** No descriptions */
const bool CHTMLReadDisplay::hasSelection() {
return KHTMLPart::hasSelection();
}
/** Reimplementation. */
TQScrollView* CHTMLReadDisplay::view() {
return KHTMLPart::view();
}
void CHTMLReadDisplay::selectAll() {
KHTMLPart::selectAll();
}
/** No descriptions */
void CHTMLReadDisplay::moveToAnchor( const TQString& anchor ) {
m_currentAnchorCache = anchor;
//This is an ugly hack to work around a KDE problem in KDE including 3.3.1 (no later versions tested so far)
TQTimer::singleShot(0, this, TQT_SLOT(slotGoToAnchor()));
// instead of:
// slotGoToAnchor();
}
void CHTMLReadDisplay::urlSelected( const TQString& url, int button, int state, const TQString& _target, KParts::URLArgs args) {
KHTMLPart::urlSelected(url, button, state, _target, args);
m_urlWorkaroundData.doWorkaround = false;
// qWarning("clicked: %s", url.latin1());
if (!url.isEmpty() && CReferenceManager::isHyperlink(url)) {
TQString module;
TQString key;
CReferenceManager::Type type;
CReferenceManager::decodeHyperlink(url, module, key, type);
if (module.isEmpty()) {
module = CReferenceManager::preferredModule( type );
}
// we have to use this workaround, otherwise the widget would scroll because it was interrupted
// between mouseClick and mouseRelease (I guess)
m_urlWorkaroundData.doWorkaround = true;
m_urlWorkaroundData.url = url;
m_urlWorkaroundData.state = state;
m_urlWorkaroundData.button = button;
m_urlWorkaroundData.target = _target;
m_urlWorkaroundData.args = args;
m_urlWorkaroundData.module = module;
m_urlWorkaroundData.key = key;
}
else if (!url.isEmpty() && (url.left(1) == "#")) { //anchor
moveToAnchor(url.mid(1));
}
else if (url.left(7) == "http://") { //open the bowser configured by kdeb
KApplication::kApplication()->invokeBrowser( url ); //ToDo: Not yet tested
}
}
/** Reimplementation. */
void CHTMLReadDisplay::khtmlMouseReleaseEvent( khtml::MouseReleaseEvent* event ) {
KHTMLPart::khtmlMouseReleaseEvent(event);
m_dndData.mousePressed = false;
m_dndData.isDragging = false;
m_dndData.node = DOM::Node();
m_dndData.anchor = DOM::DOMString();
if (m_urlWorkaroundData.doWorkaround) {
m_urlWorkaroundData.doWorkaround = false;
connectionsProxy()->emitReferenceClicked(
m_urlWorkaroundData.module,
m_urlWorkaroundData.key
);
}
}
void CHTMLReadDisplay::khtmlMousePressEvent( khtml::MousePressEvent* event ) {
m_dndData.node = DOM::Node();
m_dndData.anchor = DOM::DOMString();
m_dndData.mousePressed = false;
m_dndData.isDragging = false;
if (event->qmouseEvent()->button() == Qt::RightButton) {
DOM::Node tmpNode = event->innerNode();
DOM::Node attr;
m_nodeInfo[CDisplay::Lemma] = TQString();
do {
if (!tmpNode.isNull() && (tmpNode.nodeType() ==
DOM::Node::ELEMENT_NODE) && tmpNode.hasAttributes()) {
attr = tmpNode.attributes().getNamedItem("lemma");
if (!attr.isNull()) {
m_nodeInfo[ CDisplay::Lemma ] = attr.nodeValue().string();
break;
}
}
tmpNode = tmpNode.parentNode();
} while ( !tmpNode.isNull() );
setActiveAnchor( event->url().string() );
}
else if (event->qmouseEvent()->button() == Qt::LeftButton) {
m_dndData.node = event->innerNode();
m_dndData.anchor = event->url();
m_dndData.mousePressed = true;
m_dndData.isDragging = false;
m_dndData.startPos = TQPoint(event->x(), event->y());
m_dndData.selection = selectedText();
if (!m_dndData.node.isNull()) { //we drag a valid link
m_dndData.dragType = DNDData::Link;
}
}
KHTMLPart::khtmlMousePressEvent(event);
}
/** Reimplementation for our drag&drop system. Also needed for the mouse tracking */
void CHTMLReadDisplay::khtmlMouseMoveEvent( khtml::MouseMoveEvent* e ) {
if( e->qmouseEvent()->state() & Qt::LeftButton == Qt::LeftButton) { //left mouse button pressed
const int delay = KGlobalSettings::dndEventDelay();
TQPoint newPos = TQPoint(e->x(), e->y());
if ( (newPos.x() > m_dndData.startPos.x()+delay || newPos.x() < (m_dndData.startPos.x()-delay) ||
newPos.y() > m_dndData.startPos.y()+delay || newPos.y() < (m_dndData.startPos.y()-delay)) &&
!m_dndData.isDragging && m_dndData.mousePressed ) {
TQDragObject* d = 0;
if (!m_dndData.anchor.isEmpty() && (m_dndData.dragType == DNDData::Link) && !m_dndData.node.isNull() ) {
// create a new bookmark drag!
TQString module = TQString();
TQString key = TQString();
CReferenceManager::Type type;
if ( !CReferenceManager::decodeHyperlink(m_dndData.anchor.string(), module, key, type) )
return;
CDragDropMgr::ItemList dndItems;
//no description!
dndItems.append( CDragDropMgr::Item(module, key, TQString()) );
d = CDragDropMgr::dragObject(dndItems, KHTMLPart::view()->viewport());
}
else if ((m_dndData.dragType == DNDData::Text) && !m_dndData.selection.isEmpty()) {
// create a new plain text drag!
CDragDropMgr::ItemList dndItems;
dndItems.append( CDragDropMgr::Item(m_dndData.selection) ); //no description!
d = CDragDropMgr::dragObject(dndItems, KHTMLPart::view()->viewport());
}
if (d) {
m_dndData.isDragging = true;
m_dndData.mousePressed = false;
//first make a virtual mouse click to end the selection, it it's in progress
TQMouseEvent e(TQEvent::MouseButtonRelease, TQPoint(0,0), Qt::LeftButton, Qt::LeftButton);
KApplication::sendEvent(view()->viewport(), &e);
d->drag();
}
}
}
else if (getMouseTracking() && !(e->qmouseEvent()->state() & TQt::ShiftButton == TQt::ShiftButton)) {
//no mouse button pressed and tracking enabled
DOM::Node node = e->innerNode();
//if no link was under the mouse try to find a title attribute
if (!node.isNull() && (m_previousEventNode != node)) {
// we want to avoid processing the node again
// After some millisecs the new timer activates the Mag window update, see timerEvent()
// SHIFT key not pressed, so we start timer
if ( !(e->qmouseEvent()->state() & TQt::ShiftButton)) {
// TQObject has simple timer
killTimers();
startTimer( CBTConfig::get(CBTConfig::magDelay) );
}
m_previousEventNode = node;
}
}
KHTMLPart::khtmlMouseMoveEvent(e);
}
/** The Mag window update happens here if the mouse has not moved to another node after starting the timer.*/
void CHTMLReadDisplay::timerEvent( TQTimerEvent *e ) {
killTimers();
DOM::Node currentNode = nodeUnderMouse();
CInfoDisplay::ListInfoData infoList;
// Process the node under cursor if it is the same as at the start of the timer
if (!currentNode.isNull() && (currentNode != m_previousEventNode) && this->view()->hasMouse()) {
DOM::Node attr;
do {
if (!currentNode.isNull() && (currentNode.nodeType() == DOM::Node::ELEMENT_NODE) && currentNode.hasAttributes()) { //found right node
attr = currentNode.attributes().getNamedItem("note");
if (!attr.isNull()) {
infoList.append( tqMakePair(CInfoDisplay::Footnote, attr.nodeValue().string()) );
}
attr = currentNode.attributes().getNamedItem("lemma");
if (!attr.isNull()) {
infoList.append( tqMakePair(CInfoDisplay::Lemma, attr.nodeValue().string()) );
}
attr = currentNode.attributes().getNamedItem("morph");
if (!attr.isNull()) {
infoList.append( tqMakePair(CInfoDisplay::Morph, attr.nodeValue().string()) );
}
attr = currentNode.attributes().getNamedItem("expansion");
if (!attr.isNull()) {
infoList.append( tqMakePair(CInfoDisplay::Abbreviation, attr.nodeValue().string()) );
}
attr = currentNode.attributes().getNamedItem("crossrefs");
if (!attr.isNull()) {
infoList.append( tqMakePair(CInfoDisplay::CrossReference, attr.nodeValue().string()) );
}
}
currentNode = currentNode.parentNode();
if (!currentNode.isNull() && currentNode.hasAttributes()) {
attr = currentNode.attributes().getNamedItem("class");
if (!attr.isNull() && (attr.nodeValue().string() == "entry") || (attr.nodeValue().string() == "currententry") ) {
break;
}
}
}
while ( !currentNode.isNull() );
}
// Update the mag if there is new content
if (!(infoList.isEmpty())) {
CPointers::infoDisplay()->setInfo(infoList);
}
}
// ---------------------
CHTMLReadDisplayView::CHTMLReadDisplayView(CHTMLReadDisplay* displayWidget, TQWidget* tqparent) : KHTMLView(displayWidget, tqparent), m_display(displayWidget) {
viewport()->setAcceptDrops(true);
setMarginWidth(4);
setMarginHeight(4);
};
/** Opens the popupmenu at the given position. */
void CHTMLReadDisplayView::popupMenu( const TQString& url, const TQPoint& pos) {
if (!url.isEmpty()) {
m_display->setActiveAnchor(url);
}
if (TQPopupMenu* popup = m_display->installedPopup()) {
popup->exec(pos);
}
}
/** Reimplementation from TQScrollView. Sets the right slots */
void CHTMLReadDisplayView::polish() {
KHTMLView::polish();
connect( part(), TQT_SIGNAL(popupMenu(const TQString&, const TQPoint&)),
this, TQT_SLOT(popupMenu(const TQString&, const TQPoint&)));
}
/** Reimplementatiob from TQScrollView. */
void CHTMLReadDisplayView::contentsDropEvent( TQDropEvent* e ) {
if (CDragDropMgr::canDecode(e) && CDragDropMgr::dndType(e) == CDragDropMgr::Item::Bookmark) {
CDragDropMgr::ItemList dndItems = CDragDropMgr::decode(e);
CDragDropMgr::Item item = dndItems.first();
e->acceptAction();
m_display->connectionsProxy()->emitReferenceDropped(item.bookmarkKey());
return;
};
//don't accept the action!
e->acceptAction(false);
e->ignore();
}
/** Reimplementation from TQScrollView. */
void CHTMLReadDisplayView::contentsDragEnterEvent( TQDragEnterEvent* e ) {
if (CDragDropMgr::canDecode(e) && CDragDropMgr::dndType(e) == CDragDropMgr::Item::Bookmark) {
e->acceptAction();
return;
}
e->acceptAction(false);
e->ignore();
}
/*!
\fn CHTMLReadDisplay::slotPageLoaded()
*/
void CHTMLReadDisplay::slotGoToAnchor() {
if (!m_currentAnchorCache.isEmpty()) {
if (!gotoAnchor( m_currentAnchorCache ) ) {
qDebug("anchor %s not present!", m_currentAnchorCache.latin1());
}
}
m_currentAnchorCache = TQString();
}
void CHTMLReadDisplay::zoomIn() {
setZoomFactor( (int)((float)zoomFactor()*1.1) );
}
void CHTMLReadDisplay::zoomOut() {
setZoomFactor( (int)((float)zoomFactor()*(1.0/1.1)) );
}
void CHTMLReadDisplay::openFindTextDialog() {
#if KDE_VERSION >= 0x030300
findText();
#else
TQMessageBox::information(0, "Not Supported",
"This copy of BibleTime was built against a version of KDE older\n"
"than 3.3 (probably due to your distro), so this search feature\n"
"does not work.\n\n"
"This is a known issue. If we do not have a fix for the next\n"
"version of BibleTime, we will remove the option.");
#endif
}