/*************************************************************************** * Copyright (C) 2002, Anders Lund * * Copyright (C) 2003, 2004, Franck Qu�lain * * Copyright (C) 2004, Kevin Krammer * * Copyright (C) 2004, 2006, Oliviet Goffart * * * * 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. * ***************************************************************************/ // TQt includes #include #include // KDE include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // local includes #include "plugin_rellinks.h" /** Rellinks factory */ typedef KGenericFactory RelLinksFactory; #include #if KDE_IS_VERSION(3,2,90) #include static const KAboutData aboutdata("rellinks", I18N_NOOP("Rellinks") , "1.0" ); K_EXPORT_COMPONENT_FACTORY( librellinksplugin, RelLinksFactory(&aboutdata) ) #else K_EXPORT_COMPONENT_FACTORY( librellinksplugin, RelLinksFactory("rellinks") ) #endif /** Constructor of the plugin. */ RelLinksPlugin::RelLinksPlugin(TQObject *parent, const char *name, const TQStringList &) : KParts::Plugin( parent, name ), m_part(0), m_viewVisible(false) { setInstance(RelLinksFactory::instance()); // ------------- Navigation links -------------- kaction_map["home"] = new KAction( i18n("&Top"), "2uparrow", KShortcut("Ctrl+Alt+T"), this, TQT_SLOT(goHome()), actionCollection(), "rellinks_top" ); kaction_map["home"]->setWhatsThis( i18n("

This link references a home page or the top of some hierarchy.

") ); kaction_map["up"] = new KAction( i18n("&Up"), "1uparrow", KShortcut("Ctrl+Alt+U"), this, TQT_SLOT(goUp()), actionCollection(), "rellinks_up" ); kaction_map["up"]->setWhatsThis( i18n("

This link references the immediate parent of the current document.

") ); bool isRTL = TQApplication::reverseLayout(); kaction_map["begin"] = new KAction( i18n("&First"), isRTL ? "2rightarrow" : "2leftarrow", KShortcut("Ctrl+Alt+F"), this, TQT_SLOT(goFirst()), actionCollection(), "rellinks_first" ); kaction_map["begin"]->setWhatsThis( i18n("

This link type tells search engines which document is considered by the author to be the starting point of the collection.

") ); kaction_map["prev"] = new KAction( i18n("&Previous"), isRTL ? "1rightarrow" : "1leftarrow", KShortcut("Ctrl+Alt+P"), this, TQT_SLOT(goPrevious()), actionCollection(), "rellinks_previous" ); kaction_map["prev"]->setWhatsThis( i18n("

This link references the previous document in an ordered series of documents.

") ); kaction_map["next"] = new KAction( i18n("&Next"), isRTL ? "1leftarrow" : "1rightarrow", KShortcut("Ctrl+Alt+N"), this, TQT_SLOT(goNext()), actionCollection(), "rellinks_next" ); kaction_map["next"]->setWhatsThis( i18n("

This link references the next document in an ordered series of documents.

") ); kaction_map["last"] = new KAction( i18n("&Last"), isRTL ? "2leftarrow" : "2rightarrow", KShortcut("Ctrl+Alt+L"), this, TQT_SLOT(goLast()), actionCollection(), "rellinks_last" ); kaction_map["last"]->setWhatsThis( i18n("

This link references the end of a sequence of documents.

") ); // ------------ special items -------------------------- kaction_map["search"] = new KAction( i18n("&Search"), "filefind", KShortcut("Ctrl+Alt+S"), this, TQT_SLOT(goSearch()), actionCollection(), "rellinks_search" ); kaction_map["search"]->setWhatsThis( i18n("

This link references the search.

") ); // ------------ Document structure links --------------- m_document = new KActionMenu( i18n("Document"), "contents", actionCollection(), "rellinks_document" ); m_document->setWhatsThis( i18n("

This menu contains the links referring the document information.

") ); m_document->setDelayed(false); kaction_map["contents"] = new KAction( i18n("Table of &Contents"), "contents", KShortcut("Ctrl+Alt+C"), this, TQT_SLOT(goContents()), actionCollection(), "rellinks_toc" ); m_document->insert(kaction_map["contents"]); kaction_map["contents"]->setWhatsThis( i18n("

This link references the table of contents.

") ); kactionmenu_map["chapter"] = new KActionMenu( i18n("Chapters"), "fileopen", actionCollection(), "rellinks_chapters" ); m_document->insert(kactionmenu_map["chapter"]); connect( kactionmenu_map["chapter"]->popupMenu(), TQT_SIGNAL( activated( int ) ), this, TQT_SLOT(goChapter(int))); kactionmenu_map["chapter"]->setWhatsThis( i18n("

This menu references the chapters of the document.

") ); kactionmenu_map["chapter"]->setDelayed(false); kactionmenu_map["section"] = new KActionMenu( i18n("Sections"), "fileopen", actionCollection(), "rellinks_sections" ); m_document->insert(kactionmenu_map["section"]); connect( kactionmenu_map["section"]->popupMenu(), TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( goSection( int ) ) ); kactionmenu_map["section"]->setWhatsThis( i18n("

This menu references the sections of the document.

") ); kactionmenu_map["section"]->setDelayed(false); kactionmenu_map["subsection"] = new KActionMenu( i18n("Subsections"), "fileopen", actionCollection(), "rellinks_subsections" ); m_document->insert(kactionmenu_map["subsection"]); connect( kactionmenu_map["subsection"]->popupMenu(), TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( goSubsection( int ) ) ); kactionmenu_map["subsection"]->setWhatsThis( i18n("

This menu references the subsections of the document.

") ); kactionmenu_map["subsection"]->setDelayed(false); kactionmenu_map["appendix"] = new KActionMenu( i18n("Appendix"), "edit", actionCollection(), "rellinks_appendix" ); m_document->insert(kactionmenu_map["appendix"]); connect( kactionmenu_map["appendix"]->popupMenu(), TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( goAppendix( int ) ) ); kactionmenu_map["appendix"]->setWhatsThis( i18n("

This link references the appendix.

") ); kactionmenu_map["appendix"]->setDelayed(false); kaction_map["glossary"] = new KAction( i18n("&Glossary"), "flag", KShortcut("Ctrl+Alt+G"), this, TQT_SLOT(goGlossary()), actionCollection(), "rellinks_glossary" ); m_document->insert(kaction_map["glossary"]); kaction_map["glossary"]->setWhatsThis( i18n("

This link references the glossary.

") ); kaction_map["index"] = new KAction( i18n("&Index"), "info", KShortcut("Ctrl+Alt+I"), this, TQT_SLOT(goIndex()), actionCollection(), "rellinks_index" ); m_document->insert(kaction_map["index"]); kaction_map["index"]->setWhatsThis( i18n("

This link references the index.

") ); // Other links m_more = new KActionMenu( i18n("More"), "misc", actionCollection(), "rellinks_more" ); m_more->setWhatsThis( i18n("

This menu contains other important links.

") ); m_more->setDelayed(false); kaction_map["help"] = new KAction( i18n("&Help"), "help", KShortcut("Ctrl+Alt+H"), this, TQT_SLOT(goHelp()), actionCollection(), "rellinks_help" ); m_more->insert(kaction_map["help"]); kaction_map["help"]->setWhatsThis( i18n("

This link references the help.

") ); kaction_map["author"] = new KAction( i18n("&Authors"), "mail_new", KShortcut("Ctrl+Alt+A"), this, TQT_SLOT(goAuthor()), actionCollection(), "rellinks_authors" ); m_more->insert(kaction_map["author"]); kaction_map["author"]->setWhatsThis( i18n("

This link references the author.

") ); kaction_map["copyright"] = new KAction( i18n("Copy&right"), "signature", KShortcut("Ctrl+Alt+R"), this, TQT_SLOT(goCopyright()), actionCollection(), "rellinks_copyright" ); m_more->insert(kaction_map["copyright"]); kaction_map["copyright"]->setWhatsThis( i18n("

This link references the copyright.

") ); kactionmenu_map["bookmark"] = new KActionMenu( i18n("Bookmarks"), "bookmark_folder", actionCollection(), "rellinks_bookmarks" ); m_more->insert(kactionmenu_map["bookmark"]); kactionmenu_map["bookmark"]->setWhatsThis( i18n("

This menu references the bookmarks.

") ); connect( kactionmenu_map["bookmark"]->popupMenu(), TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( goBookmark( int ) ) ); kactionmenu_map["bookmark"]->setDelayed(false); kactionmenu_map["alternate"] = new KActionMenu( i18n("Other Versions"), "attach", actionCollection(), "rellinks_other_versions" ); m_more->insert(kactionmenu_map["alternate"]); kactionmenu_map["alternate"]->setWhatsThis( i18n("

This link references the alternate versions of this document.

") ); connect( kactionmenu_map["alternate"]->popupMenu(), TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( goAlternate( int ) ) ); kactionmenu_map["alternate"]->setDelayed(false); // Unclassified menu m_links = new KActionMenu( i18n("Miscellaneous"), "rellinks", actionCollection(), "rellinks_links" ); kactionmenu_map["unclassified"] = m_links; kactionmenu_map["unclassified"]->setWhatsThis( i18n("

Miscellaneous links.

") ); connect( kactionmenu_map["unclassified"]->popupMenu(), TQT_SIGNAL( activated( int ) ), this, TQT_SLOT( goAllElements( int ) ) ); kactionmenu_map["unclassified"]->setDelayed(false); // We unactivate all the possible actions disableAll(); // When the rendering of the HTML is done, we update the site navigation bar m_part = dynamic_cast(parent); if (!m_part) return; connect( m_part, TQT_SIGNAL( docCreated() ), this, TQT_SLOT( newDocument() ) ); connect( m_part, TQT_SIGNAL( completed() ), this, TQT_SLOT( loadingFinished() ) ); // create polling timer and connect it m_pollTimer = new TQTimer(this, "polling timer"); connect( m_pollTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( updateToolbar() ) ); // delay access to our part's members until it has finished its initialisation TQTimer::singleShot(0, this, TQT_SLOT(delayedSetup())); } /** Destructor */ RelLinksPlugin::~RelLinksPlugin() { } bool RelLinksPlugin::eventFilter(TQObject *watched, TQEvent* event) { if (m_part == 0) return false; if (watched == 0 || event == 0) return false; if (TQT_BASE_OBJECT(watched) == TQT_BASE_OBJECT(m_view)) { switch (event->type()) { case TQEvent::Show: m_viewVisible = true; updateToolbar(); break; case TQEvent::Hide: m_viewVisible = false; updateToolbar(); break; case TQEvent::Close: m_pollTimer->stop(); m_view->removeEventFilter(this); break; default: break; } } // we never filter an event, we just want to know about it return false; } void RelLinksPlugin::delayedSetup() { if (m_part == 0) return; m_view = m_part->view(); m_view->installEventFilter(this); m_viewVisible = m_view->isVisible(); } void RelLinksPlugin::newDocument() { // start calling upateToolbar periodically to get the new links as soon as possible m_pollTimer->start(500); //kdDebug(90210) << "newDocument()" << endl; updateToolbar(); } void RelLinksPlugin::loadingFinished() { m_pollTimer->stop(); //kdDebug(90210) << "loadingFinished()" << endl; updateToolbar(); guessRelations(); } /* Update the site navigation bar */ void RelLinksPlugin::updateToolbar() { // If we have a part if (!m_part) return; // We disable all disableAll(); // get a list of LINK nodes in document DOM::NodeList linkNodes = m_part->document().getElementsByTagName( "link" ); //kdDebug(90210) << "Rellinks: Link nodes =" << linkNodes.length() << endl; bool showBar = false; unsigned long nodeLength = linkNodes.length(); for ( unsigned int i=0; i < nodeLength; i++ ) { // create a entry for each one DOM::Element e( linkNodes.item( i ) ); // --- Retrieve of the relation type -- TQString rel = e.getAttribute( "rel" ).string(); rel = rel.simplifyWhiteSpace(); if (rel.isEmpty()) { // If the "rel" attribut is null then use the "rev" attribute... TQString rev = e.getAttribute( "rev" ).string(); rev = rev.simplifyWhiteSpace(); if (rev.isEmpty()) { // if "rev" attribut is also empty => ignore continue; } // Determine the "rel" equivalent of "rev" type rel = transformRevToRel(rev); } // Determin the name used internally TQString lrel = getLinkType(rel.lower()); // relation to ignore if (lrel.isEmpty()) continue; // kdDebug() << "lrel=" << lrel << endl; // -- Retrieve of other usefull informations -- TQString href = e.getAttribute( "href" ).string(); // if nowhere to go, ignore the link if (href.isEmpty()) continue; TQString title = e.getAttribute( "title" ).string(); TQString hreflang = e.getAttribute( "hreflang" ).string(); KURL ref( m_part->url(), href ); if ( title.isEmpty() ) title = ref.prettyURL(); // escape ampersand before settings as action title, otherwise the menu entry will interpret it as an // accelerator title.replace('&', "&&"); // -- Menus activation -- // Activation of "Document" menu ? if (lrel == "contents" || lrel == "glossary" || lrel == "index" || lrel == "appendix") { m_document->setEnabled(true); } // Activation of "More" menu ? if (lrel == "help" || lrel == "author" || lrel == "copyright" ) { m_more->setEnabled(true); } // -- Buttons or menu items activation / creation -- if (lrel == "bookmark" || lrel == "alternate") { int id = kactionmenu_map[lrel]->popupMenu()->insertItem( title ); m_more->setEnabled(true); kactionmenu_map[lrel]->setEnabled(true); element_map[lrel][id] = e; } else if (lrel == "appendix" || lrel == "chapter" || lrel == "section" || lrel == "subsection") { int id = kactionmenu_map[lrel]->popupMenu()->insertItem( title ); m_document->setEnabled(true); kactionmenu_map[lrel]->setEnabled(true); element_map[lrel][id] = e; } else { // It is a unique action element_map[lrel][0] = e; if (kaction_map[lrel]) { kaction_map[lrel]->setEnabled(true); // Tooltip if (hreflang.isEmpty()) { kaction_map[lrel]->setToolTip( title ); } else { kaction_map[lrel]->setToolTip( title + " [" + hreflang + "]"); } } else { // For the moment all the elements are reference in a separated menu // TODO : reference the unknown ? int id = kactionmenu_map["unclassified"]->popupMenu()->insertItem( lrel + " : " + title ); kactionmenu_map["unclassified"]->setEnabled(true); element_map["unclassified"][id] = e; } } showBar = true; } } void RelLinksPlugin::guessRelations() { m_part = dynamic_cast(parent()); if (!m_part || m_part->document().isNull() ) return; //If the page already contains some link, that mean the webmaster is aware //of the meaning of so we can consider that if prev/next was possible //they are already there. if(!element_map.isEmpty()) return; // - The number of didgit may not be more of 3, or this is certenly an id. // - We make sure that the number is followed by a dot, a &, or the end, we // don't want to match stuff like that: page.html?id=A14E12FD // - We make also sure the number is not preceded dirrectly by others number TQRegExp rx("^(.*[=/?&][^=/?&.\\-0-9]*)([\\d]{1,3})([.&][^/0-9]{0,15})?$"); const TQString zeros("0000"); TQString url=m_part->url().url(); if(rx.search(url)!=-1) { uint val=rx.cap(2).toUInt(); uint lenval=rx.cap(2).length(); TQString nval_str=TQString::number(val+1); //prepend by zeros if the original also contains zeros. if(nval_str.length() < lenval && rx.cap(2)[0]=='0') nval_str.prepend(zeros.left(lenval-nval_str.length())); TQString href=rx.cap(1)+ nval_str + rx.cap(3); KURL ref( m_part->url(), href ); TQString title = i18n("[Autodetected] %1").tqarg(ref.prettyURL()); DOM::Element e= m_part->document().createElement("link"); e.setAttribute("href",href); element_map["next"][0] = e; kaction_map["next"]->setEnabled(true); kaction_map["next"]->setToolTip( title ); if(val>1) { nval_str=TQString::number(val-1); if(nval_str.length() < lenval && rx.cap(2)[0]=='0') nval_str.prepend(zeros.left(lenval-nval_str.length())); TQString href=rx.cap(1)+ nval_str + rx.cap(3); KURL ref( m_part->url(), href ); TQString title = i18n("[Autodetected] %1").tqarg(ref.prettyURL()); e= m_part->document().createElement("link"); e.setAttribute("href",href); element_map["prev"][0] = e; kaction_map["prev"]->setEnabled(true); kaction_map["prev"]->setToolTip( title ); } } } /** Menu links */ void RelLinksPlugin::goToLink(const TQString & rel, int id) { // have the KHTML part open it KHTMLPart *part = dynamic_cast(parent()); if (!part) return; DOM::Element e = element_map[rel][id]; TQString href = e.getAttribute("href").string(); KURL url( part->url(), href ); TQString target = e.getAttribute("target").string(); // URL arguments KParts::URLArgs args; args.frameName = target; // Add base url if not valid if (url.isValid()) { part->browserExtension()->openURLRequest(url, args); } else { KURL baseURL = part->baseURL(); TQString endURL = url.prettyURL(); KURL realURL = KURL(baseURL, endURL); part->browserExtension()->openURLRequest(realURL, args); } } void RelLinksPlugin::goHome() { goToLink("home"); } void RelLinksPlugin::goUp() { goToLink("up"); } void RelLinksPlugin::goFirst() { goToLink("begin"); } void RelLinksPlugin::goPrevious() { goToLink("prev"); } void RelLinksPlugin::goNext() { goToLink("next"); } void RelLinksPlugin::goLast() { goToLink("last"); } void RelLinksPlugin::goContents() { goToLink("contents"); } void RelLinksPlugin::goIndex() { goToLink("index"); } void RelLinksPlugin::goGlossary() { goToLink("glossary"); } void RelLinksPlugin::goHelp() { goToLink("help"); } void RelLinksPlugin::goSearch() { goToLink("search"); } void RelLinksPlugin::goAuthor() { goToLink("author"); } void RelLinksPlugin::goCopyright() { goToLink("copyright"); } void RelLinksPlugin::goBookmark(int id) { goToLink("bookmark", id); } void RelLinksPlugin::goChapter(int id) { goToLink("chapter", id); } void RelLinksPlugin::goSection(int id) { goToLink("section", id); } void RelLinksPlugin::goSubsection(int id) { goToLink("subsection", id); } void RelLinksPlugin::goAppendix(int id) { goToLink("appendix", id); } void RelLinksPlugin::goAlternate(int id) { goToLink("alternate", id); } void RelLinksPlugin::goAllElements(int id) { goToLink("unclassified", id); } void RelLinksPlugin::disableAll() { element_map.clear(); // Clear actions KActionMap::Iterator it; for ( it = kaction_map.begin(); it != kaction_map.end(); ++it ) { // If I don't test it crash :( if (it.data()) { it.data()->setEnabled(false); it.data()->setToolTip(it.data()->text().remove('&')); } } // Clear actions KActionMenuMap::Iterator itmenu; for ( itmenu = kactionmenu_map.begin(); itmenu != kactionmenu_map.end(); ++itmenu ) { // If I don't test it crash :( if (itmenu.data()) { itmenu.data()->popupMenu()->clear(); itmenu.data()->setEnabled(false); itmenu.data()->setToolTip(itmenu.data()->text().remove('&')); } } // Unactivate menus m_more->setEnabled(false); m_document->setEnabled(false); } TQString RelLinksPlugin::getLinkType(const TQString &lrel) { // Relations to ignore... if (lrel.contains("stylesheet") || lrel == "script" || lrel == "icon" || lrel == "shortcut icon" || lrel == "prefetch" ) return TQString(); // ...known relations... if (lrel == "top" || lrel == "origin" || lrel == "start") return "home"; if (lrel == "parent") return "up"; if (lrel == "first") return "begin"; if (lrel == "previous") return "prev"; if (lrel == "child") return "next"; if (lrel == "end") return "last"; if (lrel == "toc") return "contents"; if (lrel == "find") return "search"; if (lrel == "alternative stylesheet") return "alternate stylesheet"; if (lrel == "authors") return "author"; if (lrel == "toc") return "contents"; //...unknown relations or name that don't need to change return lrel; } TQString RelLinksPlugin::transformRevToRel(const TQString &rev) { TQString altRev = getLinkType(rev); // Known relations if (altRev == "prev") return getLinkType("next"); if (altRev == "next") return getLinkType("prev"); if (altRev == "made") return getLinkType("author"); if (altRev == "up") return getLinkType("child"); if (altRev == "sibling") return getLinkType("sibling"); //...unknown inverse relation => ignore for the moment return TQString(); } #include "plugin_rellinks.moc"