diff options
Diffstat (limited to 'knode')
178 files changed, 36405 insertions, 0 deletions
diff --git a/knode/.kateconfig b/knode/.kateconfig new file mode 100644 index 000000000..125718d3e --- /dev/null +++ b/knode/.kateconfig @@ -0,0 +1,2 @@ +// folder-wide kate configuration +// kate: space-indent on; indent-width 2; remove-trailing-space on; remove-trailing-space-save on; diff --git a/knode/AUTHORS b/knode/AUTHORS new file mode 100644 index 000000000..2f9258c85 --- /dev/null +++ b/knode/AUTHORS @@ -0,0 +1,50 @@ +Developers: +* Christian Thurner <cthurner@freepage.de> +* Christian Gebauer <gebauer@kde.org> +* Mathias Waack <mathias@atoll-net.de> +* Dirk Mueller <mueller@kde.org> +* Matthias Kalle Dalheimer <kalle@kde.org> +* Roberto Teixeira <maragato@kde.org> +* Volker Krause <volker.krause@rwth-aachen.de> + +Documentation: +* Stephan Johach <lucardus@onlinehome.de> +* Rainer Endres <endres@physos.com> +* Thomas Schuetz <Thomas.Schuetz@gmx.li> +* Nils Holland <nils@nightcastleproductions.org> + +Translators: +* Wolfram Diestel <wolfram@steloj.de> +* Norbert Popiolek <kdesktop@enter.net.pl> +* Chih-Wei Huang <cwhuang@linux.org.tw> +* Otto Bruggeman <otto.bruggeman@home.nl> +* Gaute Hvoslef Kvalnes <ai98ghk@stud.hib.no> +* Mattias Newzella <newzella@linux.nu> +* Andris Maziks <andris.m@delfi.lv> +* Jaime Robles <jaime@kde.org> +* Hasso Tepper <hasso@estpak.ee> +* José Carlos Monteiro <jcm@netcabo.pt> +* Alessandro Astarita <aleast@capri.it> +* Gregor Rakar <gregor.rakar@kiss.uni-lj.si> +* Andrey S. Cherepanov <andrey_tiger@i.am> +* Eugenijus Paulauskas <filija@klaipeda.omnitel.net> +* Vasif İsmayıloğlu <azerb_linux@hotmail.com> +* Li Zongliang <li-zongliang@21cn.com> +* Claudiu Costin <claudiuc@geocities.com> +* Erik K. Pedersen <erik@binghamton.edu> +* Stanislav Visnovsky <visnovsky@nenya.ms.mff.cuni.cz> +* Andy Rysin <arysin@yahoo.com> +* Elvis Pfützenreuter <epx@conectiva.com> +* Delafond <g.delafond@medsyn.fr> +* Ludovic Grossard <ludovic.grossard@libertysurf.fr> +* Dimitris Kamenopoulos <el97146@mail.ntua.gr> +* Meni Livne <meni@mail.com> +* Stephan Johach <lucardus@onlinehome.de> +* Kim Enkovaara <kim.enkovaara@iki.fi> +* Christian A Strømmen [Number1/NumeroUno] +* Milan Hejpetr <mhejpetr@iss.cz> +* Sinohara <shinobo@leo.bekkoame.ne.jp> +* Sebastià Pla <sastia@retemail.es> +* Tamas Szanto <tszanto@mol.hu> +* Bjarni R. Einarsson <bre@netverjar.is> +* Oğuz YILMAZ <o.yilmaz@ieee.org> diff --git a/knode/ChangeLog b/knode/ChangeLog new file mode 100644 index 000000000..f9585f5f5 --- /dev/null +++ b/knode/ChangeLog @@ -0,0 +1,294 @@ +changes between 0.6 and 0.7 ++ due personal reasons KNode 0.7 is just a port of + KNode 0.6 to KDE3 + +changes between 0.4 and 0.6 ++ New scoring system: + - User definable scoring rules with conditions based + on the overview data and configurable actions (currently + score value adjustment and notification) + - Full-fledged regular expressions which can be used in + scoring and filter conditions. ++ Reduced memory consumption. Its now configurable how much RAM + is used to cache article bodies and headers. ++ KNode now uses dock widgets for its views, the user can + arrange them in any way he wants. Added the shortcuts + 'g', 'h' and 'j' to switch to group, header and article view. ++ Improved article handling: + - User-defined folders + - DnD between folders and groups + - Convenient mbox-folder import/export + - Nice in-place renaming of accounts, folders and groups + - New menu option that expires all groups of the current account + and a option that compacts all folders on demand. + - New expire-feature: delete article headers that + aren't available on the server + - Implemented "Smart Scrolling" (TM) in the listviews and in + the groupbrowser + - added option to expand all threads by default + - a new tab "Navigation" in the configuration dialog: + + its possible emulate cursor key behavior of KMail + + "mark all read", "mark thread as read" and "ignore thread" + can now trigger additional actions. (close thread, go to + next thread/group) ++ Reading news articles: + - Proper handling of crossposted articles, they are marked + as read simutaneously in all groups. + - ignored articles are now automatically marked as read + - Added option to retrieve the article source + - New menu option: fetch an article by message-id + - Added option to retrieve an article from groups.google.com + if it's unavailable on the local server + - Trailing empty lines can be stripped from incoming articles + - Highlighting of "news:" and "mailto:" urls, all email-addresses + and msgids + - Its now configurable what characters are recognized as quote signs + - Its no possible to disable the fancy header decorations, + saves space on small displays + - The word wrap behavior of the article widget is + now configurable + - added a menuoption and a keyboard shortcut that switches + from proportional to fixed font in the article viewer + - its now possible to deactivate the tree view in the group subscription dialog. ++ Enhancements of the article composer + - Optional integration of KMail and other external + mail programs + - Full support for the Mail-Copies-To header + - Added placeholder for the group name in the attribution line + - Support for dynamically generated signatures + - Added option to place the cursor below the attribution line, + not above the signature (off by default) + - Implemented forwarding of articles with attachments + - Files can now be attached to an article with Drag&Drop ++ Improved article filters and search function: + - Filter rules for numerical values are more flexible now, + its possible to filter something like "x < value" or + "x > value", not just ranges. + - Its now possible to filter on the message-id and references header + - The search function can show complete threads now (optionally) + - Its now possible to search in folders ++ Network related improvements: + - Articles are no fetched via article number instead of article-id + to avoid problems with broken newsservers + - Enhanced dupe protection, utilizing the "recommened id" feature + of newer inn's + - Shows a password-dialog when the server requests authentication + - Support for IPv6 and SOCKS-proxies + +changes between 0.3.3 and 0.4 +- full support for non iso-8859-x charsets +- basic PGP/GnuPG support +- full GNKSA compliance +- followup and reply at same time now possible +- the moderation status of newsgroups is detected and displayed +- rot13 de-/encoding +- server specific identities +- selection of multiple articles/groups in the listview +- improved keyboard handling in the configuration dialog +- selection dialogs for sort column (F7) and filter (F6) that + can be reached with a keyboard shortcut +- the article line count can now be displayed in the listview +- improved color and font configuration +- the interpretation of text format tags can now be disabled +- support for other webbrowsers (Mozilla, Opera, user-defined) +- added a context menu for the article pane +- improvements for the composer: + + switching from news article to mail and back on the fly + + paste as quotation + + add/remove quotation + + box quote support + + status bar + + word wrap can now be disabled + + it's now possible to get the original text if + the automatic rewrap has gone wrong + + the user can determine which part of the original + message is quoted by selecting this part before hitting reply + + implemented a lot of sanity checks. +- changed the way the draft folder works, articles + can now be saved as draft without any sanity checks, + but they have to be edited in the composer again + before the are sent. +- '>' instead of '> ' as quote prefix for quote levels >= 2 +- numerous bugfixes +- code cleanups + +changes between 0.3.2 and 0.3.3 +- bugfix for networking on solaris +- bugfix for continuous reconnection on + news servers with authentication +- sane default window sizes +- correct default for the smtp-server port +- bugfix for a problem with some smtp servers +- fixes for non-iso-8859-x users +- buxfix for the display of plain text attachments + +changes between 0.1.13 and 0.3.2 +- ported to KDE2, new style guide compilant XML-GUI +- multithreaded network-code +- correct MIME-handling including multipart-messages +- support for uuencoded binaries +- ability to display attachments inline +- posting of MIME multipart-messages (attachments) +- significantly improved composer: + + all standard editor features (find, replace, select all, ...) + + spellchecker + + optional external editor + + nice attachment handling + + insert file functionality + + quoted text can be rewarped to preserve the original quoting. + + improved signature handling, + exiting sig dashes are detected + optional: direct entry of the signature in the configuration dialog + + tab key works ;-) +- improved X-Header configuration, its possible to disable + the User-Agent identification header now. +- rewritten configuration dialog +- support for multiple newsservers +- usage of the standard mbox format for folders +- cancel/supersede function +- forward articles as e-mail +- rewritten subscribe dialog + + nicer interface with treeview + + shows group descriptions + + can check for new newsgroups now (NEWGROUPS) +- ability to freely adjust the displayed article headers, + including custom names and format options. +- improved font & color configuration, + default colors adapt to all color schemes (including inverse ones) +- markup like *word* _word_ and /word/ is supported now +- you can use netscape instead of konqueror for links +- support for news://server/group urls as commmand line argument +- vastly improved new documentation (with screenshots) + written by Stephan Johach +- implemented search functionality for newsgroups +- added placeholders for own name and email to the filter configuration +- new standard filters (own articles, threads with own articles) +- header names and standard filter names are translatable now +- many new translations, thanks to the numerous translators ;-) + +changes between 0.1.12 and 0.1.13 +- fixed the "wheelmouse-bug" +- fixed a bug that produced doubled subjects +- fixed some minor bugs +- added "allow 8-bit characters in header" as an option +- the whole keyboard stuff has been greatly improved: + Christian Gebauer has rewritten almost the whole hotkey + management, making it much more convinient to control + KNode with the keyboard. + The changes are: + + a cursor has been added, that let's you scroll through + the articles without selecting each of them + + if you hit the return-key the article, the cursor currently + points at is selected and displayed + + the space-key jumps to the next unread article AND scrolls + the article view if it's necessary +- added two new functions : + + watch thread (set score=100), hotkey 'W' + + ignore thread (set score=0), hotkey 'I' +- added a new standard-filter for watched threads (score=100) +- added translations: german, italian, spanish + +changes between 0.1.11 and 0.1.12 +- fixed numerous bugs +- fixed that annoying "Unknown charset"-bug +- 8bit characters in the header of outgoing messages + are now encoded correctly (=?<chareset>?Q?<encoded word>?=) +- added "Resort" to the Group-menu +- added a new function : "Open article in own window" : + + Added an Option to the Article-menu + + A doubleclick on an article opens it in a new window + + If you click on a "reference-link" using the middle button, the + reference is opened in a new window. This behavior is similar to + the "open link in new window"-function of a webbrowser. +- added a "focus-indicator" marking the pane that currently + has got the focus +- added support for the common commandline-arguments -h and -v + (patch by Christian Gebauer) +- now KNode uses "X-Newsreader" for postings and "X-Mailer" for emails + (patch by Christian Gebauer) +- finally some documentation has been added :-) + +changes between 0.1.10 and 0.1.11 +- fixed some minor bugs +- nntp-authentication works now +- an article can now be saved as a text-file +- rewrite of the "FromLineParser" +- fixed a bug in the expire-mechanism +- fixed a bug concerning additional X-Headers +- added "charset" to the pref-dialog (tab "post news") +- added properties for groups : nicknames, group-specific settings + for name, email, reply-to and organization +- the Message-View can display different charsets + +changes between 0.1.9 and 0.1.10 +- added support for servers that require athentication (EXPERIMENTAL !!) +- fixed some bugs in the message view +- fixed two bugs in the "FromLineParser" +- now all hotkeys are disabled/enabled correctly +- some changes in the pref-dialog +- added support for custom X-Headers +- added new option "show whole thread on expanding" +- now all dialogs remember their size +- added "next/prev group" to the "Goto-Menu" +- the signature-file can now be chosen +- a lot of minor changes and bugfixes + +changes between 0.1.8 and 0.1.9: +- the read articles counter works correctly again +- fixed a bug in the folder-manager, that made knode loose + saved articles +- added "goto next/prev article" +- added an option to use the same font in the message-view + and composer +- added support for regular expressions in filtering +- some minor changes and bugfixes + +changes between 0.1.7 and 0.1.8: +- a lot of bugfixes +- now the message-view handles links (http) +- added support for cursor-keys (scrolling) +- added wheelmouse-support (imwheel) +- added support for the ~./.signature file +- some other little improvements + +changes between 0.1.6 and 0.1.7: +- added scoring support +- added configurable key bindings +- added article-navigation (next unread article, next unread thread) +- added "expand all / collapse all" +- added "download all" +- added "-lz" to makefile.am (needed for libpng) +- fixed some bugs in the message-view +- fixed a bug in the pref-dialog +- fixed a bug in the group-list : now the unread articles are + *really* counted correctly + +changes between 0.1.5 and 0.1.6: +- only one bugfix : now the unread articles are counted correctly again + +changes between 0.1.1 and 0.1.5 : +- fixed a bug in the message view, that made KNode crash under + certain circumstances +- fixed a bug in the nntp-client; knode now works with inn +- the groups in the left-hand window are sorted alphabetically + now +- changed the behavior of the clickable From-Header in the + message view; now the reply-To address is used if present +- added the item "mail reply" to the popup-menu in the + message list +- improved the message list: + * new messages are displayed as bold + * threads with no unread articles get grey text + * threads with new messages are marked with a little + arrow +- added printing-support +- seleceted text in the message view is now copied into the + x-clipboard +- the message list now remembers it's sorting +- added customizable filters +- added an "expire now" item to the group-menu +- added "Fonts & Colors" to the preferences-dialog + +changes between 0.1 and 0.1.1 : +- the menu-item "View->show Threads" behaves now like it should +- the mailto- and references-links in the message view work now diff --git a/knode/KNode.desktop b/knode/KNode.desktop new file mode 100644 index 000000000..dda0bb6b9 --- /dev/null +++ b/knode/KNode.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Type=Application +Exec=knode -caption "%c" %i %m +Icon=knode +Terminal=false +DocPath=knode/index.html +Name=KNode +Name[eo]=Diskutrondoj +Name[hi]=के-नोड +Name[ne]=केडीई नोड +Name[ro]=Ştiri Internet +Name[sv]=Knode +Name[ta]=Kநோடு +Name[th]=อ่านข่าว - K +Name[zh_TW]=KNode 新聞閱讀器 +GenericName=News Reader +GenericName[af]=Nuus leser +GenericName[ar]=قارئ أخبار +GenericName[az]=Xəbər Oxuyucu +GenericName[bg]=Четец на новини +GenericName[br]=Lenner keleier +GenericName[bs]=Program za čitanje USENet grupa +GenericName[ca]=Lector de notícies +GenericName[cs]=Klient pro čtení diskusních skupin +GenericName[cy]=Darllenydd Newyddion +GenericName[da]=Nyhedslæser +GenericName[de]=Usenet-Newsreader +GenericName[el]=Αναγνώστης νέων +GenericName[eo]=Legilo por diskutrondoj +GenericName[es]=Lector de noticias +GenericName[et]=Uudistelugeja +GenericName[eu]=Berri Irakurlea +GenericName[fa]=خوانندۀ اخبار +GenericName[fi]=Uutisryhmien lukuohjelma +GenericName[fr]=Lecteur de forums de discussion +GenericName[fy]=Nijslêzer +GenericName[ga]=Léitheoir Nuachta +GenericName[gl]=Lector de Noticias +GenericName[he]=קורא חדשות +GenericName[hi]=समाचार वाचक +GenericName[hr]=Program za čitanje USENet grupa +GenericName[hu]=NNTP hírolvasó +GenericName[id]=Pembaca Berita +GenericName[is]=Fréttaforrit +GenericName[it]=Lettore newsgroup +GenericName[ja]=ニュースリーダー +GenericName[ka]=სიახლეების წამკითხველი +GenericName[kk]=Жаңалықтарды оқу құралы +GenericName[km]=កម្មវិធីអានព័ត៌មាន +GenericName[lt]=Naujienų skaityklė +GenericName[lv]=Ziņu Lasītājs +GenericName[mk]=Читач на вести +GenericName[ms]=Pembaca Berita +GenericName[mt]=Qarrej tal-aħbarijiet (news) +GenericName[nb]=Njusleser +GenericName[nds]=Usenet-Narichtenkieker +GenericName[ne]=समाचार वाचक +GenericName[nl]=Nieuwslezer +GenericName[nn]=Nyheitslesar +GenericName[pl]=Program do czytania list dyskusyjnych +GenericName[pt]=Leitor de Notícias +GenericName[pt_BR]=Leitor de Notícias +GenericName[ro]=Cititor de ştiri USENET +GenericName[ru]=Чтение новостей +GenericName[rw]=Musoma Amakuru +GenericName[se]=Ođaslogan +GenericName[sk]=Prehliadač správ +GenericName[sl]=Bralnik novic +GenericName[sr]=Читач вести +GenericName[sr@Latn]=Čitač vesti +GenericName[sv]=Nyhetsläsare +GenericName[ta]=செய்தி வாசிப்பவர் +GenericName[tg]=Барнома барои хондани иттилоот +GenericName[th]=เครื่องมืออ่านข่าว +GenericName[tr]=Haber Okuyucu +GenericName[uk]=Програма для перегляду новин +GenericName[ven]=Muvhali wa mafhungo +GenericName[vi]=Trình đọc News +GenericName[xh]=Umfundi weendaba +GenericName[zh_CN]=新闻阅读器 +GenericName[zh_TW]=新聞閱讀器 +GenericName[zu]=Umfundi wezindaba +X-KDE-StartupNotify=true +X-DCOP-ServiceType=Unique +Categories=Qt;KDE;Network;News; diff --git a/knode/Makefile.am b/knode/Makefile.am new file mode 100644 index 000000000..f17631453 --- /dev/null +++ b/knode/Makefile.am @@ -0,0 +1,184 @@ +KDE_CXXFLAGS = $(USE_THREADS) + +INCLUDES= -I$(top_srcdir)/libkmime \ + -I$(top_srcdir)/libkdepim \ + -I$(top_srcdir)/libkpgp \ + -I$(top_srcdir)/libemailfunctions \ + -I$(top_srcdir) \ + $(all_includes) + +lib_LTLIBRARIES = libknodecommon.la + +kde_module_LTLIBRARIES = kcm_knode.la libknodepart.la +libknodepart_la_LDFLAGS = -module -avoid-version -no-undefined $(all_libraries) $(KDE_RPATH) $(KDE_PLUGIN) +libknodepart_la_LIBADD = libknodecommon.la + +SUBDIRS = pics filters + +bin_PROGRAMS = knode + +knode_LDFLAGS = $(all_libraries) $(KDE_RPATH) +knode_LDADD = libknodecommon.la + +kcm_knode_la_SOURCES = knconfigpages.cpp +kcm_knode_la_LDFLAGS = $(all_libraries) -module -avoid-version -no-undefined +kcm_knode_la_LIBADD = libknodecommon.la $(LIB_KDECORE) +knconfigpages.lo: smtpaccountwidget_base.h + +libknodecommon_la_LDFLAGS = -version-info 3:0:0 -no-undefined $(all_libraries) +libknodecommon_la_LIBADD = $(top_builddir)/libkmime/libkmime.la $(top_builddir)/libkpgp/libkpgp.la $(top_builddir)/libkdepim/libkdepim.la $(LIB_KSPELL) $(LIB_KABC) $(LIB_KFILE) $(LIB_KUTILS) $(LIBRESOLV) $(LIB_KHTML) +libknodecommon_la_SOURCES = knconfigmanager.cpp \ + knconfig.cpp \ + knconfigwidgets.cpp \ + \ + knnetaccess.cpp \ + knprotocolclient.cpp \ + knnntpclient.cpp \ + knjobdata.cpp \ + \ + knaccountmanager.cpp \ + kncollection.cpp \ + kncollectionviewitem.cpp \ + knserverinfo.cpp \ + knnntpaccount.cpp \ + \ + kngroupmanager.cpp \ + knarticlecollection.cpp \ + kngroup.cpp \ + kngroupbrowser.cpp \ + kngroupselectdialog.cpp \ + kngroupdialog.cpp \ + kngrouppropdlg.cpp \ + \ + knfoldermanager.cpp \ + knfolder.cpp \ + \ + knmemorymanager.cpp \ + kncleanup.cpp \ + knconvert.cpp \ + \ + knarticlemanager.cpp \ + knarticle.cpp \ + kndisplayedheader.cpp \ + knsourceviewwindow.cpp \ + knarticlewindow.cpp \ + knhdrviewitem.cpp \ + \ + kncomposer.cpp \ + knarticlefactory.cpp \ + \ + knfiltermanager.cpp \ + knstatusfilter.cpp \ + knstringfilter.cpp \ + knrangefilter.cpp \ + knarticlefilter.cpp \ + knfilterconfigwidget.cpp \ + knfilterdialog.cpp \ + knsearchdialog.cpp \ + \ + utilities.cpp \ + \ + knscoring.cpp \ + \ + knwidgets.cpp \ + headerview.cpp \ + knmainwidget.cpp \ + aboutdata.cpp \ + knglobals.cpp \ + knodecomposeriface.skel \ + knodeiface.skel \ + kncollectionview.cpp \ + articlewidget.cpp \ + csshelper.cpp \ + smtpaccountwidget_base.ui +libknodecommon_la_COMPILE_FIRST = smtpaccountwidget_base.h + +knode_SOURCES = knode.cpp knapplication.cpp main.cpp + +libknodepart_la_SOURCES = knode_part.cpp + + +noinst_HEADERS = knconfigmanager.h \ + knconfig.h \ + knconfigwidgets.h \ + \ + knnetaccess.h \ + knprotocolclient.h \ + knnntpclient.h \ + knjobdata.h \ + \ + knaccountmanager.h \ + kncollection.h \ + kncollectionviewitem.h \ + knserverinfo.h \ + knnntpaccount.h \ + \ + kngroupmanager.h \ + knarticlecollection.h \ + kngroup.h \ + kngroupbrowser.h \ + kngroupselectdialog.h \ + kngroupdialog.h \ + kngrouppropdlg.h \ + \ + knfoldermanager.h \ + knfolder.h \ + \ + knmemorymanager.h \ + kncleanup.h \ + knconvert.h \ + \ + knarticlemanager.h \ + knarticle.h \ + kndisplayedheader.h \ + knsourceviewwindow.h \ + knarticlewindow.h \ + knhdrviewitem.h \ + \ + kncomposer.h \ + knarticlefactory.h \ + \ + knfiltermanager.h \ + knstatusfilter.h \ + knstringfilter.h \ + knrangefilter.h \ + knarticlefilter.h \ + knfilterconfigwidget.h \ + knfilterdialog.h \ + knsearchdialog.h \ + \ + utilities.h \ + \ + knscoring.h \ + \ + knwidgets.h \ + headerview.h \ + knode.h \ + knapplication.h \ + resource.h \ + knglobals.h \ + aboutdata.h \ + knmainwidget.h \ + kncollectionview.h \ + articlewidget.h \ + csshelper.h + + +knode_METASOURCES = AUTO + +xdg_apps_DATA = KNode.desktop + +rcdir = $(kde_datadir)/knode +rc_DATA = headers.rc knodeui.rc knreaderui.rc kncomposerui.rc + +knewsprotocol_DATA = knewsservice.protocol +knewsprotocoldir = $(kde_servicesdir) + +kde_services_DATA = knode_config_identity.desktop knode_config_accounts.desktop knode_config_appearance.desktop knode_config_read_news.desktop knode_config_post_news.desktop knode_config_privacy.desktop knode_config_cleanup.desktop + +KDE_ICON = AUTO + +EXTRA_DIST = KNode.desktop + +messages: rc.cpp + $(XGETTEXT) *.cpp *.h -o $(podir)/knode.pot diff --git a/knode/README b/knode/README new file mode 100644 index 000000000..c821dc9f5 --- /dev/null +++ b/knode/README @@ -0,0 +1,15 @@ +KNode is a newsreader for the K Desktop Environment. + +It is GNKSA compliant (unfortunally a review is still +pending), and has support for MIME and multiple servers. + +It is a online-reader, but in combination with a +local newsserver like leafnode also usable with dial-up +connections. + +KNode is written in C++ using KDevelop, a new IDE for KDE +(thanx to the whole KDevelop-team for their excellent work). +It is published under the GNU General Public License +and comes without any warranty. + +The KNode team.
\ No newline at end of file diff --git a/knode/TODO b/knode/TODO new file mode 100644 index 000000000..4a2197107 --- /dev/null +++ b/knode/TODO @@ -0,0 +1,4 @@ +* improvements for the attachment handling + (multipart articles, saving attachments automatically) +* rewrite search dialog +* ... diff --git a/knode/aboutdata.cpp b/knode/aboutdata.cpp new file mode 100644 index 000000000..714f2613b --- /dev/null +++ b/knode/aboutdata.cpp @@ -0,0 +1,64 @@ +/* + aboutdata.h + + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include "aboutdata.h" + +#include "resource.h" + +namespace KNode +{ + struct about_authors { + const char* name; + const char* desc; + const char* email; + }; + + static const about_authors authors[] = { + { "Volker Krause", I18N_NOOP("Maintainer"), "volker.krause@rwth-aachen.de" }, + { "Roberto Selbach Teixeira", I18N_NOOP("Former maintainer"), "roberto@kde.org" }, + { "Christian Gebauer", 0, "gebauer@kde.org" }, + { "Christian Thurner", 0, "cthurner@web.de" }, + { "Dirk Mueller", 0, "mueller@kde.org" }, + { "Marc Mutz", 0, "mutz@kde.org" }, + { "Mathias Waack", 0, "mathias@atoll-net.de" }, + { "Laurent Montel", 0, "montel@kde.org" }, + { "Stephan Johach", 0, "lucardus@onlinehome.de" }, + { "Matthias Kalle Dalheimer", 0, "kalle@kde.org" }, + { "Zack Rusin", 0, "zack@kde.org" } + }; + + AboutData::AboutData() + : KAboutData( "knode", + I18N_NOOP("KNode"), + KNODE_VERSION, + I18N_NOOP("A newsreader for KDE"), + KAboutData::License_GPL, + I18N_NOOP("Copyright (c) 1999-2005 the KNode authors"), + 0, + "http://knode.sourceforge.net/" ) + { + using KNode::authors; + for ( unsigned int i = 0 ; i < sizeof authors / sizeof *authors ; ++i ) + addAuthor( authors[i].name, authors[i].desc, authors[i].email ); + + addCredit( "Jakob Schroeter", 0, "js@camaya.net" ); + } + + AboutData::~AboutData() + { + } + +} // namespace KNode diff --git a/knode/aboutdata.h b/knode/aboutdata.h new file mode 100644 index 000000000..443877a86 --- /dev/null +++ b/knode/aboutdata.h @@ -0,0 +1,34 @@ +/* + aboutdata.h + + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <kaboutdata.h> +#include <kdepimmacros.h> + +#ifndef KNODE_ABOUTDATA_H +#define KNODE_ABOUTDATA_H + +namespace KNode +{ + class KDE_EXPORT AboutData : public KAboutData + { + public: + AboutData(); + ~AboutData(); + }; + +} // namespace KNode + +#endif diff --git a/knode/articlewidget.cpp b/knode/articlewidget.cpp new file mode 100644 index 000000000..805632c24 --- /dev/null +++ b/knode/articlewidget.cpp @@ -0,0 +1,1474 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#include <unistd.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <qbuffer.h> +#include <qclipboard.h> +#include <qdir.h> +#include <qfile.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpaintdevicemetrics.h> +#include <qpopupmenu.h> +#include <qstringlist.h> +#include <qtextcodec.h> +#include <qtimer.h> + +#include <kaction.h> +#include <kaddrbook.h> +#include <kapplication.h> +#include <kbookmarkmanager.h> +#include <kcharsets.h> +#include <khtml_part.h> +#include <khtmlview.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmdcodec.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <krun.h> +#include <kstandarddirs.h> +#include <ktempfile.h> +#include <kurl.h> + +#include <libemailfunctions/email.h> +#include <libemailfunctions/kasciistringtools.h> + +#include <libkpgp/kpgp.h> +#include <libkpgp/kpgpblock.h> + +#include <libkdepim/kfileio.h> +#include <libkdepim/kxface.h> +#include <libkdepim/linklocator.h> + +#include "articlewidget.h" +#include "csshelper.h" +#include "knarticle.h" +#include "knarticlecollection.h" +#include "knarticlefactory.h" +#include "knarticlemanager.h" +#include "knconfig.h" +#include "knconfigmanager.h" +#include "kndisplayedheader.h" +#include "knfolder.h" +#include "knfoldermanager.h" +#include "knglobals.h" +#include "kngroup.h" +#include "knmainwidget.h" +#include "knnntpaccount.h" +#include "knsourceviewwindow.h" + +using namespace KNode; + +QValueList<ArticleWidget*> ArticleWidget::mInstances; + +ArticleWidget::ArticleWidget( QWidget *parent, + KXMLGUIClient *guiClient, + KActionCollection *actionCollection, + const char *name ) : + QWidget( parent, name ), + mArticle( 0 ), + mViewer( 0 ), + mCSSHelper( 0 ), + mHeaderStyle( "fancy" ), + mAttachmentStyle( "inline" ), + mShowHtml( false ), + mRot13( false ), + mForceCharset( false ), + mOverrideCharset( KMime::Headers::Latin1 ), + mTimer( 0 ), + mGuiClient( guiClient ), + mActionCollection( actionCollection ) +{ + mInstances.append( this ); + + QHBoxLayout *box = new QHBoxLayout( this ); + mViewer = new KHTMLPart( this, "mViewer" ); + box->addWidget( mViewer->widget() ); + mViewer->widget()->setFocusPolicy( WheelFocus ); + mViewer->setPluginsEnabled( false ); + mViewer->setJScriptEnabled( false ); + mViewer->setJavaEnabled( false ); + mViewer->setMetaRefreshEnabled( false ); + mViewer->setOnlyLocalReferences( true ); + mViewer->view()->setFocusPolicy( QWidget::WheelFocus ); + connect( mViewer->browserExtension(), SIGNAL(openURLRequestDelayed(const KURL&, const KParts::URLArgs&)), + SLOT(slotURLClicked(const KURL&)) ); + connect( mViewer, SIGNAL(popupMenu(const QString&, const QPoint&)), + SLOT(slotURLPopup(const QString&, const QPoint&)) ); + + mTimer = new QTimer( this ); + connect( mTimer, SIGNAL(timeout()), SLOT(slotTimeout()) ); + + initActions(); + readConfig(); + clear(); + + installEventFilter( this ); +} + + +ArticleWidget::~ArticleWidget() +{ + mInstances.remove( this ); + delete mTimer; + delete mCSSHelper; + if ( mArticle && mArticle->isOrphant() ) + delete mArticle; + removeTempFiles(); +} + + +void ArticleWidget::initActions() +{ + mSaveAction = KStdAction::save( this, SLOT(slotSave()), mActionCollection ); + mSaveAction->setText( KStdGuiItem::saveAs().text() ); + mPrintAction = KStdAction::print( this, SLOT(slotPrint()), mActionCollection ); + mCopySelectionAction = KStdAction::copy( this, SLOT(slotCopySelection()), mActionCollection ); + mSelectAllAction = KStdAction::selectAll( this, SLOT(slotSelectAll()), mActionCollection ); + mFindAction = KStdAction::find( this, SLOT(slotFind()), mActionCollection, "find_in_article" ); + mFindAction->setText( i18n("F&ind in Article...") ); + mViewSourceAction = new KAction( i18n("&View Source"), Key_V , this, + SLOT(slotViewSource()), mActionCollection, "article_viewSource" ); + mReplyAction = new KAction( i18n("&Followup to Newsgroup..."), "message_reply", + Key_R, this, SLOT(slotReply()), mActionCollection, "article_postReply" ); + mRemailAction = new KAction( i18n("Reply by E&mail..."), "mail_reply", + Key_A, this, SLOT(slotRemail()), mActionCollection, "article_mailReply" ); + mForwardAction = new KAction( i18n("Forw&ard by Email..."), "mail_forward", + Key_F, this, SLOT(slotForward()), mActionCollection, "article_forward" ); + mCancelAction = new KAction( i18n("article","&Cancel Article"), + 0, this, SLOT(slotCancel()), mActionCollection, "article_cancel" ); + mSupersedeAction = new KAction(i18n("S&upersede Article"), + 0, this, SLOT(slotSupersede()), mActionCollection, "article_supersede" ); + mFixedFontToggle = new KToggleAction( i18n("U&se Fixed Font"), + Key_X ,this, SLOT(slotToggleFixedFont()), mActionCollection, "view_useFixedFont" ); + mFancyToggle = new KToggleAction( i18n("Fancy Formating"), + Key_Y, this, SLOT(slotToggleFancyFormating()), mActionCollection, "view_fancyFormating" ); + mRot13Toggle = new KToggleAction( i18n("&Unscramble (Rot 13)"), "decrypted", 0 , this, + SLOT(slotToggleRot13()), mActionCollection, "view_rot13" ); + mRot13Toggle->setChecked( false ); + + KRadioAction *ra; + mHeaderStyleMenu = new KActionMenu( i18n("&Headers"), mActionCollection, "view_headers" ); + ra = new KRadioAction( i18n("&Fancy Headers"), 0, this, SLOT(slotFancyHeaders()), + mActionCollection, "view_headers_fancy" ); + ra->setExclusiveGroup( "view_headers" ); + mHeaderStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&Standard Headers"), 0, this, SLOT(slotStandardHeaders()), + mActionCollection, "view_headers_standard" ); + ra->setExclusiveGroup( "view_headers" ); + mHeaderStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&All Headers"), 0, this, SLOT(slotAllHeaders()), + mActionCollection, "view_headers_all" ); + ra->setExclusiveGroup( "view_headers" ); + mHeaderStyleMenu->insert( ra ); + + mAttachmentStyleMenu = new KActionMenu( i18n("&Attachments"), mActionCollection, "view_attachments" ); + ra = new KRadioAction( i18n("&As Icon"), 0, this, SLOT(slotIconAttachments()), + mActionCollection, "view_attachments_icon" ); + ra->setExclusiveGroup( "view_attachments" ); + mAttachmentStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&Inline"), 0, this, SLOT(slotInlineAttachments()), + mActionCollection, "view_attachments_inline" ); + ra->setExclusiveGroup( "view_attachments" ); + mAttachmentStyleMenu->insert( ra ); + ra = new KRadioAction( i18n("&Hide"), 0, this, SLOT(slotHideAttachments()), + mActionCollection, "view_attachments_hide" ); + ra->setExclusiveGroup( "view_attachments" ); + mAttachmentStyleMenu->insert( ra ); + + mCharsetSelect = new KSelectAction( i18n("Chars&et"), 0, mActionCollection, "set_charset" ); + mCharsetSelect->setShortcutConfigurable( false ); + QStringList cs = KGlobal::charsets()->descriptiveEncodingNames(); + cs.prepend( i18n("Automatic") ); + mCharsetSelect->setItems( cs ); + mCharsetSelect->setCurrentItem( 0 ); + connect( mCharsetSelect, SIGNAL(activated(const QString&)),SLOT(slotSetCharset(const QString&)) ); + mCharsetSelectKeyb = new KAction( i18n("Charset"), Key_C, this, + SLOT(slotSetCharsetKeyboard()), mActionCollection, "set_charset_keyboard" ); + + new KAction( i18n("&Open URL"), "fileopen", 0, this, SLOT(slotOpenURL()), + mActionCollection, "open_url" ); + new KAction( i18n("&Copy Link Address"), "editcopy", 0, this, SLOT( slotCopyURL()), + mActionCollection, "copy_url" ); + new KAction( i18n("&Bookmark This Link"), "bookmark_add", 0, this, SLOT(slotAddBookmark()), + mActionCollection, "add_bookmark" ); + new KAction( i18n("&Add to Address Book"), 0, this, SLOT(slotAddToAddressBook()), + mActionCollection, "add_addr_book" ); + new KAction( i18n("&Open in Address Book"), 0, this, SLOT(slotOpenInAddressBook()), + mActionCollection, "openin_addr_book" ); + new KAction( i18n("&Open Attachment"), "fileopen", 0, this, SLOT(slotOpenAttachment()), + mActionCollection, "open_attachment" ); + new KAction( i18n("&Save Attachment As..."), "filesaveas", 0, this, SLOT(slotSaveAttachment()), + mActionCollection, "save_attachment" ); +} + + + +void ArticleWidget::enableActions() +{ + if ( !mArticle ) { + disableActions(); + return; + } + + mSaveAction->setEnabled( true ); + mPrintAction->setEnabled( true ); + mCopySelectionAction->setEnabled( true ); + mSelectAllAction->setEnabled( true ); + mFindAction->setEnabled( true ); + mForwardAction->setEnabled( true ); + mHeaderStyleMenu->setEnabled( true ); + mAttachmentStyleMenu->setEnabled( true ); + mRot13Toggle->setEnabled( true ); + mViewSourceAction->setEnabled( true ); + mCharsetSelect->setEnabled( true ); + mCharsetSelectKeyb->setEnabled( true ); + mFixedFontToggle->setEnabled( true ); + mFancyToggle->setEnabled( true ); + + // only valid for remote articles + bool enabled = ( mArticle->type() == KMime::Base::ATremote ); + mReplyAction->setEnabled( enabled ); + mRemailAction->setEnabled( enabled ); + + enabled = ( mArticle->type() == KMime::Base::ATremote + || mArticle->collection() == knGlobals.folderManager()->sent() ); + mCancelAction->setEnabled( enabled ); + mSupersedeAction->setEnabled( enabled ); +} + + +void ArticleWidget::disableActions() +{ + mSaveAction->setEnabled( false ); + mPrintAction->setEnabled( false ); + mCopySelectionAction->setEnabled( false ); + mSelectAllAction->setEnabled( false ); + mFindAction->setEnabled( false ); + mReplyAction->setEnabled( false ); + mRemailAction->setEnabled( false ); + mForwardAction->setEnabled( false ); + mCancelAction->setEnabled( false ); + mSupersedeAction->setEnabled( false ); + mHeaderStyleMenu->setEnabled( false ); + mAttachmentStyleMenu->setEnabled( false ); + mRot13Toggle->setEnabled( false ); + mViewSourceAction->setEnabled( false ); + mCharsetSelect->setEnabled( false ); + mCharsetSelectKeyb->setEnabled( false ); + mFixedFontToggle->setEnabled( false ); + mFancyToggle->setEnabled( false ); +} + + + +void ArticleWidget::readConfig() +{ + mFixedFontToggle->setChecked( knGlobals.configManager()->readNewsViewer()->useFixedFont() ); + mFancyToggle->setChecked( knGlobals.configManager()->readNewsViewer()->interpretFormatTags() ); + + mShowHtml = knGlobals.configManager()->readNewsViewer()->alwaysShowHTML(); + + KConfig *conf = knGlobals.config(); + conf->setGroup( "READNEWS" ); + mAttachmentStyle = conf->readEntry( "attachmentStyle", "inline" ); + mHeaderStyle = conf->readEntry( "headerStyle", "fancy" ); + KRadioAction *ra = 0; + ra = static_cast<KRadioAction*>( mActionCollection->action( QString("view_attachments_%1").arg(mAttachmentStyle).latin1() ) ); + ra->setChecked( true ); + ra = static_cast<KRadioAction*>( mActionCollection->action( QString("view_headers_%1").arg(mHeaderStyle).latin1() ) ); + ra->setChecked( true ); + + delete mCSSHelper; + mCSSHelper = new CSSHelper( QPaintDeviceMetrics( mViewer->view() ) ); + + if ( !knGlobals.configManager()->readNewsGeneral()->autoMark() ) + mTimer->stop(); +} + + +void ArticleWidget::writeConfig() +{ + // main viewer determines the settings + if ( knGlobals.artWidget != this ) + return; + + KConfig *conf = knGlobals.config(); + conf->setGroup( "READNEWS" ); + conf->writeEntry( "attachmentStyle", mAttachmentStyle ); + conf->writeEntry( "headerStyle", mHeaderStyle ); + + knGlobals.configManager()->readNewsViewer()->setUseFixedFont( mFixedFontToggle->isChecked() ); + knGlobals.configManager()->readNewsViewer()->setInterpretFormatTags( mFancyToggle->isChecked() ); +} + + + +void ArticleWidget::setArticle( KNArticle *article ) +{ + // don't leak orphant articles + if ( mArticle && mArticle->isOrphant() ) + delete mArticle; + + mShowHtml = knGlobals.configManager()->readNewsViewer()->alwaysShowHTML(); + mRot13 = false; + mRot13Toggle->setChecked( false ); + mTimer->stop(); + + mArticle = article; + + if ( !mArticle ) + clear(); + else { + if ( mArticle->hasContent() ) { // article is already loaded => just show it + displayArticle(); + } else { + if( !knGlobals.articleManager()->loadArticle( mArticle ) ) + articleLoadError( mArticle, i18n("Unable to load the article.") ); + else + // try again for local articles + if( mArticle->hasContent() && !( mArticle->type() == KMime::Base::ATremote ) ) + displayArticle(); + } + } +} + + +void ArticleWidget::clear() +{ + disableActions(); + mViewer->begin(); + mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); + mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); + mViewer->write( "</body></html>" ); + mViewer->end(); +} + + +void ArticleWidget::displayArticle() +{ + if ( !mArticle) { + clear(); + return; + } + + // scroll back to top + mViewer->view()->ensureVisible( 0, 0 ); + + if ( !mArticle->hasContent() ) { + displayErrorMessage( i18n("The article contains no data.") ); + return; + } + + if ( mForceCharset != mArticle->forceDefaultCS() + || ( mForceCharset && mArticle->defaultCharset() != mOverrideCharset ) ) { + mArticle->setDefaultCharset( mOverrideCharset ); + mArticle->setForceDefaultCS( mForceCharset ); + } + + KNConfig::ReadNewsViewer *rnv = knGlobals.configManager()->readNewsViewer(); + removeTempFiles(); + + mViewer->begin(); + mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); + mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); + + // headers + displayHeader(); + + // body + QString html; + KMime::Content *text = mArticle->textContent(); + + // check if codec is available + if ( text && !canDecodeText( text->contentType()->charset() ) ) { + html += QString("<table width=\"100%\" border=\"0\"><tr><td bgcolor=\"#FF0000\">%1</td></tr></table>") + .arg( i18n("Unknown charset. Default charset is used instead.") ); + kdDebug(5003) << k_funcinfo << "unknown charset = " << text->contentType()->charset() << endl; + } + + // if the article is pgp signed and the user asked for verifying the + // signature, we show a nice header: + QPtrList<Kpgp::Block> pgpBlocks; + QStrList nonPgpBlocks; + bool containsPGP = Kpgp::Module::prepareMessageForDecryption( mArticle->body(), pgpBlocks, nonPgpBlocks ); + + mViewer->write ( html ); + html = QString(); + + if ( containsPGP ) { + QPtrListIterator<Kpgp::Block> pbit( pgpBlocks ); + QStrListIterator npbit( nonPgpBlocks ); + QTextCodec *codec; + if ( text ) + codec = KGlobal::charsets()->codecForName( text->contentType()->charset() ); + else + codec = KGlobal::locale()->codecForEncoding(); + + for( ; *pbit != 0; ++pbit, ++npbit ) { + // handle non-pgp block + QCString str( *npbit ); + if( !str.isEmpty() ) { + QStringList lines = QStringList::split( '\n', codec->toUnicode( str ), true ); + displayBodyBlock( lines ); + } + // handle pgp block + Kpgp::Block* block = *pbit; + if ( block->type() == Kpgp::ClearsignedBlock ) + block->verify(); + QStringList lines = QStringList::split( '\n', codec->toUnicode( block->text() ), true ); + if ( block->isSigned() ) { + QString signClass = displaySigHeader( block ); + displayBodyBlock( lines ); + displaySigFooter( signClass ); + } else { + displayBodyBlock( lines ); + } + } + // deal with the last non-pgp block + QCString str( *npbit ); + if( !str.isEmpty() ) { + QStringList lines = QStringList::split( '\n', codec->toUnicode( str ), true ); + displayBodyBlock( lines ); + } + } + + KMime::Headers::ContentType *ct = mArticle->contentType(); + + // get attachments + mAttachments.clear(); + mAttachementMap.clear(); + if( !text || ct->isMultipart() ) + mArticle->attachments( &mAttachments, rnv->showAlternativeContents() ); + + // partial message + if(ct->isPartial()) { + mViewer->write( i18n("<br/><b>This article has the MIME type "message/partial", which KNode cannot handle yet.<br>Meanwhile you can save the article as a text file and reassemble it by hand.</b>") ); + } + + // display body text + if ( text && text->hasContent() && !ct->isPartial() ) { + // handle HTML messages + if ( text->contentType()->isHTMLText() ) { + QString htmlTxt; + text->decodedText( htmlTxt, true, knGlobals.configManager()->readNewsViewer()->removeTrailingNewlines() ); + if ( mShowHtml ) { + // strip </html> & </body> + int i = kMin( htmlTxt.findRev( "</html>", -1, false ), htmlTxt.findRev( "</body>", -1, false ) ); + if ( i >= 0 ) + htmlTxt.truncate( i ); + html += htmlTxt; + } else { + html += "<div class=\"htmlWarn\">\n"; + html += i18n("<b>Note:</b> This is an HTML message. For " + "security reasons, only the raw HTML code " + "is shown. If you trust the sender of this " + "message then you can activate formatted " + "HTML display for this message " + "<a href=\"knode:showHTML\">by clicking here</a>."); + html += "</div><br><br>"; + html += toHtmlString( htmlTxt ); + } + } + else { + if ( !containsPGP ) { + QStringList lines; + text->decodedText( lines, true, knGlobals.configManager()->readNewsViewer()->removeTrailingNewlines() ); + displayBodyBlock( lines ); + } + } + + } + mViewer->write( html ); + + // display attachments + if( !mAttachments.isEmpty() && !ct->isPartial() ) { + int attCnt = 0; + for( KMime::Content *var = mAttachments.first(); var; var = mAttachments.next() ) { + displayAttachment( var, attCnt ); + attCnt++; + } + } + + mViewer->write("</body></html>"); + mViewer->end(); + + enableActions(); + if( mArticle->type() == KMime::Base::ATremote && knGlobals.configManager()->readNewsGeneral()->autoMark() ) + mTimer->start( knGlobals.configManager()->readNewsGeneral()->autoMarkSeconds() * 1000, true ); +} + + +void ArticleWidget::displayErrorMessage( const QString &msg ) +{ + mViewer->begin(); + mViewer->setUserStyleSheet( mCSSHelper->cssDefinitions( mFixedFontToggle->isChecked() ) ); + mViewer->write( mCSSHelper->htmlHead( mFixedFontToggle->isChecked() ) ); + QString errMsg = msg; + mViewer->write( "<b><font size=\"+1\" color=\"red\">" ); + mViewer->write( i18n("An error occurred.") ); + mViewer->write( "</font></b><hr/><br/>" ); + mViewer->write( errMsg.replace( "\n", "<br/>" ) ); + mViewer->write( "</body></html>"); + mViewer->end(); + + // mark article as read if there is a negative reply from the server + if ( knGlobals.configManager()->readNewsGeneral()->autoMark() && + mArticle && mArticle->type() == KMime::Base::ATremote && !mArticle->isOrphant() && + ( msg.find("430") != -1 || msg.find("423") != -1 ) ) { + KNRemoteArticle::List l; + l.append( static_cast<KNRemoteArticle*>( mArticle ) ); + knGlobals.articleManager()->setRead( l, true ); + } + + disableActions(); +} + + + +void ArticleWidget::displayHeader() +{ + QString headerHtml; + + // full header style + if ( mHeaderStyle == "all" ) { + QCString head = mArticle->head(); + KMime::Headers::Generic *header = 0; + + while ( !head.isEmpty() ) { + header = mArticle->getNextHeader( head ); + if ( header ) { + headerHtml += "<tr>"; + headerHtml+=QString( "<td align=\"right\" valign=\"top\"><b>%1</b></td><td width=\"100%\">%2</td></tr>" ) + .arg( toHtmlString( header->type(), None ) + ": " ) + .arg( toHtmlString( header->asUnicodeString() , ParseURL ) ); + delete header; + } + } + + mViewer->write( "<div class=\"header\">" ); + mViewer->write( "<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"> " ); + mViewer->write( headerHtml ); + mViewer->write( "</table></div>" ); + return; + } + + // standard & fancy header style + KMime::Headers::Base *hb; + QValueList<KNDisplayedHeader*> dhs = knGlobals.configManager()->displayedHeaders()->headers(); + for ( QValueList<KNDisplayedHeader*>::Iterator it = dhs.begin(); it != dhs.end(); ++it ) { + KNDisplayedHeader *dh = (*it); + hb = mArticle->getHeaderByType(dh->header().latin1()); + if ( !hb || hb->is("Subject") || hb->is("Organization") ) + continue; + + if ( dh->hasName() ) { + headerHtml += "<tr>"; + if ( mHeaderStyle == "fancy" ) + headerHtml += "<th>"; + else + headerHtml += "<th align=\"right\">"; + headerHtml += toHtmlString( dh->translatedName(), None ); + headerHtml += ":</th><td width=\"100%\">"; + } + else + headerHtml+="<tr><td colspan=\"2\">"; + + if ( hb->is("From") ) { + headerHtml += QString( "<a href=\"mailto:%1\">%2</a>") + .arg( KPIM::getEmailAddress( hb->asUnicodeString() ) ) + .arg( toHtmlString( hb->asUnicodeString(), None ) ); + KMime::Headers::Base *orgHdr = mArticle->getHeaderByType( "Organization" ); + if ( orgHdr && !orgHdr->isEmpty() ) { + headerHtml += " ("; + headerHtml += toHtmlString( orgHdr->asUnicodeString() ); + headerHtml += ")"; + } + } else if ( hb->is("Date") ) { + KMime::Headers::Date *date=static_cast<KMime::Headers::Date*>(hb); + headerHtml += toHtmlString( KGlobal::locale()->formatDateTime(date->qdt(), false, true), None ); + } else if ( hb->is("Newsgroups") ) { + QString groups = hb->asUnicodeString(); + groups.replace( ',', ", " ); + headerHtml += toHtmlString( groups, ParseURL ); + } else + headerHtml += toHtmlString( hb->asUnicodeString(), ParseURL ); + + headerHtml += "</td></tr>"; + } + + // standard header style + if ( mHeaderStyle == "standard" ) { + mViewer->write( "<b style=\"font-size:130%\">" + toHtmlString( mArticle->subject()->asUnicodeString() ) + "</b>" ); + mViewer->write( "<div class=\"header\"" ); + mViewer->write( "<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr>" + headerHtml ); + mViewer->write( "</tr></table></div>" ); + return; + } + + // X-Face support + QString xfhead; + KMime::Headers::Base *temp = mArticle->getHeaderByType("X-Face"); + if (temp) + xfhead = temp->asUnicodeString(); + QString xface = ""; + if ( !xfhead.isEmpty() ) { + KPIM::KXFace xf; + xface = QString::fromLatin1( "<div class=\"senderpic\"><img src=\"%1\" width=\"48\" height=\"48\"/></div>" ) + .arg( imgToDataUrl( xf.toImage( xfhead ), "PNG" ) ); + } + + // fancy header style + mViewer->write( "<div class=\"fancy header\"" ); + mViewer->write( QString("<div>") ); + mViewer->write( toHtmlString( mArticle->subject()->asUnicodeString(), ParseURL | FancyFormatting ) ); + mViewer->write( QString("</div>") ); + + QString html = QString("<table class=\"outer\"><tr><td width=\"100%\"><table>"); + + html += headerHtml; + html+="</td></tr></table></td>"; + html += "<td align=\"center\">" + xface + "</td>"; + html += "</tr></table>"; + + // references + KMime::Headers::References *refs = mArticle->references( false ); + if ( mArticle->type() == KMime::Base::ATremote && refs + && knGlobals.configManager()->readNewsViewer()->showRefBar() ) { + html += "<div class=\"spamheader\">"; + int refCnt = refs->count(), i = 1; + QCString id = refs->first(); + id = id.mid( 1, id.length() - 2 ); // remove <> + html += QString( "<b>%1</b>" ).arg( i18n("References:") ); + + while ( i <= refCnt ) { + html += " <a href=\"news:" + QString::fromLatin1( id ) + "\">" + QString::number( i ) + "</a>"; + id = refs->next(); + id = id.mid( 1, id.length() - 2 ); // remove <> + i++; + } + html += "</div>"; + } + + mViewer->write( html ); + mViewer->write( "</div>" ); +} + + +void ArticleWidget::displayBodyBlock( const QStringList &lines ) +{ + int oldLevel = -2, newLevel = -2; + bool isSig = false; + QString line, html; + KNConfig::ReadNewsViewer *rnv = knGlobals.configManager()->readNewsViewer(); + QString quoteChars = rnv->quoteCharacters().simplifyWhiteSpace(); + if (quoteChars.isEmpty()) + quoteChars = ">"; + + for ( QStringList::const_iterator it = lines.begin(); it != lines.end(); ++it) { + line = (*it); + if ( !line.isEmpty() ) { + // signature found + if ( !isSig && line == "-- " ) { + isSig = true; + // close previous body tag (if any) and open new one + if ( newLevel != -2 ) + html += "</div>"; + html += mCSSHelper->nonQuotedFontTag(); + newLevel = -1; + if ( rnv->showSignature() ) { + html += "<hr size=\"1\"/>"; + continue; + } + else break; + } + // look for quoting characters + if ( !isSig ) { + oldLevel = newLevel; + newLevel = quotingDepth( line, quoteChars ); + if ( newLevel >= 3 ) + newLevel = 2; // no more than three levels supported (0-2) + + // quoting level changed + if ( newLevel != oldLevel ) { + if ( oldLevel != -2 ) + html += "</div>"; // close previous level + // open new level + if ( newLevel == -1 ) + html += mCSSHelper->nonQuotedFontTag(); + else + html += mCSSHelper->quoteFontTag( newLevel ); + } + // output the actual line + html += toHtmlString( line, ParseURL | FancyFormatting | AllowROT13 ) + "<br/>"; + } else { + // signature + html += toHtmlString( line, ParseURL | AllowROT13 ) + "<br/>"; + } + } else { + // empty line + html += "<br/>"; + } + } + // close body quoting level tags + if ( newLevel != -2 ) + html += "</div>"; + + mViewer->write( html ); +} + + +QString ArticleWidget::displaySigHeader( Kpgp::Block* block ) +{ + QString signClass = "signErr"; + QString signer = block->signatureUserId(); + QCString signerKey = block->signatureKeyId(); + QString message; + if ( signer.isEmpty() ) { + message = i18n( "Message was signed with unknown key 0x%1." ) + .arg( signerKey ); + message += "<br/>"; + message += i18n( "The validity of the signature cannot be verified." ); + signClass = "signWarn"; + } else { + // determine the validity of the key + Kpgp::Module *pgp = knGlobals.pgp; + Kpgp::Validity keyTrust; + if( !signerKey.isEmpty() ) + keyTrust = pgp->keyTrust( signerKey ); + else + // This is needed for the PGP 6 support because PGP 6 doesn't + // print the key id of the signing key if the key is known. + keyTrust = pgp->keyTrust( signer ); + + // HTMLize the signer's user id and create mailto: link + signer = toHtmlString( signer, None ); + signer = "<a href=\"mailto:" + KPIM::getEmailAddress( signer ) + "\">" + signer + "</a>"; + + if( !signerKey.isEmpty() ) + message += i18n( "Message was signed by %1 (Key ID: 0x%2)." ) + .arg( signer ) + .arg( signerKey ); + else + message += i18n( "Message was signed by %1." ).arg( signer ); + message += "<br/>"; + + if( block->goodSignature() ) { + if ( keyTrust < Kpgp::KPGP_VALIDITY_MARGINAL ) + signClass = "signOkKeyBad"; + else + signClass = "signOkKeyOk"; + switch( keyTrust ) { + case Kpgp::KPGP_VALIDITY_UNKNOWN: + message += i18n( "The signature is valid, but the key's " + "validity is unknown." ); + break; + case Kpgp::KPGP_VALIDITY_MARGINAL: + message += i18n( "The signature is valid and the key is " + "marginally trusted." ); + break; + case Kpgp::KPGP_VALIDITY_FULL: + message += i18n( "The signature is valid and the key is " + "fully trusted." ); + break; + case Kpgp::KPGP_VALIDITY_ULTIMATE: + message += i18n( "The signature is valid and the key is " + "ultimately trusted." ); + break; + default: + message += i18n( "The signature is valid, but the key is " + "untrusted." ); + } + } else { + message += i18n("Warning: The signature is bad."); + signClass = "signErr"; + } + } + + QString html = "<table cellspacing=\"1\" cellpadding=\"1\" class=\"" + signClass + "\">"; + html += "<tr class=\"" + signClass + "H\"><td>"; + html += message; + html += "</td></tr><tr class=\"" + signClass + "B\"><td>"; + mViewer->write( html ); + return signClass; +} + + +void ArticleWidget::displaySigFooter( const QString &signClass ) +{ + QString html = "</td></tr><tr class=\"" + signClass + "H\">"; + html += "<td>" + i18n( "End of signed message" ) + "</td></tr></table>"; + mViewer->write( html ); +} + + +void ArticleWidget::displayAttachment( KMime::Content *att, int partNum ) +{ + if ( mAttachmentStyle == "hide" ) + return; + + QString html; + KMime::Headers::ContentType *ct = att->contentType(); + + // attachment label + QString label = ct->name(); + if ( label.isEmpty() ) + label = i18n("unnamed" ); + // if label consists of only whitespace replace them by underscores + if ( (uint)label.contains( ' ' ) == label.length() ) + label.replace( QRegExp( " ", true, true ), "_" ); + label = toHtmlString( label, None ); + + // attachment comment + QString comment = att->contentDescription()->asUnicodeString(); + comment = toHtmlString( comment, ParseURL | FancyFormatting ); + + QString href; + QString fileName = writeAttachmentToTempFile( att, partNum ); + if ( fileName.isEmpty() ) { + href = "part://" + QString::number( partNum ); + } else { + href = "file:" + KURL::encode_string( fileName ); + mAttachementMap[fileName] = partNum; + } + + if ( mAttachmentStyle == "inline" && inlinePossible( att ) ) { + if ( ct->isImage() ) { + html += "<div><a href=\"" + href + "\">" + "<img src=\"" + fileName + "\" border=\"0\"></a>" + "</div><div><a href=\"" + href + "\">" + label + "</a>" + "</div><div>" + comment + "</div><br>"; + } else { //text + // frame + html += "<table cellspacing=\"1\" class=\"textAtm\">" + "<tr class=\"textAtmH\"><td>" + "<a href=\"" + href + "\">" + label + "</a>"; + if ( !comment.isEmpty() ) + html += "<br>" + comment; + html += "</td></tr><tr class=\"textAtmB\"><td>"; + // content + QString tmp; + att->decodedText( tmp ); + /*if( ct->isHTMLText() ) + // ### to dangerous, we should use the same stuff as for the main text here + html += tmp; + else*/ + html += toHtmlString( tmp, ParseURL ); + // finish frame + html += "</td></tr></table>"; + } + } else { // icon + QCString mimetype = ct->mimeType(); + KPIM::kAsciiToLower( mimetype.data() ); + QString iconName = KMimeType::mimeType( mimetype )->icon( QString::null, false ); + QString iconFile = KGlobal::instance()->iconLoader()->iconPath( iconName, KIcon::Desktop ); + html += "<div><a href=\"" + href + "\"><img src=\"" + + iconFile + "\" border=\"0\">" + label + + "</a></div><div>" + comment + "</div><br>"; + } + mViewer->write( html ); +} + + +QString ArticleWidget::toHtmlString( const QString &line, int flags ) +{ + int llflags = LinkLocator::PreserveSpaces; + if ( !(flags & ArticleWidget::ParseURL) ) + llflags |= LinkLocator::IgnoreUrls; + if ( mFancyToggle->isChecked() && (flags & ArticleWidget::FancyFormatting) ) + llflags |= LinkLocator::ReplaceSmileys | LinkLocator::HighlightText; + QString text = line; + if ( flags & ArticleWidget::AllowROT13 ) { + if ( mRot13 ) + text = KNHelper::rot13( line ); + } + return LinkLocator::convertToHtml( text, llflags ); +} + + +// from KMail headerstyle.cpp +QString ArticleWidget::imgToDataUrl( const QImage &image, const char* fmt ) +{ + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + image.save( &buffer, fmt ); + return QString::fromLatin1("data:image/%1;base64,%2") + .arg( fmt, KCodecs::base64Encode( ba ) ); +} + + + +int ArticleWidget::quotingDepth( const QString &line, const QString "eChars ) +{ + int level = -1; + for ( uint i = 0; i < line.length(); ++i ) { + // skip spaces + if ( line[i].isSpace() ) + continue; + if ( quoteChars.find( line[i] ) != -1 ) + ++level; + else + break; + } + return level; +} + + +bool ArticleWidget::inlinePossible( KMime::Content *c ) +{ + KMime::Headers::ContentType *ct = c->contentType(); + return ( ct->isText() || ct->isImage() ); +} + + +bool ArticleWidget::canDecodeText( const QCString &charset ) const +{ + if ( charset.isEmpty() ) + return false; + bool ok = true; + KGlobal::charsets()->codecForName( charset,ok ); + return ok; +} + + + +void ArticleWidget::updateContents() +{ + // save current scrollbar position + float savedPosition = (float)mViewer->view()->contentsY() / (float)mViewer->view()->contentsHeight(); + if ( mArticle && mArticle->hasContent() ) + displayArticle(); + else + clear(); + // restore scrollbar position + mViewer->view()->setContentsPos( 0, qRound( mViewer->view()->contentsHeight() * savedPosition ) ); +} + + + +QString ArticleWidget::writeAttachmentToTempFile( KMime::Content *att, int partNum ) +{ + // more or less KMail code + KTempFile *tempFile = new KTempFile( QString::null, "." + QString::number( partNum ) ); + tempFile->setAutoDelete( true ); + QString fname = tempFile->name(); + delete tempFile; + + if( ::access( QFile::encodeName( fname ), W_OK ) != 0 ) + // Not there or not writable + if( ::mkdir( QFile::encodeName( fname ), 0 ) != 0 + || ::chmod( QFile::encodeName( fname ), S_IRWXU ) != 0 ) + return QString::null; //failed create + + Q_ASSERT( !fname.isNull() ); + + mTempDirs.append( fname ); + // strip off a leading path + KMime::Headers::ContentType* ct = att->contentType(); + QString attName = ct->name(); + int slashPos = attName.findRev( '/' ); + if( -1 != slashPos ) + attName = attName.mid( slashPos + 1 ); + if( attName.isEmpty() ) + attName = "unnamed"; + fname += "/" + attName; + + QByteArray data = att->decodedContent(); + size_t size = data.size(); + // ### KMail does crlf2lf conversion here before writing the file + if( !KPIM::kBytesToFile( data.data(), size, fname, false, false, false ) ) + return QString::null; + + mTempFiles.append( fname ); + // make file read-only so that nobody gets the impression that he might + // edit attached files + ::chmod( QFile::encodeName( fname ), S_IRUSR ); + + return fname; +} + + +void ArticleWidget::removeTempFiles( ) +{ + for ( QStringList::Iterator it = mTempFiles.begin(); it != mTempFiles.end(); ++it ) + QFile::remove(*it); + mTempFiles.clear(); + for ( QStringList::Iterator it = mTempDirs.begin(); it != mTempDirs.end(); ++it ) + QDir(*it).rmdir(*it); + mTempDirs.clear(); +} + + + +void ArticleWidget::processJob( KNJobData * job ) +{ + if ( job->type() == KNJobData::JTfetchSource ) { + KNRemoteArticle *a = static_cast<KNRemoteArticle*>( job->data() ); + if ( !job->canceled() ) { + if ( !job->success() ) + KMessageBox::error( this, i18n("An error occurred while downloading the article source:\n") + .arg( job->errorString() ) ); + else + new KNSourceViewWindow( a->head() + "\n" + a->body() ); + } + delete job; + delete a; + } + else + delete job; +} + + + +typedef QValueList<ArticleWidget*>::ConstIterator InstanceIterator; + +void ArticleWidget::configChanged() +{ + for( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) { + (*it)->readConfig(); + (*it)->updateContents(); + } +} + + +bool ArticleWidget::articleVisible( KNArticle *article ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + return true; + return false; +} + + +void ArticleWidget::articleRemoved( KNArticle *article ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + (*it)->setArticle( 0 ); +} + + +void ArticleWidget::articleChanged( KNArticle *article ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + (*it)->displayArticle(); +} + + +void ArticleWidget::articleLoadError( KNArticle *article, const QString &error ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() == article ) + (*it)->displayErrorMessage( error ); +} + + +void ArticleWidget::collectionRemoved( KNArticleCollection *coll ) +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->article() && (*it)->article()->collection() == coll ) + (*it)->setArticle( 0 ); +} + + +void ArticleWidget::cleanup() +{ + for ( InstanceIterator it = mInstances.begin(); it != mInstances.end(); ++it ) + (*it)->setArticle( 0 ); //delete orphant articles => avoid crash in destructor +} + + + +bool ArticleWidget::atBottom() const +{ + const KHTMLView *view = mViewer->view(); + return view->contentsY() + view->visibleHeight() >= view->contentsHeight(); +} + +void ArticleWidget::scrollUp() +{ + mViewer->view()->scrollBy( 0, -10 ); +} + +void ArticleWidget::scrollDown() +{ + mViewer->view()->scrollBy( 0, 10 ); +} + +void ArticleWidget::scrollPrior() +{ + mViewer->view()->scrollBy( 0, -(int)(mViewer->view()->height() * 0.8) ); +} + +void ArticleWidget::scrollNext() +{ + mViewer->view()->scrollBy( 0, (int)(mViewer->view()->height() * 0.8) ); +} + + + +void ArticleWidget::slotURLClicked( const KURL &url, bool forceOpen) +{ + // internal URLs + if ( url.protocol() == "knode" ) { + if ( url.path() == "showHTML" ) { + mShowHtml = true; + updateContents(); + } + return; + } + // handle mailto + if ( url.protocol() == "mailto" ) { + KMime::Headers::AddressField addr( mArticle ); + addr.fromUnicodeString( url.path(), "" ); + knGlobals.artFactory->createMail( &addr ); + return; + } + // handle news URL's + if ( url.protocol() == "news" ) { + kdDebug( 5003 ) << k_funcinfo << url << endl; + knGlobals.top->openURL( url ); + return; + } + // handle attachments + int partNum = 0; + if ( url.protocol() == "file" || url.protocol() == "part" ) { + if ( url.protocol() == "file" ) { + if ( !mAttachementMap.contains( url.path() ) ) + return; + partNum = mAttachementMap[url.path()]; + } + if ( url.protocol() == "part" ) + partNum = url.path().toInt(); + KMime::Content *c = mAttachments.at( partNum ); + if ( !c ) + return; + // TODO: replace with message box as done in KMail + if ( forceOpen || knGlobals.configManager()->readNewsViewer()->openAttachmentsOnClick() ) + knGlobals.articleManager()->openContent( c ); + else + knGlobals.articleManager()->saveContentToFile( c, this ); + return; + } + // let KDE take care of the remaing protocols (http, ftp, etc.) + new KRun( url ); +} + + +void ArticleWidget::slotURLPopup( const QString &url, const QPoint &point ) +{ + mCurrentURL = KURL( url ); + QString popupName; + if ( url.isEmpty() ) // plain text + popupName = "body_popup"; + else if ( mCurrentURL.protocol() == "mailto" ) + popupName = "mailto_popup"; + else if ( mCurrentURL.protocol() == "file" || mCurrentURL.protocol() == "part" ) + popupName = "attachment_popup"; + // ### news URLS? + else if ( mCurrentURL.protocol() == "knode" ) + return; // skip + else + popupName = "url_popup"; // all other URLs + QPopupMenu *popup = static_cast<QPopupMenu*>( mGuiClient->factory()->container( popupName, mGuiClient ) ); + if ( popup ) + popup->popup( point ); +} + + + +void ArticleWidget::slotTimeout() +{ + if ( mArticle && mArticle->type() == KMime::Base::ATremote && !mArticle->isOrphant() ) { + KNRemoteArticle::List l; + l.append( static_cast<KNRemoteArticle*>( mArticle ) ); + knGlobals.articleManager()->setRead( l, true ); + } +} + + + +void ArticleWidget::slotSave() +{ + if ( mArticle ) + knGlobals.articleManager()->saveArticleToFile( mArticle, this ); +} + +void ArticleWidget::slotPrint( ) +{ + if ( mArticle ) + mViewer->view()->print(); +} + + +void ArticleWidget::slotCopySelection( ) +{ + kapp->clipboard()->setText( mViewer->selectedText() ); +} + + +void ArticleWidget::slotSelectAll() +{ + mViewer->selectAll(); +} + + +void ArticleWidget::slotFind() +{ + mViewer->findText(); +} + + +void ArticleWidget::slotViewSource() +{ + // local article can be shown directly + if ( mArticle && mArticle->type() == KMime::Base::ATlocal && mArticle->hasContent() ) { + new KNSourceViewWindow( mArticle->encodedContent( false ) ); + } else { + // download remote article + if ( mArticle && mArticle->type() == KMime::Base::ATremote ) { + KNGroup *g = static_cast<KNGroup*>( mArticle->collection() ); + KNRemoteArticle *a = new KNRemoteArticle( g ); //we need "g" to access the nntp-account + a->messageID( true )->from7BitString( mArticle->messageID()->as7BitString( false ) ); + a->lines( true )->from7BitString( mArticle->lines( true )->as7BitString( false ) ); + a->setArticleNumber( static_cast<KNRemoteArticle*>( mArticle)->articleNumber() ); + emitJob( new KNJobData( KNJobData::JTfetchSource, this, g->account(), a) ); + } + } +} + + +void ArticleWidget::slotReply() +{ + if ( mArticle && mArticle->type() == KMime::Base::ATremote ) + knGlobals.artFactory->createReply( static_cast<KNRemoteArticle*>( mArticle ), + mViewer->selectedText(), true, false ); +} + + +void ArticleWidget::slotRemail() +{ + if ( mArticle && mArticle->type()==KMime::Base::ATremote ) + knGlobals.artFactory->createReply( static_cast<KNRemoteArticle*>( mArticle ), + mViewer->selectedText(), false, true ); +} + + +void ArticleWidget::slotForward() +{ + knGlobals.artFactory->createForward( mArticle ); +} + + +void ArticleWidget::slotCancel() +{ + knGlobals.artFactory->createCancel( mArticle ); +} + + +void ArticleWidget::slotSupersede() +{ + knGlobals.artFactory->createSupersede( mArticle ); +} + + +void ArticleWidget::slotToggleFixedFont() +{ + writeConfig(); + updateContents(); +} + + +void ArticleWidget::slotToggleFancyFormating( ) +{ + writeConfig(); + updateContents(); +} + + +void ArticleWidget::slotFancyHeaders() +{ + mHeaderStyle = "fancy"; + writeConfig(); + updateContents(); +} + +void ArticleWidget::slotStandardHeaders() +{ + mHeaderStyle = "standard"; + writeConfig(); + updateContents(); +} + +void ArticleWidget::slotAllHeaders() +{ + mHeaderStyle = "all"; + writeConfig(); + updateContents(); +} + + +void ArticleWidget::slotIconAttachments() +{ + mAttachmentStyle = "icon"; + writeConfig(); + updateContents(); +} + +void ArticleWidget::slotInlineAttachments() +{ + mAttachmentStyle = "inline"; + writeConfig(); + updateContents(); +} + +void ArticleWidget::slotHideAttachments() +{ + mAttachmentStyle = "hide"; + writeConfig(); + updateContents(); +} + + +void ArticleWidget::slotToggleRot13() +{ + mRot13 = !mRot13; + updateContents(); +} + + + +void ArticleWidget::slotSetCharset( const QString &charset ) +{ + if ( charset.isEmpty() ) + return; + + if ( charset == i18n("Automatic") ) { + mForceCharset = false; + mOverrideCharset = KMime::Headers::Latin1; + } else { + mForceCharset = true; + mOverrideCharset = KGlobal::charsets()->encodingForName( charset ).latin1(); + } + + if ( mArticle && mArticle->hasContent() ) { + mArticle->setDefaultCharset( mOverrideCharset ); // the article will choose the correct default, + mArticle->setForceDefaultCS( mForceCharset ); // when we disable the overdrive + updateContents(); + } +} + + +void ArticleWidget::slotSetCharsetKeyboard( ) +{ + int charset = KNHelper::selectDialog( this, i18n("Select Charset"), + mCharsetSelect->items(), mCharsetSelect->currentItem() ); + if ( charset != -1 ) { + mCharsetSelect->setCurrentItem( charset ); + slotSetCharset( *(mCharsetSelect->items().at( charset )) ); + } +} + + + +void ArticleWidget::slotOpenURL() +{ + slotURLClicked( mCurrentURL ); +} + +void ArticleWidget::slotCopyURL() +{ + QString address; + if ( mCurrentURL.protocol() == "mailto" ) + address = mCurrentURL.path(); + else + address = mCurrentURL.url(); + QApplication::clipboard()->setText( address, QClipboard::Clipboard ); + QApplication::clipboard()->setText( address, QClipboard::Selection ); +} + +void ArticleWidget::slotAddBookmark() +{ + if ( mCurrentURL.isEmpty() ) + return; + QString filename = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") ); + KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename, false ); + KBookmarkGroup group = bookManager->root(); + group.addBookmark( bookManager, mCurrentURL.url(), mCurrentURL ); + bookManager->save(); +} + +void ArticleWidget::slotAddToAddressBook() +{ + KAddrBookExternal::addEmail( mCurrentURL.path(), this ); +} + +void ArticleWidget::slotOpenInAddressBook() +{ + KAddrBookExternal::openEmail( mCurrentURL.path(), this ); +} + +void ArticleWidget::slotOpenAttachment() +{ + slotURLClicked( mCurrentURL, true ); +} + +void ArticleWidget::slotSaveAttachment() +{ + if ( mCurrentURL.protocol() != "file" && mCurrentURL.protocol() != "part" ) + return; + int partNum = 0; + if ( mCurrentURL.protocol() == "file" ) { + if ( !mAttachementMap.contains( mCurrentURL.path() ) ) + return; + partNum = mAttachementMap[mCurrentURL.path()]; + } + if ( mCurrentURL.protocol() == "part" ) + partNum = mCurrentURL.path().toInt(); + KMime::Content *c = mAttachments.at( partNum ); + if ( !c ) + return; + knGlobals.articleManager()->saveContentToFile( c, this ); +} + + + +void ArticleWidget::focusInEvent( QFocusEvent *e ) +{ + emit focusChanged(e); + QWidget::focusInEvent(e); +} + +void ArticleWidget::focusOutEvent( QFocusEvent *e ) +{ + emit focusChanged(e); + QWidget::focusOutEvent(e); +} + +bool ArticleWidget::eventFilter( QObject *o, QEvent *e ) +{ + if ( e->type() == QEvent::KeyPress && (static_cast<QKeyEvent*>(e)->key() == Key_Tab) ) { + emit focusChangeRequest( this ); + if ( !hasFocus() ) // focusChangeRequest was successful + return true; + } + return QWidget::eventFilter(o, e); +} + +#include "articlewidget.moc" diff --git a/knode/articlewidget.h b/knode/articlewidget.h new file mode 100644 index 000000000..d22926196 --- /dev/null +++ b/knode/articlewidget.h @@ -0,0 +1,269 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#ifndef KNODE_ARTICLEWIDGET_H +#define KNODE_ARTICLEWIDGET_H + +#include <qmap.h> +#include <qvaluelist.h> +#include <qwidget.h> + +#include <kurl.h> + +#include <kmime_content.h> + +#include "knjobdata.h" + +class QStringList; +class QTimer; + +class KAction; +class KActionCollection; +class KActionMenu; +class KHTMLPart; +class KURL; +class KSelectAction; +class KToggleAction; +class KXMLGUIClient; + +namespace Kpgp { + class Block; +} + +class KNArticle; +class KNArticleCollection; + +namespace KNode { + +class CSSHelper; + +/** + Widget to display a news article +*/ +class ArticleWidget : public QWidget, public KNJobConsumer { + + Q_OBJECT + + public: + /// Construct a new article widget + ArticleWidget( QWidget *parent, + KXMLGUIClient *guiClient, + KActionCollection *actionCollection, + const char *name = 0 ); + ~ArticleWidget(); + + /// read config settings + void readConfig(); + /// write config settings (call only for the main viewer) + void writeConfig(); + + /// display the given article + void setArticle( KNArticle *article ); + /// returns the currently shown article + KNArticle *article() const { return mArticle; } + + KAction* setCharsetKeyboardAction() const { return mCharsetSelectKeyb; } + + /// notify all instances about a config change + static void configChanged(); + /// check wether the given article is displayed in any instance + static bool articleVisible( KNArticle *article ); + /// notify all instances that the given article has been removed + static void articleRemoved( KNArticle *article ); + /// notify all instances that the given article has changed + static void articleChanged( KNArticle *article ); + /// notify all instances about an error during loading the given article + static void articleLoadError( KNArticle *article, const QString &error ); + /// notify all instances that the given collection has been removed + static void collectionRemoved( KNArticleCollection *coll ); + /// cleanup all instances + static void cleanup(); + + /// checks wether the readers is scrolled down to the bottom + bool atBottom() const; + + public slots: + void scrollUp(); + void scrollDown(); + void scrollPrior(); + void scrollNext(); + + signals: + void focusChanged( QFocusEvent* ); + void focusChangeRequest( QWidget* ); + + protected: + /// process download jobs for view source action + void processJob( KNJobData *j ); + + virtual void focusInEvent( QFocusEvent *e ); + virtual void focusOutEvent( QFocusEvent *e ); + virtual bool eventFilter( QObject *o, QEvent *e ); + + private: + void initActions(); + + /// enable article dependent actions + void enableActions(); + /// disable article dependent actions + void disableActions(); + + /// clears the article viewer + void clear(); + /// displays the current article or clears the view if no article is set + void displayArticle(); + /// displays the given error message in the viewer + void displayErrorMessage( const QString &msg ); + + /// display the message header (should be replaced by KMail's HeaderStyle class) + void displayHeader(); + /** displays the given text block, including quote and signature handling + * @param lines A list of lines to display. + */ + void displayBodyBlock( const QStringList &lines ); + /// displays a signature block header + QString displaySigHeader( Kpgp::Block* block ); + /// displays a signature footer + void displaySigFooter( const QString &signClass ); + /// displays the given attachment + void displayAttachment( KMime::Content *att, int partNum ); + + /// HTML conversion flags for toHtmlString() + enum ConversionFlags { + None = 0, + ParseURL = 1, + FancyFormatting = 2, + AllowROT13 = 4 + }; + /// convert the given string into an HTML string + QString toHtmlString( const QString &line, int flags = ParseURL ); + /// convert the given image into a data:/ URL + static QString imgToDataUrl( const QImage &image, const char* fmt ); + + /** calculates the quoting depth of the given line + * @returns -1 if no quoting was found, the quoting level otherwise + */ + static int quotingDepth( const QString &line, const QString "eChars ); + /// checks wether the given attachment can be shown inline + bool inlinePossible( KMime::Content *c ); + /// checks if the given charset is supported + bool canDecodeText( const QCString &charset ) const; + + /// regenerated viewer content without changing scrollbar position + void updateContents(); + + /** stores the given attachment into a temporary file + * @returns the filename the attachment has been stored to + */ + QString writeAttachmentToTempFile( KMime::Content *att, int partNum ); + /// removes all temporary files + void removeTempFiles(); + + private slots: + /// called if the user clicked on an URL + void slotURLClicked( const KURL &url, bool forceOpen = false ); + /// called if the user RMB clicked on an URL + void slotURLPopup( const QString &url, const QPoint &point ); + + /// mark as read timeout + void slotTimeout(); + + void slotSave(); + void slotPrint(); + void slotCopySelection(); + void slotSelectAll(); + void slotFind(); + void slotViewSource(); + void slotReply(); + void slotRemail(); + void slotForward(); + void slotCancel(); + void slotSupersede(); + void slotToggleFixedFont(); + void slotToggleFancyFormating(); + void slotToggleRot13(); + + void slotFancyHeaders(); + void slotStandardHeaders(); + void slotAllHeaders(); + + void slotIconAttachments(); + void slotInlineAttachments(); + void slotHideAttachments(); + + void slotSetCharset( const QString &charset ); + void slotSetCharsetKeyboard(); + + void slotOpenURL(); + void slotCopyURL(); + void slotAddBookmark(); + void slotAddToAddressBook(); + void slotOpenInAddressBook(); + void slotOpenAttachment(); + void slotSaveAttachment(); + + private: + /// the currently shown article + KNArticle *mArticle; + /// attachments of the current article + KMime::Content::List mAttachments; + /// mapping of temporary file names to part numbers + QMap<QString, int> mAttachementMap; + + KHTMLPart *mViewer; + CSSHelper *mCSSHelper; + + QStringList mTempDirs, mTempFiles; + + QString mHeaderStyle; + QString mAttachmentStyle; + bool mShowHtml; + bool mRot13; + bool mForceCharset; + QCString mOverrideCharset; + + /// mark as read timer + QTimer *mTimer; + + /// the last RMB clicked URL + KURL mCurrentURL; + + /// list of all instances of this class + static QValueList<ArticleWidget*> mInstances; + + KXMLGUIClient *mGuiClient; + KActionCollection *mActionCollection; + + KAction *mSaveAction; + KAction *mPrintAction; + KAction *mCopySelectionAction; + KAction *mSelectAllAction; + KAction *mFindAction; + KAction *mViewSourceAction; + KAction *mCharsetSelectKeyb; + KAction *mReplyAction; + KAction *mRemailAction; + KAction *mForwardAction; + KAction *mCancelAction; + KAction *mSupersedeAction; + KActionMenu *mHeaderStyleMenu; + KActionMenu *mAttachmentStyleMenu; + KToggleAction *mFixedFontToggle; + KToggleAction *mFancyToggle; + KToggleAction *mRot13Toggle; + KSelectAction *mCharsetSelect; +}; + +} + +#endif diff --git a/knode/csshelper.cpp b/knode/csshelper.cpp new file mode 100644 index 000000000..144d52bc8 --- /dev/null +++ b/knode/csshelper.cpp @@ -0,0 +1,42 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#include "csshelper.h" +#include "knconfig.h" +#include "knconfigmanager.h" +#include "knglobals.h" + + +KNode::CSSHelper::CSSHelper( const QPaintDeviceMetrics &pdm ) : + KPIM::CSSHelper( pdm ) +{ + KNConfig::Appearance *app = knGlobals.configManager()->appearance(); + + mForegroundColor = app->textColor(); + mLinkColor = app->linkColor(); + mVisitedLinkColor = app->linkColor(); + mBackgroundColor = app->backgroundColor(); + for ( int i = 0; i < 3; ++i ) + mQuoteColor[i] = app->quoteColor( i ); + + cHtmlWarning = app->htmlWarningColor(); + cPgpOk1H = app->signOkKeyOkColor(); + cPgpOk0H = app->signOkKeyBadColor(); + cPgpWarnH = app->signWarnColor(); + cPgpErrH = app->signErrColor(); + + mBodyFont = mPrintFont = app->articleFont(); + mFixedFont = mFixedPrintFont = app->articleFixedFont(); + + recalculatePGPColors(); +} diff --git a/knode/csshelper.h b/knode/csshelper.h new file mode 100644 index 000000000..23b2216a0 --- /dev/null +++ b/knode/csshelper.h @@ -0,0 +1,30 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#ifndef KNODE_CSSHELPER_H +#define KNODE_CSSHELPER_H + +#include <libkdepim/csshelper.h> + +namespace KNode { + +class CSSHelper : public KPIM::CSSHelper +{ + public: + CSSHelper( const QPaintDeviceMetrics &pdm ); + +}; + +} + +#endif diff --git a/knode/filters/1.fltr b/knode/filters/1.fltr new file mode 100644 index 000000000..f66021cd3 --- /dev/null +++ b/knode/filters/1.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data= +contains=true +enabled=false +regX=false +[GENERAL] +Translate_Name=true +applyOn=0 +enabled=true +name=all +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[STATUS] +DAT_N=false +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=false +EN_NS=false +EN_R=false +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/2.fltr b/knode/filters/2.fltr new file mode 100644 index 000000000..7b11f48e2 --- /dev/null +++ b/knode/filters/2.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data= +contains=true +enabled=false +regX=false +[GENERAL] +Translate_Name=true +applyOn=0 +enabled=true +name=unread +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[STATUS] +DAT_N=false +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=false +EN_NS=false +EN_R=true +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/3.fltr b/knode/filters/3.fltr new file mode 100644 index 000000000..e7c3e0f6e --- /dev/null +++ b/knode/filters/3.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data= +contains=true +enabled=false +regX=false +[GENERAL] +Translate_Name=true +applyOn=0 +enabled=true +name=new +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[STATUS] +DAT_N=true +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=true +EN_NS=false +EN_R=false +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/4.fltr b/knode/filters/4.fltr new file mode 100644 index 000000000..4972266fd --- /dev/null +++ b/knode/filters/4.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data= +contains=true +enabled=false +regX=false +[GENERAL] +Translate_Name=true +applyOn=0 +enabled=true +name=watched +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=true +op1=0 +op2=5 +val1=0 +val2=0 +[STATUS] +DAT_N=false +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=false +EN_NS=false +EN_R=false +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/5.fltr b/knode/filters/5.fltr new file mode 100644 index 000000000..24ca696b0 --- /dev/null +++ b/knode/filters/5.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data= +contains=true +enabled=false +regX=false +[GENERAL] +Translate_Name=true +applyOn=1 +enabled=true +name=threads with unread +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[STATUS] +DAT_N=false +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=false +EN_NS=false +EN_R=true +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/6.fltr b/knode/filters/6.fltr new file mode 100644 index 000000000..7ad3d6124 --- /dev/null +++ b/knode/filters/6.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data= +contains=true +enabled=false +regX=false +[GENERAL] +Translate_Name=true +applyOn=1 +enabled=true +name=threads with new +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[STATUS] +DAT_N=true +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=true +EN_NS=false +EN_R=false +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/7.fltr b/knode/filters/7.fltr new file mode 100644 index 000000000..81fec0cde --- /dev/null +++ b/knode/filters/7.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data=%MYNAME +contains=true +enabled=true +regX=false +[GENERAL] +Translate_Name=true +applyOn=0 +enabled=true +name=own articles +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[STATUS] +DAT_N=false +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=false +EN_NS=false +EN_R=false +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/8.fltr b/knode/filters/8.fltr new file mode 100644 index 000000000..5978c616f --- /dev/null +++ b/knode/filters/8.fltr @@ -0,0 +1,42 @@ +[AGE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[FROM] +Data=%MYNAME +contains=true +enabled=true +regX=false +[GENERAL] +Translate_Name=true +applyOn=1 +enabled=true +name=threads with own articles +[LINES] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[SCORE] +enabled=false +op1=2 +op2=0 +val1=0 +val2=0 +[STATUS] +DAT_N=false +DAT_NS=false +DAT_R=false +DAT_US=false +EN_N=false +EN_NS=false +EN_R=false +EN_US=false +[SUBJECT] +Data= +contains=true +enabled=false +regX=false diff --git a/knode/filters/Makefile.am b/knode/filters/Makefile.am new file mode 100644 index 000000000..1ddad6fb9 --- /dev/null +++ b/knode/filters/Makefile.am @@ -0,0 +1,4 @@ + +appsdir = $(kde_datadir)/knode/filters +apps_DATA = 1.fltr 2.fltr 3.fltr 4.fltr 5.fltr 6.fltr 7.fltr 8.fltr filters.rc + diff --git a/knode/filters/filters.rc b/knode/filters/filters.rc new file mode 100644 index 000000000..b2ec22121 --- /dev/null +++ b/knode/filters/filters.rc @@ -0,0 +1,2 @@ +Active=1,2,3,4,5,6,7,8 +Menu=1,-1,2,3,4,7,-1,8,5,6 diff --git a/knode/headers.rc b/knode/headers.rc new file mode 100644 index 000000000..ccf409a2e --- /dev/null +++ b/knode/headers.rc @@ -0,0 +1,35 @@ +[000] +Flags=0,0,0,0,1,1,0,0 +Header=Subject +Name= +Translate_Name=true +[001] +Flags=0,1,0,0,0,0,0,0 +Header=From +Name=From +Translate_Name=true +[002] +Flags=0,1,0,0,0,0,0,0 +Header=Reply-To +Name=Reply-To +Translate_Name=true +[003] +Flags=0,1,0,0,0,0,0,0 +Header=Date +Name=Date +Translate_Name=true +[004] +Flags=0,1,0,0,0,0,0,0 +Header=To +Name=To +Translate_Name=true +[005] +Flags=0,1,0,0,0,0,0,0 +Header=Newsgroups +Name=Groups +Translate_Name=true +[006] +Flags=0,1,0,0,0,0,0,0 +Header=Followup-To +Name=Followup-To +Translate_Name=true diff --git a/knode/headerview.cpp b/knode/headerview.cpp new file mode 100644 index 000000000..aef02f13a --- /dev/null +++ b/knode/headerview.cpp @@ -0,0 +1,617 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qcursor.h> +#include <qheader.h> +#include <qstylesheet.h> +#include <qtimer.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kpopupmenu.h> + +#include "knglobals.h" +#include "knconfigmanager.h" +#include "headerview.h" +#include "knhdrviewitem.h" +#include "kngroupmanager.h" +#include "knarticle.h" +#include "knarticlemanager.h" +#include "knmainwidget.h" + + +KNHeaderView::KNHeaderView(QWidget *parent, const char *name) : + KListView(parent,name), + mSortCol( -1 ), + mSortAsc( true ), + mSortByThreadChangeDate( false ), + mDelayedCenter( -1 ), + mActiveItem( 0 ), + mShowingFolder( false ), + mInitDone( false ) +{ + mPaintInfo.subCol = addColumn( i18n("Subject"), 310 ); + mPaintInfo.senderCol = addColumn( i18n("From"), 115 ); + mPaintInfo.scoreCol = addColumn( i18n("Score"), 42 ); + mPaintInfo.sizeCol = addColumn( i18n("Lines"), 42 ); + mPaintInfo.dateCol = addColumn( i18n("Date"), 102 ); + + setDropVisualizer( false ); + setDropHighlighter( false ); + setItemsRenameable( false ); + setItemsMovable( false ); + setAcceptDrops( false ); + setDragEnabled( true ); + setAllColumnsShowFocus( true ); + setSelectionMode( QListView::Extended ); + setShowSortIndicator( true ); + setShadeSortColumn ( true ); + setRootIsDecorated( true ); + setSorting( mPaintInfo.dateCol ); + header()->setMovingEnabled( true ); + setColumnAlignment( mPaintInfo.sizeCol, Qt::AlignRight ); + setColumnAlignment( mPaintInfo.scoreCol, Qt::AlignRight ); + + // due to our own column text squeezing we need to repaint on column resizing + disconnect( header(), SIGNAL(sizeChange(int, int, int)) ); + connect( header(), SIGNAL(sizeChange(int, int, int)), + SLOT(slotSizeChanged(int, int, int)) ); + + // column selection RMB menu + mPopup = new KPopupMenu( this ); + mPopup->insertTitle( i18n("View Columns") ); + mPopup->setCheckable( true ); + mPopup->insertItem( i18n("Line Count"), KPaintInfo::COL_SIZE ); + mPopup->insertItem( i18n("Score"), KPaintInfo::COL_SCORE ); + + connect( mPopup, SIGNAL(activated(int)), this, SLOT(toggleColumn(int)) ); + + // connect to the article manager + connect( knGlobals.articleManager(), SIGNAL(aboutToShowGroup()), SLOT(prepareForGroup()) ); + connect( knGlobals.articleManager(), SIGNAL(aboutToShowFolder()), SLOT(prepareForFolder()) ); + + new KNHeaderViewToolTip( this ); + + installEventFilter( this ); +} + + +KNHeaderView::~KNHeaderView() +{ + // ### crash because KNConfigManager is already deleted here + // writeConfig(); +} + + +void KNHeaderView::readConfig() +{ + if ( !mInitDone ) { + KConfig *conf = knGlobals.config(); + conf->setGroup( "HeaderView" ); + mSortByThreadChangeDate = conf->readBoolEntry( "sortByThreadChangeDate", false ); + restoreLayout( conf, "HeaderView" ); + mInitDone = true; + } + + KNConfig::ReadNewsGeneral *rngConf = knGlobals.configManager()->readNewsGeneral(); + toggleColumn( KPaintInfo::COL_SIZE, rngConf->showLines() ); + if ( !mShowingFolder ) // score column is always hidden when showing a folder + toggleColumn( KPaintInfo::COL_SCORE, rngConf->showScore() ); + + mDateFormatter.setCustomFormat( rngConf->dateCustomFormat() ); + mDateFormatter.setFormat( rngConf->dateFormat() ); + + KNConfig::Appearance *app = knGlobals.configManager()->appearance(); + QPalette p = palette(); + p.setColor( QColorGroup::Base, app->backgroundColor() ); + p.setColor( QColorGroup::Text, app->textColor() ); + setPalette( p ); + setAlternateBackground( app->alternateBackgroundColor() ); + setFont( app->articleListFont() ); +} + + +void KNHeaderView::writeConfig() +{ + KConfig *conf = knGlobals.config(); + conf->setGroup( "HeaderView" ); + conf->writeEntry( "sortByThreadChangeDate", mSortByThreadChangeDate ); + saveLayout( conf, "HeaderView" ); + + KNConfig::ReadNewsGeneral *rngConf = knGlobals.configManager()->readNewsGeneral(); + rngConf->setShowLines( mPaintInfo.showSize ); + if ( !mShowingFolder ) // score column is always hidden when showing a folder + rngConf->setShowScore( mPaintInfo.showScore ); +} + + +void KNHeaderView::setActive( QListViewItem *i ) +{ + KNHdrViewItem *item = static_cast<KNHdrViewItem*>( i ); + + if ( !item || item->isActive() ) + return; + + if ( mActiveItem ) { + mActiveItem->setActive( false ); + repaintItem( mActiveItem ); + mActiveItem = 0; + } + + item->setActive( true ); + setSelected( item, true ); + setCurrentItem( i ); + ensureItemVisibleWithMargin( i ); + mActiveItem = item; + emit( itemSelected(item) ); +} + + +void KNHeaderView::clear() +{ + mActiveItem = 0; + QListView::clear(); +} + + +void KNHeaderView::ensureItemVisibleWithMargin( const QListViewItem *i ) +{ + if ( !i ) + return; + + QListViewItem *parent = i->parent(); + while ( parent ) { + if ( !parent->isOpen() ) + parent->setOpen( true ); + parent = parent->parent(); + } + + mDelayedCenter = -1; + int y = itemPos( i ); + int h = i->height(); + + if ( knGlobals.configManager()->readNewsGeneral()->smartScrolling() && + ((y + h + 5) >= (contentsY() + visibleHeight()) || + (y - 5 < contentsY())) ) + { + ensureVisible( contentsX(), y + h/2, 0, h/2 ); + mDelayedCenter = y + h/2; + QTimer::singleShot( 300, this, SLOT(slotCenterDelayed()) ); + } else { + ensureVisible( contentsX(), y + h/2, 0, h/2 ); + } +} + + +void KNHeaderView::slotCenterDelayed() +{ + if ( mDelayedCenter != -1 ) + ensureVisible( contentsX(), mDelayedCenter, 0, visibleHeight() / 2 ); +} + + +void KNHeaderView::setSorting( int column, bool ascending ) +{ + if ( column == mSortCol ) { + mSortAsc = ascending; + if ( mInitDone && column == mPaintInfo.dateCol && ascending ) + mSortByThreadChangeDate = !mSortByThreadChangeDate; + } else { + mSortCol = column; + emit sortingChanged( column ); + } + + KListView::setSorting( column, ascending ); + + if ( currentItem() ) + ensureItemVisible( currentItem() ); + + if ( mSortByThreadChangeDate ) + setColumnText( mPaintInfo.dateCol , i18n("Date (thread changed)") ); + else + setColumnText( mPaintInfo.dateCol, i18n("Date") ); +} + + +void KNHeaderView::nextArticle() +{ + KNHdrViewItem *it = static_cast<KNHdrViewItem*>( currentItem() ); + + if (it) { + if (it->isActive()) { // take current article, if not selected + if (it->isExpandable()) + it->setOpen(true); + it = static_cast<KNHdrViewItem*>(it->itemBelow()); + } + } else + it = static_cast<KNHdrViewItem*>( firstChild() ); + + if(it) { + clearSelection(); + setActive( it ); + setSelectionAnchor( currentItem() ); + } +} + + +void KNHeaderView::prevArticle() +{ + KNHdrViewItem *it = static_cast<KNHdrViewItem*>( currentItem() ); + + if (it && it->isActive()) { // take current article, if not selected + if (it) + it = static_cast<KNHdrViewItem*>(it->itemAbove()); + else + it = static_cast<KNHdrViewItem*>( firstChild() ); + } + + if (it) { + clearSelection(); + setActive( it ); + setSelectionAnchor( currentItem() ); + } +} + + +void KNHeaderView::incCurrentArticle() +{ + QListViewItem *lvi = currentItem(); + if ( lvi && lvi->isExpandable() ) + lvi->setOpen( true ); + if ( lvi && lvi->itemBelow() ) { + setCurrentItem( lvi->itemBelow() ); + ensureItemVisible( currentItem() ); + setFocus(); + } +} + +void KNHeaderView::decCurrentArticle() +{ + QListViewItem *lvi = currentItem(); + if ( lvi && lvi->itemAbove() ) { + if ( lvi->itemAbove()->isExpandable() ) + lvi->itemAbove()->setOpen( true ); + setCurrentItem( lvi->itemAbove() ); + ensureItemVisible( currentItem() ); + setFocus(); + } +} + + +void KNHeaderView::selectCurrentArticle() +{ + clearSelection(); + setActive( currentItem() ); +} + + +bool KNHeaderView::nextUnreadArticle() +{ + if ( !knGlobals.groupManager()->currentGroup() ) + return false; + + KNHdrViewItem *next, *current; + KNRemoteArticle *art; + + current = static_cast<KNHdrViewItem*>( currentItem() ); + if ( !current ) + current = static_cast<KNHdrViewItem*>( firstChild() ); + + if(!current) + return false; + + art = static_cast<KNRemoteArticle*>( current->art ); + + if ( !current->isActive() && !art->isRead() ) // take current article, if unread & not selected + next = current; + else { + if ( current->isExpandable() && art->hasUnreadFollowUps() && !current->isOpen() ) + setOpen( current, true ); + next = static_cast<KNHdrViewItem*>( current->itemBelow() ); + } + + while ( next ) { + art = static_cast<KNRemoteArticle*>( next->art ); + if ( !art->isRead() ) + break; + else { + if ( next->isExpandable() && art->hasUnreadFollowUps() && !next->isOpen() ) + setOpen( next, true ); + next = static_cast<KNHdrViewItem*>( next->itemBelow() ); + } + } + + if ( next ) { + clearSelection(); + setActive( next ); + setSelectionAnchor( currentItem() ); + return true; + } + return false; +} + + +bool KNHeaderView::nextUnreadThread() +{ + KNHdrViewItem *next, *current; + KNRemoteArticle *art; + + if ( !knGlobals.groupManager()->currentGroup() ) + return false; + + current = static_cast<KNHdrViewItem*>( currentItem() ); + if ( !current ) + current = static_cast<KNHdrViewItem*>( firstChild() ); + + if ( !current ) + return false; + + art = static_cast<KNRemoteArticle*>( current->art ); + + if ( current->depth() == 0 && !current->isActive() && (!art->isRead() || art->hasUnreadFollowUps()) ) + next = current; // take current article, if unread & not selected + else + next = static_cast<KNHdrViewItem*>( current->itemBelow() ); + + while ( next ) { + art = static_cast<KNRemoteArticle*>( next->art ); + + if ( next->depth() == 0 ) { + if ( !art->isRead() || art->hasUnreadFollowUps() ) + break; + } + next = static_cast<KNHdrViewItem*>( next->itemBelow() ); + } + + if ( next ) { + setCurrentItem( next ); + if ( art->isRead() ) + nextUnreadArticle(); + else { + clearSelection(); + setActive( next ); + setSelectionAnchor( currentItem() ); + } + return true; + } + return false; +} + + +void KNHeaderView::toggleColumn( int column, int mode ) +{ + bool *show = 0; + int *col = 0; + int width = 0; + + switch ( static_cast<KPaintInfo::ColumnIds>( column ) ) + { + case KPaintInfo::COL_SIZE: + show = &mPaintInfo.showSize; + col = &mPaintInfo.sizeCol; + width = 42; + break; + case KPaintInfo::COL_SCORE: + show = &mPaintInfo.showScore; + col = &mPaintInfo.scoreCol; + width = 42; + break; + default: + return; + } + + if ( mode == -1 ) + *show = !*show; + else + *show = mode; + + mPopup->setItemChecked( column, *show ); + + if (*show) { + header()->setResizeEnabled( true, *col ); + setColumnWidth( *col, width ); + } + else { + header()->setResizeEnabled( false, *col ); + header()->setStretchEnabled( false, *col ); + hideColumn( *col ); + } + + if ( mode == -1 ) // save config when toggled + writeConfig(); +} + + +void KNHeaderView::prepareForGroup() +{ + mShowingFolder = false; + header()->setLabel( mPaintInfo.senderCol, i18n("From") ); + KNConfig::ReadNewsGeneral *rngConf = knGlobals.configManager()->readNewsGeneral(); + toggleColumn( KPaintInfo::COL_SCORE, rngConf->showScore() ); +} + + +void KNHeaderView::prepareForFolder() +{ + mShowingFolder = true; + header()->setLabel( mPaintInfo.senderCol, i18n("Newsgroups / To") ); + toggleColumn( KPaintInfo::COL_SCORE, false ); // local folders have no score +} + + +bool KNHeaderView::event( QEvent *e ) +{ + // we don't want to have the alternate list background restored + // to the system defaults! + if (e->type() == QEvent::ApplicationPaletteChange) + return QListView::event(e); + else + return KListView::event(e); +} + +void KNHeaderView::contentsMousePressEvent( QMouseEvent *e ) +{ + if (!e) return; + + bool selectMode=(( e->state() & ShiftButton ) || ( e->state() & ControlButton )); + + QPoint vp = contentsToViewport(e->pos()); + QListViewItem *i = itemAt(vp); + + KListView::contentsMousePressEvent( e ); + + if ( i ) { + int decoLeft = header()->sectionPos( 0 ) + + treeStepSize() * ( (i->depth() - 1) + ( rootIsDecorated() ? 1 : 0) ); + int decoRight = kMin( decoLeft + treeStepSize() + itemMargin(), + header()->sectionPos( 0 ) + header()->sectionSize( 0 ) ); + bool rootDecoClicked = vp.x() > decoLeft && vp.x() < decoRight; + + if( !selectMode && i->isSelected() && !rootDecoClicked ) + setActive( i ); + } +} + + +void KNHeaderView::contentsMouseDoubleClickEvent( QMouseEvent *e ) +{ + if (!e) return; + + QListViewItem *i = itemAt( contentsToViewport(e->pos()) ); + if (i) { + emit doubleClick( i ); + return; + } + + KListView::contentsMouseDoubleClickEvent( e ); +} + + +void KNHeaderView::keyPressEvent(QKeyEvent *e) +{ + if (!e) return; + + QListViewItem *i = currentItem(); + + switch(e->key()) { + case Key_Space: + case Key_Backspace: + case Key_Delete: + e->ignore(); // don't eat them + break; + case Key_Enter: + case Key_Return: + setActive( i ); + break; + + default: + KListView::keyPressEvent (e); + } +} + + +QDragObject* KNHeaderView::dragObject() +{ + KNHdrViewItem *item = static_cast<KNHdrViewItem*>( itemAt(viewport()->mapFromGlobal(QCursor::pos())) ); + if (item) + return item->dragObject(); + else + return 0; +} + + +void KNHeaderView::slotSizeChanged( int section, int, int newSize ) +{ + viewport()->repaint( header()->sectionPos(section), 0, newSize, visibleHeight(), false); +} + + +bool KNHeaderView::eventFilter(QObject *o, QEvent *e) +{ + if ((e->type() == QEvent::KeyPress) && (static_cast<QKeyEvent*>(e)->key() == Key_Tab)) { + emit(focusChangeRequest(this)); + if (!hasFocus()) // focusChangeRequest was successful + return true; + } + + // right click on header + if ( e->type() == QEvent::MouseButtonPress && + static_cast<QMouseEvent*>(e)->button() == RightButton && + o->isA("QHeader") ) + { + mPopup->popup( static_cast<QMouseEvent*>(e)->globalPos() ); + return true; + } + + return KListView::eventFilter(o, e); +} + + +void KNHeaderView::focusInEvent(QFocusEvent *e) +{ + QListView::focusInEvent(e); + emit focusChanged(e); +} + + +void KNHeaderView::focusOutEvent(QFocusEvent *e) +{ + QListView::focusOutEvent(e); + emit focusChanged(e); +} + + +void KNHeaderView::resetCurrentTime() +{ + mDateFormatter.reset(); + QTimer::singleShot( 1000, this, SLOT(resetCurrentTime()) ); +} + + +//BEGIN: KNHeaderViewToolTip ================================================== + +KNHeaderViewToolTip::KNHeaderViewToolTip( KNHeaderView *parent ) : + QToolTip( parent->viewport() ), + listView( parent ) +{ +} + + +void KNHeaderViewToolTip::maybeTip( const QPoint &p ) +{ + const KNHdrViewItem *item = static_cast<KNHdrViewItem*>( listView->itemAt( p ) ); + if ( !item ) + return; + const int column = listView->header()->sectionAt( p.x() ); + if ( column == -1 ) + return; + + if ( !item->showToolTip( column ) ) + return; + + const QRect itemRect = listView->itemRect( item ); + if ( !itemRect.isValid() ) + return; + const QRect headerRect = listView->header()->sectionRect( column ); + if ( !headerRect.isValid() ) + return; + + tip( QRect( headerRect.left(), itemRect.top(), headerRect.width(), itemRect.height() ), + QStyleSheet::escape( item->text( column ) ) ); +} + +//END: KNHeaderViewToolTip ==================================================== + +#include "headerview.moc" diff --git a/knode/headerview.h b/knode/headerview.h new file mode 100644 index 000000000..411258d2f --- /dev/null +++ b/knode/headerview.h @@ -0,0 +1,120 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNHEADERVIEW_H +#define KNHEADERVIEW_H + +#include <qtooltip.h> + +#include <klistview.h> +#include <kfoldertree.h> +#include <kmime_util.h> + +class KPopupMenu; +class KNHdrViewItem; + +class KNHeaderView : public KListView { + + Q_OBJECT + + friend class KNHdrViewItem; + + public: + KNHeaderView( QWidget *parent, const char *name = 0 ); + ~KNHeaderView(); + + void setActive( QListViewItem *item ); + void clear(); + + void ensureItemVisibleWithMargin( const QListViewItem *i ); + + virtual void setSorting( int column, bool ascending = true ); + bool sortByThreadChangeDate() const { return mSortByThreadChangeDate; } + void setSortByThreadChangeDate( bool b ) { mSortByThreadChangeDate = b; } + + bool nextUnreadArticle(); + bool nextUnreadThread(); + + void readConfig(); + void writeConfig(); + + const KPaintInfo* paintInfo() const { return &mPaintInfo; } + + signals: + void itemSelected( QListViewItem* ); + void doubleClick( QListViewItem* ); + void sortingChanged( int ); + void focusChanged( QFocusEvent* ); + void focusChangeRequest( QWidget* ); + + public slots: + void nextArticle(); + void prevArticle(); + void incCurrentArticle(); + void decCurrentArticle(); + void selectCurrentArticle(); + + void toggleColumn( int column, int mode = -1 ); + void prepareForGroup(); + void prepareForFolder(); + + protected: + void activeRemoved() { mActiveItem = 0; } + /** + * Reimplemented to avoid that KListview reloads the alternate + * background on palette changes. + */ + virtual bool event( QEvent *e ); + void contentsMousePressEvent( QMouseEvent *e ); + void contentsMouseDoubleClickEvent( QMouseEvent *e ); + void keyPressEvent( QKeyEvent *e ); + bool eventFilter( QObject *, QEvent * ); + void focusInEvent( QFocusEvent *e ); + void focusOutEvent( QFocusEvent *e ); + virtual QDragObject* dragObject(); + + private: + int mSortCol; + bool mSortAsc; + bool mSortByThreadChangeDate; + int mDelayedCenter; + KNHdrViewItem *mActiveItem; + KPaintInfo mPaintInfo; + KMime::DateFormatter mDateFormatter; + KPopupMenu *mPopup; + bool mShowingFolder; + bool mInitDone; + + private slots: + void slotCenterDelayed(); + void slotSizeChanged( int, int, int ); + void resetCurrentTime(); + +}; + + +class KNHeaderViewToolTip : public QToolTip { + + public: + KNHeaderViewToolTip( KNHeaderView *parent ); + + protected: + void maybeTip( const QPoint &p ); + + private: + KNHeaderView *listView; + +}; + +#endif diff --git a/knode/hi128-app-knode.png b/knode/hi128-app-knode.png Binary files differnew file mode 100644 index 000000000..2a9aea36e --- /dev/null +++ b/knode/hi128-app-knode.png diff --git a/knode/hi128-app-knode2.png b/knode/hi128-app-knode2.png Binary files differnew file mode 100644 index 000000000..883a49617 --- /dev/null +++ b/knode/hi128-app-knode2.png diff --git a/knode/hi16-app-knode.png b/knode/hi16-app-knode.png Binary files differnew file mode 100644 index 000000000..d99813134 --- /dev/null +++ b/knode/hi16-app-knode.png diff --git a/knode/hi16-app-knode2.png b/knode/hi16-app-knode2.png Binary files differnew file mode 100644 index 000000000..75c186327 --- /dev/null +++ b/knode/hi16-app-knode2.png diff --git a/knode/hi32-app-knode.png b/knode/hi32-app-knode.png Binary files differnew file mode 100644 index 000000000..1c3e88e9f --- /dev/null +++ b/knode/hi32-app-knode.png diff --git a/knode/hi32-app-knode2.png b/knode/hi32-app-knode2.png Binary files differnew file mode 100644 index 000000000..74a8af16e --- /dev/null +++ b/knode/hi32-app-knode2.png diff --git a/knode/hi48-app-knode.png b/knode/hi48-app-knode.png Binary files differnew file mode 100644 index 000000000..521e1ee33 --- /dev/null +++ b/knode/hi48-app-knode.png diff --git a/knode/hi48-app-knode2.png b/knode/hi48-app-knode2.png Binary files differnew file mode 100644 index 000000000..7b2c5d59f --- /dev/null +++ b/knode/hi48-app-knode2.png diff --git a/knode/hi64-app-knode.png b/knode/hi64-app-knode.png Binary files differnew file mode 100644 index 000000000..d910f150e --- /dev/null +++ b/knode/hi64-app-knode.png diff --git a/knode/hi64-app-knode2.png b/knode/hi64-app-knode2.png Binary files differnew file mode 100644 index 000000000..9ff8b9ec6 --- /dev/null +++ b/knode/hi64-app-knode2.png diff --git a/knode/knaccountmanager.cpp b/knode/knaccountmanager.cpp new file mode 100644 index 000000000..be43e56b0 --- /dev/null +++ b/knode/knaccountmanager.cpp @@ -0,0 +1,305 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <stdlib.h> + +#include <qdir.h> + +#include <kdebug.h> +#include <ksimpleconfig.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kwallet.h> + +#include "kngroupmanager.h" +#include "knnntpaccount.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "utilities.h" +#include "knaccountmanager.h" +#include "knfoldermanager.h" + +KWallet::Wallet* KNAccountManager::mWallet = 0; +bool KNAccountManager::mWalletOpenFailed = false; + +KNAccountManager::KNAccountManager(KNGroupManager *gm, QObject * parent, const char * name) + : QObject(parent, name), gManager(gm), c_urrentAccount(0), + mAsyncOpening( false ) +{ + s_mtp = new KNServerInfo(); + s_mtp->setType(KNServerInfo::STsmtp); + s_mtp->setId(0); + KConfig *conf = knGlobals.config(); + conf->setGroup("MAILSERVER"); + s_mtp->readConf(conf); + + loadAccounts(); +} + + +KNAccountManager::~KNAccountManager() +{ + QValueList<KNNntpAccount*>::Iterator it; + for ( it = mAccounts.begin(); it != mAccounts.end(); ++it ) + delete (*it); + mAccounts.clear(); + delete s_mtp; + delete mWallet; + mWallet = 0; +} + + +void KNAccountManager::prepareShutdown() +{ + QValueList<KNNntpAccount*>::Iterator it; + for ( it = mAccounts.begin(); it != mAccounts.end(); ++it ) + (*it)->saveInfo(); +} + + +void KNAccountManager::loadAccounts() +{ + QString dir(locateLocal("data","knode/")); + if (dir.isNull()) { + KNHelper::displayInternalFileError(); + return; + } + QDir d(dir); + KNNntpAccount *a; + QStringList entries(d.entryList("nntp.*", QDir::Dirs)); + + QStringList::Iterator it; + for(it = entries.begin(); it != entries.end(); ++it) { + a = new KNNntpAccount(); + if (a->readInfo(dir+(*it) + "/info")) { + mAccounts.append(a); + gManager->loadGroups(a); + emit accountAdded(a); + } else { + delete a; + kdError(5003) << "Unable to load account " << (*it) << "!" << endl; + } + } +} + + +KNNntpAccount* KNAccountManager::account( int id ) +{ + if ( id <= 0 ) + return 0; + QValueList<KNNntpAccount*>::ConstIterator it; + for ( it = mAccounts.begin(); it != mAccounts.end(); ++it ) + if ( (*it)->id() == id ) + return *it; + return 0; +} + + +void KNAccountManager::setCurrentAccount(KNNntpAccount *a) +{ + c_urrentAccount = a; +} + + +// a is new account allocated and configured by the caller +bool KNAccountManager::newAccount(KNNntpAccount *a) +{ + // find a unused id for the new account... + QString dir(locateLocal("data","knode/")); + if (dir.isNull()) { + delete a; + KNHelper::displayInternalFileError(); + return false; + } + QDir d(dir); + QStringList entries(d.entryList("nntp.*", QDir::Dirs)); + + int id = 1; + while (entries.findIndex(QString("nntp.%1").arg(id))!=-1) + ++id; + + a->setId(id); + + dir = locateLocal("data",QString("knode/nntp.%1/").arg(a->id())); + if (!dir.isNull()) { + mAccounts.append(a); + emit(accountAdded(a)); + return true; + } else { + delete a; + KMessageBox::error(knGlobals.topWidget, i18n("Cannot create a folder for this account.")); + return false; + } +} + + +// a==0: remove current account +bool KNAccountManager::removeAccount(KNNntpAccount *a) +{ + if(!a) a=c_urrentAccount; + if(!a) return false; + + QValueList<KNGroup*> lst; + if(knGlobals.folderManager()->unsentForAccount(a->id()) > 0) { + KMessageBox::sorry(knGlobals.topWidget, i18n("This account cannot be deleted since there are some unsent messages for it.")); + } + else if(KMessageBox::warningContinueCancel(knGlobals.topWidget, i18n("Do you really want to delete this account?"),"",KGuiItem(i18n("&Delete"),"editdelete"))==KMessageBox::Continue) { + lst = gManager->groupsOfAccount( a ); + for ( QValueList<KNGroup*>::Iterator it = lst.begin(); it != lst.end(); ++it ) { + if ( (*it)->isLocked() ) { + KMessageBox::sorry(knGlobals.topWidget, i18n("At least one group of this account is currently in use.\nThe account cannot be deleted at the moment.")); + return false; + } + } + for ( QValueList<KNGroup*>::Iterator it = lst.begin(); it != lst.end(); ++it ) + gManager->unsubscribeGroup( (*it) ); + + QDir dir(a->path()); + if (dir.exists()) { + const QFileInfoList *list = dir.entryInfoList(); // get list of matching files and delete all + if (list) { + QFileInfoListIterator it( *list ); + while (it.current()) { + dir.remove(it.current()->fileName()); + ++it; + } + } + dir.cdUp(); // directory should now be empty, deleting it + dir.rmdir(QString("nntp.%1/").arg(a->id())); + } + + if(c_urrentAccount==a) setCurrentAccount(0); + + emit(accountRemoved(a)); + mAccounts.remove( a ); // finally delete a + return true; + } + + return false; +} + + +void KNAccountManager::editProperties(KNNntpAccount *a) +{ + if(!a) a=c_urrentAccount; + if(!a) return; + + a->editProperties(knGlobals.topWidget); + emit(accountModified(a)); +} + + +void KNAccountManager::accountRenamed(KNNntpAccount *a) +{ + if(!a) a=c_urrentAccount; + if(!a) return; + + emit(accountModified(a)); +} + + +KNNntpAccount* KNAccountManager::first() const +{ + if ( mAccounts.isEmpty() ) + return 0; + return mAccounts.first(); +} + + +void KNAccountManager::loadPasswordsAsync() +{ + if ( !mWallet && !mWalletOpenFailed ) { + if ( knGlobals.top ) + mWallet = Wallet::openWallet( Wallet::NetworkWallet(), + knGlobals.topWidget->topLevelWidget()->winId(), + Wallet::Asynchronous ); + else + mWallet = Wallet::openWallet( Wallet::NetworkWallet(), 0, Wallet::Asynchronous ); + if ( mWallet ) { + connect( mWallet, SIGNAL(walletOpened(bool)), SLOT(slotWalletOpened(bool)) ); + mAsyncOpening = true; + } + else { + mWalletOpenFailed = true; + loadPasswords(); + } + return; + } + if ( mWallet && !mAsyncOpening ) + loadPasswords(); +} + + +void KNAccountManager::loadPasswords() +{ + s_mtp->readPassword(); + QValueList<KNNntpAccount*>::Iterator it; + for ( it = mAccounts.begin(); it != mAccounts.end(); ++it ) + (*it)->readPassword(); + emit passwordsChanged(); +} + + +KWallet::Wallet* KNAccountManager::wallet() +{ + if ( mWallet && mWallet->isOpen() ) + return mWallet; + + if ( !Wallet::isEnabled() || mWalletOpenFailed ) + return 0; + + delete mWallet; + if ( knGlobals.top ) + mWallet = Wallet::openWallet( Wallet::NetworkWallet(), + knGlobals.topWidget->topLevelWidget()->winId() ); + else + mWallet = Wallet::openWallet( Wallet::NetworkWallet() ); + + if ( !mWallet ) { + mWalletOpenFailed = true; + return 0; + } + + prepareWallet(); + return mWallet; +} + + +void KNAccountManager::prepareWallet() +{ + if ( !mWallet ) + return; + if ( !mWallet->hasFolder("knode") ) + mWallet->createFolder( "knode" ); + mWallet->setFolder( "knode" ); +} + + +void KNAccountManager::slotWalletOpened( bool success ) +{ + mAsyncOpening = false; + if ( !success ) { + mWalletOpenFailed = true; + delete mWallet; + mWallet = 0; + } else { + prepareWallet(); + } + loadPasswords(); +} + +//-------------------------------- + +#include "knaccountmanager.moc" diff --git a/knode/knaccountmanager.h b/knode/knaccountmanager.h new file mode 100644 index 000000000..5b6cd4ef9 --- /dev/null +++ b/knode/knaccountmanager.h @@ -0,0 +1,93 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNACCOUNTMANAGER_H +#define KNACCOUNTMANAGER_H + +#include <qglobal.h> +#include <qvaluelist.h> + +namespace KWallet { + class Wallet; +} + +class KNGroupManager; +class KNNntpAccount; +class KNServerInfo; + + +class KNAccountManager : public QObject +{ + Q_OBJECT + + public: + KNAccountManager(KNGroupManager *gm, QObject * parent=0, const char * name=0); + ~KNAccountManager(); + + void prepareShutdown(); + + void setCurrentAccount(KNNntpAccount *a); + + bool newAccount(KNNntpAccount *a); // a is new account allocated and configured by the caller + bool removeAccount(KNNntpAccount *a=0); // a==0: remove current account + void editProperties(KNNntpAccount *a=0); + void accountRenamed(KNNntpAccount *a=0); + + bool hasCurrentAccount() const { return (c_urrentAccount!=0); } + KNNntpAccount* currentAccount() const { return c_urrentAccount; } + KNServerInfo* smtp() const { return s_mtp; } + /** Returns the account with the given id. */ + KNNntpAccount* account( int id ); + QValueList<KNNntpAccount*>::Iterator begin() { return mAccounts.begin(); } + QValueList<KNNntpAccount*>::Iterator end() { return mAccounts.end(); } + /** Returns the first account (used as fallback sometimes). */ + KNNntpAccount* first() const; + + /** Loads the passwords of all accounts, allows on-demand wallet opening */ + void loadPasswords(); + /** Loads passwords of all accounts asynchronous */ + void loadPasswordsAsync(); + + /** Returns a pointer to an open wallet if available, 0 otherwise */ + static KWallet::Wallet* wallet(); + + protected: + void loadAccounts(); + KNGroupManager *gManager; + KNNntpAccount *c_urrentAccount; + KNServerInfo *s_mtp; + + signals: + void accountAdded(KNNntpAccount *a); + void accountRemoved(KNNntpAccount *a); // don't do anything with a, it will be deleted soon + void accountModified(KNNntpAccount *a); + /** Emitted if passwords have been loaded from the wallet */ + void passwordsChanged(); + + private slots: + void slotWalletOpened( bool success ); + + private: + /** set/create wallet-folder */ + static void prepareWallet(); + + private: + QValueList<KNNntpAccount*> mAccounts; + static KWallet::Wallet *mWallet; + static bool mWalletOpenFailed; + bool mAsyncOpening; + +}; + +#endif diff --git a/knode/knapplication.cpp b/knode/knapplication.cpp new file mode 100644 index 000000000..2bbcf0494 --- /dev/null +++ b/knode/knapplication.cpp @@ -0,0 +1,88 @@ +/* + knapplication.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <kwin.h> +#include <kdebug.h> +#include <kcmdlineargs.h> +#include <kconfig.h> + +#include "knode.h" +#include "knapplication.h" +#include "knconvert.h" +#include "knglobals.h" +#include "knmainwidget.h" +#include "knapplication.moc" + + +int KNApplication::newInstance() +{ + kdDebug(5003) << "KNApplication::newInstance()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("GENERAL"); + QString ver=conf->readEntry("Version"); + + if(!ver.isEmpty() && ver!=KNODE_VERSION) { //new version installed + if(KNConvert::needToConvert(ver)) { //we need to convert + kdDebug(5003) << "KNApplication::newInstance() : conversion needed" << endl; + KNConvert *convDlg=new KNConvert(ver); + if(!convDlg->exec()) { //reject() + if(convDlg->conversionDone()) //conversion has already happened but the user has canceled afterwards + conf->writeEntry("Version", KNODE_VERSION); + exit(0); + return(0); + } else //conversion done + conf->writeEntry("Version", KNODE_VERSION); + delete convDlg; + } + else //new version but no need to convert anything => just save the new version + conf->writeEntry("Version", KNODE_VERSION); + } + + if (!mainWidget()) { + if (isRestored()) { + int n = 1; + while (KNMainWindow::canBeRestored(n)){ + if (KNMainWindow::classNameOfToplevel(n)=="KNMainWindow") { + KNMainWindow* mainWin = new KNMainWindow; + mainWin->restore(n); + if ( n == 1 ) + setMainWidget( mainWin ); + break; + } + n++; + } + } + + if (!mainWidget()) { + KNMainWindow* mainWin = new KNMainWindow; + setMainWidget(mainWin); // this makes the external viewer windows close on shutdown... + mainWin->show(); + } + } + + // Handle window activation and startup notification + KUniqueApplication::newInstance(); + + // process URLs... + KNMainWidget *w = static_cast<KNMainWindow*>(mainWidget())->mainWidget(); + w->handleCommandLine(); + + kdDebug(5003) << "KNApplication::newInstance() done" << endl; + return 0; +} + + diff --git a/knode/knapplication.h b/knode/knapplication.h new file mode 100644 index 000000000..68c051e62 --- /dev/null +++ b/knode/knapplication.h @@ -0,0 +1,34 @@ +/* + knapplication.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNAPPLICATION_H +#define KNAPPLICATION_H + +#include <kuniqueapplication.h> + +class KNApplication : public KUniqueApplication +{ + Q_OBJECT + public: + KNApplication(): KUniqueApplication() { }; + + + /** Create new instance of KNode. Make the existing + main window active if KNode is already running */ + int newInstance(); + +}; +#endif diff --git a/knode/knarticle.cpp b/knode/knarticle.cpp new file mode 100644 index 000000000..d43e501ea --- /dev/null +++ b/knode/knarticle.cpp @@ -0,0 +1,576 @@ +/* + knmime.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + + +#include <klocale.h> +#include <kmdcodec.h> +#include <kmimemagic.h> + +#include "knhdrviewitem.h" +#include "kngroup.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "utilities.h" + +using namespace KMime; + + +KNArticle::KNArticle(KNArticleCollection *c) : i_d(-1), c_ol(c), i_tem(0) +{ +} + + +KNArticle::~KNArticle() +{ + delete i_tem; +} + + + +void KNArticle::setListItem(KNHdrViewItem *it) +{ + i_tem=it; + if(i_tem) i_tem->art=this; +} + + +void KNArticle::setLocked(bool b) +{ + f_lags.set(0, b); + if(c_ol) { // local articles may have c_ol==0 ! + if(b) + c_ol->articleLocked(); + else + c_ol->articleUnlocked(); + } +} + + +//========================================================================================= + + +KNRemoteArticle::KNRemoteArticle(KNGroup *g) + : KNArticle(g), a_rticleNumber(-1), i_dRef(-1), d_ref(0), t_hrLevel(0), s_core(0), + c_olor(knGlobals.configManager()->appearance()->unreadThreadColor()), + u_nreadFups(0), n_ewFups(0), s_ubThreadChangeDate(0) +{ + m_essageID.setParent(this); + f_rom.setParent(this); + r_eferences.setParent(this); + + if (g && g->useCharset()) + setDefaultCharset( g->defaultCharset() ); + else + setDefaultCharset( knGlobals.configManager()->postNewsTechnical()->charset() ); +} + + +KNRemoteArticle::~KNRemoteArticle() +{} + + +void KNRemoteArticle::parse() +{ + KNArticle::parse(); + QCString raw; + if( !(raw=rawHeader(m_essageID.type())).isEmpty() ) + m_essageID.from7BitString(raw); + + if( !(raw=rawHeader(f_rom.type())).isEmpty() ) + f_rom.from7BitString(raw); + + if( !(raw=rawHeader(r_eferences.type())).isEmpty() ) + r_eferences.from7BitString(raw); +} + + +void KNRemoteArticle::clear() +{ + m_essageID.clear(); + f_rom.clear(); + r_eferences.clear(); + KNArticle::clear(); +} + + +Headers::Base* KNRemoteArticle::getHeaderByType(const char *type) +{ + if(strcasecmp("Message-ID", type)==0) { + if(m_essageID.isEmpty()) return 0; + else return &m_essageID; + } + else if(strcasecmp("From", type)==0) { + if(f_rom.isEmpty()) return 0; + else return &f_rom; + } + else if(strcasecmp("References", type)==0) { + if(r_eferences.isEmpty()) return 0; + else return &r_eferences; + } + else + return KNArticle::getHeaderByType(type); +} + + +void KNRemoteArticle::setHeader(Headers::Base *h) +{ + bool del=true; + if(h->is("Message-ID")) + m_essageID.from7BitString(h->as7BitString(false)); + else if(h->is("From")) { + f_rom.setEmail( (static_cast<Headers::From*>(h))->email() ); + f_rom.setName( (static_cast<Headers::From*>(h))->name() ); + } + else if(h->is("References")) { + r_eferences.from7BitString(h->as7BitString(false)); + } + else { + del=false; + KNArticle::setHeader(h); + } + + if(del) delete h; +} + + +bool KNRemoteArticle::removeHeader(const char *type) +{ + if(strcasecmp("Message-ID", type)==0) + m_essageID.clear(); + else if(strcasecmp("From", type)==0) + f_rom.clear(); + else if(strcasecmp("References", type)==0) + r_eferences.clear(); + else + return KNArticle::removeHeader(type); + + return true; +} + + +void KNRemoteArticle::initListItem() +{ + if(!i_tem) return; + + if(f_rom.hasName()) + i_tem->setText(1, f_rom.name()); + else + i_tem->setText(1, QString(f_rom.email())); + + updateListItem(); +} + + +void KNRemoteArticle::updateListItem() +{ + if(!i_tem) return; + + KNConfig::Appearance *app=knGlobals.configManager()->appearance(); + + if(isRead()) { + if(hasContent()) + i_tem->setPixmap(0, app->icon(KNConfig::Appearance::greyBallChkd)); + else + i_tem->setPixmap(0, app->icon(KNConfig::Appearance::greyBall)); + } + else { + if(hasContent()) + i_tem->setPixmap(0,app->icon(KNConfig::Appearance::redBallChkd)); + else + i_tem->setPixmap(0, app->icon(KNConfig::Appearance::redBall)); + } + + if(hasNewFollowUps()) + i_tem->setPixmap(1, app->icon(KNConfig::Appearance::newFups)); + else + i_tem->setPixmap(1, app->icon(KNConfig::Appearance::null)); + + if(isWatched()) + i_tem->setPixmap(2, app->icon(KNConfig::Appearance::eyes)); + else { + if(isIgnored()) + i_tem->setPixmap(2, app->icon(KNConfig::Appearance::ignore)); + else + i_tem->setPixmap(2, app->icon(KNConfig::Appearance::null)); + } + + i_tem->setExpandable( (threadMode() && hasVisibleFollowUps()) ); + + i_tem->repaint(); //force repaint +} + + +void KNRemoteArticle::thread(KNRemoteArticle::List &l) +{ + KNRemoteArticle *tmp=0, *ref=this; + KNGroup *g=static_cast<KNGroup*>(c_ol); + int idRef=i_dRef, topID=-1; + + while(idRef!=0) { + ref=g->byId(idRef); + if(!ref) + return; // sh#t !! + idRef=ref->idRef(); + } + + topID=ref->id(); + l.append(ref); + + for(int i=0; i<g->length(); i++) { + tmp=g->at(i); + if(tmp->idRef()!=0) { + idRef=tmp->idRef(); + while(idRef!=0) { + ref=g->byId(idRef); + idRef=ref->idRef(); + } + if(ref->id()==topID) + l.append(tmp); + } + } +} + + +void KNRemoteArticle::setForceDefaultCS(bool b) +{ + if (!b) { // restore default + KNGroup *g=static_cast<KNGroup*>(c_ol); + if (g && g->useCharset()) + setDefaultCharset( g->defaultCharset() ); + else + setDefaultCharset( knGlobals.configManager()->postNewsTechnical()->charset() ); + } + KNArticle::setForceDefaultCS(b); + initListItem(); +} + + +void KNRemoteArticle::propagateThreadChangedDate() +{ + KNRemoteArticle *ref=this; + KNGroup *g=static_cast<KNGroup*>(c_ol); + int idRef=i_dRef; + + while (idRef!=0) { + ref=g->byId(idRef); + if(!ref) + return; // sh#t !! + idRef=ref->idRef(); + } + + if (date()->unixTime() > ref->date()->unixTime()) { + ref->setSubThreadChangeDate(date()->unixTime()); + } +} + + +//========================================================================================= + + +KNLocalArticle::KNLocalArticle(KNArticleCollection *c) + : KNArticle(c), s_Offset(0), e_Offset(0), s_erverId(-1) +{ + n_ewsgroups.setParent(this); + t_o.setParent(this); + setDefaultCharset( knGlobals.configManager()->postNewsTechnical()->charset() ); +} + + +KNLocalArticle::~KNLocalArticle() +{} + + +void KNLocalArticle::parse() +{ + KNArticle::parse(); + QCString raw; + + if( !(raw=rawHeader(n_ewsgroups.type())).isEmpty() ) + n_ewsgroups.from7BitString(raw); + + if( !(raw=rawHeader(t_o.type())).isEmpty() ) + t_o.from7BitString(raw); +} + + +void KNLocalArticle::clear() +{ + KNArticle::clear(); + n_ewsgroups.clear(); + t_o.clear(); +} + + +Headers::Base* KNLocalArticle::getHeaderByType(const char *type) +{ + if(strcasecmp("Newsgroups", type)==0) + return newsgroups(false); + else if(strcasecmp("To", type)==0) + return to(false); + else + return KNArticle::getHeaderByType(type); +} + + +void KNLocalArticle::setHeader(Headers::Base *h) +{ + bool del=true; + if(h->is("To")) + t_o.from7BitString(h->as7BitString(false)); + else if(h->is("Newsgroups")) + n_ewsgroups.from7BitString(h->as7BitString(false)); + else { + del=false; + KNArticle::setHeader(h); + } + + if(del) delete h; +} + + +bool KNLocalArticle::removeHeader(const char *type) +{ + if(strcasecmp("To", type)==0) + t_o.clear(); + else if(strcasecmp("Newsgroups", type)==0) + n_ewsgroups.clear(); + else + return KNArticle::removeHeader(type); + + return true; +} + + +void KNLocalArticle::updateListItem() +{ + if(!i_tem) + return; + + QString tmp; + int idx=0; + KNConfig::Appearance *app=knGlobals.configManager()->appearance(); + + if(isSavedRemoteArticle()) { + i_tem->setPixmap(0, app->icon(KNConfig::Appearance::savedRemote)); + if (!n_ewsgroups.isEmpty()) + tmp=n_ewsgroups.asUnicodeString(); + else + tmp=t_o.asUnicodeString(); + } + else { + + if(doPost()) { + tmp+=n_ewsgroups.asUnicodeString(); + if(canceled()) + i_tem->setPixmap(idx++, app->icon(KNConfig::Appearance::canceledPosting)); + else + i_tem->setPixmap(idx++, app->icon(KNConfig::Appearance::posting)); + } + + if(doMail()) { + i_tem->setPixmap(idx++, app->icon(KNConfig::Appearance::mail)); + if(doPost()) + tmp+=" / "; + tmp+=t_o.asUnicodeString(); + } + + } + + i_tem->setText(1, tmp); +} + + +void KNLocalArticle::setForceDefaultCS(bool b) +{ + if (!b) // restore default + setDefaultCharset( knGlobals.configManager()->postNewsTechnical()->charset() ); + KNArticle::setForceDefaultCS(b); + updateListItem(); +} + + +//========================================================================================= + + +KNAttachment::KNAttachment(Content *c) + : c_ontent(c), l_oadHelper(0), f_ile(0), i_sAttached(true) +{ + Headers::ContentType *t=c->contentType(); + Headers::CTEncoding *e=c->contentTransferEncoding(); + Headers::CDescription *d=c->contentDescription(false); + + n_ame=t->name(); + + if(d) + d_escription=d->asUnicodeString(); + + + setMimeType(t->mimeType()); + + if(e->cte()==Headers::CEuuenc) { + setCte( Headers::CEbase64 ); + updateContentInfo(); + } + else + e_ncoding.setCte( e->cte() ); + + + h_asChanged=false; // has been set to "true" in setMimeType() +} + + +KNAttachment::KNAttachment(KNLoadHelper *helper) + : c_ontent(0), l_oadHelper(helper), f_ile(helper->getFile()), i_sAttached(false), h_asChanged(true) +{ + setMimeType((KMimeMagic::self()->findFileType(f_ile->name()))->mimeType()); + n_ame=helper->getURL().fileName(); +} + + +KNAttachment::~KNAttachment() +{ + if(!i_sAttached && c_ontent) + delete c_ontent; + delete l_oadHelper; +} + + +void KNAttachment::setMimeType(const QString &s) +{ + m_imeType=s.latin1(); + h_asChanged=true; + + if(m_imeType.find("text/", 0, false)==-1) { + f_b64=true; + e_ncoding.setCte(Headers::CEbase64); + } + else { + f_b64=false; + if (knGlobals.configManager()->postNewsTechnical()->allow8BitBody()) + setCte(Headers::CE8Bit); + else + setCte(Headers::CEquPr); + } +} + + +QString KNAttachment::contentSize() const +{ + QString ret; + int s=0; + + if(c_ontent && c_ontent->hasContent()) + s=c_ontent->size(); + else { + if (f_ile) + s=f_ile->size(); + } + + if(s > 1023) { + s=s/1024; + ret.setNum(s); + ret+=" kB"; + } + else { + ret.setNum(s); + ret+=" Bytes"; + } + + return ret; +} + + +void KNAttachment::updateContentInfo() +{ + if(!h_asChanged || !c_ontent) + return; + + //Content-Type + Headers::ContentType *t=c_ontent->contentType(); + t->setMimeType(m_imeType); + t->setName(n_ame, "UTF-8"); + t->setCategory(Headers::CCmixedPart); + + //Content-Description + if(d_escription.isEmpty()) + c_ontent->removeHeader("Content-Description"); + else + c_ontent->contentDescription()->fromUnicodeString(d_escription, "UTF-8"); + + //Content-Disposition + Headers::CDisposition *d=c_ontent->contentDisposition(); + d->setDisposition(Headers::CDattachment); + d->setFilename(n_ame); + + //Content-Transfer-Encoding + if(i_sAttached) + c_ontent->changeEncoding(e_ncoding.cte()); + else + c_ontent->contentTransferEncoding()->setCte(e_ncoding.cte()); + + c_ontent->assemble(); + + h_asChanged=false; +} + + + +void KNAttachment::attach(Content *c) +{ + if(i_sAttached || !f_ile) + return; + + c_ontent=new Content(); + updateContentInfo(); + Headers::ContentType *type=c_ontent->contentType(); + Headers::CTEncoding *e=c_ontent->contentTransferEncoding(); + QByteArray data(f_ile->size()); + + int readBytes=f_ile->readBlock(data.data(), f_ile->size()); + + if (readBytes<(int)f_ile->size() && f_ile->status()!=IO_Ok) { + KNHelper::displayExternalFileError(); + delete c_ontent; + c_ontent=0; + } else { + if (e_ncoding.cte()==Headers::CEbase64 || !type->isText()) { //encode base64 + c_ontent->setBody( KCodecs::base64Encode(data, true) + '\n' ); + // c_ontent->b_ody += '\n'; + e->setCte(Headers::CEbase64); + e->setDecoded(false); + } else { + c_ontent->setBody( QCString(data.data(), data.size()+1) + '\n' ); + // c_ontent->b_ody += '\n'; + e->setDecoded(true); + } + } + + if(c_ontent) { + c->addContent(c_ontent); + i_sAttached=true; + } +} + + +void KNAttachment::detach(Content *c) +{ + if(i_sAttached) { + c->removeContent(c_ontent, false); + i_sAttached=false; + } +} + + diff --git a/knode/knarticle.h b/knode/knarticle.h new file mode 100644 index 000000000..9c2fc8979 --- /dev/null +++ b/knode/knarticle.h @@ -0,0 +1,337 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNARTICLE_H +#define KNARTICLE_H + +#include <qstringlist.h> +#include <qtextstream.h> +#include <qfile.h> +#include <qfont.h> +#include <qcolor.h> +#include <qasciidict.h> +#include <qvaluelist.h> + +#include <kmime_headers.h> +#include <kmime_newsarticle.h> +#include <boolflags.h> + +#include "knjobdata.h" + +//forward declarations +class KNLoadHelper; +class KNHdrViewItem; +class KNArticleCollection; + +/** This class encapsulates a generic article. It provides all the + usual headers of a RFC822-message. Further more it contains an + unique id and can store a pointer to a @ref QListViewItem. It is + used as a base class for all visible articles. */ + +class KNArticle : public KMime::NewsArticle, public KNJobItem { + + public: + typedef QValueList<KNArticle*> List; + + KNArticle(KNArticleCollection *c); + ~KNArticle(); + + //id + int id() const { return i_d; } + void setId(int i) { i_d=i; } + + //list item handling + KNHdrViewItem* listItem() const { return i_tem; } + void setListItem(KNHdrViewItem *i); + virtual void updateListItem() {} + + //network lock (reimplemented from KNJobItem) + bool isLocked() { return f_lags.get(0); } + void setLocked(bool b=true); + + //prevent that the article is unloaded automatically + bool isNotUnloadable() { return f_lags.get(1); } + void setNotUnloadable(bool b=true) { f_lags.set(1, b); } + + //article-collection + KNArticleCollection* collection() const { return c_ol; } + void setCollection(KNArticleCollection *c) { c_ol=c; } + bool isOrphant() const { return (i_d==-1); } + + protected: + int i_d; //unique in the given collection + KNArticleCollection *c_ol; + KNHdrViewItem *i_tem; + +}; // KNArticle + + +class KNGroup; + +/** KNRemoteArticle represents an article, whos body has to be + retrieved from a remote host or from the local cache. + All articles in a newsgroup are stored in instances + of this class. */ + +class KNRemoteArticle : public KNArticle { + + public: + typedef QValueList<KNRemoteArticle*> List; + + KNRemoteArticle(KNGroup *g); + ~KNRemoteArticle(); + + // type + articleType type() { return ATremote; } + + // content handling + virtual void parse(); + virtual void assemble() {} //assembling is disabled for remote articles + virtual void clear(); + + // header access + KMime::Headers::Base* getHeaderByType(const char *type); + void setHeader(KMime::Headers::Base *h); + bool removeHeader(const char *type); + KMime::Headers::MessageID* messageID(bool create=true) { if(!create && m_essageID.isEmpty()) return 0; return &m_essageID; } + KMime::Headers::From* from(bool create=true) { if(!create && f_rom.isEmpty()) return 0; return &f_rom; } + KMime::Headers::References* references(bool create=true) { if(!create && r_eferences.isEmpty()) return 0; return &r_eferences; } + + // article number + int articleNumber() const { return a_rticleNumber; } + void setArticleNumber(int number) { a_rticleNumber = number; } + + // status + bool isNew() { return f_lags.get(2); } + void setNew(bool b=true) { f_lags.set(2, b); } + bool getReadFlag() { return f_lags.get(3); } + bool isRead() { return f_lags.get(7) || f_lags.get(3); } // ignored articles == read + void setRead(bool b=true) { f_lags.set(3, b); } + bool isExpired() { return f_lags.get(4); } + void setExpired(bool b=true) { f_lags.set(4, b); } + bool isKept() { return f_lags.get(5); } + void setKept(bool b=true) { f_lags.set(5, b); } + bool hasChanged() { return f_lags.get(6); } + void setChanged(bool b=true) { f_lags.set(6, b); } + bool isIgnored() { return f_lags.get(7); } + void setIgnored(bool b=true) { f_lags.set(7, b); } + bool isWatched() { return f_lags.get(8); } + void setWatched(bool b=true) { f_lags.set(8, b); } + + // thread info + int idRef() { return i_dRef; } + void setIdRef(int i) { if (i != id()) + i_dRef=i; + else + i_dRef=0; } + KNRemoteArticle* displayedReference() { return d_ref; } + void setDisplayedReference(KNRemoteArticle *dr) { d_ref=dr; } + bool threadMode() { return f_lags.get(9); } + void setThreadMode(bool b=true) { f_lags.set(9, b); } + unsigned char threadingLevel() { return t_hrLevel; } + void setThreadingLevel(unsigned char l) { t_hrLevel=l; } + short score() { return s_core; } + void setScore(short s) { s_core=s; } + unsigned short newFollowUps() { return n_ewFups; } + bool hasNewFollowUps() { return (n_ewFups>0); } + void setNewFollowUps(unsigned short s) { n_ewFups=s; } + void incNewFollowUps(unsigned short s=1) { n_ewFups+=s; } + void decNewFollowUps(unsigned short s=1) { n_ewFups-=s; } + unsigned short unreadFollowUps() { return u_nreadFups; } + bool hasUnreadFollowUps() { return (u_nreadFups>0); } + void setUnreadFollowUps(unsigned short s) { u_nreadFups=s; } + void incUnreadFollowUps(unsigned short s=1) { u_nreadFups+=s; } + void decUnreadFollowUps(unsigned short s=1) { u_nreadFups-=s; } + void thread(List &f); + + //filtering + bool filterResult() { return f_lags.get(10); } + void setFilterResult(bool b=true) { f_lags.set(10, b); } + bool isFiltered() { return f_lags.get(11); } + void setFiltered(bool b=true) { f_lags.set(11, b); } + bool hasVisibleFollowUps() { return f_lags.get(12); } + void setVisibleFollowUps(bool b=true) { f_lags.set(12, b); } + + // list item handling + void initListItem(); + void updateListItem(); + + void setForceDefaultCS(bool b); + + QColor color() const { return c_olor; } + void setColor(const QColor& c) { c_olor = c; } + + time_t subThreadChangeDate() { return s_ubThreadChangeDate; } + void setSubThreadChangeDate(time_t date) { s_ubThreadChangeDate = date; } + // propagate the change date to the root article + void propagateThreadChangedDate(); + + protected: + // hardcoded headers + KMime::Headers::MessageID m_essageID; + KMime::Headers::From f_rom; + KMime::Headers::References r_eferences; + + int a_rticleNumber; + int i_dRef; // id of a reference-article (0 == none) + KNRemoteArticle *d_ref; // displayed reference-article (may differ from i_dRef) + unsigned char t_hrLevel; // quality of threading + short s_core; // guess what ;-) + QColor c_olor; // color for the header list + unsigned short u_nreadFups, // number of the article's unread follow-ups + n_ewFups; // number of the article's new follow-ups + time_t s_ubThreadChangeDate; // the last time the sub-thread of this article changed + // i.e. when the last article arrived... + +}; // KNRemoteArticle + + + +/* This class encapsulates an article, that is + stored locally in an MBOX-file. All own and + saved articles are represented by instances + of this class. */ + + +class KNLocalArticle : public KNArticle { + + public: + typedef QValueList<KNLocalArticle*> List; + + KNLocalArticle(KNArticleCollection *c=0); + ~KNLocalArticle(); + + //type + articleType type() { return ATlocal; } + + //content handling + void parse(); + void clear(); + + // header access + KMime::Headers::Base* getHeaderByType(const char *type); + void setHeader(KMime::Headers::Base *h); + bool removeHeader(const char *type); + KMime::Headers::Newsgroups* newsgroups(bool create=true) { if ( (!create && n_ewsgroups.isEmpty()) || + (!create && !isSavedRemoteArticle() && !doPost()) ) + return 0; + return &n_ewsgroups; } + KMime::Headers::To* to(bool create=true) { if ( (!create && t_o.isEmpty()) || + (!create && !isSavedRemoteArticle() && !doMail()) ) + return 0; + return &t_o; } + + //send article as mail + bool doMail() { return f_lags.get(2); } + void setDoMail(bool b=true) { f_lags.set(2, b); } + bool mailed() { return f_lags.get(3); } + void setMailed(bool b=true) { f_lags.set(3, b); } + + //post article to a newsgroup + bool doPost() { return f_lags.get(4); } + void setDoPost(bool b=true) { f_lags.set(4, b); } + bool posted() { return f_lags.get(5); } + void setPosted(bool b=true) { f_lags.set(5, b); } + bool canceled() { return f_lags.get(6); } + void setCanceled(bool b=true) { f_lags.set(6, b); } + + // status + bool pending() { return ( (doPost() && !posted()) || (doMail() && !mailed()) ); } + bool isSavedRemoteArticle() { return ( !doPost() && !doMail() && editDisabled() ); } + + //edit + bool editDisabled() { return f_lags.get(7); } + void setEditDisabled(bool b=true) { f_lags.set(7, b); } + + //search + bool filterResult() { return f_lags.get(8); } + void setFilterResult(bool b=true) { f_lags.set(8, b); } + + //MBOX infos + int startOffset() const { return s_Offset; } + void setStartOffset(int so) { s_Offset=so; } + int endOffset() const { return e_Offset; } + void setEndOffset(int eo) { e_Offset=eo; } + + //nntp-server id + int serverId() { if(!doPost()) return -1; else return s_erverId; } + void setServerId(int i) { s_erverId=i; } + + //list item handling + void updateListItem(); + + void setForceDefaultCS(bool b); + + protected: + //hardcoded headers + KMime::Headers::Newsgroups n_ewsgroups; + KMime::Headers::To t_o; + int s_Offset, //position in mbox-file : start + e_Offset, //position in mbox-file : end + s_erverId; //id of the nntp-server this article is posted to +}; + + +/* KNAttachment represents a file that is + or will be attached to an article. */ + +class KNAttachment { + + public: + KNAttachment(KMime::Content *c); + KNAttachment(KNLoadHelper *helper); + ~KNAttachment(); + + //name (used as a Content-Type parameter and as filename) + const QString& name() { return n_ame; } + void setName(const QString &s) { n_ame=s; h_asChanged=true; } + + //mime type + const QCString& mimeType() { return m_imeType; } + void setMimeType(const QString &s); + + //Content-Description + const QString& description() { return d_escription; } + void setDescription(const QString &s) { d_escription=s; h_asChanged=true; } + + //Encoding + int cte() { return e_ncoding.cte(); } + void setCte(int e) { e_ncoding.setCte( (KMime::Headers::contentEncoding)(e) ); + h_asChanged=true; } + bool isFixedBase64()const { return f_b64; } + QString encoding() { return e_ncoding.asUnicodeString(); } + + //content handling + KMime::Content* content()const { return c_ontent; } + QString contentSize() const; + bool isAttached() const { return i_sAttached; } + bool hasChanged() const { return h_asChanged; } + void updateContentInfo(); + void attach(KMime::Content *c); + void detach(KMime::Content *c); + + protected: + KMime::Content *c_ontent; + KNLoadHelper *l_oadHelper; + QFile *f_ile; + QCString m_imeType; + QString n_ame, + d_escription; + KMime::Headers::CTEncoding e_ncoding; + bool i_sAttached, + h_asChanged, + f_b64; +}; + +#endif //KNARTICLE_H diff --git a/knode/knarticlecollection.cpp b/knode/knarticlecollection.cpp new file mode 100644 index 000000000..bd8def75b --- /dev/null +++ b/knode/knarticlecollection.cpp @@ -0,0 +1,403 @@ +/* + knarticlecollection.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <stdlib.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kdebug.h> + +#include "knglobals.h" +#include "knarticlecollection.h" +#include "knarticle.h" + + +static const int sizeIncr=50; + +KNArticleVector::KNArticleVector(KNArticleVector *master, SortingType sorting) + : m_aster(master), l_en(0), s_ize(0), l_ist(0), s_ortType(sorting) +{ +} + + +KNArticleVector::~KNArticleVector() +{ + clear(); +} + + +bool KNArticleVector::resize(int s) +{ + KNArticle **bak=l_ist; + int nSize; + + if(s==0) + nSize=s_ize+sizeIncr; + else + nSize=((s/sizeIncr)+1)*sizeIncr; + + l_ist=(KNArticle**) realloc(l_ist, sizeof(KNArticle*)*nSize); + + if(!l_ist) { + KMessageBox::error(knGlobals.topWidget, i18n("Memory allocation failed.\nYou should close this application now\nto avoid data loss.")); + l_ist=bak; + return false; + } + else { + s_ize=nSize; + //kdDebug(5003) << "size : " << siz << "\n" << endl; + return true; + } + +} + + +bool KNArticleVector::append(KNArticle *a, bool autoSort) +{ + if( (l_en+1 > s_ize) && !resize()) // array too small => try to realloc + return false; // allocation failed !! + + l_ist[l_en++]=a; + + if(autoSort) sort(); + return true; +} + + +void KNArticleVector::remove(int pos, bool autoDel, bool autoCompact) +{ + + if(pos < 0 || pos > l_en-1) + return; + + if(autoDel) + delete l_ist[pos]; + + l_ist[pos]=0; + + if(autoCompact) + compact(); +} + + +void KNArticleVector::clear() +{ + if(l_ist){ + if(m_aster==0) + for(int i=0; i<l_en; i++) delete l_ist[i]; + free(l_ist); + } + + l_ist=0; l_en=0; s_ize=0; +} + + +void KNArticleVector::compact() +{ + int newLen, nullStart=0, nullCnt=0, ptrStart=0, ptrCnt=0; + + for(int idx=0; idx<l_en; idx++) { + if(l_ist[idx]==0) { + ptrStart=-1; + ptrCnt=-1; + nullStart=idx; + nullCnt=1; + for(int idx2=idx+1; idx2<l_en; idx2++) { + if(l_ist[idx2]==0) nullCnt++; + else { + ptrStart=idx2; + ptrCnt=1; + break; + } + } + if(ptrStart!=-1) { + for(int idx2=ptrStart+1; idx2<l_en; idx2++) { + if(l_ist[idx2]!=0) ptrCnt++; + else break; + } + memmove(&(l_ist[nullStart]), &(l_ist[ptrStart]), ptrCnt*sizeof(KNArticle*)); + for(int idx2=nullStart+ptrCnt; idx2<nullStart+ptrCnt+nullCnt; idx2++) + l_ist[idx2]=0; + idx=nullStart+ptrCnt-1; + } + else break; + } + } + newLen=0; + while(l_ist[newLen]!=0) newLen++; + l_en=newLen; +} + + +void KNArticleVector::syncWithMaster() +{ + if(!m_aster) return; + + if(resize(m_aster->l_en)) { + memcpy(l_ist, m_aster->l_ist, (m_aster->l_en) * sizeof(KNArticle*)); + l_en=m_aster->l_en; + sort(); + } +} + + +void KNArticleVector::sort() +{ + int (*cmp)(const void*,const void*) = 0; + + switch(s_ortType) { + case STid: + cmp=compareById; + break; + case STmsgId: + cmp=compareByMsgId; + break; + default: + cmp=0; + break; + } + + if(cmp) { + //compact(); // remove null-pointers + qsort(l_ist, l_en, sizeof(KNArticle*), cmp); + } +} + + +int KNArticleVector::compareById(const void *p1, const void *p2) +{ + KNArticle *a1, *a2; + int rc=0, id1, id2; + + a1=*((KNArticle**)(p1)); + a2=*((KNArticle**)(p2)); + + id1=a1->id(), + id2=a2->id(); + + if( id1 < id2 ) rc=-1; + else if( id1 > id2 ) rc=1; + + return rc; +} + + +int KNArticleVector::compareByMsgId(const void *p1, const void *p2) +{ + KNArticle *a1, *a2; + QCString mid1, mid2; + + a1=*(KNArticle**)(p1); + a2=*(KNArticle**)(p2); + + mid1=a1->messageID(true)->as7BitString(false); + mid2=a2->messageID(true)->as7BitString(false); + + if(mid1.isNull()) mid1=""; + if(mid2.isNull()) mid2=""; + + return strcmp( mid1.data(), mid2.data() ); +} + + +KNArticle* KNArticleVector::bsearch(int id) +{ + int idx=indexForId(id); + + return ( idx>-1 ? l_ist[idx] : 0 ); +} + + +KNArticle* KNArticleVector::bsearch(const QCString &id) +{ + int idx=indexForMsgId(id); + + return ( idx>-1 ? l_ist[idx] : 0 ); +} + + +int KNArticleVector::indexForId(int id) +{ + if(s_ortType!=STid) return -1; + + int start=0, end=l_en, mid=0, currentId=0; + bool found=false; + KNArticle *current=0; + + while(start!=end && !found) { + mid=(start+end)/2; + current=l_ist[mid]; + currentId=current->id(); + + if(currentId==id) + found=true; + else if(currentId < id) + start=mid+1; + else + end=mid; + } + + if(found) + return mid; + else { + #ifndef NDEBUG + qDebug("knode: KNArticleVector::indexForId() : id=%d not found", id); + #endif + return -1; + } +} + + +int KNArticleVector::indexForMsgId(const QCString &id) +{ + if(s_ortType!=STmsgId) return -1; + + int start=0, end=l_en, mid=0; + QCString currentMid=0; + bool found=false; + KNArticle *current=0; + int cnt=0; + + while(start!=end && !found) { + mid=(start+end)/2; + current=l_ist[mid]; + currentMid=current->messageID(true)->as7BitString(false); + + if(currentMid==id) + found=true; + else if( strcmp(currentMid.data(), id.data()) < 0 ) + start=mid+1; + else + end=mid; + + cnt++; + } + + if(found) { + /*#ifndef NDEBUG + qDebug("KNArticleVector::indexForMsgID() : msgID=%s found after %d compares", id.data(), cnt); + #endif*/ + return mid; + } + else { + /*#ifndef NDEBUG + qDebug("knode: KNArticleVector::indexForMsgID() : msgID=%s not found", id.data()); + #endif*/ + return -1; + } +} + + + + +// ------------------------------------------------------------------------------------------- + + + +KNArticleCollection::KNArticleCollection(KNCollection *p) + : KNCollection(p), l_astID(0), l_ockedArticles(0), n_otUnloadable(false) +{ + a_rticles.setSortMode(KNArticleVector::STid); + m_idIndex.setSortMode(KNArticleVector::STmsgId); + m_idIndex.setMaster(&a_rticles); +} + + + +KNArticleCollection::~KNArticleCollection() +{ + clear(); +} + + + +bool KNArticleCollection::resize(int s) +{ + return a_rticles.resize(s); +} + + + +bool KNArticleCollection::append(KNArticle *a, bool autoSync) +{ + if(a_rticles.append(a, false)) { + if(a->id()==-1) + a->setId(++l_astID); + if(autoSync) + syncSearchIndex(); + return true; + } + return false; + +} + + + +void KNArticleCollection::clear() +{ + a_rticles.clear(); + m_idIndex.clear(); + l_astID=0; +} + + + +void KNArticleCollection::compact() +{ + a_rticles.compact(); + m_idIndex.clear(); +} + + +KNArticle* KNArticleCollection::byId(int id) +{ + return a_rticles.bsearch(id); +} + + +KNArticle* KNArticleCollection::byMessageId(const QCString &mid) +{ + if(m_idIndex.isEmpty()) { + m_idIndex.syncWithMaster(); + kdDebug(5003) << "KNArticleCollection::byMessageId() : created index" << endl; + } + return m_idIndex.bsearch(mid); +} + + +void KNArticleCollection::setLastID() +{ + if(a_rticles.length()>0) + l_astID=a_rticles.at(a_rticles.length()-1)->id(); + else + l_astID=0; +} + + +void KNArticleCollection::syncSearchIndex() +{ + m_idIndex.syncWithMaster(); + + /*for(int i=0; i<m_idIndex.length(); i++) { + kdDebug(5003) << m_idIndex.at(i)->id() << " , " << m_idIndex.at(i)->messageID()->as7BitString(false) << endl; + } */ +} + + +void KNArticleCollection::clearSearchIndex() +{ + m_idIndex.clear(); +} diff --git a/knode/knarticlecollection.h b/knode/knarticlecollection.h new file mode 100644 index 000000000..3a92f2182 --- /dev/null +++ b/knode/knarticlecollection.h @@ -0,0 +1,120 @@ +/* + knarticlecollection.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNARTICLECOLLECTION_H +#define KNARTICLECOLLECTION_H + +#include "kncollection.h" + +class KNArticle; + + +class KNArticleVector { + + public: + enum SortingType { STid, STmsgId, STunsorted }; + + KNArticleVector(KNArticleVector *master=0, SortingType sorting=STunsorted); + virtual ~KNArticleVector(); + + // list-info + KNArticleVector* master() { return m_aster; } + void setMaster(KNArticleVector *m) { m_aster=m; } + bool isMaster() { return (m_aster==0); } + + bool isEmpty() { return ( (l_ist==0) || (l_en==0) ); } + int length() { return l_en; } + int size() { return s_ize; } + + // list-handling + bool resize(int s=0); + bool append(KNArticle *a, bool autoSort=false); + void remove(int pos, bool autoDel=false, bool autoCompact=false); + void clear(); + void compact(); + void syncWithMaster(); + + // sorting + SortingType sortMode() { return s_ortType; } + void setSortMode(SortingType s) { s_ortType=s; } + void sort(); + static int compareById(const void *a1, const void *a2); + static int compareByMsgId(const void *a1, const void *a2); + + // article access + KNArticle* at(int i) { return ( (i>=0 && i<l_en) ? l_ist[i] : 0 ); } + KNArticle* bsearch(int id); + KNArticle* bsearch(const QCString &id); + + int indexForId(int id); + int indexForMsgId(const QCString &id); + + protected: + KNArticleVector *m_aster; + int l_en, + s_ize; + KNArticle **l_ist; + SortingType s_ortType; +}; + + +class KNArticleCollection : public KNCollection { + + public: + KNArticleCollection(KNCollection *p=0); + ~KNArticleCollection(); + + // info + bool isEmpty() { return a_rticles.isEmpty(); } + bool isLoaded() { return (c_ount==0 || a_rticles.length()>0); } + int size() { return a_rticles.size(); } + int length() { return a_rticles.length(); } + + // cache behavior + bool isNotUnloadable() { return n_otUnloadable; } + void setNotUnloadable(bool b=true) { n_otUnloadable = b; } + + // locking + unsigned int lockedArticles() { return l_ockedArticles; } + void articleLocked() { l_ockedArticles++; } + void articleUnlocked() { l_ockedArticles--; } + + // list-handling + bool resize(int s=0); + bool append(KNArticle *a, bool autoSync=false); + void clear(); + void compact(); + void setLastID(); + + // article access + KNArticle* at(int i) { return a_rticles.at(i); } + KNArticle* byId(int id); + KNArticle* byMessageId(const QCString &mid); + + // search index + void syncSearchIndex(); + void clearSearchIndex(); + + protected: + int l_astID; + unsigned int l_ockedArticles; + bool n_otUnloadable; + KNArticleVector a_rticles; + KNArticleVector m_idIndex; +}; + + +#endif diff --git a/knode/knarticlefactory.cpp b/knode/knarticlefactory.cpp new file mode 100644 index 000000000..3c072887b --- /dev/null +++ b/knode/knarticlefactory.cpp @@ -0,0 +1,1111 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qlabel.h> +#include <qvbox.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kwin.h> +#include <kapplication.h> + +#include "knarticlefactory.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "kngroupmanager.h" +#include "knaccountmanager.h" +#include "knfoldermanager.h" +#include "knarticlemanager.h" +#include "knfolder.h" +#include "kncomposer.h" +#include "knnntpaccount.h" +#include "utilities.h" +#include "resource.h" + + +KNArticleFactory::KNArticleFactory(QObject *p, const char *n) + : QObject(p, n), s_endErrDlg(0) +{ +} + + +KNArticleFactory::~KNArticleFactory() +{ + for ( QValueList<KNComposer*>::Iterator it = mCompList.begin(); it != mCompList.end(); ++it ) + delete (*it); + delete s_endErrDlg; +} + + +void KNArticleFactory::createPosting(KNNntpAccount *a) +{ + if(!a) + return; + + QString sig; + KNLocalArticle *art=newArticle(a, sig, knGlobals.configManager()->postNewsTechnical()->charset()); + if(!art) + return; + + art->setServerId(a->id()); + art->setDoPost(true); + art->setDoMail(false); + + KNComposer *c = new KNComposer( art, QString::null, sig, QString::null, true ); + mCompList.append( c ); + connect(c, SIGNAL(composerDone(KNComposer*)), this, SLOT(slotComposerDone(KNComposer*))); + c->show(); +} + + +void KNArticleFactory::createPosting(KNGroup *g) +{ + if(!g) + return; + + QCString chset; + if (g->useCharset()) + chset = g->defaultCharset(); + else + chset = knGlobals.configManager()->postNewsTechnical()->charset(); + + QString sig; + KNLocalArticle *art=newArticle(g, sig, chset); + + if(!art) + return; + + art->setServerId(g->account()->id()); + art->setDoPost(true); + art->setDoMail(false); + art->newsgroups()->fromUnicodeString(g->groupname(), art->defaultCharset()); + + KNComposer *c=new KNComposer(art, QString::null, sig, QString::null, true); + mCompList.append( c ); + connect(c, SIGNAL(composerDone(KNComposer*)), this, SLOT(slotComposerDone(KNComposer*))); + c->show(); +} + + +void KNArticleFactory::createReply(KNRemoteArticle *a, QString selectedText, bool post, bool mail) +{ + if(!a) + return; + + KNGroup *g=static_cast<KNGroup*>(a->collection()); + + QCString chset; + if (knGlobals.configManager()->postNewsTechnical()->useOwnCharset()) { + if (g->useCharset()) + chset = g->defaultCharset(); + else + chset = knGlobals.configManager()->postNewsTechnical()->charset(); + } else + chset = knGlobals.configManager()->postNewsTechnical()->findComposerCharset(a->contentType()->charset()); + + //create new article + QString sig; + KNLocalArticle *art=newArticle(g, sig, chset, true, a); + if(!art) + return; + + art->setServerId(g->account()->id()); + art->setDoPost(post); + art->setDoMail(mail); + + //------------------------- <Headers> ---------------------------- + + //subject + QString subject=a->subject()->asUnicodeString(); + if(subject.left(3).upper()!="RE:") + subject.prepend("Re: "); + art->subject()->fromUnicodeString(subject, a->subject()->rfc2047Charset()); + + //newsgroups + KMime::Headers::FollowUpTo *fup2=a->followUpTo(false); + if(fup2 && !fup2->isEmpty()) { + if( ( fup2->as7BitString(false).upper()=="POSTER" ) ) { //Followup-To: poster + if( post && // user wanted to reply by public posting? + // ask the user if she wants to ignore this F'up-To: poster + ( KMessageBox::Yes != KMessageBox::questionYesNo(knGlobals.topWidget, + i18n("The author has requested a reply by email instead\nof a followup to the newsgroup. (Followup-To: poster)\nDo you want to reply in public anyway?"), QString::null, i18n("Reply Public"), i18n("Reply by Email")) )) + { + art->setDoPost(false); + art->setDoMail(true); + } + art->newsgroups()->from7BitString(a->newsgroups()->as7BitString(false)); + } + else + art->newsgroups()->from7BitString(fup2->as7BitString(false)); + } + else + art->newsgroups()->from7BitString(a->newsgroups()->as7BitString(false)); + + //To + KMime::Headers::ReplyTo *replyTo=a->replyTo(false); + KMime::Headers::AddressField address; + if(replyTo && !replyTo->isEmpty()) { + if(replyTo->hasName()) + address.setName(replyTo->name()); + if(replyTo->hasEmail()) + address.setEmail(replyTo->email().copy()); + } + else { + KMime::Headers::From *from=a->from(); + if(from->hasName()) + address.setName(from->name()); + if(from->hasEmail()) + address.setEmail(from->email().copy()); + } + art->to()->addAddress(address); + + //References + KMime::Headers::References *references=a->references(false); + QCString refs; + if (references) + refs=references->as7BitString(false); + else + refs = ""; + + art->references()->from7BitString(refs); + art->references()->append(a->messageID()->as7BitString(false)); + + //Mail-Copies-To + bool authorDislikesMailCopies=false; + bool authorWantsMailCopies=false; + KMime::Headers::MailCopiesTo *mailCopiesTo=a->mailCopiesTo(false); + + if(mailCopiesTo && !mailCopiesTo->isEmpty() && mailCopiesTo->isValid()) { + authorDislikesMailCopies = mailCopiesTo->neverCopy(); + authorWantsMailCopies = mailCopiesTo->alwaysCopy(); + if (authorWantsMailCopies) // warn the user + KMessageBox::information(knGlobals.topWidget,i18n("The author requested a mail copy of your reply. (Mail-Copies-To header)"), + QString::null,"mailCopiesToWarning"); + if (authorWantsMailCopies && mailCopiesTo->hasEmail()) { + address.setName(mailCopiesTo->name()); + address.setEmail(mailCopiesTo->email()); + art->to()->clear(); + art->to()->addAddress(address); + } + } + + //------------------------- </Headers> --------------------------- + + //--------------------------- <Body> ----------------------------- + + // attribution line + QString attribution=knGlobals.configManager()->postNewsComposer()->intro(); + QString name(a->from()->name()); + if (name.isEmpty()) + name = QString::fromLatin1(a->from()->email()); + attribution.replace(QRegExp("%NAME"),name); + attribution.replace(QRegExp("%EMAIL"),QString::fromLatin1(a->from()->email())); + attribution.replace(QRegExp("%DATE"),KGlobal::locale()->formatDateTime(a->date()->qdt(),false)); + attribution.replace(QRegExp("%MSID"),a->messageID()->asUnicodeString()); + attribution.replace(QRegExp("%GROUP"),g->groupname()); + attribution.replace(QRegExp("%L"),"\n"); + attribution+="\n\n"; + + QString quoted=attribution; + QString notRewraped=QString::null; + QStringList text; + QStringList::Iterator line; + bool incSig=knGlobals.configManager()->postNewsComposer()->includeSignature(); + + if (selectedText.isEmpty()) { + KMime::Content *tc = a->textContent(); + if(tc) + tc->decodedText(text, true, knGlobals.configManager()->readNewsViewer()->removeTrailingNewlines()); + } + else + text = QStringList::split('\n',selectedText,true); + + for(line=text.begin(); line!=text.end(); ++line) { + if(!incSig && (*line)=="-- ") + break; + + if ((*line)[0]=='>') + quoted+=">"+(*line)+"\n"; // second quote level without space + else + quoted+="> "+(*line)+"\n"; + } + + if(knGlobals.configManager()->postNewsComposer()->rewrap()) { //rewrap original article + + notRewraped=quoted; // store the original text in here, the user can request it in the composer + quoted=attribution; + + quoted += KNHelper::rewrapStringList(text, knGlobals.configManager()->postNewsComposer()->maxLineLength(), '>', !incSig, false); + } + + //-------------------------- </Body> ----------------------------- + + if (art->doMail() && knGlobals.configManager()->postNewsTechnical()->useExternalMailer()) { + sendMailExternal(address.asUnicodeString(), subject, quoted); + art->setDoMail(false); + if (!art->doPost()) { + delete art; + return; + } + } + + //open composer + KNComposer *c=new KNComposer(art, quoted, sig, notRewraped, true, authorDislikesMailCopies, authorWantsMailCopies); + mCompList.append( c ); + connect(c, SIGNAL(composerDone(KNComposer*)), this, SLOT(slotComposerDone(KNComposer*))); + c->show(); +} + + +void KNArticleFactory::createForward(KNArticle *a) +{ + if(!a) + return; + + KMime::Headers::ContentType *ct=a->contentType(); + QCString chset; + bool incAtt = ( !knGlobals.configManager()->postNewsTechnical()->useExternalMailer() && + ct->isMultipart() && ct->isSubtype("mixed") && + KMessageBox::Yes == KMessageBox::questionYesNo(knGlobals.topWidget, + i18n("This article contains attachments. Do you want them to be forwarded as well?"), QString::null, i18n("Forward"), i18n("Do Not Forward")) + ); + + if (knGlobals.configManager()->postNewsTechnical()->useOwnCharset()) + chset = knGlobals.configManager()->postNewsTechnical()->charset(); + else + chset = knGlobals.configManager()->postNewsTechnical()->findComposerCharset(a->contentType()->charset()); + + //create new article + QString sig; + KNLocalArticle *art=newArticle(knGlobals.groupManager()->currentGroup(), sig, chset); + if(!art) + return; + + art->setDoPost(false); + art->setDoMail(true); + + //------------------------- <Headers> ---------------------------- + + //subject + QString subject=("Fwd: "+a->subject()->asUnicodeString()); + art->subject()->fromUnicodeString(subject, a->subject()->rfc2047Charset()); + + //------------------------- </Headers> --------------------------- + + //--------------------------- <Body> ----------------------------- + + QString fwd = QString("\n--------------- %1\n\n").arg(i18n("Forwarded message (begin)")); + + fwd+=( i18n("Subject") + ": " + a->subject()->asUnicodeString() + "\n" ); + fwd+=( i18n("From") + ": " + a->from()->asUnicodeString() + "\n" ); + fwd+=( i18n("Date") + ": " + a->date()->asUnicodeString() + "\n" ); + fwd+=( i18n("Newsgroup") + ": " + a->newsgroups()->asUnicodeString() + "\n\n" ); + + KMime::Content *text=a->textContent(); + if(text) { + QStringList decodedLines; + text->decodedText( decodedLines, false, false ); + for(QStringList::Iterator it=decodedLines.begin(); it!=decodedLines.end(); ++it) + fwd += (*it) + "\n"; + } + + fwd += QString("\n--------------- %1\n").arg(i18n("Forwarded message (end)")); + + //--------------------------- </Body> ---------------------------- + + + //------------------------ <Attachments> ------------------------- + + if(incAtt) { + KMime::Content::List al; + + a->attachments(&al, false); + for(KMime::Content *c=al.first(); c; c=al.next()) { + art->addContent( new KMime::Content(c->head(), c->body()) ); + } + } + + //------------------------ </Attachments> ------------------------ + + + if (knGlobals.configManager()->postNewsTechnical()->useExternalMailer()) { + sendMailExternal(QString::null, subject, fwd); + delete art; + return; + } + + //open composer + KNComposer *c=new KNComposer(art, fwd, sig, QString::null, true); + mCompList.append( c ); + connect(c, SIGNAL(composerDone(KNComposer*)), this, SLOT(slotComposerDone(KNComposer*))); + c->show(); +} + + +void KNArticleFactory::createCancel(KNArticle *a) +{ + if(!cancelAllowed(a)) + return; + + if(KMessageBox::No==KMessageBox::questionYesNo(knGlobals.topWidget, + i18n("Do you really want to cancel this article?"), QString::null, i18n("Cancel Article"), KStdGuiItem::cancel())) + return; + + bool sendNow; + switch (KMessageBox::warningYesNoCancel(knGlobals.topWidget, i18n("Do you want to send the cancel\nmessage now or later?"), i18n("Question"),i18n("&Now"),i18n("&Later"))) { + case KMessageBox::Yes : sendNow = true; break; + case KMessageBox::No : sendNow = false; break; + default : return; + } + + KNGroup *grp; + KNNntpAccount *nntp=0; + + if(a->type()==KMime::Base::ATremote) + nntp=(static_cast<KNGroup*>(a->collection()))->account(); + else { + if(!nntp) + nntp=knGlobals.accountManager()->first(); + if(!nntp) { + KMessageBox::error(knGlobals.topWidget, i18n("You have no valid news accounts configured.")); + return; + } + KNLocalArticle *la=static_cast<KNLocalArticle*>(a); + la->setCanceled(true); + la->updateListItem(); + nntp=knGlobals.accountManager()->account(la->serverId()); + } + + grp=knGlobals.groupManager()->group(a->newsgroups()->firstGroup(), nntp); + + QString sig; + KNLocalArticle *art=newArticle(grp, sig, "us-ascii", false); + if(!art) + return; + + //init + art->setDoPost(true); + art->setDoMail(false); + + //server + art->setServerId(nntp->id()); + + //subject + KMime::Headers::MessageID *msgId=a->messageID(); + QCString tmp; + tmp="cancel of "+msgId->as7BitString(false); + art->subject()->from7BitString(tmp); + + //newsgroups + art->newsgroups()->from7BitString(a->newsgroups()->as7BitString(false)); + + //control + tmp="cancel "+msgId->as7BitString(false); + art->control()->from7BitString(tmp); + + //Lines + art->lines()->setNumberOfLines(1); + + //body + art->fromUnicodeString(QString::fromLatin1("cancel by original author\n")); + + //assemble + art->assemble(); + + //send + KNLocalArticle::List lst; + lst.append(art); + sendArticles( lst, sendNow ); +} + + +void KNArticleFactory::createSupersede(KNArticle *a) +{ + if (!a) + return; + + if(!cancelAllowed(a)) + return; + + if(KMessageBox::No==KMessageBox::questionYesNo(knGlobals.topWidget, + i18n("Do you really want to supersede this article?"), QString::null, i18n("Supersede"), KStdGuiItem::cancel())) + return; + + KNGroup *grp; + KNNntpAccount *nntp; + + if(a->type()==KMime::Base::ATremote) + nntp=(static_cast<KNGroup*>(a->collection()))->account(); + else { + KNLocalArticle *la=static_cast<KNLocalArticle*>(a); + la->setCanceled(true); + la->updateListItem(); + nntp=knGlobals.accountManager()->account(la->serverId()); + if(!nntp) + nntp=knGlobals.accountManager()->first(); + if(!nntp) { + KMessageBox::error(knGlobals.topWidget, i18n("You have no valid news accounts configured.")); + return; + } + } + + grp=knGlobals.groupManager()->group(a->newsgroups()->firstGroup(), nntp); + + //new article + QString sig; + KNLocalArticle *art=newArticle(grp, sig, knGlobals.configManager()->postNewsTechnical()->findComposerCharset(a->contentType()->charset())); + if(!art) + return; + + art->setDoPost(true); + art->setDoMail(false); + + //server + art->setServerId(nntp->id()); + + //subject + art->subject()->fromUnicodeString(a->subject()->asUnicodeString(), a->subject()->rfc2047Charset()); + + //newsgroups + art->newsgroups()->from7BitString(a->newsgroups()->as7BitString(false)); + + //followup-to + art->followUpTo()->from7BitString(a->followUpTo()->as7BitString(false)); + + //References + if ( !a->references()->isEmpty() ) + art->references()->from7BitString( a->references()->as7BitString(false) ); + + //Supersedes + art->supersedes()->from7BitString(a->messageID()->as7BitString(false)); + + //Body + QString text; + KMime::Content *textContent=a->textContent(); + if(textContent) + textContent->decodedText(text); + + //open composer + KNComposer *c=new KNComposer(art, text, sig); + mCompList.append( c ); + connect(c, SIGNAL(composerDone(KNComposer*)), this, SLOT(slotComposerDone(KNComposer*))); + c->show(); +} + + +void KNArticleFactory::createMail(KMime::Headers::AddressField *address) +{ + if (knGlobals.configManager()->postNewsTechnical()->useExternalMailer()) { + sendMailExternal(address->asUnicodeString()); + return; + } + + //create new article + QString sig; + KNLocalArticle *art=newArticle(knGlobals.groupManager()->currentGroup(), sig, knGlobals.configManager()->postNewsTechnical()->charset()); + if(!art) + return; + + art->setDoMail(true); + art->setDoPost(false); + art->to()->addAddress((*address)); + + //open composer + KNComposer *c=new KNComposer(art, QString::null, sig, QString::null, true); + mCompList.append( c ); + connect(c, SIGNAL(composerDone(KNComposer*)), this, SLOT(slotComposerDone(KNComposer*))); + c->show(); +} + + +void KNArticleFactory::sendMailExternal(const QString &address, const QString &subject, const QString &body) +{ + KURL mailtoURL; + QStringList queries; + QString query=QString::null; + mailtoURL.setProtocol("mailto"); + + if (!address.isEmpty()) + mailtoURL.setPath(address); + if (!subject.isEmpty()) + queries.append("subject="+KURL::encode_string(subject)); + if (!body.isEmpty()) + queries.append("body="+KURL::encode_string(body)); + + if (queries.count() > 0) { + query = "?"; + for ( QStringList::Iterator it = queries.begin(); it != queries.end(); ++it ) { + if (it != queries.begin()) + query.append("&"); + query.append((*it)); + } + } + + if (!query.isEmpty()) + mailtoURL.setQuery(query); + + kapp->invokeMailer(mailtoURL); +} + + +void KNArticleFactory::edit(KNLocalArticle *a) +{ + if(!a) + return; + + KNComposer *com=findComposer(a); + if(com) { + KWin::activateWindow(com->winId()); + return; + } + + if(a->editDisabled()) { + KMessageBox::sorry(knGlobals.topWidget, i18n("This article cannot be edited.")); + return; + } + + //find signature + KNConfig::Identity *id=knGlobals.configManager()->identity(); + + if(a->doPost()) { + KNNntpAccount *acc=knGlobals.accountManager()->account(a->serverId()); + if(acc) { + KMime::Headers::Newsgroups *grps=a->newsgroups(); + KNGroup *grp=knGlobals.groupManager()->group(grps->firstGroup(), acc); + if (grp && grp->identity()) + id=grp->identity(); + else if (acc->identity()) + id=acc->identity(); + } + } + + //load article body + if(!a->hasContent()) + knGlobals.articleManager()->loadArticle(a); + + //open composer + com=new KNComposer(a, QString::null, id->getSignature()); + if(id->useSigGenerator() && !id->getSigGeneratorStdErr().isEmpty()) + KMessageBox::information(knGlobals.topWidget, + i18n("<qt>The signature generator program produced the " + "following output:<br><br>%1</qt>") + .arg(id->getSigGeneratorStdErr())); + + mCompList.append( com ); + connect(com, SIGNAL(composerDone(KNComposer*)), this, SLOT(slotComposerDone(KNComposer*))); + com->show(); +} + + +void KNArticleFactory::sendArticles( KNLocalArticle::List &l, bool now ) +{ + KNJobData *job=0; + KNServerInfo *ser=0; + + KNLocalArticle::List unsent, sent; + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + if ( (*it)->pending() ) + unsent.append( (*it) ); + else + sent.append( (*it) ); + } + + if(!sent.isEmpty()) { + showSendErrorDialog(); + for ( KNLocalArticle::List::Iterator it = sent.begin(); it != sent.end(); ++it ) + s_endErrDlg->append( (*it)->subject()->asUnicodeString(), i18n("Article has already been sent.") ); + } + + if(!now) { + knGlobals.articleManager()->moveIntoFolder(unsent, knGlobals.folderManager()->outbox()); + return; + } + + + for ( KNLocalArticle::List::Iterator it = unsent.begin(); it != unsent.end(); ++it ) { + + if ( (*it)->isLocked() ) + continue; + + if ( !(*it)->hasContent() ) { + if ( !knGlobals.articleManager()->loadArticle( (*it) ) ) { + showSendErrorDialog(); + s_endErrDlg->append( (*it)->subject()->asUnicodeString(), i18n("Unable to load article.") ); + continue; + } + } + + if ( (*it)->doPost() && !(*it)->posted() ) { + ser = knGlobals.accountManager()->account( (*it)->serverId() ); + job = new KNJobData( KNJobData::JTpostArticle, this, ser, (*it) ); + emitJob(job); + } + else if( (*it)->doMail() && !(*it)->mailed() ) { + ser = knGlobals.accountManager()->smtp(); + job = new KNJobData( KNJobData::JTmail, this, ser, (*it) ); + emitJob(job); + } + } +} + + +void KNArticleFactory::sendOutbox() +{ + KNLocalArticle::List lst; + KNFolder *ob=0; + + if(!knGlobals.folderManager()->loadOutbox()) { + KMessageBox::error(knGlobals.topWidget, i18n("Unable to load the outbox-folder.")); + return; + } + + ob=knGlobals.folderManager()->outbox(); + for(int i=0; i< ob->length(); i++) + lst.append(ob->at(i)); + + sendArticles( lst, true ); +} + + +bool KNArticleFactory::closeComposeWindows() +{ + while ( !mCompList.isEmpty() ) { + QValueList<KNComposer*>::Iterator it = mCompList.begin(); + if ( !(*it)->close() ) + return false; + } + + return true; +} + + +void KNArticleFactory::deleteComposerForArticle(KNLocalArticle *a) +{ + KNComposer *com = findComposer( a ); + if ( com ) { + mCompList.remove( com ); + delete com; + } +} + + +KNComposer* KNArticleFactory::findComposer(KNLocalArticle *a) +{ + for ( QValueList<KNComposer*>::Iterator it = mCompList.begin(); it != mCompList.end(); ++it ) + if ( (*it)->article() == a ) + return (*it); + return 0; +} + + +void KNArticleFactory::configChanged() +{ + for ( QValueList<KNComposer*>::Iterator it = mCompList.begin(); it != mCompList.end(); ++it ) + (*it)->setConfig( false ); +} + + +void KNArticleFactory::processJob(KNJobData *j) +{ + KNLocalArticle *art=static_cast<KNLocalArticle*>(j->data()); + KNLocalArticle::List lst; + lst.append(art); + + if(j->canceled()) { + delete j; + + //sending of this article was canceled => move it to the "Outbox-Folder" + if(art->collection()!=knGlobals.folderManager()->outbox()) + knGlobals.articleManager()->moveIntoFolder(lst, knGlobals.folderManager()->outbox()); + + KMessageBox::information(knGlobals.topWidget, i18n("You have aborted the posting of articles. The unsent articles are stored in the \"Outbox\" folder.")); + + return; + } + + if(!j->success()) { + showSendErrorDialog(); + s_endErrDlg->append(art->subject()->asUnicodeString(), j->errorString()); + delete j; //unlock article + + //sending of this article failed => move it to the "Outbox-Folder" + if(art->collection()!=knGlobals.folderManager()->outbox()) + knGlobals.articleManager()->moveIntoFolder(lst, knGlobals.folderManager()->outbox()); + } + else { + + //disable edit + art->setEditDisabled(true); + + switch(j->type()) { + + case KNJobData::JTpostArticle: + delete j; //unlock article + art->setPosted(true); + if(art->doMail() && !art->mailed()) { //article has been posted, now mail it + sendArticles( lst, true ); + return; + } + break; + + case KNJobData::JTmail: + delete j; //unlock article + art->setMailed(true); + break; + + default: break; + }; + + //article has been sent successfully => move it to the "Sent-folder" + knGlobals.articleManager()->moveIntoFolder(lst, knGlobals.folderManager()->sent()); + } +} + + +KNLocalArticle* KNArticleFactory::newArticle(KNCollection *col, QString &sig, QCString defChset, bool withXHeaders, KNArticle *origPost) +{ + KNConfig::PostNewsTechnical *pnt=knGlobals.configManager()->postNewsTechnical(); + + if(pnt->generateMessageID() && pnt->hostname().isEmpty()) { + KMessageBox::sorry(knGlobals.topWidget, i18n("Please set a hostname for the generation\nof the message-id or disable it.")); + return 0; + } + + KNLocalArticle *art=new KNLocalArticle(0); + KNConfig::Identity *tmpId=0, *id=0; + + if (col) { + if (col->type() == KNCollection::CTgroup) { + id = (static_cast<KNGroup *>(col))->identity(); + tmpId = (static_cast<KNGroup *>(col))->account()->identity(); + } else + if (col->type() == KNCollection::CTnntpAccount) { + id = (static_cast<KNNntpAccount *>(col))->identity(); + } + } + + // determine active innermost non-empty identity + if (!id) { + if (tmpId) + id = tmpId; + else + id = knGlobals.configManager()->identity(); + } + + //Message-id + if(pnt->generateMessageID()) + art->messageID()->generate(pnt->hostname()); + + //From + KMime::Headers::From *from=art->from(); + from->setRFC2047Charset(pnt->charset()); + + //name + if(id->hasName()) + from->setName(id->name()); + + //email + if(id->hasEmail()&&id->emailIsValid()) + from->setEmail(id->email().latin1()); + else { + if ( id->hasEmail() ) + KMessageBox::sorry(knGlobals.topWidget, + i18n("Please enter a valid email address at the identity tab of the account configuration dialog.")); + else + KMessageBox::sorry(knGlobals.topWidget, + i18n("Please enter a valid email address at the identity section of the configuration dialog.")); + delete art; + return 0; + } + + //Reply-To + if(id->hasReplyTo()) { + art->replyTo()->fromUnicodeString(id->replyTo(), pnt->charset()); + if (!art->replyTo()->hasEmail()) // the header is invalid => drop it + art->removeHeader("Reply-To"); + } + + //Mail-Copies-To + if(id->hasMailCopiesTo()) { + art->mailCopiesTo()->fromUnicodeString(id->mailCopiesTo(), pnt->charset()); + if (!art->mailCopiesTo()->isValid()) // the header is invalid => drop it + art->removeHeader("Mail-Copies-To"); + } + + //Organization + if(id->hasOrga()) + art->organization()->fromUnicodeString(id->orga(), pnt->charset()); + + //Date + art->date()->setUnixTime(); //set current date+time + + //User-Agent + if( !pnt->noUserAgent() ) { + art->userAgent()->from7BitString("KNode/" KNODE_VERSION); + } + + //Mime + KMime::Headers::ContentType *type=art->contentType(); + type->setMimeType("text/plain"); + + type->setCharset(defChset); + + if (defChset.lower()=="us-ascii") + art->contentTransferEncoding()->setCte(KMime::Headers::CE7Bit); + else + art->contentTransferEncoding()->setCte(pnt->allow8BitBody()? KMime::Headers::CE8Bit : KMime::Headers::CEquPr); + + //X-Headers + if(withXHeaders) { + KNConfig::XHeaders::Iterator it; + for(it=pnt->xHeaders().begin(); it!=pnt->xHeaders().end(); ++it) { + QString value = (*it).value(); + if(origPost) { + QString name(origPost->from()->name()); + if (name.isEmpty()) + name = QString::fromLatin1(origPost->from()->email()); + value.replace(QRegExp("%NAME"), name); + value.replace(QRegExp("%EMAIL"), QString::fromLatin1(origPost->from()->email())); + } + else + if(value.find("%NAME") != -1 || value.find("%EMAIL") != -1) + continue; + + art->setHeader( new KMime::Headers::Generic( (QCString("X-")+(*it).name()), art, value, pnt->charset() ) ); + } + } + + //Signature + if(id->hasSignature()) + { + sig=id->getSignature(); + if(id->useSigGenerator() && !id->getSigGeneratorStdErr().isEmpty()) + KMessageBox::information(knGlobals.topWidget, + i18n("<qt>The signature generator program produced the " + "following output:<br><br>%1</qt>") + .arg(id->getSigGeneratorStdErr())); + } + else + sig=QString::null; + + return art; +} + + +bool KNArticleFactory::cancelAllowed(KNArticle *a) +{ + if(!a) + return false; + + if(a->type()==KMime::Base::ATlocal) { + KNLocalArticle *localArt=static_cast<KNLocalArticle*>(a); + + if(localArt->doMail() && !localArt->doPost()) { + KMessageBox::sorry(knGlobals.topWidget, i18n("Emails cannot be canceled or superseded.")); + return false; + } + + KMime::Headers::Control *ctrl=localArt->control(false); + if(ctrl && ctrl->isCancel()) { + KMessageBox::sorry(knGlobals.topWidget, i18n("Cancel messages cannot be canceled or superseded.")); + return false; + } + + if(!localArt->posted()) { + KMessageBox::sorry(knGlobals.topWidget, i18n("Only sent articles can be canceled or superseded.")); + return false; + } + + if(localArt->canceled()) { + KMessageBox::sorry(knGlobals.topWidget, i18n("This article has already been canceled or superseded.")); + return false; + } + + KMime::Headers::MessageID *mid=localArt->messageID(false); + if(!mid || mid->isEmpty()) { + KMessageBox::sorry(knGlobals.topWidget, i18n( +"This article cannot be canceled or superseded,\n\ +because its message-id has not been created by KNode.\n\ +But you can look for your article in the newsgroup\n\ +and cancel (or supersede) it there.")); + return false; + } + + return true; + } + else if(a->type()==KMime::Base::ATremote) { + + KNRemoteArticle *remArt=static_cast<KNRemoteArticle*>(a); + KNGroup *g=static_cast<KNGroup*>(a->collection()); + KNConfig::Identity *defId=knGlobals.configManager()->identity(), + *gid=g->identity(), + *accId=g->account()->identity(); + bool ownArticle = false; + + if (gid && gid->hasName()) + ownArticle |= ( gid->name() == remArt->from()->name() ); + if (accId && accId->hasName()) + ownArticle |= ( accId->name() == remArt->from()->name() ); + ownArticle |= ( defId->name() == remArt->from()->name() ); + + if(ownArticle) { + ownArticle = false; + if(gid && gid->hasEmail()) + ownArticle |= ( gid->email().latin1() == remArt->from()->email() ); + if (accId && accId->hasEmail()) + ownArticle |= ( accId->email().latin1() == remArt->from()->email() ); + ownArticle |= ( defId->email().latin1() == remArt->from()->email() ); + } + + if(!ownArticle) { + KMessageBox::sorry(knGlobals.topWidget, i18n("This article does not appear to be from you.\nYou can only cancel or supersede your own articles.")); + return false; + } + + if(!remArt->hasContent()) { + KMessageBox::sorry(knGlobals.topWidget, i18n("You have to download the article body\nbefore you can cancel or supersede the article.")); + return false; + } + + return true; + } + + return false; +} + + +void KNArticleFactory::showSendErrorDialog() +{ + if(!s_endErrDlg) { + s_endErrDlg=new KNSendErrorDialog(); + connect(s_endErrDlg, SIGNAL(closeClicked()), this, SLOT(slotSendErrorDialogDone())); + } + s_endErrDlg->show(); +} + + +void KNArticleFactory::slotComposerDone(KNComposer *com) +{ + bool delCom=true; + KNLocalArticle::List lst; + lst.append(com->article()); + + switch(com->result()) { + + case KNComposer::CRsendNow: + delCom=com->hasValidData(); + if(delCom) { + if ( com->applyChanges() ) + sendArticles( lst, true ); + else + delCom = false; + } + break; + + case KNComposer::CRsendLater: + delCom=com->hasValidData(); + if(delCom) { + if ( com->applyChanges() ) + sendArticles( lst, false ); + else + delCom = false; + } + break; + + case KNComposer::CRsave : + if ( com->applyChanges() ) + knGlobals.articleManager()->moveIntoFolder(lst, knGlobals.folderManager()->drafts()); + break; + + case KNComposer::CRdelAsk: + delCom=knGlobals.articleManager()->deleteArticles(lst, true); + break; + + case KNComposer::CRdel: + delCom=knGlobals.articleManager()->deleteArticles(lst, false); + break; + + case KNComposer::CRcancel: + // just close... + break; + + default: break; + + }; + + if ( delCom ) { + mCompList.remove( com ); + delete com; + } else + KWin::activateWindow(com->winId()); +} + + +void KNArticleFactory::slotSendErrorDialogDone() +{ + s_endErrDlg->delayedDestruct(); + s_endErrDlg=0; +} + + +//====================================================================================================== + + +KNSendErrorDialog::KNSendErrorDialog() + : KDialogBase(knGlobals.topWidget, 0, true, i18n("Errors While Sending"), Close, Close, true) +{ + p_ixmap=knGlobals.configManager()->appearance()->icon(KNConfig::Appearance::sendErr); + + QVBox *page = makeVBoxMainWidget(); + + new QLabel(QString("<b>%1</b><br>%2").arg(i18n("Errors occurred while sending these articles:")) + .arg(i18n("The unsent articles are stored in the \"Outbox\" folder.")), page); + j_obs=new KNDialogListBox(true, page); + e_rror=new QLabel(QString::null, page); + + connect(j_obs, SIGNAL(highlighted(int)), this, SLOT(slotHighlighted(int))); + + KNHelper::restoreWindowSize("sendDlg", this, QSize(320,250)); +} + + +KNSendErrorDialog::~KNSendErrorDialog() +{ + KNHelper::saveWindowSize("sendDlg", size()); +} + + +void KNSendErrorDialog::append(const QString &subject, const QString &error) +{ + + LBoxItem *it=new LBoxItem(error, subject, &p_ixmap); + j_obs->insertItem(it); + j_obs->setCurrentItem(it); +} + + +void KNSendErrorDialog::slotHighlighted(int idx) +{ + LBoxItem *it=static_cast<LBoxItem*>(j_obs->item(idx)); + if(it) { + QString tmp=i18n("<b>Error message:</b><br>")+it->error; + e_rror->setText(tmp); + } +} + +//------------------------------- +#include "knarticlefactory.moc" + +// kate: space-indent on; indent-width 2; diff --git a/knode/knarticlefactory.h b/knode/knarticlefactory.h new file mode 100644 index 000000000..6b18b6e5b --- /dev/null +++ b/knode/knarticlefactory.h @@ -0,0 +1,124 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNARTICLEFACTORY_H +#define KNARTICLEFACTORY_H + +#include <qvaluelist.h> +#include <kdialogbase.h> + +#include "knjobdata.h" +#include "knarticle.h" +#include "knwidgets.h" + +class QLabel; + +class KNGroup; +class KNFolder; +class KNCollection; +class KNComposer; +class KNSendErrorDialog; +class KNNntpAccount; + +namespace KNConfig { + class Identity; +} + + +class KNArticleFactory : public QObject , public KNJobConsumer { + + Q_OBJECT + + public: + enum replyType { RTgroup, RTmail, RTboth }; + + KNArticleFactory(QObject *p=0, const char *n=0); + ~KNArticleFactory(); + + //factory methods + void createPosting(KNNntpAccount *a); + void createPosting(KNGroup *g); + void createReply(KNRemoteArticle *a, QString selectedText=QString::null, bool post=true, bool mail=false); + void createForward(KNArticle *a); + void createCancel(KNArticle *a); + void createSupersede(KNArticle *a); + void createMail(KMime::Headers::AddressField *address); + + // send a mail via an external program... + void sendMailExternal(const QString &address=QString::null, const QString &subject=QString::null, const QString &body=QString::null); + + //article handling + void edit(KNLocalArticle *a); + void sendArticles( KNLocalArticle::List &l, bool now = true ); + void sendOutbox(); + + //composer handling + bool closeComposeWindows(); // try to close all composers, return false if user objects + void deleteComposerForArticle(KNLocalArticle *a); + KNComposer* findComposer(KNLocalArticle *a); + void configChanged(); + + protected: + //job handling + void processJob(KNJobData *j); //reimplemented from KNJobConsumer + + //article generation + // col: group or account + KNLocalArticle* newArticle(KNCollection *col, QString &sig, QCString defChset, bool withXHeaders=true, KNArticle *origPost=0); + + //cancel & supersede + bool cancelAllowed(KNArticle *a); + + //send-errors + void showSendErrorDialog(); + + QValueList<KNComposer*> mCompList; + KNSendErrorDialog *s_endErrDlg; + + protected slots: + void slotComposerDone(KNComposer *com); + void slotSendErrorDialogDone(); + +}; + + +class KNSendErrorDialog : public KDialogBase { + + Q_OBJECT + + public: + KNSendErrorDialog(); + ~KNSendErrorDialog(); + + void append(const QString &subject, const QString &error); + + protected: + class LBoxItem : public KNListBoxItem { + public: + LBoxItem(const QString &e, const QString &t, QPixmap *p=0) + : KNListBoxItem(t, p) , error(e) {} + ~LBoxItem() {} + + QString error; + }; + + KNDialogListBox *j_obs; + QLabel *e_rror; + QPixmap p_ixmap; + + protected slots: + void slotHighlighted(int idx); +}; + +#endif //KNARTICLEFACTORY_H diff --git a/knode/knarticlefilter.cpp b/knode/knarticlefilter.cpp new file mode 100644 index 000000000..b5a49d55b --- /dev/null +++ b/knode/knarticlefilter.cpp @@ -0,0 +1,383 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <kstandarddirs.h> +#include <ksimpleconfig.h> +#include <klocale.h> +#include <kdebug.h> + +#include "kngroup.h" +#include "knfolder.h" +#include "utilities.h" +#include "knarticlefilter.h" + +//============================================================================================================= + + +// the names of our default filters +static const char *defFil[] = { "all","unread","new","watched","threads with unread", + "threads with new","own articles","threads with own articles", 0 }; +void dummyFilter() +{ + i18n("default filter name","all"); + i18n("default filter name","unread"); + i18n("default filter name","new"); + i18n("default filter name","watched"); + i18n("default filter name","threads with unread"); + i18n("default filter name","threads with new"); + i18n("default filter name","own articles"); + i18n("default filter name","threads with own articles"); +} + + +//============================================================================================================= + + +KNArticleFilter::KNArticleFilter(int id) +: i_d(id), c_ount(0), l_oaded(false), e_nabled(true), translateName(true), s_earchFilter(false), apon(articles) +{} + + + +// constructs a copy of org +KNArticleFilter::KNArticleFilter(const KNArticleFilter& org) +: i_d(-1), c_ount(0), l_oaded(false), e_nabled(org.e_nabled), translateName(true), s_earchFilter(org.s_earchFilter), apon(org.apon) +{ + status = org.status; + score = org.score; + age = org.age; + lines = org.lines; + subject = org.subject; + from = org.from; + messageId = org.messageId; + references = org.messageId; +} + + + +KNArticleFilter::~KNArticleFilter() +{} + + + +bool KNArticleFilter::loadInfo() +{ + if (i_d!=-1) { + QString fname(locate("data",QString( "knode/filters/%1.fltr" ).arg(i_d) ) ); + + if (fname.isNull()) + return false; + KSimpleConfig conf(fname,true); + + conf.setGroup("GENERAL"); + n_ame=conf.readEntry("name"); + translateName = conf.readBoolEntry("Translate_Name",true); + e_nabled=conf.readBoolEntry("enabled", true); + apon=(ApOn) conf.readNumEntry("applyOn", 0); + return true; + } + return false; +} + + + +void KNArticleFilter::load() +{ + QString fname(locate("data",QString( "knode/filters/%1.fltr").arg(i_d) ) ); + + if (fname.isNull()) + return; + KSimpleConfig conf(fname,true); + + conf.setGroup("STATUS"); + status.load(&conf); + + conf.setGroup("SCORE"); + score.load(&conf); + + conf.setGroup("AGE"); + age.load(&conf); + + conf.setGroup("LINES"); + lines.load(&conf); + + conf.setGroup("SUBJECT"); + subject.load(&conf); + + conf.setGroup("FROM"); + from.load(&conf); + + conf.setGroup("MESSAGEID"); + messageId.load(&conf); + + conf.setGroup("REFERENCES"); + references.load(&conf); + + l_oaded=true; + + kdDebug(5003) << "KNMessageFilter: filter loaded \"" << n_ame << "\" " << endl; + +} + + + +void KNArticleFilter::save() +{ + if (i_d==-1) + return; + QString dir(locateLocal("data","knode/")+"filters/"); + if (dir.isNull()) { + KNHelper::displayInternalFileError(); + return; + } + KSimpleConfig conf(dir+QString("%1.fltr").arg(i_d)); + + conf.setGroup("GENERAL"); + conf.writeEntry("name", QString(n_ame)); + conf.writeEntry("Translate_Name",translateName); + conf.writeEntry("enabled", e_nabled); + conf.writeEntry("applyOn", (int) apon); + + conf.setGroup("STATUS"); + status.save(&conf); + + conf.setGroup("SCORE"); + score.save(&conf); + + conf.setGroup("AGE"); + age.save(&conf); + + conf.setGroup("LINES"); + lines.save(&conf); + + conf.setGroup("SUBJECT"); + subject.save(&conf); + + conf.setGroup("FROM"); + from.save(&conf); + + conf.setGroup("MESSAGEID"); + messageId.save(&conf); + + conf.setGroup("REFERENCES"); + references.save(&conf); + + kdDebug(5003) << "KNMessageFilter: filter saved \"" << n_ame << "\" " << endl; +} + + + +void KNArticleFilter::doFilter(KNGroup *g) +{ + c_ount=0; + KNRemoteArticle *art=0, *ref=0; + KNRemoteArticle::List orphant_threads; + int idRef; + int mergeCnt=0; + bool inThread=false; + + if(!l_oaded) load(); + + subject.expand(g); // replace placeholders + from.expand(g); + messageId.expand(g); + references.expand(g); + + for(int idx=0; idx<g->length(); idx++) { + art=g->at(idx); + art->setFiltered(false); + art->setVisibleFollowUps(false); + art->setDisplayedReference(0); + } + + for(int idx=0; idx<g->length(); idx++) { + + art=g->at(idx); + + if(!art->isFiltered() && applyFilter(art) && apon==threads) { + idRef=art->idRef(); + while(idRef!=0) { + ref=g->byId(idRef); + ref->setFilterResult(true); + ref->setFiltered(true); + if ( idRef==ref->idRef() ) break; + idRef=ref->idRef(); + } + } + + } + + for(int idx=0; idx<g->length(); idx++) { + + art=g->at(idx); + + if( apon==threads && !art->filterResult() ) { + inThread=false; + idRef=art->idRef(); + while(idRef!=0 && !inThread) { + ref=g->byId(idRef); + inThread=ref->filterResult(); + idRef=ref->idRef(); + } + art->setFilterResult(inThread); + } + + if(art->filterResult()) { + c_ount++; + + ref = (art->idRef()>0) ? g->byId(art->idRef()) : 0; + while(ref && !ref->filterResult()) + ref = (ref->idRef()>0) ? g->byId(ref->idRef()) : 0; + + art->setDisplayedReference(ref); + if(ref) + ref->setVisibleFollowUps(true); + else if(art->idRef()>0) { + orphant_threads.append(art); + } + } + + } + + if( orphant_threads.count() > 0 ) { + // try to merge orphant threads by subject + KNRemoteArticle::List same_subjects; + QString s; + for ( KNRemoteArticle::List::Iterator it = orphant_threads.begin(); it != orphant_threads.end(); ++it ) { + if ( (*it)->displayedReference() ) // already processed + continue; + + s = (*it)->subject()->asUnicodeString(); + same_subjects.clear(); + for ( KNRemoteArticle::List::Iterator it2 = orphant_threads.begin(); it2 != orphant_threads.end(); ++it2 ) { + if ( (*it2) != (*it) && (*it2)->subject()->asUnicodeString() == s ) + same_subjects.append( (*it2) ); + } + + (*it)->setVisibleFollowUps( (*it)->hasVisibleFollowUps() || same_subjects.count() > 0 ); + for ( KNRemoteArticle::List::Iterator it2 = same_subjects.begin(); it2 != same_subjects.end(); ++it2 ) { + (*it2)->setDisplayedReference( (*it) ); + mergeCnt++; + } + } + } + + kdDebug(5003) << "KNArticleFilter::doFilter() : matched " << c_ount + << " articles , merged " << mergeCnt + << " threads by subject" << endl; + +} + + +void KNArticleFilter::doFilter(KNFolder *f) +{ + c_ount=0; + KNLocalArticle *art=0; + + if(!l_oaded) load(); + + subject.expand(0); // replace placeholders + from.expand(0); + messageId.expand(0); + references.expand(0); + + for(int idx=0; idx<f->length(); idx++) { + art=f->at(idx); + if (applyFilter(art)) + c_ount++; + } +} + + +// *tries* to translate the name +QString KNArticleFilter::translatedName() +{ + if (translateName) { + // major hack alert !!! + if (!n_ame.isEmpty()) { + if (i18n("default filter name",n_ame.local8Bit())!=n_ame.local8Bit().data()) // try to guess if this english or not + return i18n("default filter name",n_ame.local8Bit()); + else + return n_ame; + } else + return QString::null; + } else + return n_ame; +} + + + +// *tries* to retranslate the name to english +void KNArticleFilter::setTranslatedName(const QString &s) +{ + bool retranslated = false; + for (const char **c=defFil;(*c)!=0;c++) // ok, try if it matches any of the standard filter names + if (s==i18n("default filter name",*c)) { + n_ame = QString::fromLatin1(*c); + retranslated = true; + break; + } + + if (!retranslated) { // ok, we give up and store the maybe non-english string + n_ame = s; + translateName = false; // and don't try to translate it, so a german user *can* use the original english name + } else + translateName = true; +} + + + +bool KNArticleFilter::applyFilter(KNRemoteArticle *a) +{ + bool result=true; + + if(result) result=status.doFilter(a); + if(result) result=score.doFilter(a->score()); + if(result) result=lines.doFilter(a->lines()->numberOfLines()); + if(result) result=age.doFilter(a->date()->ageInDays()); + if(result) result=subject.doFilter(a->subject()->asUnicodeString()); + if(result) { + QString tmp = (a->from()->name()+"##") + QString(a->from()->email().data()); + result=from.doFilter(tmp); + } + if(result) result=messageId.doFilter(a->messageID()->asUnicodeString()); + if(result) result=references.doFilter(a->references()->asUnicodeString()); + + a->setFilterResult(result); + a->setFiltered(true); + + return result; +} + + +bool KNArticleFilter::applyFilter(KNLocalArticle *a) +{ + bool result=true; + + if (isSearchFilter()) { + if(result) result=lines.doFilter(a->lines()->numberOfLines()); + if(result) result=age.doFilter(a->date()->ageInDays()); + if(result) result=subject.doFilter(a->subject()->asUnicodeString()); + if(result) { + QString tmp = (a->from()->name()+"##") + QString(a->from()->email().data()); + result=from.doFilter(tmp); + } + if(result) result=messageId.doFilter(a->messageID()->asUnicodeString()); + if(result) result=references.doFilter(a->references()->asUnicodeString()); + } + + a->setFilterResult(result); + + return result; +} diff --git a/knode/knarticlefilter.h b/knode/knarticlefilter.h new file mode 100644 index 000000000..6d544342d --- /dev/null +++ b/knode/knarticlefilter.h @@ -0,0 +1,80 @@ +/* + knarticlefilter.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNARTICLEFILTER_H +#define KNARTICLEFILTER_H + +#include "knstatusfilter.h" +#include "knrangefilter.h" +#include "knstringfilter.h" + +class KNRemoteArticle; +class KNLocalArticle; +class KNGroup; +class KNFolder; + + +class KNArticleFilter { + + friend class KNFilterManager; + friend class KNFilterDialog; + friend class KNSearchDialog; + + public: + KNArticleFilter(int id=-1); + KNArticleFilter(const KNArticleFilter& org); // constructs a copy of org + ~KNArticleFilter(); + + bool loadInfo(); + void load(); + void save(); + + void doFilter(KNGroup *g); + void doFilter(KNFolder *f); + int count()const { return c_ount; } + int id()const { return i_d; } + int applyOn() { return static_cast<int>(apon); } + const QString& name() { return n_ame; } + QString translatedName(); // *tries* to translate the name + bool isEnabled()const { return e_nabled; } + bool loaded()const { return l_oaded; } + bool isSearchFilter()const { return s_earchFilter; } + + void setId(int i) { i_d=i; } + void setApplyOn(int i) { apon=(ApOn)i; } + void setLoaded(bool l) { l_oaded=l; } + void setName(const QString &s) { n_ame=s; } + void setTranslatedName(const QString &s); // *tries* to retranslate the name to english + void setEnabled(bool l) { e_nabled=l; } + void setSearchFilter(bool b) { s_earchFilter = b; } + + protected: + + enum ApOn { articles=0 , threads=1 }; + bool applyFilter(KNRemoteArticle *a); + bool applyFilter(KNLocalArticle *a); + + QString n_ame; + int i_d, c_ount; + bool l_oaded, e_nabled, translateName, s_earchFilter; + ApOn apon; + + KNStatusFilter status; + KNRangeFilter score, age, lines; + KNStringFilter subject, from, messageId, references; +}; + +#endif diff --git a/knode/knarticlemanager.cpp b/knode/knarticlemanager.cpp new file mode 100644 index 000000000..4f1786e8c --- /dev/null +++ b/knode/knarticlemanager.cpp @@ -0,0 +1,1090 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <kmessagebox.h> +#include <kuserprofile.h> +#include <kopenwith.h> +#include <klocale.h> +#include <kdebug.h> +#include <kwin.h> +#include <ktempfile.h> + +#include "articlewidget.h" +#include "knmainwidget.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "utilities.h" +#include "knarticlemanager.h" +#include "kngroupmanager.h" +#include "knsearchdialog.h" +#include "knfiltermanager.h" +#include "knfolder.h" +#include "knarticlefilter.h" +#include "knhdrviewitem.h" +#include "knnetaccess.h" +#include "knnntpaccount.h" +#include "knscoring.h" +#include "knmemorymanager.h" +#include "knarticlefactory.h" +#include "knarticlewindow.h" +#include "knfoldermanager.h" +#include "headerview.h" + +using namespace KNode; + + +KNArticleManager::KNArticleManager() : QObject(0,0) +{ + g_roup=0; + f_older=0; + f_ilterMgr = knGlobals.filterManager(); + f_ilter = f_ilterMgr->currentFilter(); + s_earchDlg=0; + d_isableExpander=false; + + connect(f_ilterMgr, SIGNAL(filterChanged(KNArticleFilter*)), this, + SLOT(slotFilterChanged(KNArticleFilter*))); +} + + +KNArticleManager::~KNArticleManager() +{ + delete s_earchDlg; +} + + +void KNArticleManager::deleteTempFiles() +{ + for ( QValueList<KTempFile*>::Iterator it = mTempFiles.begin(); it != mTempFiles.end(); ++it ) { + (*it)->unlink(); + delete (*it); + } + mTempFiles.clear(); +} + + +void KNArticleManager::saveContentToFile(KMime::Content *c, QWidget *parent) +{ + KNSaveHelper helper(c->contentType()->name(),parent); + + QFile *file = helper.getFile(i18n("Save Attachment")); + + if (file) { + QByteArray data=c->decodedContent(); + if (file->writeBlock(data.data(), data.size()) == -1 ) + KNHelper::displayExternalFileError( parent ); + } +} + + +void KNArticleManager::saveArticleToFile(KNArticle *a, QWidget *parent) +{ + QString fName = a->subject()->asUnicodeString(); + QString s = ""; + + for (unsigned int i=0; i<fName.length(); i++) + if (fName[i].isLetterOrNumber()) + s.append(fName[i]); + else + s.append(' '); + fName = s.simplifyWhiteSpace(); + fName.replace(QRegExp("[\\s]"),"_"); + + KNSaveHelper helper(fName,parent); + QFile *file = helper.getFile(i18n("Save Article")); + + if (file) { + QCString tmp=a->encodedContent(false); + if ( file->writeBlock(tmp.data(), tmp.size()) == -1 ) + KNHelper::displayExternalFileError( parent ); + } +} + + +QString KNArticleManager::saveContentToTemp(KMime::Content *c) +{ + QString path; + KTempFile* tmpFile; + KMime::Headers::Base *pathHdr=c->getHeaderByType("X-KNode-Tempfile"); // check for existing temp file + + if(pathHdr) { + path = pathHdr->asUnicodeString(); + bool found=false; + + // lets see if the tempfile-path is still valid... + for ( QValueList<KTempFile*>::Iterator it = mTempFiles.begin(); it != mTempFiles.end(); ++it ) { + if ( (*it)->name() == path ) { + found = true; + break; + } + } + + if (found) + return path; + else + c->removeHeader("X-KNode-Tempfile"); + } + + tmpFile=new KTempFile(); + if (tmpFile->status()!=0) { + KNHelper::displayTempFileError(); + delete tmpFile; + return QString::null; + } + + mTempFiles.append(tmpFile); + QFile *f=tmpFile->file(); + QByteArray data=c->decodedContent(); + f->writeBlock(data.data(), data.size()); + tmpFile->close(); + path=tmpFile->name(); + pathHdr=new KMime::Headers::Generic("X-KNode-Tempfile", c, path, "UTF-8"); + c->setHeader(pathHdr); + + return path; +} + + +void KNArticleManager::openContent(KMime::Content *c) +{ + QString path=saveContentToTemp(c); + if(path.isNull()) return; + + KService::Ptr offer = KServiceTypeProfile::preferredService(c->contentType()->mimeType(), "Application"); + KURL::List lst; + KURL url; + url.setPath(path); + lst.append(url); + + if (offer) + KRun::run(*offer, lst); + else + KRun::displayOpenWithDialog(lst); +} + + +void KNArticleManager::showHdrs(bool clear) +{ + if(!g_roup && !f_older) return; + + bool setFirstChild=true; + bool showThreads=knGlobals.configManager()->readNewsGeneral()->showThreads(); + bool expandThreads=knGlobals.configManager()->readNewsGeneral()->defaultToExpandedThreads(); + + if(clear) + v_iew->clear(); + + knGlobals.top->setCursorBusy(true); + knGlobals.setStatusMsg(i18n(" Creating list...")); + knGlobals.top->secureProcessEvents(); + + if(g_roup) { + KNRemoteArticle *art, *ref, *current; + + current = static_cast<KNRemoteArticle*>( knGlobals.top->articleViewer()->article() ); + + if(current && (current->collection() != g_roup)) { + current=0; + knGlobals.top->articleViewer()->setArticle( 0 ); + } + + if(g_roup->isLocked()) + knGlobals.netAccess()->nntpMutex().lock(); + + if(f_ilter) + f_ilter->doFilter(g_roup); + else + for(int i=0; i<g_roup->length(); i++) { + art=g_roup->at(i); + art->setFilterResult(true); + art->setFiltered(true); + ref=(art->idRef()!=0) ? g_roup->byId(art->idRef()) : 0; + art->setDisplayedReference(ref); + if(ref) + ref->setVisibleFollowUps(true); + } + + d_isableExpander=true; + + for(int i=0; i<g_roup->length(); i++) { + + art=g_roup->at(i); + art->setThreadMode(showThreads); + + if(showThreads) { + art->propagateThreadChangedDate(); + + if( !art->listItem() && art->filterResult() ) { + + // ### disable delayed header view item creation for now, it breaks + // the quick search + // since it doesn't seem to improve performance at all, it probably + // could be removed entirely (see also slotItemExpanded(), etc.) + /*if (!expandThreads) { + + if( (ref=art->displayedReference()) ) { + + if( ref->listItem() && ( ref->listItem()->isOpen() || ref->listItem()->childCount()>0 ) ) { + art->setListItem(new KNHdrViewItem(ref->listItem())); + art->initListItem(); + } + + } + else { + art->setListItem(new KNHdrViewItem(v_iew)); + art->initListItem(); + } + + } else { // expandThreads == true */ + createThread(art); + if ( expandThreads ) + art->listItem()->setOpen(true); +// } + + } + else if(art->listItem()) { + art->updateListItem(); + if (expandThreads) + art->listItem()->setOpen(true); + } + + } + else { + + if(!art->listItem() && art->filterResult()) { + art->setListItem(new KNHdrViewItem(v_iew)); + art->initListItem(); + } else if(art->listItem()) + art->updateListItem(); + + } + + } + + if (current && !current->filterResult()) { // try to find a parent that is visible + int idRef; + while (current && !current->filterResult()) { + idRef=current->idRef(); + if (idRef == -1) + break; + current = g_roup->byId(idRef); + } + } + + if(current && current->filterResult()) { + if(!current->listItem()) + createCompleteThread(current); + v_iew->setActive( current->listItem() ); + setFirstChild=false; + } + + d_isableExpander=false; + + if (g_roup->isLocked()) + knGlobals.netAccess()->nntpMutex().unlock(); + } + + else { //folder + + KNLocalArticle *art; + if(f_ilter) { + f_ilter->doFilter(f_older); + } else { + for(int i=0; i<f_older->length(); i++) { + art=f_older->at(i); + art->setFilterResult(true); + } + } + + for(int idx=0; idx<f_older->length(); idx++) { + art=f_older->at(idx); + + if(!art->listItem() && art->filterResult()) { + art->setListItem( new KNHdrViewItem(v_iew, art) ); + art->updateListItem(); + } else if(art->listItem()) + art->updateListItem(); + } + + } + + if(setFirstChild && v_iew->firstChild()) { + v_iew->setCurrentItem(v_iew->firstChild()); + knGlobals.top->articleViewer()->setArticle( 0 ); + } + + knGlobals.setStatusMsg(QString::null); + updateStatusString(); + knGlobals.top->setCursorBusy(false); +} + + +void KNArticleManager::updateViewForCollection(KNArticleCollection *c) +{ + if(g_roup==c || f_older==c) + showHdrs(false); +} + + +void KNArticleManager::updateListViewItems() +{ + if(!g_roup && !f_older) return; + + if(g_roup) { + KNRemoteArticle *art; + + for(int i=0; i<g_roup->length(); i++) { + art=g_roup->at(i); + if(art->listItem()) + art->updateListItem(); + } + } else { //folder + KNLocalArticle *art; + + for(int idx=0; idx<f_older->length(); idx++) { + art=f_older->at(idx); + if(art->listItem()) + art->updateListItem(); + } + } +} + + +void KNArticleManager::setAllThreadsOpen(bool b) +{ + KNRemoteArticle *art; + if(g_roup) { + knGlobals.top->setCursorBusy(true); + d_isableExpander = true; + for(int idx=0; idx<g_roup->length(); idx++) { + art = g_roup->at(idx); + if (art->listItem()) + art->listItem()->setOpen(b); + else + if (b && art->filterResult()) { + createThread(art); + art->listItem()->setOpen(true); + } + } + d_isableExpander = false; + knGlobals.top->setCursorBusy(false); + } +} + + +void KNArticleManager::search() +{ + if(s_earchDlg) { + s_earchDlg->show(); + KWin::activateWindow(s_earchDlg->winId()); + } else { + s_earchDlg=new KNSearchDialog(KNSearchDialog::STgroupSearch, 0); + connect(s_earchDlg, SIGNAL(doSearch(KNArticleFilter*)), this, + SLOT(slotFilterChanged(KNArticleFilter*))); + connect(s_earchDlg, SIGNAL(dialogDone()), this, + SLOT(slotSearchDialogDone())); + s_earchDlg->show(); + } +} + + +void KNArticleManager::setGroup(KNGroup *g) +{ + g_roup = g; + if ( g ) + emit aboutToShowGroup(); +} + + +void KNArticleManager::setFolder(KNFolder *f) +{ + f_older = f; + if ( f ) + emit aboutToShowFolder(); +} + + +KNArticleCollection* KNArticleManager::collection() +{ + if(g_roup) + return g_roup; + if(f_older) + return f_older; + + return 0; +} + + +bool KNArticleManager::loadArticle(KNArticle *a) +{ + if (!a) + return false; + + if (a->hasContent()) + return true; + + if (a->isLocked()) { + if (a->type()==KMime::Base::ATremote) + return true; // locked == we are already loading this article... + else + return false; + } + + if(a->type()==KMime::Base::ATremote) { + KNGroup *g=static_cast<KNGroup*>(a->collection()); + if(g) + emitJob( new KNJobData(KNJobData::JTfetchArticle, this, g->account(), a) ); + else + return false; + } + else { // local article + KNFolder *f=static_cast<KNFolder*>(a->collection()); + if( f && f->loadArticle( static_cast<KNLocalArticle*>(a) ) ) + knGlobals.memoryManager()->updateCacheEntry(a); + else + return false; + } + return true; +} + + +bool KNArticleManager::unloadArticle(KNArticle *a, bool force) +{ + if(!a || a->isLocked() ) + return false; + if(!a->hasContent()) + return true; + + if (!force && a->isNotUnloadable()) + return false; + + if ( !force && ( ArticleWidget::articleVisible( a ) ) ) + return false; + + if (!force && (a->type()==KMime::Base::ATlocal) && + (knGlobals.artFactory->findComposer(static_cast<KNLocalArticle*>(a))!=0)) + return false; + + if (!KNArticleWindow::closeAllWindowsForArticle(a, force)) + if (!force) + return false; + + ArticleWidget::articleRemoved( a ); + if ( a->type() != KMime::Base::ATlocal ) + knGlobals.artFactory->deleteComposerForArticle(static_cast<KNLocalArticle*>(a)); + a->KMime::Content::clear(); + a->updateListItem(); + knGlobals.memoryManager()->removeCacheEntry(a); + + return true; +} + + +void KNArticleManager::copyIntoFolder(KNArticle::List &l, KNFolder *f) +{ + if(!f) return; + + KNLocalArticle *loc; + KNLocalArticle::List l2; + + for ( KNArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + if ( !(*it)->hasContent() ) + continue; + loc=new KNLocalArticle(0); + loc->setEditDisabled(true); + loc->setContent( (*it)->encodedContent() ); + loc->parse(); + l2.append(loc); + } + + if ( !l2.isEmpty() ) { + + f->setNotUnloadable(true); + + if ( !f->isLoaded() && !knGlobals.folderManager()->loadHeaders( f ) ) { + for ( KNLocalArticle::List::Iterator it = l2.begin(); it != l2.end(); ++it ) + delete (*it); + l2.clear(); + f->setNotUnloadable(false); + return; + } + + if( !f->saveArticles( l2 ) ) { + for ( KNLocalArticle::List::Iterator it = l2.begin(); it != l2.end(); ++it ) { + if ( (*it)->isOrphant() ) + delete (*it); // ok, this is ugly; we simply delete orphant articles + else + (*it)->KMime::Content::clear(); // no need to keep them in memory + } + KNHelper::displayInternalFileError(); + } else { + for ( KNLocalArticle::List::Iterator it = l2.begin(); it != l2.end(); ++it ) + (*it)->KMime::Content::clear(); // no need to keep them in memory + knGlobals.memoryManager()->updateCacheEntry(f); + } + + f->setNotUnloadable(false); + } +} + + +void KNArticleManager::moveIntoFolder(KNLocalArticle::List &l, KNFolder *f) +{ + if(!f) return; + kdDebug(5003) << k_funcinfo << " Target folder: " << f->name() << endl; + + f->setNotUnloadable(true); + + if (!f->isLoaded() && !knGlobals.folderManager()->loadHeaders(f)) { + f->setNotUnloadable(false); + return; + } + + if ( f->saveArticles( l ) ) { + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) + knGlobals.memoryManager()->updateCacheEntry( (*it) ); + knGlobals.memoryManager()->updateCacheEntry(f); + } else { + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) + if ( (*it)->isOrphant() ) + delete (*it); // ok, this is ugly; we simply delete orphant articles + KNHelper::displayInternalFileError(); + } + + f->setNotUnloadable(false); +} + + +bool KNArticleManager::deleteArticles(KNLocalArticle::List &l, bool ask) +{ + if(ask) { + QStringList lst; + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + if ( (*it)->isLocked() ) + continue; + if ( (*it)->subject()->isEmpty() ) + lst << i18n("no subject"); + else + lst << (*it)->subject()->asUnicodeString(); + } + if( KMessageBox::Cancel == KMessageBox::warningContinueCancelList( + knGlobals.topWidget, i18n("Do you really want to delete these articles?"), lst, + i18n("Delete Articles"), KGuiItem(i18n("&Delete"),"editdelete")) ) + return false; + } + + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) + knGlobals.memoryManager()->removeCacheEntry( (*it) ); + + KNFolder *f=static_cast<KNFolder*>(l.first()->collection()); + if ( f ) { + f->removeArticles( l, true ); + knGlobals.memoryManager()->updateCacheEntry( f ); + } + else { + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) + delete (*it); + } + + return true; +} + + +void KNArticleManager::setAllRead( bool read, int lastcount ) +{ + if ( !g_roup ) + return; + + int groupLength = g_roup->length(); + int newCount = g_roup->newCount(); + int readCount = g_roup->readCount(); + int offset = lastcount; + + if ( lastcount > groupLength || lastcount < 0 ) + offset = groupLength; + + KNRemoteArticle *a; + for ( int i = groupLength - offset; i < groupLength; i++ ) { + a = g_roup->at( i ); + if ( a->getReadFlag() != read && !a->isIgnored() ) { + a->setRead( read ); + a->setChanged( true ); + if ( !read ) { + readCount--; + if ( a->isNew() ) + newCount++; + } else { + readCount++; + if ( a->isNew() ) + newCount--; + } + } + } + + g_roup->updateThreadInfo(); + if ( lastcount < 0 && read ) { + // HACK: try to hide the effects of the ignore/filter new/unread count bug + g_roup->setReadCount( groupLength ); + g_roup->setNewCount( 0 ); + } else { + g_roup->setReadCount( readCount ); + g_roup->setNewCount( newCount ); + } + + g_roup->updateListItem(); + showHdrs( true ); +} + + +void KNArticleManager::setRead(KNRemoteArticle::List &l, bool r, bool handleXPosts) +{ + if ( l.isEmpty() ) + return; + + KNRemoteArticle *ref = 0; + KNGroup *g=static_cast<KNGroup*>( l.first()->collection() ); + int changeCnt=0, idRef=0; + + for ( KNRemoteArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + if( r && knGlobals.configManager()->readNewsGeneral()->markCrossposts() && + handleXPosts && (*it)->newsgroups()->isCrossposted() ) { + + QStringList groups = (*it)->newsgroups()->getGroups(); + KNGroup *targetGroup=0; + KNRemoteArticle *xp=0; + KNRemoteArticle::List al; + QCString mid = (*it)->messageID()->as7BitString( false ); + + for ( QStringList::Iterator it2 = groups.begin(); it2 != groups.end(); ++it2 ) { + targetGroup = knGlobals.groupManager()->group(*it2, g->account()); + if (targetGroup) { + if (targetGroup->isLoaded() && (xp=targetGroup->byMessageId(mid)) ) { + al.clear(); + al.append(xp); + setRead(al, r, false); + } else { + targetGroup->appendXPostID(mid); + } + } + } + } + + else if ( (*it)->getReadFlag() != r ) { + (*it)->setRead( r ); + (*it)->setChanged( true ); + (*it)->updateListItem(); + + if ( !(*it)->isIgnored() ) { + changeCnt++; + idRef = (*it)->idRef(); + + while ( idRef != 0 ) { + ref=g->byId(idRef); + if(r) { + ref->decUnreadFollowUps(); + if ( (*it)->isNew() ) + ref->decNewFollowUps(); + } + else { + ref->incUnreadFollowUps(); + if ( (*it)->isNew() ) + ref->incNewFollowUps(); + } + + if(ref->listItem() && + ((ref->unreadFollowUps()==0 || ref->unreadFollowUps()==1) || + (ref->newFollowUps()==0 || ref->newFollowUps()==1))) + ref->updateListItem(); + + idRef=ref->idRef(); + } + + if(r) { + g->incReadCount(); + if ( (*it)->isNew() ) + g->decNewCount(); + } + else { + g->decReadCount(); + if ( (*it)->isNew() ) + g->incNewCount(); + } + } + } + } + + if(changeCnt>0) { + g->updateListItem(); + if(g==g_roup) + updateStatusString(); + } +} + + +void KNArticleManager::setAllNotNew() +{ + if ( !g_roup ) + return; + KNRemoteArticle *a; + for ( int i = 0; i < g_roup->length(); ++i) { + a = g_roup->at(i); + if ( a->isNew() ) { + a->setNew( false ); + a->setChanged( true ); + } + } + g_roup->setFirstNewIndex( -1 ); + g_roup->setNewCount( 0 ); + g_roup->updateThreadInfo(); +} + + +bool KNArticleManager::toggleWatched(KNRemoteArticle::List &l) +{ + if(l.isEmpty()) + return true; + + KNRemoteArticle *a=l.first(), *ref=0; + bool watch = (!a->isWatched()); + KNGroup *g=static_cast<KNGroup*>(a->collection() ); + int changeCnt=0, idRef=0; + + for ( KNRemoteArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + if ( (*it)->isIgnored() ) { + (*it)->setIgnored(false); + + if ( !(*it)->getReadFlag() ) { + changeCnt++; + idRef = (*it)->idRef(); + + while ( idRef != 0 ) { + ref=g->byId(idRef); + + ref->incUnreadFollowUps(); + if ( (*it)->isNew() ) + ref->incNewFollowUps(); + + if(ref->listItem() && + ((ref->unreadFollowUps()==0 || ref->unreadFollowUps()==1) || + (ref->newFollowUps()==0 || ref->newFollowUps()==1))) + ref->updateListItem(); + + idRef=ref->idRef(); + } + g->decReadCount(); + if ( (*it)->isNew() ) + g->incNewCount(); + } + } + + (*it)->setWatched( watch ); + (*it)->updateListItem(); + (*it)->setChanged( true ); + } + + if(changeCnt>0) { + g->updateListItem(); + if(g==g_roup) + updateStatusString(); + } + + return watch; +} + + +bool KNArticleManager::toggleIgnored(KNRemoteArticle::List &l) +{ + if(l.isEmpty()) + return true; + + KNRemoteArticle *ref = 0; + bool ignore = !l.first()->isIgnored(); + KNGroup *g = static_cast<KNGroup*>( l.first()->collection() ); + int changeCnt = 0, idRef = 0; + + for ( KNRemoteArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + (*it)->setWatched(false); + if ( (*it)->isIgnored() != ignore ) { + (*it)->setIgnored( ignore ); + + if ( !(*it)->getReadFlag() ) { + changeCnt++; + idRef = (*it)->idRef(); + + while ( idRef != 0 ) { + ref = g->byId( idRef ); + + if ( ignore ) { + ref->decUnreadFollowUps(); + if ( (*it)->isNew() ) + ref->decNewFollowUps(); + } else { + ref->incUnreadFollowUps(); + if ( (*it)->isNew() ) + ref->incNewFollowUps(); + } + + if(ref->listItem() && + ((ref->unreadFollowUps()==0 || ref->unreadFollowUps()==1) || + (ref->newFollowUps()==0 || ref->newFollowUps()==1))) + ref->updateListItem(); + + idRef=ref->idRef(); + } + + if ( ignore ) { + g->incReadCount(); + if ( (*it)->isNew() ) + g->decNewCount(); + } else { + g->decReadCount(); + if ( (*it)->isNew() ) + g->incNewCount(); + } + + } + } + (*it)->updateListItem(); + (*it)->setChanged(true); + } + + if(changeCnt>0) { + g->updateListItem(); + if(g==g_roup) + updateStatusString(); + } + + return ignore; +} + + +void KNArticleManager::rescoreArticles(KNRemoteArticle::List &l) +{ + if ( l.isEmpty() ) + return; + + KNGroup *g = static_cast<KNGroup*>( l.first()->collection() ); + KScoringManager *sm = knGlobals.scoringManager(); + sm->initCache(g->groupname()); + + for ( KNRemoteArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + int defScore = 0; + if ( (*it)->isIgnored()) + defScore = knGlobals.configManager()->scoring()->ignoredThreshold(); + else if ( (*it)->isWatched() ) + defScore = knGlobals.configManager()->scoring()->watchedThreshold(); + (*it)->setScore(defScore); + + bool read = (*it)->isRead(); + + KNScorableArticle sa( (*it) ); + sm->applyRules(sa); + (*it)->updateListItem(); + (*it)->setChanged( true ); + + if ( !read && (*it)->isRead() != read ) + g_roup->incReadCount(); + } +} + + +void KNArticleManager::processJob(KNJobData *j) +{ + if(j->type()==KNJobData::JTfetchArticle && !j->canceled()) { + if(j->success()) { + KNRemoteArticle *a=static_cast<KNRemoteArticle*>(j->data()); + ArticleWidget::articleChanged( a ); + if(!a->isOrphant()) //orphant articles are deleted by the displaying widget + knGlobals.memoryManager()->updateCacheEntry(a); + if(a->listItem()) + a->updateListItem(); + } + else + ArticleWidget::articleLoadError(static_cast<KNRemoteArticle*>(j->data()), j->errorString()); + } + + delete j; +} + + +void KNArticleManager::createThread(KNRemoteArticle *a) +{ + KNRemoteArticle *ref=a->displayedReference(); + + if(ref) { + if(!ref->listItem()) + createThread(ref); + a->setListItem(new KNHdrViewItem(ref->listItem())); + } + else + a->setListItem(new KNHdrViewItem(v_iew)); + + a->setThreadMode(knGlobals.configManager()->readNewsGeneral()->showThreads()); + a->initListItem(); +} + + +void KNArticleManager::createCompleteThread(KNRemoteArticle *a) +{ + KNRemoteArticle *ref=a->displayedReference(), *art, *top; + bool inThread=false; + bool showThreads=knGlobals.configManager()->readNewsGeneral()->showThreads(); + KNConfig::ReadNewsGeneral *rng=knGlobals.configManager()->readNewsGeneral(); + + while (ref->displayedReference() != 0) + ref=ref->displayedReference(); + + top = ref; + + if (!top->listItem()) // shouldn't happen + return; + + for(int i=0; i<g_roup->count(); i++) { + art=g_roup->at(i); + if(art->filterResult() && !art->listItem()) { + + if(art->displayedReference()==top) { + art->setListItem(new KNHdrViewItem(top->listItem())); + art->setThreadMode(showThreads); + art->initListItem(); + } + else { + ref=art->displayedReference(); + inThread=false; + while(ref && !inThread) { + inThread=(ref==top); + ref=ref->displayedReference(); + } + if(inThread) + createThread(art); + } + } + } + + if(rng->totalExpandThreads()) + top->listItem()->expandChildren(); +} + + +void KNArticleManager::updateStatusString() +{ + int displCnt=0; + + if(g_roup) { + if(f_ilter) + displCnt=f_ilter->count(); + else + displCnt=g_roup->count(); + + QString name = g_roup->name(); + if (g_roup->status()==KNGroup::moderated) + name += i18n(" (moderated)"); + + knGlobals.setStatusMsg(i18n(" %1: %2 new , %3 displayed") + .arg(name).arg(g_roup->newCount()).arg(displCnt),SB_GROUP); + + if(f_ilter) + knGlobals.setStatusMsg(i18n(" Filter: %1").arg(f_ilter->translatedName()), SB_FILTER); + else + knGlobals.setStatusMsg(QString::null, SB_FILTER); + } + else if(f_older) { + if(f_ilter) + displCnt=f_ilter->count(); + else + displCnt=f_older->count(); + knGlobals.setStatusMsg(i18n(" %1: %2 displayed") + .arg(f_older->name()).arg(displCnt), SB_GROUP); + knGlobals.setStatusMsg(QString::null, SB_FILTER); + } else { + knGlobals.setStatusMsg(QString::null, SB_GROUP); + knGlobals.setStatusMsg(QString::null, SB_FILTER); + } +} + + +void KNArticleManager::slotFilterChanged(KNArticleFilter *f) +{ + f_ilter=f; + showHdrs(); +} + + +void KNArticleManager::slotSearchDialogDone() +{ + s_earchDlg->hide(); + slotFilterChanged(f_ilterMgr->currentFilter()); +} + + +void KNArticleManager::slotItemExpanded(QListViewItem *p) +{ + if (d_isableExpander) // we don't want to call this method recursively + return; + d_isableExpander = true; + + KNRemoteArticle *top, *art, *ref; + KNHdrViewItem *hdrItem; + bool inThread=false; + bool showThreads=knGlobals.configManager()->readNewsGeneral()->showThreads(); + KNConfig::ReadNewsGeneral *rng=knGlobals.configManager()->readNewsGeneral(); + hdrItem=static_cast<KNHdrViewItem*>(p); + top=static_cast<KNRemoteArticle*>(hdrItem->art); + + if (p->childCount() == 0) { + + knGlobals.top->setCursorBusy(true); + + for(int i=0; i<g_roup->count(); i++) { + art=g_roup->at(i); + if(art->filterResult() && !art->listItem()) { + + if(art->displayedReference()==top) { + art->setListItem(new KNHdrViewItem(hdrItem)); + art->setThreadMode(showThreads); + art->initListItem(); + } + else if(rng->totalExpandThreads()) { //totalExpand + ref=art->displayedReference(); + inThread=false; + while(ref && !inThread) { + inThread=(ref==top); + ref=ref->displayedReference(); + } + if(inThread) + createThread(art); + } + } + } + + knGlobals.top->setCursorBusy(false); + } + + if(rng->totalExpandThreads()) + hdrItem->expandChildren(); + + d_isableExpander = false; +} + + +void KNArticleManager::setView(KNHeaderView* v) { + v_iew = v; + if(v) { + connect(v, SIGNAL(expanded(QListViewItem*)), this, + SLOT(slotItemExpanded(QListViewItem*))); + } +} + +//----------------------------- +#include "knarticlemanager.moc" diff --git a/knode/knarticlemanager.h b/knode/knarticlemanager.h new file mode 100644 index 000000000..07335e1c1 --- /dev/null +++ b/knode/knarticlemanager.h @@ -0,0 +1,122 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNARTICLEMANAGER_H +#define KNARTICLEMANAGER_H + +#include <qvaluelist.h> + +#include "knjobdata.h" +#include "knarticle.h" + +class QListViewItem; + +class KTempFile; + +class KNArticle; +class KNHeaderView; +class KNThread; +class KNArticleCollection; +class KNGroup; +class KNFolder; +class KNArticleFilter; +class KNFilterManager; +class KNSearchDialog; +class KNJobData; + + +class KNArticleManager : public QObject, public KNJobConsumer { + + Q_OBJECT + + public: + KNArticleManager(); + virtual ~KNArticleManager(); + + //content handling + void deleteTempFiles(); + void saveContentToFile(KMime::Content *c, QWidget *parent); + void saveArticleToFile(KNArticle *a, QWidget *parent); + QString saveContentToTemp(KMime::Content *c); + void openContent(KMime::Content *c); + + //listview handling + void showHdrs(bool clear=true); + void updateViewForCollection(KNArticleCollection *c); + void updateListViewItems(); + void setAllThreadsOpen(bool b=true); + + void updateStatusString(); + + //filter + KNArticleFilter* filter() const { return f_ilter; } + void search(); + + //collection handling + void setGroup(KNGroup *g); + void setFolder(KNFolder *f); + KNArticleCollection* collection(); + + //article loading + bool loadArticle(KNArticle *a); + bool unloadArticle(KNArticle *a, bool force=true); + + //article storage + void copyIntoFolder(KNArticle::List &l, KNFolder *f); + void moveIntoFolder(KNLocalArticle::List &l, KNFolder *f); + bool deleteArticles(KNLocalArticle::List &l, bool ask=true); + + //article handling + void setAllRead( bool read = true, int lastcount = -1 ); + void setRead(KNRemoteArticle::List &l, bool r=true, bool handleXPosts=true); + /// mark all articles in the current group as not new + void setAllNotNew(); + + // returns false if the changes were reverted (i.e. ignored articles->neutral articles) + bool toggleWatched(KNRemoteArticle::List &l); + bool toggleIgnored(KNRemoteArticle::List &l); + + void rescoreArticles(KNRemoteArticle::List &l); + + // Allow to delay the setup of UI elements, since the knode part may not + // be available when the config dialog is called + void setView(KNHeaderView* v); + + signals: + // signals for the header view to adapt to the upcoming content + void aboutToShowGroup(); + void aboutToShowFolder(); + + protected: + void processJob(KNJobData *j); + void createThread(KNRemoteArticle *a); + void createCompleteThread(KNRemoteArticle *a); + + KNHeaderView *v_iew; + KNGroup *g_roup; + KNFolder *f_older; + KNArticleFilter *f_ilter; + KNFilterManager *f_ilterMgr; + KNSearchDialog *s_earchDlg; + QValueList<KTempFile*> mTempFiles; + bool d_isableExpander; + + public slots: + void slotFilterChanged(KNArticleFilter *f); + void slotSearchDialogDone(); + void slotItemExpanded(QListViewItem *p); + +}; + +#endif diff --git a/knode/knarticlewindow.cpp b/knode/knarticlewindow.cpp new file mode 100644 index 000000000..3e6898182 --- /dev/null +++ b/knode/knarticlewindow.cpp @@ -0,0 +1,131 @@ +// -*- c-basic-offset: 2 -*- +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <kwin.h> + +#include <kstdaction.h> +#include <kconfig.h> +#include <kaccel.h> +#include <kaction.h> + +#include "knarticle.h" +#include "articlewidget.h" +#include "utilities.h" +#include "knglobals.h" +#include "knmainwidget.h" +#include "knarticlewindow.h" + +using namespace KNode; + +QValueList<KNArticleWindow*> KNArticleWindow::mInstances; + + +bool KNArticleWindow::closeAllWindowsForCollection( KNArticleCollection *col, bool force ) +{ + QValueList<KNArticleWindow*> list = mInstances; + for ( QValueList<KNArticleWindow*>::Iterator it = list.begin(); it != list.end(); ++it ) + if ( (*it)->artW->article() && (*it)->artW->article()->collection() == col ) { + if ( force ) + (*it)->close(); + else + return false; + } + return true; +} + + +bool KNArticleWindow::closeAllWindowsForArticle( KNArticle *art, bool force ) +{ + QValueList<KNArticleWindow*> list = mInstances; + for ( QValueList<KNArticleWindow*>::Iterator it = list.begin(); it != list.end(); ++it ) + if ( (*it)->artW->article() && (*it)->artW->article() == art ) { + if ( force ) + (*it)->close(); + else + return false; + } + return true; +} + + +bool KNArticleWindow::raiseWindowForArticle( KNArticle *art ) +{ + for ( QValueList<KNArticleWindow*>::Iterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->artW->article() && (*it)->artW->article() == art ) { + KWin::activateWindow( (*it)->winId() ); + return true; + } + return false; +} + + +bool KNArticleWindow::raiseWindowForArticle(const QCString &mid) +{ + for ( QValueList<KNArticleWindow*>::Iterator it = mInstances.begin(); it != mInstances.end(); ++it ) + if ( (*it)->artW->article() && (*it)->artW->article()->messageID()->as7BitString( false ) == mid ) { + KWin::activateWindow( (*it)->winId() ); + return true; + } + + return false; +} + + +//================================================================================== + +KNArticleWindow::KNArticleWindow(KNArticle *art) + : KMainWindow(0, "articleWindow") +{ + if(knGlobals.instance) + setInstance(knGlobals.instance); + + if(art) + setCaption(art->subject()->asUnicodeString()); + + artW = new ArticleWidget( this, this, actionCollection() ); + artW->setArticle(art); + setCentralWidget(artW); + + mInstances.append( this ); + + // file menu + KStdAction::close( this, SLOT(close()), actionCollection() ); + + // settings menu + KStdAction::preferences(knGlobals.top, SLOT(slotSettings()), actionCollection()); + + KAccel *accel = new KAccel( this ); + artW->setCharsetKeyboardAction()->plugAccel( accel ); + + setupGUI( ToolBar|Keys|Create, "knreaderui.rc"); + + KConfig *conf = knGlobals.config(); + conf->setGroup("articleWindow_options"); + resize(500,400); // default optimized for 800x600 + applyMainWindowSettings(conf); +} + + +KNArticleWindow::~KNArticleWindow() +{ + mInstances.remove( this ); + KConfig *conf = knGlobals.config(); + conf->setGroup("articleWindow_options"); + saveMainWindowSettings(conf); +} + +//-------------------------------- + +#include "knarticlewindow.moc" diff --git a/knode/knarticlewindow.h b/knode/knarticlewindow.h new file mode 100644 index 000000000..224af1dfb --- /dev/null +++ b/knode/knarticlewindow.h @@ -0,0 +1,47 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNARTICLEWINDOW_H +#define KNARTICLEWINDOW_H + +#include <kmainwindow.h> + +class KNArticle; +class KNArticleCollection; + +namespace KNode { + class ArticleWidget; +} + +class KNArticleWindow : public KMainWindow { + + Q_OBJECT + + public: + KNArticleWindow(KNArticle *art); + ~KNArticleWindow(); + KNode::ArticleWidget* artWidget()const { return artW; } + + static bool closeAllWindowsForCollection(KNArticleCollection *col, bool force=true); + static bool closeAllWindowsForArticle(KNArticle *art, bool force=true); + static bool raiseWindowForArticle(KNArticle *art); // false: no window found + static bool raiseWindowForArticle(const QCString &mid); + + protected: + KNode::ArticleWidget *artW; + static QValueList<KNArticleWindow*> mInstances; + +}; + +#endif diff --git a/knode/kncleanup.cpp b/knode/kncleanup.cpp new file mode 100644 index 000000000..4998bd4c2 --- /dev/null +++ b/knode/kncleanup.cpp @@ -0,0 +1,319 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <stdlib.h> + +#include <qdir.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qprogressbar.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kseparator.h> +#include <kapplication.h> +#include <kdebug.h> + +#include "knfolder.h" +#include "knglobals.h" +#include "kncleanup.h" +#include "knconfig.h" +#include "knfoldermanager.h" +#include "kngroupmanager.h" +#include "knarticlemanager.h" +#include "knnntpaccount.h" + + +KNCleanUp::KNCleanUp() : d_lg(0) +{ +} + + +KNCleanUp::~KNCleanUp() +{ + delete d_lg; +} + + +void KNCleanUp::start() +{ + if ( mColList.isEmpty() ) + return; + + d_lg = new ProgressDialog( mColList.count() ); + d_lg->show(); + + for ( QValueList<KNArticleCollection*>::Iterator it = mColList.begin(); it != mColList.end(); ++it ) { + if ( (*it)->type() == KNCollection::CTgroup ) { + d_lg->showMessage( i18n("Deleting expired articles in <b>%1</b>").arg( (*it)->name() ) ); + kapp->processEvents(); + expireGroup( static_cast<KNGroup*>( (*it) ) ); + d_lg->doProgress(); + } + else if ( (*it)->type() == KNCollection::CTfolder ) { + d_lg->showMessage( i18n("Compacting folder <b>%1</b>").arg( (*it)->name() ) ); + kapp->processEvents(); + compactFolder( static_cast<KNFolder*>( (*it) ) ); + d_lg->doProgress(); + } + } + + delete d_lg; + d_lg=0; +} + + +void KNCleanUp::reset() +{ + mColList.clear(); + if(d_lg) { + delete d_lg; + d_lg=0; + } +} + + +void KNCleanUp::expireGroup( KNGroup *g, bool showResult ) +{ + int expDays=0, idRef=0, foundId=-1, delCnt=0, leftCnt=0, newCnt=0, firstArtNr=g->firstNr(), firstNew=-1; + bool unavailable=false; + KNRemoteArticle *art, *ref; + + if (!g) + return; + + KNConfig::Cleanup *conf = g->activeCleanupConfig(); + + g->setNotUnloadable(true); + + if (!g->isLoaded() && !knGlobals.groupManager()->loadHeaders(g)) { + g->setNotUnloadable(false); + return; + } + + //find all expired + for(int i=0; i<g->length(); i++) { + art=g->at(i); + if(art->isRead()) + expDays = conf->maxAgeForRead(); + else + expDays = conf->maxAgeForUnread(); + + unavailable = false; + if ((art->articleNumber() != -1) && conf->removeUnavailable()) + unavailable = (art->articleNumber() < firstArtNr); + + art->setExpired( (art->date()->ageInDays() >= expDays) || unavailable ); + } + + //save threads + if (conf->preserveThreads()) { + for(int i=0; i<g->length(); i++) { + art=g->at(i); + if(!art->isExpired()) { + idRef=art->idRef(); + while(idRef!=0) { + ref=g->byId(idRef); + ref->setExpired(false); + idRef=ref->idRef(); + } + } + } + } + + //restore threading + for(int i=0; i<g->length(); i++) { + art=g->at(i); + if(!art->isExpired()) { + idRef=art->idRef(); + foundId=0; + while(foundId==0 && idRef!=0) { + ref=g->byId(idRef); + if(!ref->isExpired()) foundId=ref->id(); + idRef=ref->idRef(); + } + art->setIdRef(foundId); + } + } + + //delete expired + for(int i=0; i<g->length(); i++) { + art=g->at(i); + if(art->isExpired()) { + if(art->isRead()) + g->decReadCount(); + delCnt++; + if (art->hasContent()) + knGlobals.articleManager()->unloadArticle(art, true); + } + else if(art->isNew() && !art->isRead()) { + if(firstNew==-1) + firstNew=i; + newCnt++; + } + } + + g->setNotUnloadable(false); + + if(delCnt>0) { + g->saveStaticData(g->length(), true); + g->saveDynamicData(g->length(), true); + g->decCount(delCnt); + g->setNewCount(newCnt); + g->setFirstNewIndex(firstNew); + g->saveInfo(); + knGlobals.groupManager()->unloadHeaders(g, true); + } + else + g->syncDynamicData(); + + conf->setLastExpireDate(); + g->saveInfo(); + leftCnt=g->count(); + + kdDebug(5003) << "KNCleanUp::expireGroup() : " << g->groupname() << ": " + << delCnt << " deleted , " << leftCnt << " left" << endl; + + if(showResult) + KMessageBox::information(knGlobals.topWidget, + i18n("<b>%1</b><br>expired: %2<br>left: %3").arg(g->groupname()).arg(delCnt).arg(leftCnt)); +} + + +void KNCleanUp::compactFolder(KNFolder *f) +{ + KNLocalArticle *art; + + if (!f) + return; + + QDir dir(f->path()); + + if(!dir.exists()) + return; + + f->setNotUnloadable(true); + + if (!f->isLoaded() && !knGlobals.folderManager()->loadHeaders(f)) { + f->setNotUnloadable(false); + return; + } + + f->closeFiles(); + QFileInfo info(f->m_boxFile); + QString oldName=info.fileName(); + QString newName=oldName+".new"; + KNFile newMBoxFile(info.dirPath(true)+"/"+newName); + + if( (f->m_boxFile.open(IO_ReadOnly)) && (newMBoxFile.open(IO_WriteOnly)) ) { + QTextStream ts(&newMBoxFile); + ts.setEncoding(QTextStream::Latin1); + for(int idx=0; idx<f->length(); idx++) { + art=f->at(idx); + if(f->m_boxFile.at(art->startOffset())) { + ts << "From aaa@aaa Mon Jan 01 00:00:00 1997\n"; + art->setStartOffset(newMBoxFile.at()); + while(f->m_boxFile.at() < (uint)art->endOffset()) + ts << f->m_boxFile.readLineWnewLine(); + art->setEndOffset(newMBoxFile.at()); + newMBoxFile.putch('\n'); + } + } + + f->syncIndex(true); + newMBoxFile.close(); + f->closeFiles(); + + dir.remove(oldName); + dir.rename(newName, oldName); + } + + f->setNotUnloadable(false); +} + + +//=============================================================================================== + + +KNCleanUp::ProgressDialog::ProgressDialog(int steps) + : QDialog(knGlobals.topWidget, 0, true) +{ + const int w=400, + h=160; + + p_rogress=0; + s_teps=steps; + + setCaption(kapp->makeStdCaption(i18n("Cleaning Up"))); + + setFixedSize(w,h); + QFrame *top=new QFrame(this); + top->setGeometry(0,0, w,h); + + QVBoxLayout *topL=new QVBoxLayout(top, 10); + + QLabel *l=new QLabel(i18n("Cleaning up. Please wait..."), top); + topL->addWidget(l); + + KSeparator *sep=new KSeparator(top); + topL->addWidget(sep); + + m_sg=new QLabel(top); + topL->addWidget(m_sg); + + p_bar=new QProgressBar(top); + topL->addWidget(p_bar); + p_bar->setTotalSteps(100*s_teps); + p_bar->setProgress(1); + + + if(knGlobals.topWidget->isVisible()) { + int x, y; + x=(knGlobals.topWidget->width()-w)/2; + y=(knGlobals.topWidget->height()-h)/2; + if(x<0 || y<0) { + x=0; + y=0; + } + x+=knGlobals.topWidget->x(); + y+=knGlobals.topWidget->y(); + move(x,y); + } +} + + +KNCleanUp::ProgressDialog::~ProgressDialog() +{ +} + + +void KNCleanUp::ProgressDialog::showMessage(const QString &s) +{ + m_sg->setText(s); +} + + +void KNCleanUp::ProgressDialog::doProgress() +{ + p_rogress++; + p_bar->setProgress(p_rogress*100); +} + + +void KNCleanUp::ProgressDialog::closeEvent(QCloseEvent *) +{ + // do nothing => prevent that the user closes the window +} + +// kate: space-indent on; indent-width 2; diff --git a/knode/kncleanup.h b/knode/kncleanup.h new file mode 100644 index 000000000..81e453dff --- /dev/null +++ b/knode/kncleanup.h @@ -0,0 +1,72 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCLEANUP_H +#define KNCLEANUP_H + +#include <qsemimodal.h> + +class QProgressBar; +class QLabel; + +class KNArticleCollection; +class KNGroup; +class KNFolder; + +namespace KNConfig { +class Cleanup; +} + + +class KNCleanUp { + + public: + KNCleanUp(); + ~KNCleanUp(); + + void appendCollection(KNArticleCollection *c) { mColList.append( c ); } + void start(); + void reset(); + + void expireGroup( KNGroup *g, bool showResult = false ); + void compactFolder(KNFolder *f); + + protected: + + class ProgressDialog : public QDialog { + + public: + ProgressDialog(int steps); + ~ProgressDialog(); + + void showMessage(const QString &s); + void doProgress(); + + protected: + void closeEvent(QCloseEvent *e); + + QLabel *m_sg; + QProgressBar *p_bar; + + int s_teps, p_rogress; + }; + + ProgressDialog *d_lg; + QValueList<KNArticleCollection*> mColList; + +}; + +#endif + +// kate: space-indent on; indent-width 2; diff --git a/knode/kncollection.cpp b/knode/kncollection.cpp new file mode 100644 index 000000000..28651fad9 --- /dev/null +++ b/knode/kncollection.cpp @@ -0,0 +1,50 @@ +/* + kncollection.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include "kncollection.h" +#include "kncollectionviewitem.h" + +KNCollection::KNCollection(KNCollection *p) +{ + p_arent=p; + l_istItem=0; + c_ount=0; +} + + + +KNCollection::~KNCollection() +{ + delete l_istItem; +} + + + +void KNCollection::setListItem(KNCollectionViewItem *i) +{ + l_istItem=i; + if(i) { + i->coll=this; + i->setText(0, name()); + } +} + + + +void KNCollection::updateListItem() +{ + if(l_istItem) l_istItem->setText(0, name()); +} diff --git a/knode/kncollection.h b/knode/kncollection.h new file mode 100644 index 000000000..80907ca55 --- /dev/null +++ b/knode/kncollection.h @@ -0,0 +1,70 @@ +/* + kncollection.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCOLLECTION_H +#define KNCOLLECTION_H + +#include <qstring.h> + +class KNCollectionViewItem; + + +class KNCollection { + + public: + enum collectionType { CTnntpAccount, CTgroup, + CTfolder, CTcategory, + CTvirtualGroup }; + + KNCollection(KNCollection *p); + virtual ~KNCollection(); + + // type + virtual collectionType type()=0; + + // list item handling + KNCollectionViewItem* listItem()const { return l_istItem; } + void setListItem(KNCollectionViewItem *i); + virtual void updateListItem(); + + // info + virtual QString path()=0; + virtual bool readInfo(const QString &confPath)=0; + virtual void saveInfo()=0; + + // parent + KNCollection* parent()const { return p_arent; } + virtual void setParent(KNCollection *p) { p_arent=p; } + + // name + virtual const QString& name() { return n_ame; } + void setName(const QString &s) { n_ame=s; } + + // count + int count()const { return c_ount; } + void setCount(int i) { c_ount=i; } + void incCount(int i) { c_ount+=i; } + void decCount(int i) { c_ount-=i; } + + protected: + KNCollection *p_arent; + KNCollectionViewItem *l_istItem; + QString n_ame; + int c_ount; + +}; + +#endif diff --git a/knode/kncollectionview.cpp b/knode/kncollectionview.cpp new file mode 100644 index 000000000..2d48a9e5d --- /dev/null +++ b/knode/kncollectionview.cpp @@ -0,0 +1,457 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2004-2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#include <qcursor.h> +#include <qheader.h> + +#include <kiconloader.h> +#include <klistview.h> +#include <klocale.h> +#include <kpopupmenu.h> + +#include "knglobals.h" +#include "knconfig.h" +#include "knconfigmanager.h" +#include "knnntpaccount.h" +#include "knaccountmanager.h" +#include "kngroup.h" +#include "kngroupmanager.h" +#include "knfolder.h" +#include "knfoldermanager.h" +#include "headerview.h" +#include "kncollectionview.h" +#include "kncollectionviewitem.h" + +KNCollectionView::KNCollectionView(QWidget *parent, const char* name) : + KFolderTree(parent, name), + mActiveItem( 0 ), + mPopup( 0 ) +{ + setDragEnabled(true); + addAcceptableDropMimetype("x-knode-drag/article", false); + addAcceptableDropMimetype("x-knode-drag/folder", true); + addColumn(i18n("Name"),162); + setDropHighlighter(true); + + // popup menu to enable/disable unread and total columns + header()->setClickEnabled( true ); + header()->installEventFilter( this ); + mPopup = new KPopupMenu( this ); + mPopup->insertTitle( i18n("View Columns") ); + mPopup->setCheckable( true ); + mUnreadPop = mPopup->insertItem( i18n("Unread Column"), this, SLOT(toggleUnreadColumn()) ); + mTotalPop = mPopup->insertItem( i18n("Total Column"), this, SLOT(toggleTotalColumn()) ); + + // add unread and total columns if necessary + readConfig(); + + // load accounts and folders + reloadAccounts(); + reloadFolders(); + + // connect to the account manager + KNAccountManager* am = knGlobals.accountManager(); + connect(am, SIGNAL(accountAdded(KNNntpAccount*)), SLOT(addAccount(KNNntpAccount*))); + connect(am, SIGNAL(accountRemoved(KNNntpAccount*)), SLOT(removeAccount(KNNntpAccount*))); + connect(am, SIGNAL(accountModified(KNNntpAccount*)), SLOT(updateAccount(KNNntpAccount*))); + + // connect to the group manager + KNGroupManager* gm = knGlobals.groupManager(); + connect(gm, SIGNAL(groupAdded(KNGroup*)), SLOT(addGroup(KNGroup*))); + connect(gm, SIGNAL(groupRemoved(KNGroup*)), SLOT(removeGroup(KNGroup*))); + connect(gm, SIGNAL(groupUpdated(KNGroup*)), SLOT(updateGroup(KNGroup*))); + + // connect to the folder manager + KNFolderManager* fm = knGlobals.folderManager(); + connect(fm, SIGNAL(folderAdded(KNFolder*)), SLOT(addPendingFolders())); + connect(fm, SIGNAL(folderRemoved(KNFolder*)), SLOT(removeFolder(KNFolder*))); + connect(fm, SIGNAL(folderActivated(KNFolder*)), SLOT(activateFolder(KNFolder*))); + + installEventFilter(this); +} + + +KNCollectionView::~KNCollectionView() +{ + writeConfig(); +} + + + +void KNCollectionView::readConfig() +{ + KConfig *conf = knGlobals.config(); + conf->setGroup( "GroupView" ); + + // execute the listview layout stuff only once + static bool initDone = false; + if (!initDone) { + initDone = true; + const int unreadColumn = conf->readNumEntry("UnreadColumn", 1); + const int totalColumn = conf->readNumEntry("TotalColumn", 2); + + // we need to _activate_ them in the correct order + // this is ugly because we can't use header()->moveSection + // but otherwise the restoreLayout doesn't know that to do + if (unreadColumn != -1 && unreadColumn < totalColumn) + addUnreadColumn( i18n("Unread"), 48 ); + if (totalColumn != -1) + addTotalColumn( i18n("Total"), 36 ); + if (unreadColumn != -1 && unreadColumn > totalColumn) + addUnreadColumn( i18n("Unread"), 48 ); + updatePopup(); + + restoreLayout( knGlobals.config(), "GroupView" ); + } + + // font & color settings + KNConfig::Appearance *app = knGlobals.configManager()->appearance(); + setFont( app->groupListFont() ); + + QPalette p = palette(); + p.setColor( QColorGroup::Base, app->backgroundColor() ); + p.setColor( QColorGroup::Text, app->textColor() ); + setPalette( p ); + setAlternateBackground( app->backgroundColor() ); + // FIXME: make this configurable + mPaintInfo.colUnread = QColor( "blue" ); + mPaintInfo.colFore = app->textColor(); + mPaintInfo.colBack = app->backgroundColor(); +} + + +void KNCollectionView::writeConfig() +{ + KConfig *conf = knGlobals.config(); + conf->setGroup( "GroupView" ); + saveLayout( knGlobals.config(), "GroupView" ); + conf->writeEntry( "UnreadColumn", unreadIndex() ); + conf->writeEntry( "TotalColumn", totalIndex() ); +} + + + +void KNCollectionView::addAccount(KNNntpAccount *a) +{ + // add account item + KNCollectionViewItem* item = new KNCollectionViewItem( this, KFolderTreeItem::News ); + a->setListItem( item ); + item->setOpen( a->wasOpen() ); + + // add groups for this account + QValueList<KNGroup*> groups = knGlobals.groupManager()->groupsOfAccount( a ); + for ( QValueList<KNGroup*>::Iterator it = groups.begin(); it != groups.end(); ++it ) { + KNCollectionViewItem *gitem = new KNCollectionViewItem( item, KFolderTreeItem::News ); + (*it)->setListItem( gitem ); + (*it)->updateListItem(); + } +} + + +void KNCollectionView::removeAccount(KNNntpAccount *a) +{ + if(!a->listItem()) + return; + KNCollectionViewItem *child = 0, *aitem = a->listItem(); + while((child = static_cast<KNCollectionViewItem*>(aitem->firstChild()))) + removeGroup(static_cast<KNGroup*>(child->coll)); + delete aitem; + a->setListItem(0); +} + + +void KNCollectionView::updateAccount(KNNntpAccount *a) +{ + a->updateListItem(); +} + + +void KNCollectionView::reloadAccounts() +{ + KNAccountManager* am = knGlobals.accountManager(); + QValueList<KNNntpAccount*>::Iterator it; + for ( it = am->begin(); it != am->end(); ++it ) { + removeAccount( *it ); + addAccount( *it ); + } +} + + + +void KNCollectionView::addGroup(KNGroup *g) +{ + if (!g->account()->listItem()) + return; + + KNCollectionViewItem *gitem = + new KNCollectionViewItem( g->account()->listItem(), KFolderTreeItem::News ); + g->setListItem(gitem); + updateGroup(g); +} + + +void KNCollectionView::removeGroup(KNGroup *g) +{ + if (!g->listItem()) + return; + + delete g->listItem(); + g->setListItem(0); +} + + +void KNCollectionView::updateGroup(KNGroup *g) +{ + g->updateListItem(); +} + + + +void KNCollectionView::addFolder(KNFolder *f) +{ + KNCollectionViewItem *it; + + if (!f->parent()) { + // root folder + it = new KNCollectionViewItem(this, KFolderTreeItem::Local); + } else { + // make sure the parent folder has already been added + if (!f->parent()->listItem()) + addFolder( static_cast<KNFolder*>(f->parent()) ); + // handle special folders + KFolderTreeItem::Type type = KFolderTreeItem::Other; + switch ( f->id() ) { + case 1: + type = KFolderTreeItem::Drafts; break; + case 2: + type = KFolderTreeItem::Outbox; break; + case 3: + type = KFolderTreeItem::SentMail; break; + } + it = new KNCollectionViewItem( f->parent()->listItem(), KFolderTreeItem::Local, type ); + } + f->setListItem( it ); + updateFolder( f ); +} + + +void KNCollectionView::removeFolder(KNFolder* f) +{ + if(!f->listItem()) + return; + KNCollectionViewItem *child = 0, *it = f->listItem(); + while((child = static_cast<KNCollectionViewItem*>(it->firstChild()))) + removeFolder(static_cast<KNFolder*>(child->coll)); + delete f->listItem(); + f->setListItem(0); +} + + +void KNCollectionView::reloadFolders() +{ + // remove existing folder items + removeFolder(knGlobals.folderManager()->root()); + + // add folder items + addPendingFolders(); +} + + +void KNCollectionView::addPendingFolders() +{ + QValueList<KNFolder*> folders = knGlobals.folderManager()->folders(); + for ( QValueList<KNFolder*>::Iterator it = folders.begin(); it != folders.end(); ++it ) + if ( !(*it)->listItem() ) + addFolder( (*it) ); + // now open the folders if they were open in the last session + for ( QValueList<KNFolder*>::Iterator it = folders.begin(); it != folders.end(); ++it ) + if ( (*it)->listItem()) + (*it)->listItem()->setOpen( (*it)->wasOpen() ); +} + + +void KNCollectionView::activateFolder(KNFolder* f) +{ + if(f->listItem()) + setActive( f->listItem() ); +} + + +void KNCollectionView::updateFolder(KNFolder* f) +{ + f->updateListItem(); +} + + +void KNCollectionView::reload() +{ + reloadAccounts(); + reloadFolders(); +} + +void KNCollectionView::setActive( QListViewItem *i ) +{ + if (!i || mActiveItem == i) + return; + + clearSelection(); + setSelected( i, true ); + setCurrentItem( i ); + mActiveItem = i; + emit( selectionChanged( i ) ); +} + + +void KNCollectionView::nextGroup() +{ + incCurrentFolder(); + setActive( currentItem() ); +} + + +void KNCollectionView::prevGroup() +{ + decCurrentFolder(); + setActive( currentItem() ); +} + + +void KNCollectionView::decCurrentFolder() +{ + QListViewItemIterator it( currentItem() ); + --it; + KFolderTreeItem* fti = static_cast<KFolderTreeItem*>(it.current()); + if (fti) { + ensureItemVisible( fti ); + setFocus(); + setCurrentItem( fti ); + } +} + + +void KNCollectionView::incCurrentFolder() +{ + QListViewItemIterator it( currentItem() ); + ++it; + KFolderTreeItem* fti = static_cast<KFolderTreeItem*>(it.current()); + if (fti) { + ensureItemVisible( fti ); + setFocus(); + setCurrentItem( fti ); + } +} + + +void KNCollectionView::selectCurrentFolder() +{ + KFolderTreeItem* fti = static_cast<KFolderTreeItem*>( currentItem() ); + if (fti) { + ensureItemVisible( fti ); + setActive( fti ); + } +} + + +QDragObject* KNCollectionView::dragObject() +{ + KFolderTreeItem *item = static_cast<KFolderTreeItem*> + (itemAt(viewport()->mapFromGlobal(QCursor::pos()))); + if ( item && item->protocol() == KFolderTreeItem::Local && item->type() == KFolderTreeItem::Other ) { + QDragObject *d = new QStoredDrag( "x-knode-drag/folder", viewport() ); + d->setPixmap( SmallIcon("folder") ); + return d; + } + return 0; +} + + +void KNCollectionView::contentsDropEvent( QDropEvent *e ) +{ + cleanItemHighlighter(); // necessary since we overwrite KListView::contentsDropEvent() + QListViewItem *item = itemAt( contentsToViewport(e->pos()) ); + KNCollectionViewItem *fti = static_cast<KNCollectionViewItem*>(item); + if (fti && (fti->coll) && acceptDrag(e)) { + emit folderDrop( e, fti ); + e->accept( true ); + } + else + e->accept( false ); +} + + + +void KNCollectionView::toggleUnreadColumn() +{ + if ( isUnreadActive() ) + removeUnreadColumn(); + else + addUnreadColumn( i18n("Unread"), 48 ); + mPopup->setItemChecked( mUnreadPop, isUnreadActive() ); + reload(); +} + + +void KNCollectionView::toggleTotalColumn() +{ + if ( isTotalActive() ) + removeTotalColumn(); + else + addTotalColumn( i18n("Total"), 36 ); + mPopup->setItemChecked( mTotalPop, isTotalActive() ); + reload(); +} + +void KNCollectionView::updatePopup() const +{ + mPopup->setItemChecked( mUnreadPop, isUnreadActive() ); + mPopup->setItemChecked( mTotalPop, isTotalActive() ); +} + + + +bool KNCollectionView::eventFilter(QObject *o, QEvent *e) +{ + if ((e->type() == QEvent::KeyPress) && (static_cast<QKeyEvent*>(e)->key() == Key_Tab)) { + emit(focusChangeRequest(this)); + if (!hasFocus()) // focusChangeRequest was successful + return true; + } + + // header popup menu + if ( e->type() == QEvent::MouseButtonPress && + static_cast<QMouseEvent*>(e)->button() == RightButton && + o->isA("QHeader") ) + { + mPopup->popup( static_cast<QMouseEvent*>(e)->globalPos() ); + return true; + } + + return KFolderTree::eventFilter(o, e); +} + + +void KNCollectionView::focusInEvent(QFocusEvent *e) +{ + QListView::focusInEvent(e); + emit focusChanged(e); +} + + +void KNCollectionView::focusOutEvent(QFocusEvent *e) +{ + QListView::focusOutEvent(e); + emit focusChanged(e); +} + + +#include "kncollectionview.moc" diff --git a/knode/kncollectionview.h b/knode/kncollectionview.h new file mode 100644 index 000000000..3c7e0f61c --- /dev/null +++ b/knode/kncollectionview.h @@ -0,0 +1,93 @@ +/* + kncollectionview.h + + KNode, the KDE newsreader + Copyright (c) 2004 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#ifndef KNCOLLECTIONTREE_H +#define KNCOLLECTIONTREE_H + +#include <kfoldertree.h> + +class KPopupMenu; +class KNNntpAccount; +class KNGroup; +class KNFolder; +class KNCollectionViewItem; + +class KNCollectionView : public KFolderTree { + + Q_OBJECT + + public: + KNCollectionView(QWidget *parent, const char *name = 0); + ~KNCollectionView(); + + void setActive(QListViewItem *item); + + void readConfig(); + void writeConfig(); + + public slots: + void addAccount(KNNntpAccount* a); + void removeAccount(KNNntpAccount* a); + void updateAccount(KNNntpAccount* a); + void reloadAccounts(); + + void addGroup(KNGroup* g); + void removeGroup(KNGroup* g); + void updateGroup(KNGroup* g); + + void addFolder(KNFolder* f); + void removeFolder(KNFolder* f); + void activateFolder(KNFolder* f); + void updateFolder(KNFolder* f); + void addPendingFolders(); + void reloadFolders(); + + void reload(); + + void nextGroup(); + void prevGroup(); + + // KMail like keyboard navigation + void decCurrentFolder(); + void incCurrentFolder(); + void selectCurrentFolder(); + + void toggleUnreadColumn(); + void toggleTotalColumn(); + void updatePopup() const; + + signals: + void folderDrop( QDropEvent *e, KNCollectionViewItem *item ); + + void focusChanged( QFocusEvent* ); + void focusChangeRequest( QWidget* ); + + protected: + // dnd + virtual QDragObject* dragObject(); + virtual void contentsDropEvent( QDropEvent *e ); + + bool eventFilter( QObject *, QEvent * ); + void focusInEvent( QFocusEvent *e ); + void focusOutEvent( QFocusEvent *e ); + + private: + QListViewItem *mActiveItem; + KPopupMenu *mPopup; + int mUnreadPop, mTotalPop; + +}; + +#endif diff --git a/knode/kncollectionviewitem.cpp b/knode/kncollectionviewitem.cpp new file mode 100644 index 000000000..ace78a63a --- /dev/null +++ b/knode/kncollectionviewitem.cpp @@ -0,0 +1,172 @@ +/* + kncollectionviewitem.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <qdragobject.h> +#include <qpainter.h> + +#include <kiconloader.h> +#include <kstringhandler.h> + +#include "kncollectionviewitem.h" +#include "kncollectionview.h" +#include "kngroup.h" +#include "knfolder.h" +#include "knglobals.h" +#include "knconfigmanager.h" + + +KNCollectionViewItem::KNCollectionViewItem( KFolderTree *parent, Protocol protocol, Type type) : + KFolderTreeItem(parent, QString::null, protocol, type), coll(0) +{ + setIcon(); +} + + +KNCollectionViewItem::KNCollectionViewItem( KFolderTreeItem *it, Protocol protocol, Type type, int unread, int total ) : + KFolderTreeItem(it, QString::null, protocol, type, unread, total), coll(0) +{ + setIcon(); +} + + +KNCollectionViewItem::~KNCollectionViewItem() +{ + if(coll) coll->setListItem(0); +} + + +void KNCollectionViewItem::setIcon() { + if ( protocol() == KFolderTreeItem::News ) { + // news servers/groups + switch ( type() ) { + case KFolderTreeItem::Root: + setPixmap( 0, SmallIcon("server") ); + break; + default: + setPixmap( 0, UserIcon("group") ); + } + } else { + // local folders + switch ( type() ) { + case KFolderTreeItem::Outbox: + setPixmap( 0, SmallIcon("folder_outbox") ); + break; + case KFolderTreeItem::Drafts: + setPixmap( 0, SmallIcon("edit") ); + break; + case KFolderTreeItem::SentMail: + setPixmap( 0, SmallIcon("folder_sent_mail") ); + break; + default: + setPixmap( 0, SmallIcon("folder") ); + } + } +} + + +int KNCollectionViewItem::compare(QListViewItem *i, int col, bool ascending) const +{ + KFolderTreeItem *other = static_cast<KFolderTreeItem*>(i); + + // folders should be always on the bottom + if (protocol() == KFolderTreeItem::Local) { + if (other && other->protocol() == KFolderTreeItem::News) + return ascending ? 1 : -1; + } + + // news servers should be always on top + if (protocol() == KFolderTreeItem::News) { + if (other && other->protocol() == KFolderTreeItem::Local) + return ascending ? -1 : 1; + } + + return KFolderTreeItem::compare(i, col, ascending); +} + + +bool KNCollectionViewItem::acceptDrag(QDropEvent* event) const +{ + if (event && coll && coll->type()==KNCollection::CTfolder) { + if (event->provides("x-knode-drag/article")) + return !(static_cast<KNFolder*>(coll)->isRootFolder()); // don't drop articles on the root folder + else if (event->provides("x-knode-drag/folder")) + return !isSelected(); // don't drop on itself + } + return false; +} + + +void KNCollectionViewItem::paintCell( QPainter * p, const QColorGroup & cg,int column, int width, int align ) +{ + KFolderTree *ft = static_cast<KFolderTree*>( listView() ); + + // we only need to deal with the case where we paint the folder/group name + // and the unread count is displayed in a separate column + if ( !ft->isUnreadActive() || column != 0 ) { + KFolderTreeItem::paintCell( p, cg, column, width, align ); + return; + } + + // find out if we will use bold font, necessary for the text squeezing + if ( (column == 0 || column == ft->unreadIndex()) && ( mUnread > 0 ) ) { + QFont f = p->font(); + f.setWeight(QFont::Bold); + p->setFont(f); + } + + // consider pixmap size for squeezing + int pxWidth = 8; + const QPixmap *px = pixmap(column); + if (px) + pxWidth += px->width(); + + // temporary set the squeezed text and use the parent class to paint it + QString curText = text( column ); + if ( p->fontMetrics().width( curText ) > width - pxWidth ) { + setText( column, squeezeFolderName( curText, p->fontMetrics(), width - pxWidth ) ); + KFolderTreeItem::paintCell( p, cg, column, width, align ); + setText( column, curText ); + } else + KFolderTreeItem::paintCell( p, cg, column, width, align ); +} + + +QString KNCollectionViewItem::squeezeFolderName( const QString &text, + const QFontMetrics &fm, + uint width ) const +{ + if (protocol() == KFolderTreeItem::News && type() == KFolderTreeItem::Other) { + QString t(text); + int curPos = 0, nextPos = 0; + QString temp; + while ( (uint)fm.width(t) > width && nextPos != -1 ) { + nextPos = t.find('.', curPos); + if ( nextPos != -1 ) { + temp = t[curPos]; + t.replace( curPos, nextPos - curPos, temp ); + curPos += 2; + } + } + if ( (uint)fm.width( t ) > width ) + t = KStringHandler::rPixelSqueeze( t, fm, width ); + return t; + } else + return KFolderTreeItem::squeezeFolderName( text, fm, width ); +} diff --git a/knode/kncollectionviewitem.h b/knode/kncollectionviewitem.h new file mode 100644 index 000000000..0d13678f2 --- /dev/null +++ b/knode/kncollectionviewitem.h @@ -0,0 +1,56 @@ +/* + kncollectionviewitem.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCOLLECTIONVIEWITEM_H +#define KNCOLLECTIONVIEWITEM_H + +#include <kfoldertree.h> + +class QPainter; +class QColorGroup; + +class KNCollection; + + +class KNCollectionViewItem : public KFolderTreeItem { + + public: + KNCollectionViewItem( KFolderTree *parent, Protocol protocol = NONE, Type type = Root); + KNCollectionViewItem( KFolderTreeItem *parent, Protocol protocol = NONE, + Type type = Other, int unread = 0, int total = 0 ); + ~KNCollectionViewItem(); + + void paintCell( QPainter * p, const QColorGroup & cg, + int column, int width, int align ); + + int compare(QListViewItem *i, int col, bool ascending) const; + + // DND + virtual bool acceptDrag(QDropEvent* event) const; + + KNCollection *coll; + + protected: + virtual QString squeezeFolderName( const QString &text, + const QFontMetrics &fm, + uint width ) const; + + private: + void setIcon(); + +}; + +#endif diff --git a/knode/kncomposer.cpp b/knode/kncomposer.cpp new file mode 100644 index 000000000..b6fdd4249 --- /dev/null +++ b/knode/kncomposer.cpp @@ -0,0 +1,2661 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qheader.h> +#include <qtextcodec.h> +#include <qclipboard.h> +#include <qapplication.h> +#include <kspelldlg.h> +#include <kdeversion.h> +#include "addressesdialog.h" +using KPIM::AddressesDialog; +#include "recentaddresses.h" +using KRecentAddress::RecentAddresses; +#include <kaccel.h> +#include <kcharsets.h> +#include <kmessagebox.h> +#include <kabc/addresseedialog.h> +#include <kaction.h> +#include <kstdaction.h> +#include <kkeydialog.h> +#include <kedittoolbar.h> +#include <kpopupmenu.h> +#include <kfiledialog.h> +#include <kdebug.h> +#include <klineedit.h> +#include <kcombobox.h> +#include <kspell.h> +#include <ktempfile.h> +#include <kpgp.h> +#include <kpgpblock.h> +#include <kprocess.h> +#include <kqcstringsplitter.h> +#include <ksyntaxhighlighter.h> +#include <qcursor.h> +#include <kurldrag.h> +#include <kcompletionbox.h> + +#include <kapplication.h> +#include "kngroupselectdialog.h" +#include "utilities.h" +#include "knglobals.h" +#include "kncomposer.h" +#include "knmainwidget.h" +#include "knconfigmanager.h" +#include "knaccountmanager.h" +#include "knnntpaccount.h" +#include "knarticlefactory.h" +#include <kstatusbar.h> +#include <klocale.h> +#include <qpopupmenu.h> +#include <spellingfilter.h> +#include <kstdguiitem.h> + +KNLineEdit::KNLineEdit(KNComposer::ComposerView *_composerView, bool useCompletion, + QWidget *parent, const char *name) + : KNLineEditInherited(parent,useCompletion,name) , composerView(_composerView) + +{ +} + + +QPopupMenu *KNLineEdit::createPopupMenu() +{ + QPopupMenu *menu = KLineEdit::createPopupMenu(); + if ( !menu ) + return 0; + + menu->insertSeparator(); + menu->insertItem( i18n( "Edit Recent Addresses..." ), + this, SLOT( editRecentAddresses() ) ); + + return menu; +} + +void KNLineEdit::editRecentAddresses() +{ + KRecentAddress::RecentAddressDialog dlg( this ); + dlg.setAddresses( RecentAddresses::self( knGlobals.config() )->addresses() ); + if ( dlg.exec() ) { + RecentAddresses::self( knGlobals.config() )->clear(); + QStringList addrList = dlg.addresses(); + QStringList::Iterator it; + for ( it = addrList.begin(); it != addrList.end(); ++it ) + RecentAddresses::self( knGlobals.config() )->add( *it ); + + loadAddresses(); + } +} + +void KNLineEdit::loadAddresses() +{ + KNLineEditInherited::loadAddresses(); + + QStringList recent = RecentAddresses::self(knGlobals.config())->addresses(); + QStringList::Iterator it = recent.begin(); + for ( ; it != recent.end(); ++it ) + addAddress( *it ); +} + +void KNLineEdit::keyPressEvent(QKeyEvent *e) +{ + // ---sven's Return is same Tab and arrow key navigation start --- + if ((e->key() == Key_Enter || e->key() == Key_Return) && + !completionBox()->isVisible()) + { + composerView->focusNextPrevEdit( this, true ); + return; + } + if (e->key() == Key_Up) + { + composerView->focusNextPrevEdit( this, false ); // Go up + return; + } + if (e->key() == Key_Down) + { + composerView->focusNextPrevEdit( this, true ); // Go down + return; + } + // ---sven's Return is same Tab and arrow key navigation end --- + KNLineEditInherited::keyPressEvent(e); +} + +KNLineEditSpell::KNLineEditSpell( KNComposer::ComposerView *_composerView, bool useCompletion,QWidget * parent, const char * name) + :KNLineEdit( _composerView, useCompletion, parent,name ) +{ +} + +void KNLineEditSpell::highLightWord( unsigned int length, unsigned int pos ) +{ + setSelection ( pos, length ); +} + +void KNLineEditSpell::spellCheckDone( const QString &s ) +{ + if( s != text() ) + setText( s ); +} + +void KNLineEditSpell::spellCheckerMisspelling( const QString &_text, const QStringList &, unsigned int pos) +{ + highLightWord( _text.length(),pos ); +} + +void KNLineEditSpell::spellCheckerCorrected( const QString &old, const QString &corr, unsigned int pos) +{ + if( old!= corr ) + { + setSelection ( pos, old.length() ); + insert( corr ); + setSelection ( pos, corr.length() ); + } +} + + +KNComposer::KNComposer(KNLocalArticle *a, const QString &text, const QString &sig, const QString &unwraped, bool firstEdit, bool dislikesCopies, bool createCopy) + : KMainWindow(0,"composerWindow"), r_esult(CRsave), a_rticle(a), s_ignature(sig), u_nwraped(unwraped), + n_eeds8Bit(true), v_alidated(false), a_uthorDislikesMailCopies(dislikesCopies), e_xternalEdited(false), e_xternalEditor(0), + e_ditorTempfile(0), s_pellChecker(0), a_ttChanged(false), + mFirstEdit( firstEdit ) +{ + mSpellingFilter = 0; + spellLineEdit = false; + m_listAction.setAutoDelete( true ); + + if(knGlobals.instance) + setInstance(knGlobals.instance); + + // activate dnd of attachments... + setAcceptDrops(true); + + //init v_iew + v_iew=new ComposerView(this); + setCentralWidget(v_iew); + + connect(v_iew->c_ancelEditorBtn, SIGNAL(clicked()), SLOT(slotCancelEditor())); + connect(v_iew->e_dit, SIGNAL(sigDragEnterEvent(QDragEnterEvent *)), SLOT(slotDragEnterEvent(QDragEnterEvent *))); + connect(v_iew->e_dit, SIGNAL(sigDropEvent(QDropEvent *)), SLOT(slotDropEvent(QDropEvent *))); + + //statusbar + KStatusBar *sb=statusBar(); + sb->insertItem(QString::null, 1,1); // type + sb->setItemAlignment (1,AlignLeft | AlignVCenter); + sb->insertItem(QString::null, 2,1); // charset + sb->setItemAlignment (2,AlignLeft | AlignVCenter); + sb->insertItem(QString::null, 3,0); // column + sb->setItemAlignment (3,AlignCenter | AlignVCenter); + sb->insertItem(QString::null, 4,0); // column + sb->setItemAlignment (4,AlignCenter | AlignVCenter); + sb->insertItem(QString::null, 5,0); // line + sb->setItemAlignment (5,AlignCenter | AlignVCenter); + connect(v_iew->e_dit, SIGNAL(CursorPositionChanged()), SLOT(slotUpdateCursorPos())); + connect(v_iew->e_dit, SIGNAL(toggle_overwrite_signal()), SLOT(slotUpdateStatusBar())); + + //------------------------------- <Actions> -------------------------------------- + + //file menu + new KAction(i18n("&Send Now"),"mail_send", CTRL + Key_Return , this, + SLOT(slotSendNow()), actionCollection(), "send_now"); + + new KAction(i18n("Send &Later"), "queue", 0, this, + SLOT(slotSendLater()), actionCollection(), "send_later"); + + new KAction(i18n("Save as &Draft"),"filesave", 0 , this, + SLOT(slotSaveAsDraft()), actionCollection(), "save_as_draft"); + + new KAction(i18n("D&elete"),"editdelete", 0 , this, + SLOT(slotArtDelete()), actionCollection(), "art_delete"); + + KStdAction::close(this, SLOT(close()),actionCollection()); + + //edit menu + KStdAction::undo(this, SLOT(slotUndo()), actionCollection()); + KStdAction::redo(this, SLOT(slotRedo()), actionCollection()); + + KStdAction::cut(this, SLOT(slotCut()), actionCollection()); + + + KStdAction::copy(this, SLOT(slotCopy()), actionCollection()); + + KStdAction::pasteText(this, SLOT(slotPaste()), actionCollection()); + + new KAction(i18n("Paste as &Quotation"), 0, v_iew->e_dit, + SLOT(slotPasteAsQuotation()), actionCollection(), "paste_quoted"); + + KStdAction::selectAll(this, SLOT(slotSelectAll()), actionCollection()); + + KStdAction::find(v_iew->e_dit, SLOT(slotFind()), actionCollection()); + KStdAction::findNext(v_iew->e_dit, SLOT(slotSearchAgain()), actionCollection()); + + KStdAction::replace(v_iew->e_dit, SLOT(slotReplace()), actionCollection()); + + //attach menu + new KAction(i18n("Append &Signature"), 0 , this, SLOT(slotAppendSig()), + actionCollection(), "append_signature"); + + new KAction(i18n("&Insert File..."), 0, this, SLOT(slotInsertFile()), + actionCollection(), "insert_file"); + + new KAction(i18n("Insert File (in a &box)..."), 0, this, SLOT(slotInsertFileBoxed()), + actionCollection(), "insert_file_boxed"); + + new KAction(i18n("Attach &File..."), "attach", 0, this, SLOT(slotAttachFile()), + actionCollection(), "attach_file"); + + a_ctPGPsign = new KToggleAction(i18n("Sign Article with &PGP"), + "signature", 0, + actionCollection(), "sign_article"); + + a_ctRemoveAttachment = new KAction(i18n("&Remove"), 0, this, + SLOT(slotRemoveAttachment()), actionCollection(), "remove_attachment"); + + a_ctAttachmentProperties = new KAction(i18n("&Properties"), 0, this, + SLOT(slotAttachmentProperties()), actionCollection(), "attachment_properties"); + + //options menu + + a_ctDoPost = new KToggleAction(i18n("Send &News Article"), "filenew", 0 , this, + SLOT(slotToggleDoPost()), actionCollection(), "send_news"); + + a_ctDoMail = new KToggleAction(i18n("Send E&mail"), "mail_generic" , 0 , this, + SLOT(slotToggleDoMail()), actionCollection(), "send_mail"); + + a_ctSetCharset = new KSelectAction(i18n("Set &Charset"), 0, actionCollection(), "set_charset"); + a_ctSetCharset->setItems(knGlobals.configManager()->postNewsTechnical()->composerCharsets()); + a_ctSetCharset->setShortcutConfigurable(false); + connect(a_ctSetCharset, SIGNAL(activated(const QString&)), + this, SLOT(slotSetCharset(const QString&))); + + a_ctSetCharsetKeyb = new KAction(i18n("Set Charset"), 0, this, + SLOT(slotSetCharsetKeyboard()), actionCollection(), "set_charset_keyboard"); + + + a_ctWordWrap = new KToggleAction(i18n("&Word Wrap"), 0 , this, + SLOT(slotToggleWordWrap()), actionCollection(), "toggle_wordwrap"); + + //tools menu + + new KAction(i18n("Add &Quote Characters"), 0, v_iew->e_dit, + SLOT(slotAddQuotes()), actionCollection(), "tools_quote"); + + new KAction(i18n("&Remove Quote Characters"), 0, v_iew->e_dit, + SLOT(slotRemoveQuotes()), actionCollection(), "tools_unquote"); + + new KAction(i18n("Add &Box"), 0, v_iew->e_dit, + SLOT(slotAddBox()), actionCollection(), "tools_box"); + + new KAction(i18n("Re&move Box"), 0, v_iew->e_dit, + SLOT(slotRemoveBox()), actionCollection(), "tools_unbox"); + + KAction *undoRewrap = new KAction(i18n("Get &Original Text (not re-wrapped)"), 0, this, + SLOT(slotUndoRewrap()), actionCollection(), "tools_undoRewrap"); + undoRewrap->setEnabled(!u_nwraped.isNull()); + + KAction *rot13 = new KAction(i18n("S&cramble (Rot 13)"), "encrypted", 0, v_iew->e_dit, + SLOT(slotRot13()), actionCollection(), "tools_rot13"); + rot13->setEnabled(false); + connect(v_iew->e_dit, SIGNAL(copyAvailable(bool)), rot13, SLOT(setEnabled(bool))); + + a_ctExternalEditor = new KAction(i18n("Start &External Editor"), "run", 0, this, + SLOT(slotExternalEditor()), actionCollection(), "external_editor"); + + a_ctSpellCheck = KStdAction::spelling (this, SLOT(slotSpellcheck()), actionCollection()); + + //settings menu + createStandardStatusBarAction(); + setStandardToolBarMenuEnabled(true); + + KStdAction::keyBindings(this, SLOT(slotConfKeys()), actionCollection()); + + KStdAction::configureToolbars(this, SLOT(slotConfToolbar()), actionCollection()); + + KStdAction::preferences(knGlobals.top, SLOT(slotSettings()), actionCollection()); + + a_ccel=new KAccel(this); + a_ctSetCharsetKeyb->plugAccel(a_ccel); + + createGUI("kncomposerui.rc", false); + + //---------------------------------- </Actions> ---------------------------------------- + + + //attachment popup + a_ttPopup=static_cast<QPopupMenu*> (factory()->container("attachment_popup", this)); + if(!a_ttPopup) a_ttPopup = new QPopupMenu(); + slotAttachmentSelected(0); + + //init + initData(text); + + //apply configuration + setConfig(false); + + if (firstEdit) { // now we place the cursor at the end of the quoted text / below the attribution line + if (knGlobals.configManager()->postNewsComposer()->cursorOnTop()) { + int numLines = knGlobals.configManager()->postNewsComposer()->intro().contains("%L"); + v_iew->e_dit->setCursorPosition(numLines+1,0); + } + else + v_iew->e_dit->setCursorPosition(v_iew->e_dit->numLines()-1,0); + } else + v_iew->e_dit->setCursorPosition(0,0); + + v_iew->e_dit->setFocus(); + + if (v_iew->s_ubject->text().length() == 0) { + v_iew->s_ubject->setFocus(); + } + + if (v_iew->g_roups->text().length() == 0 && m_ode == news) { + v_iew->g_roups->setFocus(); + } + + if (v_iew->t_o->text().length() == 0 && m_ode == mail) { + v_iew->t_o->setFocus(); + } + + if(firstEdit && knGlobals.configManager()->postNewsComposer()->appendOwnSignature()) + slotAppendSig(); + + if (createCopy && (m_ode==news)) { + a_ctDoMail->setChecked(true); + slotToggleDoMail(); + } + + v_iew->e_dit->setModified(false); + + // restore window & toolbar configuration + KConfig *conf = knGlobals.config(); + conf->setGroup("composerWindow_options"); + resize(535,450); // default optimized for 800x600 + applyMainWindowSettings(conf); + + // starting the external editor + if(knGlobals.configManager()->postNewsComposer()->useExternalEditor()) + slotExternalEditor(); +} + + +KNComposer::~KNComposer() +{ + delete s_pellChecker; + delete mSpellingFilter; + delete e_xternalEditor; // this also kills the editor process if it's still running + + if(e_ditorTempfile) { + e_ditorTempfile->unlink(); + delete e_ditorTempfile; + } + + for ( QValueList<KNAttachment*>::Iterator it = mDeletedAttachments.begin(); it != mDeletedAttachments.end(); ++it ) + delete (*it); + + KConfig *conf = knGlobals.config(); + conf->setGroup("composerWindow_options"); + saveMainWindowSettings(conf); +} + +int KNComposer::listOfResultOfCheckWord( const QStringList & lst , const QString & selectWord) +{ + createGUI("kncomposerui.rc", false); + unplugActionList("spell_result" ); + m_listAction.clear(); + if ( !lst.contains( selectWord ) ) + { + QStringList::ConstIterator it = lst.begin(); + for ( ; it != lst.end() ; ++it ) + { + if ( !(*it).isEmpty() ) // in case of removed subtypes or placeholders + { + KAction * act = new KAction( *it ); + + connect( act, SIGNAL(activated()), v_iew->e_dit, SLOT(slotCorrectWord()) ); + m_listAction.append( act ); + } + } + } + if ( m_listAction.count()>0 ) + plugActionList("spell_result", m_listAction ); + return m_listAction.count(); +} + +void KNComposer::slotUndo() +{ + QWidget* fw = focusWidget(); + if (!fw) return; + + if (fw->inherits("KEdit")) + ((QMultiLineEdit*)fw)->undo(); + else if (fw->inherits("QLineEdit")) + ((QLineEdit*)fw)->undo(); +} + +void KNComposer::slotRedo() +{ + QWidget* fw = focusWidget(); + if (!fw) return; + + if (fw->inherits("KEdit")) + ((QMultiLineEdit*)fw)->redo(); + else if (fw->inherits("QLineEdit")) + ((QLineEdit*)fw)->redo(); +} + +void KNComposer::slotCut() +{ + QWidget* fw = focusWidget(); + if (!fw) return; + + if (fw->inherits("KEdit")) + ((QMultiLineEdit*)fw)->cut(); + else if (fw->inherits("QLineEdit")) + ((QLineEdit*)fw)->cut(); + else kdDebug(5003) << "wrong focus widget" << endl; +} + +void KNComposer::slotCopy() +{ + QWidget* fw = focusWidget(); + if (!fw) return; + + if (fw->inherits("KEdit")) + ((QMultiLineEdit*)fw)->copy(); + else if (fw->inherits("QLineEdit")) + ((QLineEdit*)fw)->copy(); + else kdDebug(5003) << "wrong focus widget" << endl; + +} + + +void KNComposer::slotPaste() +{ + QWidget* fw = focusWidget(); + if (!fw) return; + + if (fw->inherits("KEdit")) + ((QMultiLineEdit*)fw)->paste(); + else if (fw->inherits("QLineEdit")) + ((QLineEdit*)fw)->paste(); + else kdDebug(5003) << "wrong focus widget" << endl; +} + +void KNComposer::slotSelectAll() +{ + QWidget* fw = focusWidget(); + if (!fw) return; + + if (fw->inherits("QLineEdit")) + ((QLineEdit*)fw)->selectAll(); + else if (fw->inherits("QMultiLineEdit")) + ((QMultiLineEdit*)fw)->selectAll(); +} + + +void KNComposer::setConfig(bool onlyFonts) +{ + if (!onlyFonts) { + v_iew->e_dit->setWordWrap(knGlobals.configManager()->postNewsComposer()->wordWrap()? + QMultiLineEdit::FixedColumnWidth : QMultiLineEdit::NoWrap); + v_iew->e_dit->setWrapColumnOrWidth(knGlobals.configManager()->postNewsComposer()->maxLineLength()); + a_ctWordWrap->setChecked(knGlobals.configManager()->postNewsComposer()->wordWrap()); + + Kpgp::Module *pgp = Kpgp::Module::getKpgp(); + a_ctPGPsign->setEnabled(pgp->usePGP()); + } + + QFont fnt=knGlobals.configManager()->appearance()->composerFont(); + v_iew->s_ubject->setFont(fnt); + v_iew->t_o->setFont(fnt); + v_iew->g_roups->setFont(fnt); + v_iew->f_up2->setFont(fnt); + v_iew->e_dit->setFont(fnt); + + slotUpdateStatusBar(); +} + + +void KNComposer::setMessageMode(MessageMode mode) +{ + m_ode = mode; + a_ctDoPost->setChecked(m_ode!=mail); + a_ctDoMail->setChecked(m_ode!=news); + v_iew->setMessageMode(m_ode); + + if (m_ode == news_mail) { + QString s = v_iew->e_dit->textLine(0); + if (!s.contains(i18n("<posted & mailed>"))) + v_iew->e_dit->insertAt(i18n("<posted & mailed>\n\n"),0,0); + } else { + if (v_iew->e_dit->textLine(0)==i18n("<posted & mailed>")) { + v_iew->e_dit->removeLine(0); + if (v_iew->e_dit->textLine(0).isEmpty()) + v_iew->e_dit->removeLine(0); + } + } + + slotUpdateStatusBar(); +} + + +bool KNComposer::hasValidData() +{ + v_alidated=false; + n_eeds8Bit=false; + + // header checks + + if (v_iew->s_ubject->text().isEmpty()) { + KMessageBox::sorry(this, i18n("Please enter a subject.")); + return false; + } + if (!n_eeds8Bit && !KMime::isUsAscii(v_iew->s_ubject->text())) + n_eeds8Bit=true; + + if (m_ode != mail) { + if (v_iew->g_roups->text().isEmpty()) { + KMessageBox::sorry(this, i18n("Please enter a newsgroup.")); + return false; + } + + int groupCount = QStringList::split(',',v_iew->g_roups->text()).count(); + int fupCount = QStringList::split(',',v_iew->f_up2->currentText()).count(); + bool followUp = !v_iew->f_up2->currentText().isEmpty(); + + if (groupCount>12) { + KMessageBox::sorry(this, i18n("You are crossposting to more than 12 newsgroups.\nPlease remove all newsgroups in which your article is off-topic.")); + return false; + } + + if (groupCount>5) + if (!(KMessageBox::warningYesNo( this, i18n("You are crossposting to more than five newsgroups.\nPlease reconsider whether this is really useful\nand remove groups in which your article is off-topic.\nDo you want to re-edit the article or send it anyway?"), + QString::null, i18n("&Send"),i18n("edit article","&Edit")) == KMessageBox::Yes)) + return false; + + if ( !followUp && groupCount > 2 ) { + if ( KMessageBox::warningYesNo( this, + i18n("You are crossposting to more than two newsgroups.\n" + "Please use the \"Followup-To\" header to direct the replies " + "to your article into one group.\n" + "Do you want to re-edit the article or send it anyway?"), + QString::null, i18n("&Send"), i18n("edit article","&Edit"), "missingFollowUpTo" ) + != KMessageBox::Yes ) + return false; + } + + if (fupCount>12) { + KMessageBox::sorry(this, i18n("You are directing replies to more than 12 newsgroups.\nPlease remove some newsgroups from the \"Followup-To\" header.")); + return false; + } + + if (fupCount>5) + if (!(KMessageBox::warningYesNo( this, i18n("You are directing replies to more than five newsgroups.\nPlease reconsider whether this is really useful.\nDo you want to re-edit the article or send it anyway?"), + QString::null, i18n("&Send"),i18n("edit article","&Edit")) == KMessageBox::Yes)) + return false; + } + + if (m_ode != news) { + if (v_iew->t_o->text().isEmpty() ) { + KMessageBox::sorry(this, i18n("Please enter the email address.")); + return false; + } + if (!n_eeds8Bit && !KMime::isUsAscii(v_iew->t_o->text())) + n_eeds8Bit=true; + } + + //GNKSA body checks + bool firstLine = true; + bool empty = true; + bool longLine = false; + bool hasAttributionLine = false; + int sigLength = 0; + int notQuoted = 0; + int textLines = 0; + QStringList text = v_iew->e_dit->processedText(); + + for (QStringList::Iterator it = text.begin(); it != text.end(); ++it) { + + if (!n_eeds8Bit && !KMime::isUsAscii(*it)) + n_eeds8Bit=true; + + if (*it == "-- ") { // signature text + for (++it; it != text.end(); ++it) { + + if (!n_eeds8Bit && !KMime::isUsAscii(*it)) + n_eeds8Bit=true; + + sigLength++; + if((*it).length()>80) { + longLine = true; + } + } + break; + } + + if(!(*it).isEmpty()) { + empty = false; + textLines++; + if ((*it)[0]!='>') { + notQuoted++; + if (firstLine) hasAttributionLine = true; + } + } + if((*it).length()>80) { + longLine = true; + } + + firstLine = false; + } + + if (n_eeds8Bit && (c_harset.lower()=="us-ascii")) { + KMessageBox::sorry(this, i18n("Your message contains characters which are not included\nin the \"us-ascii\" character set; please choose\na suitable character set from the \"Options\" menu.")); + return false; + } + + if (empty) { + KMessageBox::sorry(this, i18n("You cannot post an empty message.")); + return false; + } + + if ((textLines>1)&&(notQuoted==1)) { + if (hasAttributionLine) + if (!(KMessageBox::warningYesNo( this, i18n("Your article seems to consist entirely of quoted text;\ndo you want to re-edit the article or send it anyway?"), + QString::null, i18n("&Send"),i18n("edit article","&Edit")) == KMessageBox::Yes)) + return false; + } else { + if (notQuoted==0) { + KMessageBox::sorry(this, i18n("You cannot post an article consisting\n" + "entirely of quoted text.")); + return false; + } + } + + if (longLine) + if (!(KMessageBox::warningYesNo( this, + i18n("Your article contains lines longer than 80 characters.\n" + "Do you want to re-edit the article or send it anyway?"), + QString::null, i18n("&Send"), + i18n("edit article","&Edit")) == KMessageBox::Yes)) + return false; + + if (sigLength>8) { + if (!(KMessageBox::warningYesNo( this, i18n("Your signature is more than 8 lines long.\nYou should shorten it to match the widely accepted limit of 4 lines.\nDo you want to re-edit the article or send it anyway?"), + QString::null, i18n("&Send"),i18n("edit article","&Edit")) == KMessageBox::Yes)) + return false; + } else + if (sigLength>4) + KMessageBox::information(this, i18n("Your signature exceeds the widely-accepted limit of 4 lines:\nplease consider shortening your signature;\notherwise, you will probably annoy your readers."), + QString::null,"longSignatureWarning"); + + // check if article can be signed + if ( a_ctPGPsign->isChecked() ) { + // try to get the signing key + QCString signingKey = knGlobals.configManager()->identity()->signingKey(); + KNNntpAccount *acc = knGlobals.accountManager()->account( a_rticle->serverId() ); + if ( acc ) { + KMime::Headers::Newsgroups *grps = a_rticle->newsgroups(); + KNGroup *grp = knGlobals.groupManager()->group( grps->firstGroup(), acc ); + if (grp && grp->identity()) + signingKey = grp->identity()->signingKey(); + else if (acc->identity()) + signingKey = acc->identity()->signingKey(); + } + + // the article can only be signed if we have a key + if (signingKey.isEmpty()) { + if ( KMessageBox::warningContinueCancel( this, + i18n("You have not configured your preferred " + "signing key yet;\n" + "please specify it in the global " + "identity configuration,\n" + "in the account properties or in the " + "group properties.\n" + "The article will be sent unsigned." ), + QString::null, i18n( "Send Unsigned" ), + "sendUnsignedDialog" ) + == KMessageBox::Cancel ) + return false; + } + } + + v_alidated=true; + return true; +} + + +bool KNComposer::applyChanges() +{ + KMime::Content *text=0; + KNAttachment *a=0; + + //Date + a_rticle->date()->setUnixTime(); //set current date+time + + //Subject + a_rticle->subject()->fromUnicodeString(v_iew->s_ubject->text(), c_harset); + + //Newsgroups + if (m_ode != mail) { + a_rticle->newsgroups()->fromUnicodeString(v_iew->g_roups->text().remove(QRegExp("\\s")), KMime::Headers::Latin1); + a_rticle->setDoPost(true); + } else + a_rticle->setDoPost(false); + + //To + if (m_ode != news) { + a_rticle->to()->fromUnicodeString(v_iew->t_o->text(), c_harset); + a_rticle->setDoMail(true); + } else + a_rticle->setDoMail(false); + + //Followup-To + if( a_rticle->doPost() && !v_iew->f_up2->currentText().isEmpty()) + a_rticle->followUpTo()->fromUnicodeString(v_iew->f_up2->currentText(), KMime::Headers::Latin1); + else + a_rticle->removeHeader("Followup-To"); + + if(a_ttChanged && (v_iew->a_ttView)) { + + QListViewItemIterator it(v_iew->a_ttView); + while(it.current()) { + a=(static_cast<AttachmentViewItem*> (it.current()))->attachment; + if(a->hasChanged()) { + if(a->isAttached()) + a->updateContentInfo(); + else + a->attach(a_rticle); + } + ++it; + } + } + + for ( QValueList<KNAttachment*>::Iterator it = mDeletedAttachments.begin(); it != mDeletedAttachments.end(); ++it ) + if ( (*it)->isAttached() ) + (*it)->detach( a_rticle ); + + text=a_rticle->textContent(); + + if(!text) { + text=new KMime::Content(); + KMime::Headers::ContentType *type=text->contentType(); + KMime::Headers::CTEncoding *enc=text->contentTransferEncoding(); + type->setMimeType("text/plain"); + enc->setDecoded(true); + text->assemble(); + a_rticle->addContent(text, true); + } + + //set text + KNConfig::PostNewsTechnical *pnt=knGlobals.configManager()->postNewsTechnical(); + if (v_alidated) { + if (n_eeds8Bit) { + text->contentType()->setCharset(c_harset); + if (pnt->allow8BitBody()) + text->contentTransferEncoding()->setCte(KMime::Headers::CE8Bit); + else + text->contentTransferEncoding()->setCte(KMime::Headers::CEquPr); + } else { + text->contentType()->setCharset("us-ascii"); // fall back to us-ascii + text->contentTransferEncoding()->setCte(KMime::Headers::CE7Bit); + } + } else { // save as draft + text->contentType()->setCharset(c_harset); + if (c_harset.lower()=="us-ascii") + text->contentTransferEncoding()->setCte(KMime::Headers::CE7Bit); + else + text->contentTransferEncoding()->setCte(pnt->allow8BitBody()? KMime::Headers::CE8Bit : KMime::Headers::CEquPr); + } + + //assemble the text line by line + QString tmp; + QStringList textLines = v_iew->e_dit->processedText(); + for (QStringList::Iterator it = textLines.begin(); it != textLines.end(); ++it) + tmp += *it + "\n"; + + // Sign article if needed + if ( a_ctPGPsign->isChecked() ) { + // first get the signing key + QCString signingKey = knGlobals.configManager()->identity()->signingKey(); + KNNntpAccount *acc = knGlobals.accountManager()->account( a_rticle->serverId() ); + if ( acc ) { + KMime::Headers::Newsgroups *grps = a_rticle->newsgroups(); + KNGroup *grp = knGlobals.groupManager()->group( grps->firstGroup(), acc ); + if (grp && grp->identity()) + signingKey = grp->identity()->signingKey(); + else if (acc->identity()) + signingKey = acc->identity()->signingKey(); + } + // now try to sign the article + if (!signingKey.isEmpty()) { + QString tmpText = tmp; + Kpgp::Block block; + bool ok=true; + QTextCodec *codec=KGlobal::charsets()->codecForName(c_harset, ok); + if(!ok) // no suitable codec found => try local settings and hope the best ;-) + codec=KGlobal::locale()->codecForEncoding(); + + block.setText( codec->fromUnicode(tmpText) ); + kdDebug(5003) << "signing article from " << article()->from()->email() << endl; + if( block.clearsign( signingKey, codec->name() ) == Kpgp::Ok ) { + QCString result = block.text(); + tmp = codec->toUnicode(result.data(), result.length() ); + } else { + return false; + } + } + } + + text->fromUnicodeString(tmp); + + //text is set and all attached contents have been assembled => now set lines + a_rticle->lines()->setNumberOfLines(a_rticle->lineCount()); + + a_rticle->assemble(); + a_rticle->updateListItem(); + return true; +} + + +void KNComposer::closeEvent(QCloseEvent *e) +{ + if(!v_iew->e_dit->isModified() && !a_ttChanged) { // nothing to save, don't show nag screen + if(a_rticle->id()==-1) + r_esult=CRdel; + else + r_esult=CRcancel; + } + else { + switch ( KMessageBox::warningYesNoCancel( this, i18n("Do you want to save this article in the draft folder?"), + QString::null, KStdGuiItem::save(), KStdGuiItem::discard())) { + case KMessageBox::Yes : + r_esult=CRsave; + break; + case KMessageBox::No : + if (a_rticle->id()==-1) r_esult=CRdel; + else r_esult=CRcancel; + break; + default: // cancel + e->ignore(); + return; + } + } + + e->accept(); + emit composerDone(this); + // we're dead at this point, don't access members! +} + + +void KNComposer::initData(const QString &text) +{ + //Subject + if(a_rticle->subject()->isEmpty()) + slotSubjectChanged(QString::null); + else + v_iew->s_ubject->setText(a_rticle->subject()->asUnicodeString()); + + //Newsgroups + v_iew->g_roups->setText(a_rticle->newsgroups()->asUnicodeString()); + + //To + v_iew->t_o->setText(a_rticle->to()->asUnicodeString()); + + //Followup-To + KMime::Headers::FollowUpTo *fup2=a_rticle->followUpTo(false); + if(fup2 && !fup2->isEmpty()) + v_iew->f_up2->lineEdit()->setText(fup2->asUnicodeString()); + + KMime::Content *textContent=a_rticle->textContent(); + QString s; + + if(text.isEmpty()) { + if(textContent) + textContent->decodedText(s); + } else + s = text; + + v_iew->e_dit->setText(s); + + // initialize the charset select action + if(textContent) + c_harset=textContent->contentType()->charset(); + else + c_harset=knGlobals.configManager()->postNewsTechnical()->charset(); + + a_ctSetCharset->setCurrentItem(knGlobals.configManager()->postNewsTechnical()->indexForCharset(c_harset)); + + // initialize the message type select action + if (a_rticle->doPost() && a_rticle->doMail()) + m_ode = news_mail; + else + if (a_rticle->doPost()) + m_ode = news; + else + m_ode = mail; + setMessageMode(m_ode); + + if(a_rticle->contentType()->isMultipart()) { + v_iew->showAttachmentView(); + KMime::Content::List attList; + AttachmentViewItem *item=0; + attList.setAutoDelete(false); + a_rticle->attachments(&attList); + for(KMime::Content *c=attList.first(); c; c=attList.next()) { + item=new AttachmentViewItem(v_iew->a_ttView, new KNAttachment(c)); + } + } +} + + +// inserts at cursor position if clear is false, replaces content otherwise +// puts the file content into a box if box==true +// "file" is already open for reading +void KNComposer::insertFile(QFile *file, bool clear, bool box, QString boxTitle) +{ + QString temp; + bool ok=true; + QTextCodec *codec=KGlobal::charsets()->codecForName(c_harset, ok); + QTextStream ts(file); + ts.setCodec(codec); + + if (box) + temp = QString::fromLatin1(",----[ %1 ]\n").arg(boxTitle); + + if (box && (v_iew->e_dit->wordWrap()!=QMultiLineEdit::NoWrap)) { + int wrapAt = v_iew->e_dit->wrapColumnOrWidth(); + QStringList lst; + QString line; + while(!file->atEnd()) { + line=ts.readLine(); + if (!file->atEnd()) + line+="\n"; + lst.append(line); + } + temp+=KNHelper::rewrapStringList(lst, wrapAt, '|', false, true); + } else { + while(!file->atEnd()) { + if (box) + temp+="| "; + temp+=ts.readLine(); + if (!file->atEnd()) + temp += "\n"; + } + } + + if (box) + temp += QString::fromLatin1("`----"); + + if(clear) + v_iew->e_dit->setText(temp); + else + v_iew->e_dit->insert(temp); +} + + +// ask for a filename, handle network urls +void KNComposer::insertFile(bool clear, bool box) +{ + KNLoadHelper helper(this); + QFile *file = helper.getFile(i18n("Insert File")); + KURL url; + QString boxName; + + if (file) { + url = helper.getURL(); + + if (url.isLocalFile()) + boxName = url.path(); + else + boxName = url.prettyURL(); + + insertFile(file,clear,box,boxName); + } +} + + +//-------------------------------- <Actions> ------------------------------------ + + +void KNComposer::addRecentAddress() +{ + if( !v_iew->t_o->isHidden() ) + RecentAddresses::self(knGlobals.config())->add( v_iew->t_o->text() ); +} + +void KNComposer::slotSendNow() +{ + r_esult=CRsendNow; + addRecentAddress(); + emit composerDone(this); +} + + +void KNComposer::slotSendLater() +{ + r_esult=CRsendLater; + addRecentAddress(); + emit composerDone(this); +} + + +void KNComposer::slotSaveAsDraft() +{ + r_esult=CRsave; + addRecentAddress(); + emit composerDone(this); +} + + +void KNComposer::slotArtDelete() +{ + r_esult=CRdelAsk; + emit composerDone(this); +} + + +void KNComposer::slotAppendSig() +{ + if(!s_ignature.isEmpty()) { + v_iew->e_dit->append("\n"+s_ignature); + v_iew->e_dit->setModified(true); + } +} + + +void KNComposer::slotInsertFile() +{ + insertFile(false,false); +} + + +void KNComposer::slotInsertFileBoxed() +{ + insertFile(false,true); +} + + +void KNComposer::slotAttachFile() +{ + KNLoadHelper *helper = new KNLoadHelper(this); + + if (helper->getFile(i18n("Attach File"))) { + if (!v_iew->v_iewOpen) { + KNHelper::saveWindowSize("composer", size()); + v_iew->showAttachmentView(); + } + (void) new AttachmentViewItem(v_iew->a_ttView, new KNAttachment(helper)); + a_ttChanged=true; + } else { + delete helper; + } +} + + +void KNComposer::slotRemoveAttachment() +{ + if(!v_iew->v_iewOpen) return; + + if(v_iew->a_ttView->currentItem()) { + AttachmentViewItem *it=static_cast<AttachmentViewItem*>(v_iew->a_ttView->currentItem()); + if(it->attachment->isAttached()) { + mDeletedAttachments.append( it->attachment ); + it->attachment=0; + } + delete it; + + if(v_iew->a_ttView->childCount()==0) { + KNHelper::saveWindowSize("composerAtt", size()); + v_iew->hideAttachmentView(); + } + + a_ttChanged=true; + } +} + +void KNComposer::slotAttachmentProperties() +{ + if(!v_iew->v_iewOpen) return; + + if(v_iew->a_ttView->currentItem()) { + AttachmentViewItem *it=static_cast<AttachmentViewItem*>(v_iew->a_ttView->currentItem()); + AttachmentPropertiesDlg *d=new AttachmentPropertiesDlg(it->attachment, this); + if(d->exec()) { + d->apply(); + it->setText(1, it->attachment->mimeType()); + it->setText(3, it->attachment->description()); + it->setText(4, it->attachment->encoding()); + } + delete d; + a_ttChanged=true; + } +} + + +void KNComposer::slotToggleDoPost() +{ + if (a_ctDoPost->isChecked()) { + if (a_ctDoMail->isChecked()) + m_ode=news_mail; + else + m_ode=news; + } else { + if (a_ctDoMail->isChecked()) + m_ode=mail; + else { // invalid + a_ctDoPost->setChecked(true); //revert + return; + } + } + setMessageMode(m_ode); +} + + +void KNComposer::slotToggleDoMail() +{ + if (a_ctDoMail->isChecked()) { + if (a_uthorDislikesMailCopies) { + if (!(KMessageBox::warningContinueCancel(this, i18n("The poster does not want a mail copy of your reply (Mail-Copies-To: nobody);\nplease respect their request."), + QString::null, i18n("&Send Copy")) == KMessageBox::Continue)) { + a_ctDoMail->setChecked(false); //revert + return; + } + } + + if (knGlobals.configManager()->postNewsTechnical()->useExternalMailer()) { + QString s = v_iew->e_dit->textLine(0); + if (!s.contains(i18n("<posted & mailed>"))) + v_iew->e_dit->insertAt(i18n("<posted & mailed>\n\n"),0,0); + QString tmp; + QStringList textLines = v_iew->e_dit->processedText(); + for (QStringList::Iterator it = textLines.begin(); it != textLines.end(); ++it) { + if (*it == "-- ") // try to be smart, don't include the signature, + break; // kmail will append one, too. + tmp+=*it+"\n"; + } + knGlobals.artFactory->sendMailExternal(v_iew->t_o->text(), v_iew->s_ubject->text(), tmp); + a_ctDoMail->setChecked(false); //revert + return; + } else { + if (a_ctDoPost->isChecked()) + m_ode=news_mail; + else + m_ode=mail; + } + } else { + if (a_ctDoPost->isChecked()) + m_ode=news; + else { // invalid + a_ctDoMail->setChecked(true); //revert + return; + } + } + setMessageMode(m_ode); +} + + +void KNComposer::slotSetCharset(const QString &s) +{ + if(s.isEmpty()) + return; + + c_harset=s.latin1(); + setConfig(true); //adjust fonts +} + + +void KNComposer::slotSetCharsetKeyboard() +{ + int newCS = KNHelper::selectDialog(this, i18n("Select Charset"), a_ctSetCharset->items(), a_ctSetCharset->currentItem()); + if (newCS != -1) { + a_ctSetCharset->setCurrentItem(newCS); + slotSetCharset(*(a_ctSetCharset->items().at(newCS))); + } +} + + +void KNComposer::slotToggleWordWrap() +{ + v_iew->e_dit->setWordWrap(a_ctWordWrap->isChecked()? QMultiLineEdit::FixedColumnWidth : QMultiLineEdit::NoWrap); +} + + +void KNComposer::slotUndoRewrap() +{ + if (KMessageBox::warningContinueCancel( this, i18n("This will replace all text you have written.")) == KMessageBox::Continue) { + v_iew->e_dit->setText(u_nwraped); + slotAppendSig(); + } +} + +void KNComposer::slotExternalEditor() +{ + if(e_xternalEditor) // in progress... + return; + + QString editorCommand=knGlobals.configManager()->postNewsComposer()->externalEditor(); + + if(editorCommand.isEmpty()) + KMessageBox::sorry(this, i18n("No editor configured.\nPlease do this in the settings dialog.")); + + if(e_ditorTempfile) { // shouldn't happen... + e_ditorTempfile->unlink(); + delete e_ditorTempfile; + e_ditorTempfile=0; + } + + e_ditorTempfile=new KTempFile(); + + if(e_ditorTempfile->status()!=0) { + KNHelper::displayTempFileError(this); + e_ditorTempfile->unlink(); + delete e_ditorTempfile; + e_ditorTempfile=0; + return; + } + + bool ok=true; + QTextCodec *codec=KGlobal::charsets()->codecForName(c_harset, ok); + + QString tmp; + QStringList textLines = v_iew->e_dit->processedText(); + for (QStringList::Iterator it = textLines.begin(); it != textLines.end();) { + tmp += *it; + ++it; + if (it != textLines.end()) + tmp+="\n"; + } + + QCString local = codec->fromUnicode(tmp); + e_ditorTempfile->file()->writeBlock(local.data(),local.length()); + e_ditorTempfile->file()->flush(); + + if(e_ditorTempfile->status()!=0) { + KNHelper::displayTempFileError(this); + e_ditorTempfile->unlink(); + delete e_ditorTempfile; + e_ditorTempfile=0; + return; + } + + e_xternalEditor=new KProcess(); + + // construct command line... + QStringList command = QStringList::split(' ',editorCommand); + bool filenameAdded=false; + for ( QStringList::Iterator it = command.begin(); it != command.end(); ++it ) { + if ((*it).contains("%f")) { + (*it).replace(QRegExp("%f"),e_ditorTempfile->name()); + filenameAdded=true; + } + (*e_xternalEditor) << (*it); + } + if(!filenameAdded) // no %f in the editor command + (*e_xternalEditor) << e_ditorTempfile->name(); + + connect(e_xternalEditor, SIGNAL(processExited(KProcess *)),this, SLOT(slotEditorFinished(KProcess *))); + if(!e_xternalEditor->start()) { + KMessageBox::error(this, i18n("Unable to start external editor.\nPlease check your configuration in the settings dialog.")); + delete e_xternalEditor; + e_xternalEditor=0; + e_ditorTempfile->unlink(); + delete e_ditorTempfile; + e_ditorTempfile=0; + return; + } + + a_ctExternalEditor->setEnabled(false); // block other edit action while the editor is running... + a_ctSpellCheck->setEnabled(false); + v_iew->showExternalNotification(); +} + + +void KNComposer::slotSpellcheck() +{ + if(s_pellChecker) // in progress... + return; + spellLineEdit = !spellLineEdit; + a_ctExternalEditor->setEnabled(false); + a_ctSpellCheck->setEnabled(false); + + s_pellChecker = new KSpell(this, i18n("Spellcheck"), this, SLOT(slotSpellStarted(KSpell *))); + QStringList l = KSpellingHighlighter::personalWords(); + for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) { + s_pellChecker->addPersonal( *it ); + } + connect(s_pellChecker, SIGNAL(death()), this, SLOT(slotSpellFinished())); + connect(s_pellChecker, SIGNAL(done(const QString&)), this, SLOT(slotSpellDone(const QString&))); + connect(s_pellChecker, SIGNAL(misspelling (const QString &, const QStringList &, unsigned int)), + this, SLOT(slotMisspelling (const QString &, const QStringList &, unsigned int))); + connect(s_pellChecker, SIGNAL(corrected (const QString &, const QString &, unsigned int)), + this, SLOT(slotCorrected (const QString &, const QString &, unsigned int))); +} + + +void KNComposer::slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos) +{ + if( spellLineEdit ) + v_iew->s_ubject->spellCheckerMisspelling( text, lst, pos); + else + v_iew->e_dit->misspelling(text, lst, pos); + +} + +void KNComposer::slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos) +{ + if( spellLineEdit ) + v_iew->s_ubject->spellCheckerCorrected( oldWord, newWord, pos); + else + v_iew->e_dit->corrected(oldWord, newWord, pos); +} + +void KNComposer::slotUpdateStatusBar() +{ + QString typeDesc; + switch (m_ode) { + case news: typeDesc = i18n("News Article"); + break; + case mail: typeDesc = i18n("Email"); + break; + default : typeDesc = i18n("News Article & Email"); + } + QString overwriteDesc; + if (v_iew->e_dit->isOverwriteMode()) + overwriteDesc = i18n(" OVR "); + else + overwriteDesc = i18n(" INS "); + + statusBar()->changeItem(i18n(" Type: %1 ").arg(typeDesc), 1); + statusBar()->changeItem(i18n(" Charset: %1 ").arg(c_harset), 2); + statusBar()->changeItem(overwriteDesc, 3); + statusBar()->changeItem(i18n(" Column: %1 ").arg(v_iew->e_dit->currentColumn() + 1), 4); + statusBar()->changeItem(i18n(" Line: %1 ").arg(v_iew->e_dit->currentLine() + 1), 5); +} + + +void KNComposer::slotUpdateCursorPos() +{ + statusBar()->changeItem(i18n(" Column: %1 ").arg(v_iew->e_dit->currentColumn() + 1), 4); + statusBar()->changeItem(i18n(" Line: %1 ").arg(v_iew->e_dit->currentLine() + 1), 5); +} + + +void KNComposer::slotConfKeys() +{ + KKeyDialog::configure(actionCollection(), this, true); +} + + +void KNComposer::slotConfToolbar() +{ + KConfig *conf = knGlobals.config(); + conf->setGroup("composerWindow_options"); + saveMainWindowSettings(conf); + KEditToolbar dlg(guiFactory(),this); + connect(&dlg,SIGNAL( newToolbarConfig() ), this, SLOT( slotNewToolbarConfig() )); + dlg.exec(); +} + +void KNComposer::slotNewToolbarConfig() +{ + createGUI("kncomposerui.rc"); + + a_ttPopup=static_cast<QPopupMenu*> (factory()->container("attachment_popup", this)); + if(!a_ttPopup) a_ttPopup = new QPopupMenu(); + + KConfig *conf = knGlobals.config(); + conf->setGroup("composerWindow_options"); + applyMainWindowSettings(conf); +} + +//-------------------------------- </Actions> ----------------------------------- + + +void KNComposer::slotSubjectChanged(const QString &t) +{ + // replace newlines + QString subject = t; + subject.replace( '\n', ' ' ); + subject.replace( '\r', ' ' ); + if ( subject != t ) // setText() sets the cursor to the end + v_iew->s_ubject->setText( subject ); + // update caption + if( !subject.isEmpty() ) + setCaption( subject ); + else + setCaption( i18n("No Subject") ); +} + + +void KNComposer::slotGroupsChanged(const QString &t) +{ + KQCStringSplitter split; + bool splitOk; + QString currText=v_iew->f_up2->currentText(); + + v_iew->f_up2->clear(); + + split.init(t.latin1(), ","); + splitOk=split.first(); + while(splitOk) { + v_iew->f_up2->insertItem(QString::fromLatin1(split.string())); + splitOk=split.next(); + } + v_iew->f_up2->insertItem(""); + + if ( !currText.isEmpty() || !mFirstEdit ) // user might have cleared fup2 intentionally during last edit + v_iew->f_up2->lineEdit()->setText(currText); +} + + +void KNComposer::slotToBtnClicked() +{ + AddressesDialog dlg( this ); + QString txt; + QString to = v_iew->t_o->text(); + dlg.setShowBCC(false); + dlg.setShowCC(false); +#if 0 + QStringList lst; + + + txt = mEdtTo->text().stripWhiteSpace(); + if ( !txt.isEmpty() ) { + lst = KMMessage::splitEmailAddrList( txt ); + dlg.setSelectedTo( lst ); + } +#endif + dlg.setRecentAddresses( RecentAddresses::self(knGlobals.config())->kabcAddresses() ); + if (dlg.exec()==QDialog::Rejected) return; + + if(!to.isEmpty()) + to+=", "; + to+=dlg.to().join(", "); + + v_iew->t_o->setText(to); + +} + + +void KNComposer::slotGroupsBtnClicked() +{ + int id=a_rticle->serverId(); + KNNntpAccount *nntp=0; + + if(id!=-1) + nntp=knGlobals.accountManager()->account(id); + + if(!nntp) + nntp=knGlobals.accountManager()->first(); + + if(!nntp) { + KMessageBox::error(this, i18n("You have no valid news accounts configured.")); + v_iew->g_roups->clear(); + return; + } + + if(id==-1) + a_rticle->setServerId(nntp->id()); + + KNGroupSelectDialog *dlg=new KNGroupSelectDialog(this, nntp, v_iew->g_roups->text().remove(QRegExp("\\s"))); + + connect(dlg, SIGNAL(loadList(KNNntpAccount*)), + knGlobals.groupManager(), SLOT(slotLoadGroupList(KNNntpAccount*))); + connect(knGlobals.groupManager(), SIGNAL(newListReady(KNGroupListData*)), + dlg, SLOT(slotReceiveList(KNGroupListData*))); + + if(dlg->exec()) + v_iew->g_roups->setText(dlg->selectedGroups()); + + delete dlg; +} + + +void KNComposer::slotEditorFinished(KProcess *) +{ + if(e_xternalEditor->normalExit()) { + e_ditorTempfile->file()->close(); + e_ditorTempfile->file()->open(IO_ReadOnly); + insertFile(e_ditorTempfile->file(), true); + e_xternalEdited=true; + } + + slotCancelEditor(); // cleanup... +} + + +void KNComposer::slotCancelEditor() +{ + delete e_xternalEditor; // this also kills the editor process if it's still running + e_xternalEditor=0; + e_ditorTempfile->unlink(); + delete e_ditorTempfile; + e_ditorTempfile=0; + + a_ctExternalEditor->setEnabled(true); + a_ctSpellCheck->setEnabled(true); + v_iew->hideExternalNotification(); +} + + +void KNComposer::slotAttachmentPopup(KListView*, QListViewItem *it, const QPoint &p) +{ + if(it) + a_ttPopup->popup(p); +} + + +void KNComposer::slotAttachmentSelected(QListViewItem *it) +{ + if(v_iew->a_ttWidget) { + v_iew->a_ttRemoveBtn->setEnabled((it!=0)); + v_iew->a_ttEditBtn->setEnabled((it!=0)); + } +} + + +void KNComposer::slotAttachmentEdit(QListViewItem *) +{ + slotAttachmentProperties(); +} + + +void KNComposer::slotAttachmentRemove(QListViewItem *) +{ + slotRemoveAttachment(); +} + + +//============================================================================== +// spellchecking code copied form kedit (Bernd Johannes Wuebben) +//============================================================================== + + +void KNComposer::slotSpellStarted( KSpell *) +{ + if( !spellLineEdit ) + { + v_iew->e_dit->spellcheck_start(); + s_pellChecker->setProgressResolution(2); + + // read the quote indicator from the preferences + KConfig *config=knGlobals.config(); + KConfigGroupSaver saver(config, "READNEWS"); + QString quotePrefix; + quotePrefix = config->readEntry("quoteCharacters",">"); +//todo fixme +//quotePrefix = mComposer->msg()->formatString(quotePrefix); + + kdDebug(5003) << "spelling: new SpellingFilter with prefix=\"" << quotePrefix << "\"" << endl; + mSpellingFilter = new SpellingFilter(v_iew->e_dit->text(), quotePrefix, SpellingFilter::FilterUrls, + SpellingFilter::FilterEmailAddresses); + + s_pellChecker->check(mSpellingFilter->filteredText()); + } + else + s_pellChecker->check( v_iew->s_ubject->text()); +} + +void KNComposer::slotSpellDone(const QString &newtext) +{ + a_ctExternalEditor->setEnabled(true); + a_ctSpellCheck->setEnabled(true); + if ( !spellLineEdit ) + v_iew->e_dit->spellcheck_stop(); + + int dlgResult = s_pellChecker->dlgResult(); + if ( dlgResult == KS_CANCEL ) + { + if( spellLineEdit) + { + //stop spell check + spellLineEdit = false; + QString tmpText( newtext); + tmpText = tmpText.remove('\n'); + + if( tmpText != v_iew->s_ubject->text() ) + v_iew->s_ubject->setText( tmpText ); + } + else + { + kdDebug(5003) << "spelling: canceled - restoring text from SpellingFilter" << endl; + kdDebug(5003)<<" mSpellingFilter->originalText() :"<<mSpellingFilter->originalText()<<endl; + v_iew->e_dit->setText(mSpellingFilter->originalText()); + + //v_iew->e_dit->setModified(mWasModifiedBeforeSpellCheck); + } + } + s_pellChecker->cleanUp(); + KDictSpellingHighlighter::dictionaryChanged(); +} + + +void KNComposer::slotSpellFinished() +{ + a_ctExternalEditor->setEnabled(true); + a_ctSpellCheck->setEnabled(true); + KSpell::spellStatus status=s_pellChecker->status(); + delete s_pellChecker; + s_pellChecker=0; + + kdDebug(5003) << "spelling: delete SpellingFilter" << endl; + delete mSpellingFilter; + mSpellingFilter = 0; + + if(status==KSpell::Error) { + KMessageBox::error(this, i18n("ISpell could not be started.\n" + "Please make sure you have ISpell properly configured and in your PATH.")); + } + else if(status==KSpell::Crashed) { + v_iew->e_dit->spellcheck_stop(); + KMessageBox::error(this, i18n("ISpell seems to have crashed.")); + } + else + { + if( spellLineEdit ) + slotSpellcheck(); + else if( status == KSpell::FinishedNoMisspellingsEncountered ) + KMessageBox::information( this, i18n("No misspellings encountered.")); + } +} + + +void KNComposer::slotDragEnterEvent(QDragEnterEvent *ev) +{ + QStringList files; + ev->accept(KURLDrag::canDecode(ev)); +} + + +void KNComposer::slotDropEvent(QDropEvent *ev) +{ + KURL::List urls; + + if (!KURLDrag::decode(ev, urls)) + return; + + for (KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it) { + const KURL &url = *it; + KNLoadHelper *helper = new KNLoadHelper(this); + + if (helper->setURL(url)) { + if (!v_iew->v_iewOpen) { + KNHelper::saveWindowSize("composer", size()); + v_iew->showAttachmentView(); + } + (void) new AttachmentViewItem(v_iew->a_ttView, new KNAttachment(helper)); + a_ttChanged=true; + } else { + delete helper; + } + } +} + + +void KNComposer::dragEnterEvent(QDragEnterEvent *ev) +{ + slotDragEnterEvent(ev); +} + + +void KNComposer::dropEvent(QDropEvent *ev) +{ + slotDropEvent(ev); +} + +QPopupMenu * KNComposer::popupMenu( const QString& name ) +{ + Q_ASSERT(factory()); + if ( factory() ) + return ((QPopupMenu*)factory()->container( name, this )); + return 0L; +} + + +//===================================================================================== + + +KNComposer::ComposerView::ComposerView(KNComposer *composer, const char *n) + : QSplitter(QSplitter::Vertical, composer, n), a_ttWidget(0), a_ttView(0), v_iewOpen(false) +{ + QWidget *main=new QWidget(this); + + //headers + QFrame *hdrFrame=new QFrame(main); + hdrFrame->setFrameStyle(QFrame::Box | QFrame::Sunken); + QGridLayout *hdrL=new QGridLayout(hdrFrame, 4,3, 7,5); + hdrL->setColStretch(1,1); + + //To + t_o=new KNLineEdit(this, true, hdrFrame); + mEdtList.append(t_o); + + l_to=new QLabel(t_o, i18n("T&o:"), hdrFrame); + t_oBtn=new QPushButton(i18n("&Browse..."), hdrFrame); + hdrL->addWidget(l_to, 0,0); + hdrL->addWidget(t_o, 0,1); + hdrL->addWidget(t_oBtn, 0,2); + connect(t_oBtn, SIGNAL(clicked()), parent(), SLOT(slotToBtnClicked())); + + //Newsgroups + g_roups=new KNLineEdit(this, false, hdrFrame); + mEdtList.append(g_roups); + + l_groups=new QLabel(g_roups, i18n("&Groups:"), hdrFrame); + g_roupsBtn=new QPushButton(i18n("B&rowse..."), hdrFrame); + hdrL->addWidget(l_groups, 1,0); + hdrL->addWidget(g_roups, 1,1); + hdrL->addWidget(g_roupsBtn, 1,2); + connect(g_roups, SIGNAL(textChanged(const QString&)), + parent(), SLOT(slotGroupsChanged(const QString&))); + connect(g_roupsBtn, SIGNAL(clicked()), parent(), SLOT(slotGroupsBtnClicked())); + + //Followup-To + f_up2=new KComboBox(true, hdrFrame); + l_fup2=new QLabel(f_up2, i18n("Follo&wup-To:"), hdrFrame); + hdrL->addWidget(l_fup2, 2,0); + hdrL->addMultiCellWidget(f_up2, 2,2, 1,2); + + //subject + s_ubject=new KNLineEditSpell(this, false, hdrFrame); + mEdtList.append(s_ubject); + + QLabel *l=new QLabel(s_ubject, i18n("S&ubject:"), hdrFrame); + hdrL->addWidget(l, 3,0); + hdrL->addMultiCellWidget(s_ubject, 3,3, 1,2); + connect(s_ubject, SIGNAL(textChanged(const QString&)), + parent(), SLOT(slotSubjectChanged(const QString&))); + + //Editor + e_dit=new Editor(this, composer, main); + e_dit->setMinimumHeight(50); + + KConfig *config = knGlobals.config(); + KConfigGroupSaver saver(config, "VISUAL_APPEARANCE"); + QColor defaultColor1( kapp->palette().active().text()); // defaults from kmreaderwin.cpp + QColor defaultColor2( kapp->palette().active().text() ); + QColor defaultColor3( kapp->palette().active().text() ); + QColor defaultForeground( kapp->palette().active().text() ); + QColor col1 = config->readColorEntry( "ForegroundColor", &defaultForeground ); + QColor col2 = config->readColorEntry( "quote3Color", &defaultColor3 ); + QColor col3 = config->readColorEntry( "quote2Color", &defaultColor2 ); + QColor col4 = config->readColorEntry( "quote1Color", &defaultColor1 ); + QColor c = QColor("red"); + mSpellChecker = new KDictSpellingHighlighter(e_dit, /*active*/ true, /*autoEnabled*/ true, + /*spellColor*/ config->readColorEntry("NewMessage", &c), + /*colorQuoting*/ true, col1, col2, col3, col4); + connect( mSpellChecker, SIGNAL(newSuggestions(const QString&, const QStringList&, unsigned int)), e_dit, + SLOT(slotAddSuggestion(const QString&, const QStringList&, unsigned int)) ); + + QVBoxLayout *notL=new QVBoxLayout(e_dit); + notL->addStretch(1); + n_otification=new QGroupBox(2, Qt::Horizontal, e_dit); + l=new QLabel(i18n("You are currently editing the article body\nin an external editor. To continue, you have\nto close the external editor."), n_otification); + c_ancelEditorBtn=new QPushButton(i18n("&Kill External Editor"), n_otification); + n_otification->setFrameStyle(QFrame::Panel | QFrame::Raised); + n_otification->setLineWidth(2); + n_otification->hide(); + notL->addWidget(n_otification, 0, Qt::AlignHCenter); + notL->addStretch(1); + + //finish GUI + QVBoxLayout *topL=new QVBoxLayout(main, 4,4); + topL->addWidget(hdrFrame); + topL->addWidget(e_dit, 1); +} + + +KNComposer::ComposerView::~ComposerView() +{ + if(v_iewOpen) { + KConfig *conf=knGlobals.config(); + conf->setGroup("POSTNEWS"); + + conf->writeEntry("Att_Splitter",sizes()); // save splitter pos + + QValueList<int> lst; // save header sizes + QHeader *h=a_ttView->header(); + for (int i=0; i<5; i++) + lst << h->sectionSize(i); + conf->writeEntry("Att_Headers",lst); + } + delete mSpellChecker; +} + + +void KNComposer::ComposerView::focusNextPrevEdit(const QWidget* aCur, bool aNext) +{ + QValueList<QWidget*>::Iterator it; + + if ( !aCur ) { + it = --( mEdtList.end() ); + } else { + for ( QValueList<QWidget*>::Iterator it2 = mEdtList.begin(); it2 != mEdtList.end(); ++it2 ) { + if ( (*it2) == aCur ) { + it = it2; + break; + } + } + if ( it == mEdtList.end() ) + return; + if ( aNext ) + ++it; + else { + if ( it != mEdtList.begin() ) + --it; + else + return; + } + } + if ( it != mEdtList.end() ) { + if ( (*it)->isVisible() ) + (*it)->setFocus(); + } else if ( aNext ) + e_dit->setFocus(); +} + + +void KNComposer::ComposerView::setMessageMode(KNComposer::MessageMode mode) +{ + if (mode != KNComposer::news) { + l_to->show(); + t_o->show(); + t_oBtn->show(); + } else { + l_to->hide(); + t_o->hide(); + t_oBtn->hide(); + } + if (mode != KNComposer::mail) { + l_groups->show(); + l_fup2->show(); + g_roups->show(); + f_up2->show(); + g_roupsBtn->show(); + + } else { + l_groups->hide(); + l_fup2->hide(); + g_roups->hide(); + f_up2->hide(); + g_roupsBtn->hide(); + } +} + +void KNComposer::ComposerView::restartBackgroundSpellCheck() +{ + mSpellChecker->restartBackgroundSpellCheck(); +} + +void KNComposer::ComposerView::showAttachmentView() +{ + if(!a_ttWidget) { + a_ttWidget=new QWidget(this); + QGridLayout *topL=new QGridLayout(a_ttWidget, 3, 2, 4, 4); + + a_ttView=new AttachmentView(a_ttWidget); + topL->addMultiCellWidget(a_ttView, 0,2, 0,0); + + //connections + connect(a_ttView, SIGNAL(currentChanged(QListViewItem*)), + parent(), SLOT(slotAttachmentSelected(QListViewItem*))); + connect(a_ttView, SIGNAL(clicked ( QListViewItem * )), + parent(), SLOT(slotAttachmentSelected(QListViewItem*))); + + connect(a_ttView, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + parent(), SLOT(slotAttachmentPopup(KListView*, QListViewItem*, const QPoint&))); + connect(a_ttView, SIGNAL(delPressed(QListViewItem*)), + parent(), SLOT(slotAttachmentRemove(QListViewItem*))); + connect(a_ttView, SIGNAL(doubleClicked(QListViewItem*)), + parent(), SLOT(slotAttachmentEdit(QListViewItem*))); + connect(a_ttView, SIGNAL(returnPressed(QListViewItem*)), + parent(), SLOT(slotAttachmentEdit(QListViewItem*))); + + //buttons + a_ttAddBtn=new QPushButton(i18n("A&dd..."),a_ttWidget); + connect(a_ttAddBtn, SIGNAL(clicked()), parent(), SLOT(slotAttachFile())); + topL->addWidget(a_ttAddBtn, 0,1); + + a_ttRemoveBtn=new QPushButton(i18n("&Remove"), a_ttWidget); + a_ttRemoveBtn->setEnabled(false); + connect(a_ttRemoveBtn, SIGNAL(clicked()), parent(), SLOT(slotRemoveAttachment())); + topL->addWidget(a_ttRemoveBtn, 1,1); + + a_ttEditBtn=new QPushButton(i18n("&Properties"), a_ttWidget); + a_ttEditBtn->setEnabled(false); + connect(a_ttEditBtn, SIGNAL(clicked()), parent(), SLOT(slotAttachmentProperties())); + topL->addWidget(a_ttEditBtn, 2,1, Qt::AlignTop); + + topL->setRowStretch(2,1); + topL->setColStretch(0,1); + } + + if(!v_iewOpen) { + v_iewOpen=true; + a_ttWidget->show(); + + KConfig *conf=knGlobals.config(); + conf->setGroup("POSTNEWS"); + + QValueList<int> lst=conf->readIntListEntry("Att_Splitter"); + if(lst.count()!=2) + lst << 267 << 112; + setSizes(lst); + + lst=conf->readIntListEntry("Att_Headers"); + if(lst.count()==5) { + QValueList<int>::Iterator it=lst.begin(); + + QHeader *h=a_ttView->header(); + for(int i=0; i<5; i++) { + h->resizeSection(i,(*it)); + ++it; + } + } + } +} + + +void KNComposer::ComposerView::hideAttachmentView() +{ + if(v_iewOpen) { + a_ttWidget->hide(); + v_iewOpen=false; + } +} + + +void KNComposer::ComposerView::showExternalNotification() +{ + e_dit->setReadOnly(true); + n_otification->show(); +} + + +void KNComposer::ComposerView::hideExternalNotification() +{ + e_dit->setReadOnly(false); + n_otification->hide(); +} + + +//===================================================================================== + +#include <kcursor.h> +KNComposer::Editor::Editor(KNComposer::ComposerView *_composerView, KNComposer *_composer, QWidget *parent, char *name) + : KEdit(parent, name), m_composer( _composer ), m_composerView(_composerView) +{ + setOverwriteEnabled(true); + spell = 0L; + installEventFilter(this); + KCursor::setAutoHideCursor( this, true, true ); + m_bound = QRegExp( QString::fromLatin1("[\\s\\W]") ); +} + + +KNComposer::Editor::~Editor() +{ + removeEventFilter(this); + delete spell; +} + +//----------------------------------------------------------------------------- +bool KNComposer::Editor::eventFilter(QObject*o, QEvent* e) +{ + if (o == this) + KCursor::autoHideEventFilter(o, e); + + if (e->type() == QEvent::KeyPress) + { + QKeyEvent *k = (QKeyEvent*)e; + // ---sven's Arrow key navigation start --- + // Key Up in first line takes you to Subject line. + if (k->key() == Key_Up && k->state() != ShiftButton && currentLine() == 0 + && lineOfChar(0, currentColumn()) == 0) + { + deselect(); + m_composerView->focusNextPrevEdit(0, false); //take me up + return true; + } + // ---sven's Arrow key navigation end --- + + if (k->key() == Key_Backtab && k->state() == ShiftButton) + { + deselect(); + m_composerView->focusNextPrevEdit(0, false); + return true; + } + } else if ( e->type() == QEvent::ContextMenu ) { + QContextMenuEvent *event = (QContextMenuEvent*) e; + + int para = 1, charPos, firstSpace, lastSpace; + + //Get the character at the position of the click + charPos = charAt( viewportToContents(event->pos() ), ¶ ); + QString paraText = text( para ); + + if( !paraText.at(charPos).isSpace() ) + { + //Get word right clicked on + firstSpace = paraText.findRev( m_bound, charPos ) + 1; + lastSpace = paraText.find( m_bound, charPos ); + if( lastSpace == -1 ) + lastSpace = paraText.length(); + QString word = paraText.mid( firstSpace, lastSpace - firstSpace ); + //Continue if this word was misspelled + if( !word.isEmpty() && m_replacements.contains( word ) ) + { + KPopupMenu p; + p.insertTitle( i18n("Suggestions") ); + + //Add the suggestions to the popup menu + QStringList reps = m_replacements[word]; + if( reps.count() > 0 ) + { + int listPos = 0; + for ( QStringList::Iterator it = reps.begin(); it != reps.end(); ++it ) { + p.insertItem( *it, listPos ); + listPos++; + } + } + else + { + p.insertItem( QString::fromLatin1("No Suggestions"), -2 ); + } + + //Execute the popup inline + int id = p.exec( mapToGlobal( event->pos() ) ); + + if( id > -1 ) + { + //Save the cursor position + int parIdx = 1, txtIdx = 1; + getCursorPosition(&parIdx, &txtIdx); + setSelection(para, firstSpace, para, lastSpace); + insert(m_replacements[word][id]); + // Restore the cursor position; if the cursor was behind the + // misspelled word then adjust the cursor position + if ( para == parIdx && txtIdx >= lastSpace ) + txtIdx += m_replacements[word][id].length() - word.length(); + setCursorPosition(parIdx, txtIdx); + } + //Cancel original event + return true; + } + } + } + + return KEdit::eventFilter(o, e); +} + +void KNComposer::Editor::slotAddSuggestion( const QString &text, const QStringList &lst, unsigned int ) +{ + m_replacements[text] = lst; +} + +// expand tabs to avoid the "tab-damage", +// auto-wraped paragraphs have to split (code taken from KEdit::saveText) +QStringList KNComposer::Editor::processedText() +{ + QStringList ret; + int lines = numLines()-1; + if (lines < 0) + return ret; + + if (wordWrap() == NoWrap) { + for (int i = 0; i <= lines; i++) + ret.append(textLine(i)); + } else { + for (int i = 0; i <= lines; i++) { + int lines_in_parag = linesOfParagraph(i); + + if (lines_in_parag == 1) { + ret.append(textLine(i)); + } else { + QString parag_text = textLine(i); + int pos = 0; + int last_pos = 0; + int current_line = 0; + while (current_line+1 < lines_in_parag) { + while (lineOfChar(i, pos) == current_line) pos++; + ret.append(parag_text.mid(last_pos, pos - last_pos - 1)); + current_line++; + last_pos = pos; + } + // add last line + ret.append(parag_text.mid(pos)); + } + } + } + + QString replacement; + int tabPos; + for (QStringList::Iterator it = ret.begin(); it != ret.end(); ++it ) { + while ((tabPos=(*it).find('\t'))!=-1) { + replacement.fill(QChar(' '), 8-(tabPos%8)); + (*it).replace(tabPos, 1, replacement); + } + } + + return ret; +} + + +void KNComposer::Editor::slotPasteAsQuotation() +{ + QString s = QApplication::clipboard()->text(); + if (!s.isEmpty()) { + for (int i=0; (uint)i<s.length(); i++) { + if ( s[i] < ' ' && s[i] != '\n' && s[i] != '\t' ) + s[i] = ' '; + } + s.prepend("> "); + s.replace(QRegExp("\n"),"\n> "); + insert(s); + } +} + + +void KNComposer::Editor::slotFind() +{ + search(); +} + +void KNComposer::Editor::slotSearchAgain() +{ + repeatSearch(); +} + +void KNComposer::Editor::slotReplace() +{ + replace(); +} + + +void KNComposer::Editor::slotAddQuotes() +{ + if (hasMarkedText()) { + QString s = markedText(); + s.prepend("> "); + s.replace(QRegExp("\n"),"\n> "); + insert(s); + } else { + int l = currentLine(); + int c = currentColumn(); + QString s = textLine(l); + s.prepend("> "); + insertLine(s,l); + removeLine(l+1); + setCursorPosition(l,c+2); + } +} + + +void KNComposer::Editor::slotRemoveQuotes() +{ + if (hasMarkedText()) { + QString s = markedText(); + if (s.left(2) == "> ") + s.remove(0,2); + s.replace(QRegExp("\n> "),"\n"); + insert(s); + } else { + int l = currentLine(); + int c = currentColumn(); + QString s = textLine(l); + if (s.left(2) == "> ") { + s.remove(0,2); + insertLine(s,l); + removeLine(l+1); + setCursorPosition(l,c-2); + } + } +} + + +void KNComposer::Editor::slotAddBox() +{ + if (hasMarkedText()) { + QString s = markedText(); + s.prepend(",----[ ]\n"); + s.replace(QRegExp("\n"),"\n| "); + s.append("\n`----"); + insert(s); + } else { + int l = currentLine(); + int c = currentColumn(); + QString s = QString::fromLatin1(",----[ ]\n| %1\n`----").arg(textLine(l)); + insertLine(s,l); + removeLine(l+3); + setCursorPosition(l+1,c+2); + } +} + + +void KNComposer::Editor::slotRemoveBox() +{ + if (hasMarkedText()) { + QString s = QString::fromLatin1("\n") + markedText() + QString::fromLatin1("\n"); + s.replace(QRegExp("\n,----[^\n]*\n"),"\n"); + s.replace(QRegExp("\n| "),"\n"); + s.replace(QRegExp("\n`----[^\n]*\n"),"\n"); + s.remove(0,1); + s.truncate(s.length()-1); + insert(s); + } else { + int l = currentLine(); + int c = currentColumn(); + + QString s = textLine(l); // test if we are in a box + if (!((s.left(2) == "| ")||(s.left(5)==",----")||(s.left(5)=="`----"))) + return; + + setAutoUpdate(false); + + // find & remove box begin + int x = l; + while ((x>=0)&&(textLine(x).left(5)!=",----")) + x--; + if ((x>=0)&&(textLine(x).left(5)==",----")) { + removeLine(x); + l--; + for (int i=x;i<=l;i++) { // remove quotation + s = textLine(i); + if (s.left(2) == "| ") { + s.remove(0,2); + insertLine(s,i); + removeLine(i+1); + } + } + } + + // find & remove box end + x = l; + while ((x<numLines())&&(textLine(x).left(5)!="`----")) + x++; + if ((x<numLines())&&(textLine(x).left(5)=="`----")) { + removeLine(x); + for (int i=l+1;i<x;i++) { // remove quotation + s = textLine(i); + if (s.left(2) == "| ") { + s.remove(0,2); + insertLine(s,i); + removeLine(i+1); + } + } + } + + setCursorPosition(l,c-2); + + setAutoUpdate(true); + repaint(false); + } +} + + +void KNComposer::Editor::slotRot13() +{ + if (hasMarkedText()) + insert(KNHelper::rot13(markedText())); +} + + +void KNComposer::Editor::contentsDragEnterEvent(QDragEnterEvent *ev) +{ + if (KURLDrag::canDecode(ev)) + emit(sigDragEnterEvent(ev)); + else + KEdit::dragEnterEvent(ev); +} + + +void KNComposer::Editor::contentsDropEvent(QDropEvent *ev) +{ + if (KURLDrag::canDecode(ev)) + emit(sigDropEvent(ev)); + else + KEdit::dropEvent(ev); +} + +void KNComposer::Editor::keyPressEvent ( QKeyEvent *e) +{ + if( e->key() == Key_Return ) { + int line, col; + getCursorPosition( &line, &col ); + QString lineText = text( line ); + // returns line with additional trailing space (bug in Qt?), cut it off + lineText.truncate( lineText.length() - 1 ); + // special treatment of quoted lines only if the cursor is neither at + // the begin nor at the end of the line + if( ( col > 0 ) && ( col < int( lineText.length() ) ) ) { + bool isQuotedLine = false; + uint bot = 0; // bot = begin of text after quote indicators + while( bot < lineText.length() ) { + if( ( lineText[bot] == '>' ) || ( lineText[bot] == '|' ) ) { + isQuotedLine = true; + ++bot; + } + else if( lineText[bot].isSpace() ) { + ++bot; + } + else { + break; + } + } + + KEdit::keyPressEvent( e ); + + // duplicate quote indicators of the previous line before the new + // line if the line actually contained text (apart from the quote + // indicators) and the cursor is behind the quote indicators + if( isQuotedLine + && ( bot != lineText.length() ) + && ( col >= int( bot ) ) ) { + QString newLine = text( line + 1 ); + // remove leading white space from the new line and instead + // add the quote indicators of the previous line + unsigned int leadingWhiteSpaceCount = 0; + while( ( leadingWhiteSpaceCount < newLine.length() ) + && newLine[leadingWhiteSpaceCount].isSpace() ) { + ++leadingWhiteSpaceCount; + } + newLine = newLine.replace( 0, leadingWhiteSpaceCount, + lineText.left( bot ) ); + removeParagraph( line + 1 ); + insertParagraph( newLine, line + 1 ); + // place the cursor at the begin of the new line since + // we assume that the user split the quoted line in order + // to add a comment to the first part of the quoted line + setCursorPosition( line + 1 , 0 ); + } + } + else + KEdit::keyPressEvent( e ); + } + else + KEdit::keyPressEvent( e ); +} + + +void KNComposer::Editor::contentsContextMenuEvent( QContextMenuEvent */*e*/ ) +{ + QString selectWord = selectWordUnderCursor(); + QPopupMenu* popup = 0L; + if ( selectWord.isEmpty()) + { + popup = m_composer ? m_composer->popupMenu( "edit" ): 0; + if ( popup ) + popup->popup(QCursor::pos()); + } + else + { + spell = new KSpell(this, i18n("Spellcheck"), this, SLOT(slotSpellStarted(KSpell *))); + QStringList l = KSpellingHighlighter::personalWords(); + for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) { + spell->addPersonal( *it ); + } + connect(spell, SIGNAL(death()), this, SLOT(slotSpellFinished())); + connect(spell, SIGNAL(done(const QString&)), this, SLOT(slotSpellDone(const QString&))); + connect(spell, SIGNAL(misspelling (const QString &, const QStringList &, unsigned int)), + this, SLOT(slotMisspelling (const QString &, const QStringList &, unsigned int))); + } +} + +void KNComposer::Editor::slotSpellStarted( KSpell *) +{ + spell->check( selectWordUnderCursor(),false ); +} + + +void KNComposer::Editor::slotSpellDone(const QString &/*newtext*/) +{ + spell->cleanUp(); +} + +void KNComposer::Editor::slotSpellFinished() +{ + KSpell::spellStatus status=spell->status(); + delete spell; + spell=0; + + if(status==KSpell::Error) { + KMessageBox::error(this, i18n("ISpell could not be started.\n" + "Please make sure you have ISpell properly configured and in your PATH.")); + } + else if(status==KSpell::Crashed) { + + KMessageBox::error(this, i18n("ISpell seems to have crashed.")); + } +} + +void KNComposer::Editor::cut() +{ + KEdit::cut(); + m_composer->v_iew->restartBackgroundSpellCheck(); +} + +void KNComposer::Editor::clear() +{ + KEdit::clear(); + m_composer->v_iew->restartBackgroundSpellCheck(); +} + +void KNComposer::Editor::del() +{ + KEdit::del(); + m_composer->v_iew->restartBackgroundSpellCheck(); +} + + +void KNComposer::Editor::slotMisspelling (const QString &, const QStringList &lst, unsigned int) +{ + int countAction = m_composer->listOfResultOfCheckWord( lst , selectWordUnderCursor()); + if ( countAction>0 ) + { + QPopupMenu* popup = m_composer ? m_composer->popupMenu( "edit_with_spell" ): 0; + if ( popup ) + popup->popup(QCursor::pos()); + } + else + { + QPopupMenu* popup = m_composer ? m_composer->popupMenu( "edit" ): 0; + if ( popup ) + popup->popup(QCursor::pos()); + } +} + +void KNComposer::Editor::slotCorrectWord() +{ + removeSelectedText(); + KAction * act = (KAction *)(sender()); + int line, col; + getCursorPosition(&line,&col); + + + + insertAt( act->text(), line, col ); + + //insert( act->text() ); +} + +//===================================================================================== + + +KNComposer::AttachmentView::AttachmentView(QWidget *parent, char *name) + : KListView(parent, name) +{ + setFrameStyle(QFrame::WinPanel | QFrame::Sunken); // match the QMultiLineEdit style + addColumn(i18n("File"), 115); + addColumn(i18n("Type"), 91); + addColumn(i18n("Size"), 55); + addColumn(i18n("Description"), 110); + addColumn(i18n("Encoding"), 60); + header()->setClickEnabled(false); + setAllColumnsShowFocus(true); +} + + +KNComposer::AttachmentView::~AttachmentView() +{ +} + + +void KNComposer::AttachmentView::keyPressEvent(QKeyEvent *e) +{ + if(!e) + return; // subclass bug + + if( (e->key()==Key_Delete) && (currentItem()) ) + emit(delPressed(currentItem())); + else + KListView::keyPressEvent(e); +} + + +//===================================================================================== + + +KNComposer::AttachmentViewItem::AttachmentViewItem(KListView *v, KNAttachment *a) : + KListViewItem(v), attachment(a) +{ + setText(0, a->name()); + setText(1, a->mimeType()); + setText(2, a->contentSize()); + setText(3, a->description()); + setText(4, a->encoding()); +} + + + +KNComposer::AttachmentViewItem::~AttachmentViewItem() +{ + delete attachment; +} + + +//===================================================================================== + + +KNComposer::AttachmentPropertiesDlg::AttachmentPropertiesDlg(KNAttachment *a, QWidget *p, const char *n) : + KDialogBase(p, n, true, i18n("Attachment Properties"), Help|Ok|Cancel, Ok), a_ttachment(a), + n_onTextAsText(false) +{ + //init GUI + QWidget *page=new QWidget(this); + setMainWidget(page); + QVBoxLayout *topL=new QVBoxLayout(page); + + //file info + QGroupBox *fileGB=new QGroupBox(i18n("File"), page); + QGridLayout *fileL=new QGridLayout(fileGB, 3,2, 15,5); + + fileL->addRowSpacing(0, fontMetrics().lineSpacing()-9); + fileL->addWidget(new QLabel(i18n("Name:"), fileGB) ,1,0); + fileL->addWidget(new QLabel(QString("<b>%1</b>").arg(a->name()), fileGB), 1,1, Qt::AlignLeft); + fileL->addWidget(new QLabel(i18n("Size:"), fileGB), 2,0); + fileL->addWidget(new QLabel(a->contentSize(), fileGB), 2,1, Qt::AlignLeft); + + fileL->setColStretch(1,1); + topL->addWidget(fileGB); + + //mime info + QGroupBox *mimeGB=new QGroupBox(i18n("Mime"), page); + QGridLayout *mimeL=new QGridLayout(mimeGB, 4,2, 15,5); + + mimeL->addRowSpacing(0, fontMetrics().lineSpacing()-9); + m_imeType=new KLineEdit(mimeGB); + m_imeType->setText(a->mimeType()); + mimeL->addWidget(m_imeType, 1,1); + mimeL->addWidget(new QLabel(m_imeType, i18n("&Mime-Type:"), mimeGB), 1,0); + + d_escription=new KLineEdit(mimeGB); + d_escription->setText(a->description()); + mimeL->addWidget(d_escription, 2,1); + mimeL->addWidget(new QLabel(d_escription, i18n("&Description:"), mimeGB), 2,0); + + e_ncoding=new QComboBox(false, mimeGB); + e_ncoding->insertItem("7Bit"); + e_ncoding->insertItem("8Bit"); + e_ncoding->insertItem("quoted-printable"); + e_ncoding->insertItem("base64"); + if(a->isFixedBase64()) { + e_ncoding->setCurrentItem(3); + e_ncoding->setEnabled(false); + } + else + e_ncoding->setCurrentItem(a->cte()); + mimeL->addWidget(e_ncoding, 3,1); + mimeL->addWidget(new QLabel(e_ncoding, i18n("&Encoding:"), mimeGB), 3,0); + + mimeL->setColStretch(1,1); + topL->addWidget(mimeGB); + + //connections + connect(m_imeType, SIGNAL(textChanged(const QString&)), + this, SLOT(slotMimeTypeTextChanged(const QString&))); + + //finish GUI + setFixedHeight(sizeHint().height()); + KNHelper::restoreWindowSize("attProperties", this, QSize(300,250)); + setHelp("anc-knode-editor-advanced"); +} + + +KNComposer::AttachmentPropertiesDlg::~AttachmentPropertiesDlg() +{ + KNHelper::saveWindowSize("attProperties", this->size()); +} + + +void KNComposer::AttachmentPropertiesDlg::apply() +{ + a_ttachment->setDescription(d_escription->text()); + a_ttachment->setMimeType(m_imeType->text()); + a_ttachment->setCte(e_ncoding->currentItem()); +} + + +void KNComposer::AttachmentPropertiesDlg::accept() +{ + if(m_imeType->text().find('/')==-1) { + KMessageBox::sorry(this, i18n("You have set an invalid mime-type.\nPlease change it.")); + return; + } + else if(n_onTextAsText && m_imeType->text().find("text/", 0, false)!=-1 && + KMessageBox::warningContinueCancel(this, + i18n("You have changed the mime-type of this non-textual attachment\nto text. This might cause an error while loading or encoding the file.\nProceed?") + ) == KMessageBox::Cancel) return; + + KDialogBase::accept(); +} + + +void KNComposer::AttachmentPropertiesDlg::slotMimeTypeTextChanged(const QString &text) +{ + enableButtonOK( !text.isEmpty() ); + if(text.left(5)!="text/") { + n_onTextAsText=a_ttachment->isFixedBase64(); + e_ncoding->setCurrentItem(3); + e_ncoding->setEnabled(false); + } + else { + e_ncoding->setCurrentItem(a_ttachment->cte()); + e_ncoding->setEnabled(true); + } +} + + +//-------------------------------- + +#include "kncomposer.moc" + +// kate: space-indent on; indent-width 2; diff --git a/knode/kncomposer.h b/knode/kncomposer.h new file mode 100644 index 000000000..12c2150f8 --- /dev/null +++ b/knode/kncomposer.h @@ -0,0 +1,384 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCOMPOSER_H +#define KNCOMPOSER_H + +#include <klistview.h> + +#include <kmainwindow.h> +#include <kdialogbase.h> +#include <keditcl.h> +#include <qlineedit.h> +#include <qregexp.h> + +#include <kdeversion.h> +#include <keditcl.h> + +#include <kabc/addresslineedit.h> +#include <knodecomposeriface.h> + +class QGroupBox; + +class KProcess; +class KSpell; +class KDictSpellingHighlighter; +class KSelectAction; +class KToggleAction; + +class KNLocalArticle; +class KNAttachment; +class SpellingFilter; + +class KNComposer : public KMainWindow , virtual public KNodeComposerIface { + + Q_OBJECT + + public: + enum composerResult { CRsendNow, CRsendLater, CRdelAsk, + CRdel, CRsave, CRcancel }; + enum MessageMode { news=0, mail=1, news_mail=2 }; + + // unwraped == original, not rewraped text + // firstEdit==true: place the cursor at the end of the article + KNComposer(KNLocalArticle *a, const QString &text=QString::null, const QString &sig=QString::null, const QString &unwraped=QString::null, bool firstEdit=false, bool dislikesCopies=false, bool createCopy=false); + ~KNComposer(); + void setConfig(bool onlyFonts); + void setMessageMode(MessageMode mode); + + //get result + bool hasValidData(); + composerResult result() const { return r_esult; } + KNLocalArticle* article()const { return a_rticle; } + bool applyChanges(); + + void closeEvent(QCloseEvent *e); + + //set data from the given article + void initData(const QString &text); + + // inserts at cursor position if clear is false, replaces content otherwise + // puts the file content into a box if box==true + // "file" is already open for reading + void insertFile(QFile *file, bool clear=false, bool box=false, QString boxTitle=QString::null); + + // ask for a filename, handle network urls + void insertFile(bool clear=false, bool box=false); + + QPopupMenu * popupMenu( const QString& name ); + int listOfResultOfCheckWord( const QStringList & lst , const QString & selectWord); + +//internal classes + class ComposerView; + class Editor; + class AttachmentView; + class AttachmentViewItem; + class AttachmentPropertiesDlg; + + //GUI + ComposerView *v_iew; + QPopupMenu *a_ttPopup; + + //Data + composerResult r_esult; + KNLocalArticle *a_rticle; + QString s_ignature, u_nwraped; + QCString c_harset; + MessageMode m_ode; + bool n_eeds8Bit, // false: fall back to us-ascii + v_alidated, // hasValidData was run and found no problems, n_eeds8Bit is valid + a_uthorDislikesMailCopies; + + //edit + bool e_xternalEdited; + KProcess *e_xternalEditor; + KTempFile *e_ditorTempfile; + KSpell *s_pellChecker; + SpellingFilter* mSpellingFilter; + + //Attachments + QValueList<KNAttachment*> mDeletedAttachments; + QPtrList<KAction> m_listAction; + bool a_ttChanged; + + //------------------------------ <Actions> ----------------------------- + + KAccel *a_ccel; + KAction *a_ctExternalEditor, + *a_ctSpellCheck, + *a_ctRemoveAttachment, + *a_ctAttachmentProperties, + *a_ctSetCharsetKeyb; + KToggleAction *a_ctPGPsign, + *a_ctDoPost, *a_ctDoMail, *a_ctWordWrap; + KSelectAction *a_ctSetCharset; + bool spellLineEdit; + protected slots: + void slotSendNow(); + void slotSendLater(); + void slotSaveAsDraft(); + void slotArtDelete(); + void slotAppendSig(); + void slotInsertFile(); + void slotInsertFileBoxed(); + void slotAttachFile(); + void slotRemoveAttachment(); + void slotAttachmentProperties(); + void slotToggleDoPost(); + void slotToggleDoMail(); + void slotSetCharset(const QString &s); + void slotSetCharsetKeyboard(); + void slotToggleWordWrap(); + void slotUndoRewrap(); + void slotExternalEditor(); + void slotSpellcheck(); + + void slotUpdateStatusBar(); + void slotUpdateCursorPos(); + void slotConfKeys(); + void slotConfToolbar(); + void slotNewToolbarConfig(); + + //------------------------------ </Actions> ---------------------------- + + // GUI + void slotSubjectChanged(const QString &t); + void slotGroupsChanged(const QString &t); + void slotToBtnClicked(); + void slotGroupsBtnClicked(); + + // external editor + void slotEditorFinished(KProcess *); + void slotCancelEditor(); + + // attachment list + void slotAttachmentPopup(KListView*, QListViewItem *it, const QPoint &p); + void slotAttachmentSelected(QListViewItem *it); + void slotAttachmentEdit(QListViewItem *it); + void slotAttachmentRemove(QListViewItem *it); + + // spellcheck operation + void slotSpellStarted(KSpell *); + void slotSpellDone(const QString&); + void slotSpellFinished(); + + // DND handling + virtual void slotDragEnterEvent(QDragEnterEvent *); + virtual void slotDropEvent(QDropEvent *); + + void slotUndo(); + void slotRedo(); + void slotCut(); + void slotCopy(); + void slotPaste(); + void slotSelectAll(); + void slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos); + void slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos); + void addRecentAddress(); + + protected: + + // DND handling + virtual void dragEnterEvent(QDragEnterEvent *); + virtual void dropEvent(QDropEvent *); + + signals: + void composerDone(KNComposer*); + + private: + bool mFirstEdit; + +}; + + + +class KNLineEditSpell; +class KNLineEdit; + +class KNComposer::ComposerView : public QSplitter { + + public: + ComposerView(KNComposer *_composer, const char *n=0); + ~ComposerView(); + void focusNextPrevEdit(const QWidget* aCur, bool aNext); + void setMessageMode(KNComposer::MessageMode mode); + void showAttachmentView(); + void hideAttachmentView(); + void showExternalNotification(); + void hideExternalNotification(); + void restartBackgroundSpellCheck(); + QValueList<QWidget*> mEdtList; + + QLabel *l_to, + *l_groups, + *l_fup2; + KNLineEditSpell *s_ubject; + + KNLineEdit *g_roups; + KNLineEdit *t_o; + + KComboBox *f_up2; + QPushButton *g_roupsBtn, + *t_oBtn; + + Editor *e_dit; + QGroupBox *n_otification; + QPushButton *c_ancelEditorBtn; + + QWidget *a_ttWidget; + AttachmentView *a_ttView; + QPushButton *a_ttAddBtn, + *a_ttRemoveBtn, + *a_ttEditBtn; + KDictSpellingHighlighter *mSpellChecker; + + bool v_iewOpen; +}; + + +//internal class : handle Tabs... (expanding them in textLine(), etc.) +class KNComposer::Editor : public KEdit { + + Q_OBJECT + + public: + Editor(KNComposer::ComposerView *_composerView, KNComposer *_composer, QWidget *parent=0, char *name=0); + ~Editor(); + QStringList processedText(); + + public slots: + void slotPasteAsQuotation(); + void slotFind(); + void slotSearchAgain(); + void slotReplace(); + void slotAddQuotes(); + void slotRemoveQuotes(); + void slotAddBox(); + void slotRemoveBox(); + void slotRot13(); + void slotCorrectWord(); + +protected slots: + void slotSpellStarted( KSpell *); + void slotSpellDone(const QString &); + void slotSpellFinished(); + void slotMisspelling (const QString &, const QStringList &lst, unsigned int); + virtual void cut(); + virtual void clear(); + virtual void del(); + void slotAddSuggestion( const QString &, const QStringList &lst, unsigned int ); + signals: + void sigDragEnterEvent(QDragEnterEvent *); + void sigDropEvent(QDropEvent *); + + protected: + + // DND handling + virtual void contentsDragEnterEvent(QDragEnterEvent *); + virtual void contentsDropEvent(QDropEvent *); + virtual void contentsContextMenuEvent( QContextMenuEvent *e ); + virtual void keyPressEvent ( QKeyEvent *e); + + virtual bool eventFilter(QObject*, QEvent*); +private: + KNComposer *m_composer; + KNComposer::ComposerView *m_composerView; + KSpell *spell; + QMap<QString,QStringList> m_replacements; + QRegExp m_bound; +}; + + +class KNComposer::AttachmentView : public KListView { + + Q_OBJECT + + public: + AttachmentView(QWidget *parent, char *name=0); + ~AttachmentView(); + + protected: + void keyPressEvent( QKeyEvent *e ); + + signals: + void delPressed ( QListViewItem * ); // the user used Key_Delete on a list view item +}; + + +class KNComposer::AttachmentViewItem : public KListViewItem { + + public: + AttachmentViewItem(KListView *v, KNAttachment *a); + ~AttachmentViewItem(); + + KNAttachment *attachment; + +}; + + +class KNComposer::AttachmentPropertiesDlg : public KDialogBase { + + Q_OBJECT + + public: + AttachmentPropertiesDlg( KNAttachment *a, QWidget *p=0, const char *n=0); + ~AttachmentPropertiesDlg(); + + void apply(); + + protected: + KLineEdit *m_imeType, + *d_escription; + QComboBox *e_ncoding; + + KNAttachment *a_ttachment; + bool n_onTextAsText; + + protected slots: + void accept(); + void slotMimeTypeTextChanged(const QString &text); +}; + +//----------------------------------------------------------------------------- +class KNLineEdit : public KABC::AddressLineEdit +{ + Q_OBJECT + typedef KABC::AddressLineEdit KNLineEditInherited; +public: + + KNLineEdit(KNComposer::ComposerView *_composerView, bool useCompletion, QWidget *parent = 0, + const char *name = 0); +protected: + // Inherited. Always called by the parent when this widget is created. + virtual void loadAddresses(); + void keyPressEvent(QKeyEvent *e); + virtual QPopupMenu *createPopupMenu(); +private slots: + void editRecentAddresses(); +private: + KNComposer::ComposerView *composerView; +}; + +class KNLineEditSpell : public KNLineEdit +{ + Q_OBJECT +public: + KNLineEditSpell(KNComposer::ComposerView *_composerView, bool useCompletion,QWidget * parent, const char * name = 0); + void highLightWord( unsigned int length, unsigned int pos ); + void spellCheckDone( const QString &s ); + void spellCheckerMisspelling( const QString &text, const QStringList &, unsigned int pos); + void spellCheckerCorrected( const QString &old, const QString &corr, unsigned int pos); +}; + +#endif diff --git a/knode/kncomposerui.rc b/knode/kncomposerui.rc new file mode 100644 index 000000000..0a203d266 --- /dev/null +++ b/knode/kncomposerui.rc @@ -0,0 +1,102 @@ +<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> +<kpartgui name="KNComposer" version="20"> + +<MenuBar> + <Menu noMerge="1" name="file"><text>&File</text> + <Action name="send_now"/> + <Action name="send_later"/> + <Action name="save_as_draft"/> + <Action name="art_delete"/> + <Separator/> + <Action name="file_close"/> + </Menu> + <Menu noMerge="1" name="edit"><text>&Edit</text> + <Action name="edit_undo"/> + <Action name="edit_redo"/> + <Separator/> + <Action name="edit_cut"/> + <Action name="edit_copy"/> + <Action name="edit_paste"/> + <Action name="paste_quoted"/> + <Action name="edit_select_all"/> + <Separator/> + <Action name="edit_find"/> + <Action name="edit_find_next"/> + <Action name="edit_replace"/> + </Menu> + <Menu name="attach"><text>&Attach</text> + <Action name="append_signature"/> + <Action name="insert_file"/> + <Action name="insert_file_boxed"/> + <Action name="attach_file"/> + </Menu> + <Menu name="options"><text>Optio&ns</text> + <Action name="send_news"/> + <Action name="send_mail"/> + <Separator/> + <Action name="set_charset"/> + <Action name="toggle_wordwrap"/> + </Menu> + <Menu noMerge="1" name="tools"><text>&Tools</text> + <Action name="tools_quote"/> + <Action name="tools_unquote"/> + <Action name="tools_box"/> + <Action name="tools_unbox"/> + <Separator/> + <Action name="sign_article"/> + <Action name="tools_undoRewrap"/> + <Action name="tools_rot13"/> + <Separator/> + <Action name="external_editor"/> + <Action name="tools_spelling"/> + </Menu> +</MenuBar> + +<ToolBar noMerge="1" name="mainToolBar"><text>Main Toolbar</text> + <Action name="send_now"/> + <Action name="send_later"/> + <Action name="save_as_draft"/> + <Action name="art_delete"/> + <Separator/> + <Action name="edit_undo"/> + <Action name="edit_redo"/> + <Separator/> + <Action name="edit_cut"/> + <Action name="edit_copy"/> + <Action name="edit_paste"/> + <Separator/> + <Action name="send_news"/> + <Action name="send_mail"/> + <Separator/> + <Action name="attach_file"/> + <Action name="sign_article"/> + <Action name="tools_spelling"/> +</ToolBar> + +<StatusBar/> + +<Menu name="attachment_popup"> + <Action name="attachment_properties"/> + <Action name="remove_attachment"/> +</Menu> + + +<Menu name="edit_with_spell"> + <Action name="edit_undo"/> + <Action name="edit_redo"/> + <Separator/> + <Action name="edit_cut"/> + <Action name="edit_copy"/> + <Action name="edit_paste"/> + <Action name="paste_quoted"/> + <Action name="edit_select_all"/> + <Separator/> + <Action name="edit_find"/> + <Action name="edit_replace"/> + <Separator/> + <Menu name="spell"><text>Spell Result</text> + <ActionList name="spell_result"/> + </Menu> +</Menu> + +</kpartgui> diff --git a/knode/knconfig.cpp b/knode/knconfig.cpp new file mode 100644 index 000000000..cadf402b8 --- /dev/null +++ b/knode/knconfig.cpp @@ -0,0 +1,1252 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include "knconfig.h" + +#include <stdlib.h> + +#include <qtextcodec.h> + +#include <ksimpleconfig.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kapplication.h> +#include <kglobalsettings.h> +#include <kstandarddirs.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <kiconeffect.h> +#include <kprocess.h> + +#include <email.h> + +#include "kndisplayedheader.h" +#include "knglobals.h" +#include "kngroupmanager.h" +#include "utilities.h" + + + +KNConfig::Identity::Identity(bool g) + : u_seSigFile(false), u_seSigGenerator(false), g_lobal(g) +{ + if(g_lobal) { + KConfig *c=knGlobals.config(); + c->setGroup("IDENTITY"); + loadConfig(c); + } +} + + +KNConfig::Identity::~Identity() +{} + + +void KNConfig::Identity::loadConfig(KConfigBase *c) +{ + n_ame=c->readEntry("Name"); + e_mail=c->readEntry("Email"); + r_eplyTo=c->readEntry("Reply-To"); + m_ailCopiesTo=c->readEntry("Mail-Copies-To"); + o_rga=c->readEntry("Org"); + s_igningKey = c->readEntry("SigningKey").local8Bit(); + u_seSigFile=c->readBoolEntry("UseSigFile",false); + u_seSigGenerator=c->readBoolEntry("UseSigGenerator",false); + s_igPath=c->readPathEntry("sigFile"); + s_igText=c->readEntry("sigText"); +} + + +void KNConfig::Identity::saveConfig(KConfigBase *c) +{ + c->writeEntry("Name", n_ame); + c->writeEntry("Email", e_mail); + c->writeEntry("Reply-To", r_eplyTo); + c->writeEntry("Mail-Copies-To", m_ailCopiesTo); + c->writeEntry("Org", o_rga); + c->writeEntry("SigningKey", QString(s_igningKey)); + c->writeEntry("UseSigFile", u_seSigFile); + c->writeEntry("UseSigGenerator",u_seSigGenerator); + c->writePathEntry("sigFile", s_igPath); + c->writeEntry("sigText", s_igText); + c->sync(); +} + + +void KNConfig::Identity::save() +{ + kdDebug(5003) << "KNConfig::Identity::save()" << endl; + if(g_lobal) { + KConfig *c=knGlobals.config(); + c->setGroup("IDENTITY"); + saveConfig(c); + } +} + + +bool KNConfig::Identity::isEmpty() +{ + return ( n_ame.isEmpty() && e_mail.isEmpty() && + r_eplyTo.isEmpty() && m_ailCopiesTo.isEmpty() && + o_rga.isEmpty() && s_igPath.isEmpty() && s_igText.isEmpty() && + s_igningKey.isEmpty() ); +} + + +bool KNConfig::Identity::emailIsValid() +{ + return KPIM::isValidSimpleEmailAddress( e_mail ); +} + + +QString KNConfig::Identity::getSignature() +{ + s_igContents = QString::null; // don't cache file contents + s_igStdErr = QString::null; + + if (u_seSigFile) { + if(!s_igPath.isEmpty()) { + if (!u_seSigGenerator) { + QFile f(s_igPath); + if(f.open(IO_ReadOnly)) { + QTextStream ts(&f); + while(!ts.atEnd()) { + s_igContents += ts.readLine(); + if (!ts.atEnd()) + s_igContents += "\n"; + } + f.close(); + } + else + KMessageBox::error(knGlobals.topWidget, i18n("Cannot open the signature file.")); + } else { + KProcess process; + + // construct command line... + QStringList command = QStringList::split(' ',s_igPath); + for ( QStringList::Iterator it = command.begin(); it != command.end(); ++it ) + process << (*it); + + connect(&process, SIGNAL(receivedStdout(KProcess *, char *, int)), SLOT(slotReceiveStdout(KProcess *, char *, int))); + connect(&process, SIGNAL(receivedStderr(KProcess *, char *, int)), SLOT(slotReceiveStderr(KProcess *, char *, int))); + + if (!process.start(KProcess::Block,KProcess::AllOutput)) + KMessageBox::error(knGlobals.topWidget, i18n("Cannot run the signature generator.")); + } + } + } + else + s_igContents = s_igText; + + if (!s_igContents.isEmpty() && !s_igContents.contains("\n-- \n") && !(s_igContents.left(4) == "-- \n")) + s_igContents.prepend("-- \n"); + + return s_igContents; +} + + +void KNConfig::Identity::slotReceiveStdout(KProcess *, char *buffer, int buflen) +{ + s_igContents.append(QString::fromLocal8Bit(buffer,buflen)); +} + + +void KNConfig::Identity::slotReceiveStderr(KProcess *, char *buffer, int buflen) +{ + s_igStdErr.append(QString::fromLocal8Bit(buffer,buflen)); +} + + +//============================================================================================================== + + +KNConfig::Appearance::Appearance() +{ + KConfig *c=knGlobals.config(); + c->setGroup("VISUAL_APPEARANCE"); + + //colors + u_seColors=c->readBoolEntry("customColors", false); + + QColor defCol = defaultColor( background ); + c_olors[background] = c->readColorEntry( "backgroundColor", &defCol ); + c_olorNames[background] = i18n("Background"); + + defCol = defaultColor( alternateBackground ); + c_olors[alternateBackground] = c->readColorEntry("alternateBackgroundColor", &defCol ); + c_olorNames[alternateBackground] = i18n("Alternate Background"); + + defCol = defaultColor( normalText ); + c_olors[normalText] = c->readColorEntry( "textColor", &defCol ); + c_olorNames[normalText] = i18n("Normal Text"); + + defCol = defaultColor( quoted1 ); + c_olors[quoted1] = c->readColorEntry( "quote1Color", &defCol ); + c_olorNames[quoted1] = i18n("Quoted Text - First level"); + + defCol = defaultColor( quoted2 ); + c_olors[quoted2] = c->readColorEntry( "quote2Color", &defCol ); + c_olorNames[quoted2] = i18n("Quoted Text - Second level"); + + defCol = defaultColor( quoted3 ); + c_olors[quoted3] = c->readColorEntry( "quote3Color", &defCol ); + c_olorNames[quoted3] = i18n("Quoted Text - Third level"); + + defCol = defaultColor( url ); + c_olors[url] = c->readColorEntry( "URLColor", &defCol ); + c_olorNames[url] = i18n("Link"); + + defCol = defaultColor( readThread ); + c_olors[readThread] = c->readColorEntry( "readThreadColor", &defCol ); + c_olorNames[readThread] = i18n("Read Thread"); + + defCol = defaultColor( unreadThread ); + c_olors[unreadThread] = c->readColorEntry("unreadThreadColor", &defCol ); + c_olorNames[unreadThread] = i18n("Unread Thread"); + + defCol = defaultColor( readThread ); + c_olors[readArticle] = c->readColorEntry("readArtColor", &defCol ); + c_olorNames[readArticle] = i18n("Read Article"); + + defCol = defaultColor( unreadArticle ); + c_olors[unreadArticle] = c->readColorEntry("unreadArtColor", &defCol ); + c_olorNames[unreadArticle] = i18n("Unread Article"); + + defCol = defaultColor( signOkKeyOk ); + c_olors[signOkKeyOk] = c->readColorEntry("signOkKeyOk", &defCol ); + defCol = defaultColor( signOkKeyBad ); + c_olors[signOkKeyBad] = c->readColorEntry("signOkKeyBad", &defCol ); + defCol = defaultColor( signWarn ); + c_olors[signWarn] = c->readColorEntry("signWarn", &defCol ); + defCol = defaultColor( signErr ); + c_olors[signErr] = c->readColorEntry("signErr", &defCol ); + defCol = defaultColor( htmlWarning ); + c_olors[htmlWarning] = c->readColorEntry("htmlWarning", &defCol ); + + c_olorNames[signOkKeyOk] = i18n("Valid Signature with Trusted Key"); + c_olorNames[signOkKeyBad] = i18n("Valid Signature with Untrusted Key"); + c_olorNames[signWarn] = i18n("Unchecked Signature"); + c_olorNames[signErr] = i18n("Bad Signature"); + c_olorNames[htmlWarning] = i18n("HTML Message Warning"); + + //fonts + u_seFonts = c->readBoolEntry("customFonts", false); + QFont defFont=KGlobalSettings::generalFont(); + f_onts[article]=c->readFontEntry("articleFont",&defFont); + f_ontNames[article]=i18n("Article Body"); + + defFont=KGlobalSettings::fixedFont(); + f_onts[articleFixed]=c->readFontEntry("articleFixedFont",&defFont); + f_ontNames[articleFixed]=i18n("Article Body (Fixed)"); + + f_onts[composer]=c->readFontEntry("composerFont",&defFont); + f_ontNames[composer]=i18n("Composer"); + + defFont=KGlobalSettings::generalFont(); + f_onts[groupList]=c->readFontEntry("groupListFont",&defFont); + f_ontNames[groupList]=i18n("Group List"); + + f_onts[articleList]=c->readFontEntry("articleListFont",&defFont); + f_ontNames[articleList]=i18n("Article List"); + + //icons + KGlobal::iconLoader()->addAppDir("knode"); + recreateLVIcons(); + i_cons[newFups] = UserIcon("newsubs"); + i_cons[eyes] = UserIcon("eyes"); + i_cons[ignore] = UserIcon("ignore"); + i_cons[mail] = SmallIcon("mail_generic"); + i_cons[posting] = UserIcon("article"); + i_cons[canceledPosting] = SmallIcon("editdelete"); + i_cons[savedRemote] = SmallIcon("editcopy"); + i_cons[group] = UserIcon("group"); + i_cons[sendErr] = UserIcon("snderr"); +} + + +KNConfig::Appearance::~Appearance() +{ +} + + +void KNConfig::Appearance::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::Appearance::save()" << endl; + + KConfig *c=knGlobals.config(); + c->setGroup("VISUAL_APPEARANCE"); + + c->writeEntry("customColors", u_seColors); + c->writeEntry("backgroundColor", c_olors[background]); + c->writeEntry("alternateBackgroundColor", c_olors[alternateBackground]); + c->writeEntry("textColor", c_olors[normalText]); + c->writeEntry("quote1Color", c_olors[quoted1]); + c->writeEntry("quote2Color", c_olors[quoted2]); + c->writeEntry("quote3Color", c_olors[quoted3]); + c->writeEntry("URLColor", c_olors[url]); + c->writeEntry("readThreadColor", c_olors[readThread]); + c->writeEntry("unreadThreadColor", c_olors[unreadThread]); + c->writeEntry("readArtColor", c_olors[readArticle]); + c->writeEntry("unreadArtColor", c_olors[unreadArticle]); + c->writeEntry( "signOkKeyOk", c_olors[signOkKeyOk] ); + c->writeEntry( "signOkKeyBad", c_olors[signOkKeyBad] ); + c->writeEntry( "signWarn", c_olors[signWarn] ); + c->writeEntry( "signErr", c_olors[signErr] ); + c->writeEntry( "htmlWarning", c_olors[htmlWarning] ); + + c->writeEntry("customFonts", u_seFonts); + c->writeEntry("articleFont", f_onts[article]); + c->writeEntry("articleFixedFont", f_onts[articleFixed]); + c->writeEntry("composerFont", f_onts[composer]); + c->writeEntry("groupListFont", f_onts[groupList]); + c->writeEntry("articleListFont", f_onts[articleList]); + c->sync(); + d_irty = false; +} + + +QColor KNConfig::Appearance::backgroundColor() const +{ + if(u_seColors) + return c_olors[background]; + else + return defaultColor( background ); +} + + +QColor KNConfig::Appearance::alternateBackgroundColor() const +{ + if(u_seColors) + return c_olors[alternateBackground]; + else + return defaultColor( alternateBackground ); +} + + +QColor KNConfig::Appearance::textColor() const +{ + if(u_seColors) + return c_olors[normalText]; + else + return defaultColor( normalText ); +} + + +QColor KNConfig::Appearance::quoteColor( int depth ) const +{ + if ( u_seColors ) + return c_olors[quoted1 + depth]; + else + return defaultColor( quoted1 + depth ); +} + + +QColor KNConfig::Appearance::linkColor() const +{ + if(u_seColors) + return c_olors[url]; + else + return defaultColor( url ); + +} + + +QColor KNConfig::Appearance::unreadThreadColor() const +{ + if(u_seColors) + return c_olors[unreadThread]; + else + return defaultColor( unreadThread ); +} + + +QColor KNConfig::Appearance::readThreadColor() const +{ + if(u_seColors) + return c_olors[readThread]; + else + return defaultColor( readThread ); +} + + +QColor KNConfig::Appearance::unreadArticleColor() const +{ + if(u_seColors) + return c_olors[unreadArticle]; + else + return defaultColor( unreadArticle ); +} + + +QColor KNConfig::Appearance::readArticleColor() const +{ + if(u_seColors) + return c_olors[readArticle]; + else + return defaultColor( readArticle ); +} + + +QFont KNConfig::Appearance::articleFont() const +{ + if(u_seFonts) + return f_onts[article]; + else + return defaultFont( article ); +} + + +QFont KNConfig::Appearance::articleFixedFont() const +{ + if(u_seFonts) + return f_onts[articleFixed]; + else + return defaultFont( articleFixed ); +} + + +QFont KNConfig::Appearance::composerFont() const +{ + if(u_seFonts) + return f_onts[composer]; + else + return defaultFont( composer ); +} + + +QFont KNConfig::Appearance::groupListFont() const +{ + if(u_seFonts) + return f_onts[groupList]; + else + return defaultFont( groupList ); +} + + +QFont KNConfig::Appearance::articleListFont() const +{ + if(u_seFonts) + return f_onts[articleList]; + else + return defaultFont( articleList ); +} + + +QColor KNConfig::Appearance::defaultColor(int i) const +{ + // defaults should match libkdepim/csshelper.cpp + switch(i) { + + case background: + return kapp->palette().active().base(); + + case alternateBackground: + return KGlobalSettings::alternateBackgroundColor(); + + case quoted1: + return QColor( 0x00, 0x80, 0x00 ); + case quoted2: + return QColor( 0x00, 0x70, 0x00 ); + case quoted3: + return QColor( 0x00, 0x60, 0x00 ); + + case normalText: + case unreadThread: + return kapp->palette().active().text(); + + case url: + return KGlobalSettings::linkColor(); + + case readThread: + return kapp->palette().disabled().text(); + + case unreadArticle: + return QColor( 183, 154, 11 ); + + case readArticle: + return QColor( 136, 136, 136 ); + + case signOkKeyOk: + return QColor( 0x40, 0xFF, 0x00 ); + case signOkKeyBad: + case signWarn: + return QColor( 0xFF, 0xFF, 0x40 ); + case signErr: + return Qt::red; + + case htmlWarning: + return QColor( 0xFF, 0x40, 0x40 ); + } + + return kapp->palette().disabled().text(); +} + + +QFont KNConfig::Appearance::defaultFont(int i) const +{ + if ( i == articleFixed || i == composer ) + return KGlobalSettings::fixedFont(); + else + return KGlobalSettings::generalFont(); +} + + +void KNConfig::Appearance::recreateLVIcons() +{ + QPixmap tempPix = UserIcon("greyball"); + + QImage tempImg=tempPix.convertToImage(); + KIconEffect::colorize(tempImg, readArticleColor(), 1.0); + i_cons[greyBall].convertFromImage(tempImg); + + tempImg=tempPix.convertToImage(); + KIconEffect::colorize(tempImg, unreadArticleColor(), 1.0); + i_cons[redBall].convertFromImage(tempImg); + + tempPix = UserIcon("greyballchk"); + + tempImg=tempPix.convertToImage(); + KIconEffect::colorize(tempImg, readArticleColor(), 1.0); + i_cons[greyBallChkd].convertFromImage(tempImg); + + tempImg=tempPix.convertToImage(); + KIconEffect::colorize(tempImg, unreadArticleColor(), 1.0); + i_cons[redBallChkd].convertFromImage(tempImg); +} + + +//============================================================================================================== + + +KNConfig::ReadNewsGeneral::ReadNewsGeneral() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS"); + + a_utoCheck=conf->readBoolEntry("autoCheck", true); + m_axFetch=conf->readNumEntry("maxFetch", 1000); + if (m_axFetch<0) m_axFetch = 0; + a_utoMark=conf->readBoolEntry("autoMark", true); + m_arkSecs=conf->readNumEntry("markSecs", 0); + if (m_arkSecs<0) m_arkSecs = 0; + m_arkCrossposts=conf->readBoolEntry("markCrossposts", true); + s_martScrolling=conf->readBoolEntry("smartScrolling", true); + t_otalExpand=conf->readBoolEntry("totalExpand", true); + d_efaultExpand=conf->readBoolEntry("defaultExpand", false); + s_howLines=conf->readBoolEntry("showLines3", true); + s_howScore=conf->readBoolEntry("showScore3", true); + s_howUnread=conf->readBoolEntry("showUnread", true); + s_howThreads = conf->readBoolEntry("showThreads", true); + mDateFormat = (KMime::DateFormatter::FormatType) conf->readNumEntry( "dateFormat", KMime::DateFormatter::Localized ); + mDateCustomFormat = conf->readEntry( "customDateFormat" ); + + conf->setGroup("CACHE"); + c_ollCacheSize=conf->readNumEntry("collMemSize", 2048); + a_rtCacheSize=conf->readNumEntry("artMemSize", 1024); +} + + +KNConfig::ReadNewsGeneral::~ReadNewsGeneral() +{ +} + + +void KNConfig::ReadNewsGeneral::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::ReadNewsGeneral::save()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS"); + + conf->writeEntry("autoCheck", a_utoCheck); + conf->writeEntry("maxFetch", m_axFetch); + conf->writeEntry("autoMark", a_utoMark); + conf->writeEntry("markSecs", m_arkSecs); + conf->writeEntry("markCrossposts", m_arkCrossposts); + conf->writeEntry("smartScrolling", s_martScrolling); + conf->writeEntry("totalExpand", t_otalExpand); + conf->writeEntry("defaultExpand", d_efaultExpand); + conf->writeEntry("showLines3", s_howLines); + conf->writeEntry("showScore3", s_howScore); + conf->writeEntry("showUnread", s_howUnread); + conf->writeEntry("showThreads", s_howThreads); + conf->writeEntry( "dateFormat", mDateFormat ); + conf->writeEntry( "customDateFormat", mDateCustomFormat ); + + conf->setGroup("CACHE"); + conf->writeEntry("collMemSize", c_ollCacheSize); + conf->writeEntry("artMemSize", a_rtCacheSize); + conf->sync(); + d_irty = false; +} + + +//============================================================================================================== + + +KNConfig::ReadNewsNavigation::ReadNewsNavigation() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS_NAVIGATION"); + + m_arkAllReadGoNext=conf->readBoolEntry("markAllReadGoNext", false); + m_arkThreadReadGoNext=conf->readBoolEntry("markThreadReadGoNext", false); + m_arkThreadReadCloseThread=conf->readBoolEntry("markThreadReadCloseThread", false); + i_gnoreThreadGoNext=conf->readBoolEntry("ignoreThreadGoNext", false); + i_gnoreThreadCloseThread=conf->readBoolEntry("ignoreThreadCloseThread", false); + mLeaveGroupMarkAsRead = conf->readBoolEntry( "leaveGroupMarkAsRead", false ); +} + + +KNConfig::ReadNewsNavigation::~ReadNewsNavigation() +{ +} + + +void KNConfig::ReadNewsNavigation::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::ReadNewsNavigation::save()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS_NAVIGATION"); + + conf->writeEntry("markAllReadGoNext", m_arkAllReadGoNext); + conf->writeEntry("markThreadReadGoNext", m_arkThreadReadGoNext); + conf->writeEntry("markThreadReadCloseThread", m_arkThreadReadCloseThread); + conf->writeEntry("ignoreThreadGoNext", i_gnoreThreadGoNext); + conf->writeEntry("ignoreThreadCloseThread", i_gnoreThreadCloseThread); + conf->writeEntry("leaveGroupMarkAsRead=true", mLeaveGroupMarkAsRead ); + conf->sync(); + d_irty = false; +} + + +//============================================================================================================== + + +KNConfig::ReadNewsViewer::ReadNewsViewer() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS"); + + r_ewrapBody=conf->readBoolEntry("rewrapBody", true); + r_emoveTrailingNewlines=conf->readBoolEntry("removeTrailingNewlines", true); + s_howSig=conf->readBoolEntry("showSig", true); + i_nterpretFormatTags=conf->readBoolEntry("interpretFormatTags", true); + q_uoteCharacters=conf->readEntry("quoteCharacters",">:"); + o_penAtt=conf->readBoolEntry("openAtt", false) ; + s_howAlts=conf->readBoolEntry("showAlts", false); + u_seFixedFont=conf->readBoolEntry("articleBodyFixedFont", false); + mShowRefBar = conf->readBoolEntry( "showRefBar", true ); + mAlwaysShowHTML = conf->readBoolEntry( "alwaysShowHTML", false ); +} + + +KNConfig::ReadNewsViewer::~ReadNewsViewer() +{ +} + + +void KNConfig::ReadNewsViewer::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::ReadNewsViewer::save()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS"); + + conf->writeEntry("rewrapBody", r_ewrapBody); + conf->writeEntry("removeTrailingNewlines", r_emoveTrailingNewlines); + conf->writeEntry("showSig", s_howSig); + conf->writeEntry("interpretFormatTags", i_nterpretFormatTags); + conf->writeEntry("quoteCharacters",q_uoteCharacters); + conf->writeEntry("openAtt", o_penAtt); + conf->writeEntry("showAlts", s_howAlts); + conf->writeEntry("articleBodyFixedFont", u_seFixedFont); + conf->writeEntry("showRefBar", mShowRefBar ); + conf->writeEntry( "alwaysShowHTML", mAlwaysShowHTML ); + conf->sync(); + d_irty = false; +} + + +//============================================================================================================== + + +KNConfig::DisplayedHeaders::DisplayedHeaders() +{ + QString fname( locate("data","knode/headers.rc") ); + + if (!fname.isNull()) { + KSimpleConfig headerConf(fname,true); + QStringList headers = headerConf.groupList(); + headers.remove("<default>"); + headers.sort(); + + KNDisplayedHeader *h; + QValueList<int> flags; + + QStringList::Iterator it; + for( it = headers.begin(); it != headers.end(); ++it ) { + h=createNewHeader(); + headerConf.setGroup((*it)); + h->setName(headerConf.readEntry("Name")); + h->setTranslateName(headerConf.readBoolEntry("Translate_Name",true)); + h->setHeader(headerConf.readEntry("Header")); + flags=headerConf.readIntListEntry("Flags"); + if(h->name().isNull() || h->header().isNull() || (flags.count()!=8)) { + kdDebug(5003) << "KNConfig::DisplayedHeaders::DisplayedHeaders() : ignoring invalid/incomplete Header" << endl; + remove(h); + } + else { + for (int i=0; i<8; i++) + h->setFlag(i, (flags[i]>0)); + h->createTags(); + } + } + } +} + + +KNConfig::DisplayedHeaders::~DisplayedHeaders() +{ + for ( QValueList<KNDisplayedHeader*>::Iterator it = mHeaderList.begin(); it != mHeaderList.end(); ++it ) + delete (*it); +} + + +void KNConfig::DisplayedHeaders::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::DisplayedHeaders::save()" << endl; + + QString dir(locateLocal("data","knode/")); + if (dir.isNull()) { + KNHelper::displayInternalFileError(); + return; + } + KSimpleConfig headerConf(dir+"headers.rc"); + QStringList oldHeaders = headerConf.groupList(); + + QStringList::Iterator oldIt=oldHeaders.begin(); + for( ;oldIt != oldHeaders.end(); ++oldIt ) // remove all old groups + headerConf.deleteGroup((*oldIt)); // find a better way to do it? + + QValueList<int> flags; + int idx=0; + QString group; + + for ( QValueList<KNDisplayedHeader*>::Iterator it = mHeaderList.begin(); it != mHeaderList.end(); ++it ) { + group.setNum(idx++); + while (group.length()<3) + group.prepend("0"); + headerConf.setGroup(group); + headerConf.writeEntry("Name",(*it)->name()); + headerConf.writeEntry("Translate_Name",(*it)->translateName()); + headerConf.writeEntry("Header",(*it)->header()); + flags.clear(); + for (int i=0; i<8; i++) { + if ((*it)->flag(i)) + flags << 1; + else + flags << 0; + } + headerConf.writeEntry("Flags",flags); + } + headerConf.sync(); + d_irty = false; +} + + +KNDisplayedHeader* KNConfig::DisplayedHeaders::createNewHeader() +{ + KNDisplayedHeader *h=new KNDisplayedHeader(); + mHeaderList.append( h ); + + return h; +} + + +void KNConfig::DisplayedHeaders::remove(KNDisplayedHeader *h) +{ + if ( !mHeaderList.remove( h ) ) + kdDebug(5003) << "KNConfig::DisplayedHeaders::remove() : cannot find pointer in list!" << endl; + +} + + +void KNConfig::DisplayedHeaders::up(KNDisplayedHeader *h) +{ + int idx = mHeaderList.findIndex( h ); + if ( idx != -1 ) { + mHeaderList.remove( mHeaderList.at( idx ) ); + mHeaderList.insert( mHeaderList.at( idx - 1 ), h ); + } + else kdDebug(5003) << "KNConfig::DisplayedHeaders::up() : item not found in list" << endl; +} + + +void KNConfig::DisplayedHeaders::down(KNDisplayedHeader *h) +{ + int idx = mHeaderList.findIndex( h ); + if ( idx != -1 ) { + mHeaderList.remove( mHeaderList.at( idx ) ); + mHeaderList.insert( mHeaderList.at( idx + 1 ), h ); + } + else kdDebug(5003) << "KNConfig::DisplayedHeaders::down() : item not found in list" << endl; +} + + +//============================================================================================================== + + +KNConfig::Scoring::Scoring() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("SCORING"); + + i_gnoredThreshold=conf->readNumEntry("ignoredThreshold", -100); + w_atchedThreshold=conf->readNumEntry("watchedThreshold", 100); +} + + +KNConfig::Scoring::~Scoring() +{ +} + + +void KNConfig::Scoring::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::Scoring::save()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("SCORING"); + + conf->writeEntry("ignoredThreshold", i_gnoredThreshold); + conf->writeEntry("watchedThreshold", w_atchedThreshold); + conf->sync(); + d_irty = false; +} + + +//============================================================================================================== + + +KNConfig::XHeader::XHeader(const QString &s) +{ + if(s.left(2)=="X-") { + int pos=s.find(": "); + if(pos!=-1) { + n_ame=s.mid(2, pos-2).latin1(); + pos+=2; + v_alue=s.mid(pos, s.length()-pos); + } + } +} + + +//============================================================================================================== + + +KNConfig::PostNewsTechnical::PostNewsTechnical() + : findComposerCSCache(113) +{ + findComposerCSCache.setAutoDelete(true); + + KConfig *conf=knGlobals.config(); + conf->setGroup("POSTNEWS"); + + c_omposerCharsets=conf->readListEntry("ComposerCharsets"); + if (c_omposerCharsets.isEmpty()) + c_omposerCharsets=QStringList::split(',',"us-ascii,utf-8,iso-8859-1,iso-8859-2," + "iso-8859-3,iso-8859-4,iso-8859-5,iso-8859-6,iso-8859-7,iso-8859-8," + "iso-8859-9,iso-8859-10,iso-8859-13,iso-8859-14,iso-8859-15,koi8-r,koi8-u," + "iso-2022-jp,iso-2022-jp-2,iso-2022-kr,euc-jp,euc-kr,Big5,gb2312"); + + c_harset=conf->readEntry("Charset").latin1(); + if (c_harset.isEmpty()) { + QCString localeCharset(QTextCodec::codecForLocale()->mimeName()); + + // special logic for japanese users: + // "euc-jp" is default encoding for them, but in the news + // "iso-2022-jp" is used + if (localeCharset.lower() == "euc-jp") + localeCharset = "iso-2022-jp"; + + c_harset=findComposerCharset(localeCharset); + if (c_harset.isEmpty()) + c_harset="iso-8859-1"; // shit + } + + h_ostname=conf->readEntry("MIdhost").latin1(); + a_llow8BitBody=conf->readBoolEntry("8BitEncoding",true); + u_seOwnCharset=conf->readBoolEntry("UseOwnCharset",true); + g_enerateMID=conf->readBoolEntry("generateMId", false); + d_ontIncludeUA=conf->readBoolEntry("dontIncludeUA", false); + u_seExternalMailer=conf->readBoolEntry("useExternalMailer", false); + + QString dir(locateLocal("data","knode/")); + if (!dir.isNull()) { + QFile f(dir+"xheaders"); + if(f.open(IO_ReadOnly)) { + QTextStream ts(&f); + while(!ts.eof()) + x_headers.append( XHeader(ts.readLine()) ); + + f.close(); + } + } +} + + +KNConfig::PostNewsTechnical::~PostNewsTechnical() +{ +} + + +void KNConfig::PostNewsTechnical::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::PostNewsTechnical::save()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("POSTNEWS"); + + conf->writeEntry("ComposerCharsets", c_omposerCharsets); + conf->writeEntry("Charset", QString::fromLatin1(c_harset)); + conf->writeEntry("8BitEncoding", a_llow8BitBody); + conf->writeEntry("UseOwnCharset", u_seOwnCharset); + conf->writeEntry("generateMId", g_enerateMID); + conf->writeEntry("MIdhost", QString::fromLatin1(h_ostname)); + conf->writeEntry("dontIncludeUA", d_ontIncludeUA); + conf->writeEntry("useExternalMailer", u_seExternalMailer); + + QString dir(locateLocal("data","knode/")); + if (dir.isNull()) + KNHelper::displayInternalFileError(); + else { + QFile f(dir+"xheaders"); + if(f.open(IO_WriteOnly)) { + QTextStream ts(&f); + XHeaders::Iterator it; + for(it=x_headers.begin(); it!=x_headers.end(); ++it) + ts << (*it).header() << "\n"; + f.close(); + } + else + KNHelper::displayInternalFileError(); + } + conf->sync(); + d_irty = false; +} + + +int KNConfig::PostNewsTechnical::indexForCharset(const QCString &str) +{ + int i=0; + bool found=false; + for ( QStringList::Iterator it = c_omposerCharsets.begin(); it != c_omposerCharsets.end(); ++it ) { + if ((*it).lower() == str.lower().data()) { + found = true; + break; + } + i++; + } + if (!found) { + i=0; + for ( QStringList::Iterator it = c_omposerCharsets.begin(); it != c_omposerCharsets.end(); ++it ) { + if ((*it).lower() == c_harset.lower().data()) { + found = true; + break; + } + i++; + } + if (!found) + i=0; + } + return i; +} + + +QCString KNConfig::PostNewsTechnical::findComposerCharset(QCString cs) +{ + QCString *ret=findComposerCSCache.find(cs); + if (ret) + return *ret; + + QCString s; + + QStringList::Iterator it; + for( it = c_omposerCharsets.begin(); it != c_omposerCharsets.end(); ++it ) { + // match by name + if ((*it).lower()==cs.lower().data()) { + s = (*it).latin1(); + break; + } + } + + if (s.isEmpty()) { + for( it = c_omposerCharsets.begin(); it != c_omposerCharsets.end(); ++it ) { + // match by charset, avoid to return "us-ascii" for iso-8859-1 + if ((*it).lower()!="us-ascii") { + QTextCodec *composerCodec = QTextCodec::codecForName((*it).latin1()); + QTextCodec *csCodec = QTextCodec::codecForName(cs); + if ((composerCodec != 0) && + (csCodec != 0) && + (0 == strcmp(composerCodec->name(), csCodec->name()))) { + s = (*it).latin1(); + break; + } + } + } + } + + if (s.isEmpty()) + s = "us-ascii"; + + findComposerCSCache.insert(cs, new QCString(s)); + + return s; +} + + +//============================================================================================================== + + +KNConfig::PostNewsComposer::PostNewsComposer() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("POSTNEWS"); + + w_ordWrap=conf->readBoolEntry("wordWrap",true); + m_axLen=conf->readNumEntry("maxLength", 76); + a_ppSig=conf->readBoolEntry("appSig",true); + r_ewrap=conf->readBoolEntry("rewrap",true); + i_ncSig=conf->readBoolEntry("incSig",false); + c_ursorOnTop=conf->readBoolEntry("cursorOnTop",false); + u_seExtEditor=conf->readBoolEntry("useExternalEditor",false); + i_ntro=conf->readEntry("Intro","%NAME wrote:"); + e_xternalEditor=conf->readEntry("externalEditor","kwrite %f"); +} + + +KNConfig::PostNewsComposer::~PostNewsComposer() +{ +} + + +void KNConfig::PostNewsComposer::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::PostNewsComposer::save()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("POSTNEWS"); + + conf->writeEntry("wordWrap", w_ordWrap); + conf->writeEntry("maxLength", m_axLen); + conf->writeEntry("appSig", a_ppSig); + conf->writeEntry("rewrap",r_ewrap); + conf->writeEntry("incSig", i_ncSig); + conf->writeEntry("cursorOnTop", c_ursorOnTop); + conf->writeEntry("useExternalEditor", u_seExtEditor); + conf->writeEntry("Intro", i_ntro); + conf->writeEntry("externalEditor", e_xternalEditor); + conf->sync(); + + d_irty = false; +} + +//============================================================================================================== + + + + +// BEGIN: Cleanup configuration =============================================== + + +KNConfig::Cleanup::Cleanup( bool global ) : + // default values for new accounts / groups + d_oExpire( true ), r_emoveUnavailable( true ), p_reserveThr( true ), + e_xpireInterval( 5 ), r_eadMaxAge( 10 ), u_nreadMaxAge( 15 ), + mGlobal(global), mDefault(!global), mLastExpDate( QDate::currentDate() ) +{ + if (mGlobal) { + KConfig *conf = knGlobals.config(); + conf->setGroup( "EXPIRE" ); + loadConfig( conf ); + } +} + + +void KNConfig::Cleanup::loadConfig(KConfigBase *conf) +{ + // group expire settings + d_oExpire = conf->readBoolEntry( "doExpire", true ); + r_emoveUnavailable = conf->readBoolEntry( "removeUnavailable", true ); + p_reserveThr = conf->readBoolEntry( "saveThreads", true ); + e_xpireInterval = conf->readNumEntry( "expInterval", 5 ); + r_eadMaxAge = conf->readNumEntry( "readDays", 10 ); + u_nreadMaxAge = conf->readNumEntry( "unreadDays", 15 ); + mLastExpDate = conf->readDateTimeEntry( "lastExpire" ).date(); + + // folder compaction settings (only available globally) + if (mGlobal) { + d_oCompact = conf->readBoolEntry( "doCompact", true ); + c_ompactInterval = conf->readNumEntry( "comInterval", 5 ); + mLastCompDate = conf->readDateTimeEntry( "lastCompact" ).date(); + } + + if (!mGlobal) + mDefault = conf->readBoolEntry( "UseDefaultExpConf", true ); +} + + +void KNConfig::Cleanup::saveConfig(KConfigBase *conf) +{ + // group expire settings + conf->writeEntry( "doExpire", d_oExpire ); + conf->writeEntry( "removeUnavailable", r_emoveUnavailable ); + conf->writeEntry( "saveThreads", p_reserveThr ); + conf->writeEntry( "expInterval", e_xpireInterval ); + conf->writeEntry( "readDays", r_eadMaxAge ); + conf->writeEntry( "unreadDays", u_nreadMaxAge ); + conf->writeEntry( "lastExpire", mLastExpDate ); + + // folder compaction settings (only available globally) + if (mGlobal) { + conf->writeEntry( "doCompact", d_oCompact ); + conf->writeEntry( "comInterval", c_ompactInterval ); + conf->writeEntry( "lastCompact", mLastCompDate ); + } + + if (!mGlobal) + conf->writeEntry( "UseDefaultExpConf", mDefault ); + + conf->sync(); +} + + +void KNConfig::Cleanup::save() +{ + kdDebug(5003) << "KNConfig::Cleanup::save()" << endl; + if (mGlobal) { + KConfig *conf = knGlobals.config(); + conf->setGroup( "EXPIRE" ); + saveConfig( conf ); + } +} + + +bool KNConfig::Cleanup::expireToday() +{ + if (!d_oExpire) + return false; + + QDate today = QDate::currentDate(); + if (mLastExpDate == today) + return false; + + return (mLastExpDate.daysTo( today ) >= e_xpireInterval); +} + + +void KNConfig::Cleanup::setLastExpireDate() +{ + mLastExpDate = QDateTime::currentDateTime(); +} + + +bool KNConfig::Cleanup::compactToday() +{ + if (!d_oCompact) + return false; + + QDate today = QDate::currentDate(); + if (mLastCompDate == today) + return false; + + return (mLastCompDate.daysTo( today ) >= c_ompactInterval); +} + + +void KNConfig::Cleanup::setLastCompactDate() +{ + mLastCompDate = QDateTime::currentDateTime(); +} + + +// END: Cleanup configuration ================================================= + + + +/*KNConfig::Cache::Cache() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("CACHE"); + + m_emMaxArt=conf->readNumEntry("memMaxArt", 1000); + m_emMaxKB=conf->readNumEntry("memMaxKB", 1024); + + d_iskMaxArt=conf->readNumEntry("diskMaxArt", 1000); + d_iskMaxKB=conf->readNumEntry("diskMaxKB", 1024); +} + + +KNConfig::Cache::~Cache() +{ +} + + +void KNConfig::Cache::save() +{ + if(!d_irty) + return; + + kdDebug(5003) << "KNConfig::Cache::save()" << endl; + + KConfig *conf=knGlobals.config(); + conf->setGroup("CACHE"); + + conf->writeEntry("memMaxArt", m_emMaxArt); + conf->writeEntry("memMaxKB", m_emMaxKB); + + conf->writeEntry("diskMaxArt", d_iskMaxArt); + conf->writeEntry("diskMaxKB", d_iskMaxKB); + + d_irty = false; +} +*/ + +#include "knconfig.moc" diff --git a/knode/knconfig.h b/knode/knconfig.h new file mode 100644 index 000000000..2248a361c --- /dev/null +++ b/knode/knconfig.h @@ -0,0 +1,566 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCONFIG_H +#define KNCONFIG_H + +#include <qasciidict.h> +#include <qcolor.h> +#include <qdatetime.h> +#include <qfont.h> +#include <qobject.h> +#include <qpixmap.h> + +#include <kconfig.h> + +#include <kmime_util.h> + +class KScoringRule; +class KProcess; +class KSpellConfig; +namespace Kpgp { + class Config; +} + +class KNNntpAccount; +class KNAccountManager; +class KNArticleFilter; +class KNFilterManager; +class KNDisplayedHeader; +class KNServerInfo; +class KNScoringManager; +namespace KNConfig { + class IdentityWidget; +} + +namespace KNConfig { + +class Base { + + public: + Base() : d_irty(false) {} + virtual ~Base() {} + + virtual void save() {} + + bool dirty()const { return d_irty; } + void setDirty(bool b) { d_irty=b; } + + protected: + bool d_irty; + +}; + + +class KDE_EXPORT Identity : public QObject, public Base { + +Q_OBJECT + + friend class IdentityWidget; + + public: + Identity(bool g=true); + ~Identity(); + + void loadConfig(KConfigBase *c); + void saveConfig(KConfigBase *c); + void save(); + bool isEmpty(); + bool isGlobal()const { return g_lobal; } + + //personal information + bool hasName() { return (!n_ame.isEmpty()); } + QString name() const { return n_ame; } + void setName(const QString &s) { n_ame=s; } + bool emailIsValid(); + bool hasEmail() { return (!e_mail.isEmpty()); } + QString email() { return e_mail; } + void setEmail(const QCString &s) { e_mail=s; } + bool hasReplyTo() { return (!r_eplyTo.isEmpty()); } + QString replyTo() { return r_eplyTo; } + void setReplyTo(const QString &s) { r_eplyTo=s; } + bool hasMailCopiesTo() { return (!m_ailCopiesTo.isEmpty()); } + QString mailCopiesTo() { return m_ailCopiesTo; } + void setMailCopiesTo(const QString &s) { m_ailCopiesTo=s; } + bool hasOrga() { return (!o_rga.isEmpty()); } + QString orga() const { return o_rga; } + void setOrga(const QString &s) { o_rga=s; } + + // OpenPGP signing key + bool hasSigningKey() { return (!s_igningKey.isEmpty()); } + QCString signingKey() { return s_igningKey; } + void setSigningKey(const QCString &s) { s_igningKey=s;} + + //signature + bool hasSignature() { return ( (u_seSigFile && !s_igPath.isEmpty()) || !s_igText.isEmpty() ); } + bool useSigFile() const { return u_seSigFile; } + bool useSigGenerator()const { return u_seSigGenerator; } + QString sigPath()const { return s_igPath; } + QString sigText()const { return s_igText; } + QString getSignature(); + QString getSigGeneratorStdErr() { return s_igStdErr; } + + + protected slots: + void slotReceiveStdout(KProcess *proc, char *buffer, int buflen); + void slotReceiveStderr(KProcess *proc, char *buffer, int buflen); + + protected: + QString n_ame, + e_mail, + o_rga, + r_eplyTo, + m_ailCopiesTo, + s_igText, + s_igContents, + s_igStdErr, + s_igPath; + QCString s_igningKey; + bool u_seSigFile, + u_seSigGenerator, + g_lobal; +}; + + +class KDE_EXPORT Appearance : public Base { + +#define COL_CNT 16 +#define FNT_CNT 5 +#define HEX_CNT 4 +#define ICON_CNT 14 + + friend class AppearanceWidget; + + public: + enum ColorIndex { background=0, alternateBackground=1, normalText=2, quoted1=3, + quoted2=4, quoted3=5, url=6, unreadThread=7, readThread=8, + unreadArticle=9, readArticle=10, signOkKeyOk=11, signOkKeyBad=12, + signWarn=13, signErr=14, htmlWarning=15 }; + + enum FontIndex { article=0, articleFixed=1, composer=2, groupList=3, articleList=4 }; + + enum IconIndex { greyBall=0, redBall=1, greyBallChkd=2, + redBallChkd=3, newFups=4, eyes=5, + ignore=6, mail=7, posting=8, + canceledPosting=9, savedRemote=10, group=11, + sendErr=12, null=13 }; + Appearance(); + ~Appearance(); + + void save(); + + QColor backgroundColor() const; + QColor alternateBackgroundColor() const; + QColor textColor() const; + QColor quoteColor( int depth ) const; + QColor linkColor() const; + QColor unreadThreadColor() const; + QColor readThreadColor() const; + QColor unreadArticleColor() const; + QColor readArticleColor() const; + QColor signOkKeyOkColor() const { return u_seColors ? c_olors[signOkKeyOk] : defaultColor( signOkKeyOk ); } + QColor signOkKeyBadColor() const { return u_seColors ? c_olors[signOkKeyBad] : defaultColor( signOkKeyBad ); } + QColor signWarnColor() const { return u_seColors ? c_olors[signWarn] : defaultColor( signWarn ); } + QColor signErrColor() const { return u_seColors ? c_olors[signErr] : defaultColor( signErr ); } + QColor htmlWarningColor() const { return u_seColors ? c_olors[htmlWarning] : defaultColor( htmlWarning ); } + + QFont articleFont() const; + QFont articleFixedFont() const; + QFont composerFont() const; + QFont groupListFont() const; + QFont articleListFont() const; + + const QPixmap& icon(IconIndex i) { return i_cons[i]; } + + protected: + const QColor& color( int i ) const { return c_olors[i]; } + const QString& colorName( int i ) const { return c_olorNames[i]; } + int colorCount() const { return COL_CNT; } + QColor defaultColor(int i) const; + + const QFont& font(int i) const { return f_onts[i]; } + const QString& fontName(int i) const { return f_ontNames[i]; } + int fontCount() const { return FNT_CNT; } + QFont defaultFont(int) const; + + void recreateLVIcons(); + + bool u_seColors, + u_seFonts; + QColor c_olors[COL_CNT]; + QString c_olorNames[COL_CNT]; + QFont f_onts[FNT_CNT]; + QString f_ontNames[FNT_CNT]; + QPixmap i_cons[ICON_CNT]; + +}; + + +class KDE_EXPORT ReadNewsGeneral : public Base { + + friend class ReadNewsGeneralWidget; + + public: + ReadNewsGeneral(); + ~ReadNewsGeneral(); + + void save(); + + bool autoCheckGroups()const { return a_utoCheck; } + int maxToFetch()const { return m_axFetch; } + bool autoMark()const { return a_utoMark; } + int autoMarkSeconds()const { return m_arkSecs; } + bool markCrossposts()const { return m_arkCrossposts; } + + bool smartScrolling()const { return s_martScrolling; } + bool totalExpandThreads()const { return t_otalExpand; } + bool defaultToExpandedThreads()const { return d_efaultExpand; } + bool showLines()const { return s_howLines; } + bool showScore()const { return s_howScore; } + bool showUnread()const { return s_howUnread; } + + int collCacheSize()const { return c_ollCacheSize; } + int artCacheSize()const { return a_rtCacheSize; } + + bool showThreads()const { return s_howThreads; } + void setShowThreads(bool b) { d_irty=true; s_howThreads=b;} + + KMime::DateFormatter::FormatType dateFormat() const { return mDateFormat; } + QString dateCustomFormat() const { return mDateCustomFormat; } + + void setShowLines( bool show ) { d_irty = true; s_howLines = show; } + void setShowScore( bool show ) { d_irty = true; s_howScore = show; } + + protected: + bool a_utoCheck, + a_utoMark, + m_arkCrossposts, + s_martScrolling, + t_otalExpand, + d_efaultExpand, + s_howLines, + s_howScore, + s_howUnread, + s_howThreads; + + int m_axFetch, + m_arkSecs, + c_ollCacheSize, + a_rtCacheSize; + + KMime::DateFormatter::FormatType mDateFormat; + QString mDateCustomFormat; + +}; + + +class KDE_EXPORT ReadNewsNavigation : public Base { + + friend class ReadNewsNavigationWidget; + + public: + ReadNewsNavigation(); + ~ReadNewsNavigation(); + + void save(); + + bool markAllReadGoNext()const { return m_arkAllReadGoNext; } + bool markThreadReadGoNext() const { return m_arkThreadReadGoNext; } + bool markThreadReadCloseThread()const { return m_arkThreadReadCloseThread; } + bool ignoreThreadGoNext()const { return i_gnoreThreadGoNext; } + bool ignoreThreadCloseThread()const { return i_gnoreThreadCloseThread; } + bool leaveGroupMarkAsRead() const { return mLeaveGroupMarkAsRead; } + + protected: + bool m_arkAllReadGoNext, + m_arkThreadReadGoNext, + m_arkThreadReadCloseThread, + i_gnoreThreadGoNext, + i_gnoreThreadCloseThread, + mLeaveGroupMarkAsRead; + +}; + + +class KDE_EXPORT ReadNewsViewer : public Base { + + friend class ReadNewsViewerWidget; + + public: + ReadNewsViewer(); + ~ReadNewsViewer(); + + void save(); + + bool rewrapBody()const { return r_ewrapBody; } + bool removeTrailingNewlines()const { return r_emoveTrailingNewlines; } + bool showSignature()const { return s_howSig; } + bool interpretFormatTags()const { return i_nterpretFormatTags; } + void setInterpretFormatTags( bool f ) { d_irty = true; i_nterpretFormatTags = f; } + QString quoteCharacters()const { return q_uoteCharacters; } + + bool openAttachmentsOnClick()const { return o_penAtt; } + bool showAlternativeContents()const { return s_howAlts; } + + bool useFixedFont() const { return u_seFixedFont; } + void setUseFixedFont(bool b) { d_irty = true; u_seFixedFont=b; } + + bool showRefBar() const { return mShowRefBar; } + void setShowRefBar( bool b ) { d_irty = true; mShowRefBar = b; } + + bool alwaysShowHTML() const { return mAlwaysShowHTML; } + void setAlwaysShowHTML( bool b ) { d_irty = true; mAlwaysShowHTML = b; } + + protected: + bool r_ewrapBody, + r_emoveTrailingNewlines, + s_howSig, + i_nterpretFormatTags, + o_penAtt, + s_howAlts, + u_seFixedFont, + mShowRefBar, + mAlwaysShowHTML; + QString q_uoteCharacters; +}; + + +class KDE_EXPORT DisplayedHeaders : public Base +{ + public: + DisplayedHeaders(); + ~DisplayedHeaders(); + + void save(); + + KNDisplayedHeader* createNewHeader(); + void remove(KNDisplayedHeader *h); + void up(KNDisplayedHeader *h); + void down(KNDisplayedHeader *h); + + QValueList<KNDisplayedHeader*> headers() const { return mHeaderList; } + + + protected: + QValueList<KNDisplayedHeader*> mHeaderList; + +}; + + +class KDE_EXPORT Scoring : public Base { + + friend class ScoringWidget; + + public: + Scoring(); + ~Scoring(); + + void save(); + + int ignoredThreshold() { return i_gnoredThreshold; } + int watchedThreshold() { return w_atchedThreshold; } + + protected: + int i_gnoredThreshold, + w_atchedThreshold; + +}; + + +class KDE_EXPORT XHeader { + + public: + XHeader() {} + XHeader(const QString &s); + XHeader(const XHeader &s) { n_ame=s.n_ame; v_alue=s.v_alue; } + ~XHeader() {} + + XHeader& operator=(const XHeader &s) { n_ame=s.n_ame; v_alue=s.v_alue; return (*this); } + + QCString name() { return n_ame; } + QString value() { return v_alue; } + QString header() { return (QString::fromLatin1(("X-"+n_ame+": "))+v_alue); } + + protected: + QCString n_ame; + QString v_alue; + +}; +typedef QValueList<XHeader> XHeaders; + + +class KDE_EXPORT PostNewsTechnical : public Base { + + friend class PostNewsTechnicalWidget; + friend class SmtpAccountWidget; + + public: + PostNewsTechnical(); + ~PostNewsTechnical(); + + void save(); + + QCString charset() const { return c_harset; } + QStringList composerCharsets() { return c_omposerCharsets; } + int indexForCharset(const QCString &str); + QCString findComposerCharset(QCString cs); + + bool allow8BitBody() const { return a_llow8BitBody; } + bool useOwnCharset() const { return u_seOwnCharset; } + bool generateMessageID()const { return g_enerateMID; } + QCString hostname()const { return h_ostname; } + XHeaders& xHeaders() { return x_headers; } + bool noUserAgent()const { return d_ontIncludeUA; } + bool useExternalMailer()const { return u_seExternalMailer; } + + protected: + QCString c_harset, + h_ostname; + QStringList c_omposerCharsets; + + bool a_llow8BitBody, + u_seOwnCharset, + g_enerateMID, + d_ontIncludeUA, + u_seExternalMailer; + + XHeaders x_headers; + + QAsciiDict<QCString> findComposerCSCache; +}; + + +class PostNewsComposer : public Base { + + friend class PostNewsComposerWidget; + + public: + PostNewsComposer(); + ~PostNewsComposer(); + + void save(); + + bool wordWrap()const { return w_ordWrap; } + int maxLineLength()const { return m_axLen; } + bool appendOwnSignature()const { return a_ppSig; } + + QString intro()const { return i_ntro; } + bool rewrap()const { return r_ewrap; } + bool includeSignature()const { return i_ncSig; } + bool cursorOnTop()const { return c_ursorOnTop; } + + QString externalEditor()const { return e_xternalEditor; } + bool useExternalEditor()const { return u_seExtEditor; } + + + protected: + int m_axLen; + bool w_ordWrap, + a_ppSig, + r_ewrap, + i_ncSig, + c_ursorOnTop, + u_seExtEditor; + QString i_ntro, + e_xternalEditor; + +}; + + +//BEGIN: Cleanup configuration ----------------------------------------------- + +class KDE_EXPORT Cleanup : public Base { + + friend class CleanupWidget; + friend class GroupCleanupWidget; + + public: + Cleanup( bool global = true ); + ~Cleanup() {} + + void loadConfig( KConfigBase *conf ); + void saveConfig( KConfigBase *conf ); + void save(); + + //expire + int maxAgeForRead() const { return r_eadMaxAge; } + int maxAgeForUnread() const { return u_nreadMaxAge; } + bool removeUnavailable() const { return r_emoveUnavailable; } + bool preserveThreads() const { return p_reserveThr; } + bool isGlobal() const { return mGlobal; } + bool useDefault() const { return mDefault; } + bool expireToday(); + void setLastExpireDate(); + + void setUseDefault( bool def ) { mDefault = def; } + + //compact + bool compactToday(); + void setLastCompactDate(); + + + protected: + bool d_oExpire, + r_emoveUnavailable, + p_reserveThr, + d_oCompact; + int e_xpireInterval, + r_eadMaxAge, + u_nreadMaxAge, + c_ompactInterval; + + private: + /** global vs. per account or per group configuration */ + bool mGlobal; + /** use default cleanup configuration */ + bool mDefault; + /** last expiration and last comapction date */ + QDateTime mLastExpDate, mLastCompDate; + +}; + +//END: Cleanup configuration ------------------------------------------------- + + +/*class Cache : public Base { + + friend class CacheWidget; + + public: + Cache(); + ~Cache(); + + void save(); + + // memory-cache + int memoryMaxArticles() { return m_emMaxArt; } + int memoryMaxKBytes() { return m_emMaxKB; } + + // disk-cache + int diskMaxArticles() { return d_iskMaxArt; } + int diskMaxKBytes() { return d_iskMaxKB; } + + + protected: + int m_emMaxArt, + m_emMaxKB, + d_iskMaxArt, + d_iskMaxKB; + +};*/ + + +} //KNConfig + +#endif //KNCONFIG_H diff --git a/knode/knconfigmanager.cpp b/knode/knconfigmanager.cpp new file mode 100644 index 000000000..b117f3c49 --- /dev/null +++ b/knode/knconfigmanager.cpp @@ -0,0 +1,133 @@ +/* + knconfigmanager.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include "knconfigmanager.h" + +#include <kcmultidialog.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kwin.h> + +#include <qhbox.h> + +#include "utilities.h" +#include "knglobals.h" +#include "articlewidget.h" +#include "knarticlefactory.h" +#include "knmainwidget.h" + + +KNConfigManager::KNConfigManager(QObject *p, const char *n) + : QObject(p, n), d_ialog(0) +{ + i_dentity = new KNConfig::Identity(); + a_ppearance = new KNConfig::Appearance(); + r_eadNewsGeneral = new KNConfig::ReadNewsGeneral(); + r_eadNewsNavigation = new KNConfig::ReadNewsNavigation(); + r_eadNewsViewer = new KNConfig::ReadNewsViewer(); + d_isplayedHeaders = new KNConfig::DisplayedHeaders(); + s_coring = new KNConfig::Scoring(); + p_ostNewsTechnical = new KNConfig::PostNewsTechnical(); + p_ostNewsCompose = new KNConfig::PostNewsComposer(); + c_leanup = new KNConfig::Cleanup(); + //c_ache = new KNConfig::Cache(); +} + + +KNConfigManager::~KNConfigManager() +{ + delete i_dentity; + delete a_ppearance; + delete r_eadNewsGeneral; + delete r_eadNewsNavigation; + delete r_eadNewsViewer; + delete d_isplayedHeaders; + delete s_coring; + delete p_ostNewsTechnical; + delete p_ostNewsCompose; + delete c_leanup; + //delete c_ache; +} + + +void KNConfigManager::configure() +{ + if(!d_ialog) { + d_ialog=new KNConfigDialog(knGlobals.topWidget, "Preferences_Dlg"); + connect(d_ialog, SIGNAL(finished()), this, SLOT(slotDialogDone())); + d_ialog->show(); + } + else + KWin::activateWindow(d_ialog->winId()); +} + + +void KNConfigManager::syncConfig() +{ + a_ppearance->save(); + r_eadNewsGeneral->save(); + r_eadNewsNavigation->save(); + r_eadNewsViewer->save(); + d_isplayedHeaders->save(); + s_coring->save(); + p_ostNewsTechnical->save(); + p_ostNewsCompose->save(); + c_leanup->save(); + //c_ache->save(); +} + + +void KNConfigManager::slotDialogDone() +{ + d_ialog->delayedDestruct(); + d_ialog=0; +} + + +//=================================================================================================== + + +KNConfigDialog::KNConfigDialog(QWidget *p, const char *n) + : KCMultiDialog(p, n) +{ + addModule ( "knode_config_identity", false ); + addModule ( "knode_config_accounts", false ); + addModule ( "knode_config_appearance", false ); + addModule ( "knode_config_read_news", false ); + addModule ( "knode_config_post_news", false ); + addModule ( "knode_config_privacy", false ); + addModule ( "knode_config_cleanup", false ); + + setHelp("anc-setting-your-identity"); + + connect(this, SIGNAL(configCommitted()), this, SLOT(slotConfigCommitted())); +} + + +void KNConfigDialog::slotConfigCommitted() +{ + knGlobals.configManager()->syncConfig(); + + KNode::ArticleWidget::configChanged(); + if(knGlobals.top) + knGlobals.top->configChanged(); + if(knGlobals.artFactory) + knGlobals.artFactory->configChanged(); +} + + +//----------------------------- +#include "knconfigmanager.moc" diff --git a/knode/knconfigmanager.h b/knode/knconfigmanager.h new file mode 100644 index 000000000..6f0db527d --- /dev/null +++ b/knode/knconfigmanager.h @@ -0,0 +1,83 @@ +/* + knconfigmanager.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCONFIGMANAGER_H +#define KNCONFIGMANAGER_H + +#include <kcmultidialog.h> + +#include "knconfig.h" + +class KNConfigDialog; + + +class KNConfigManager : QObject { + + Q_OBJECT + + public: + KNConfigManager(QObject *p=0, const char *n=0); + ~KNConfigManager(); + + KNConfig::Identity* identity() const { return i_dentity; } + KNConfig::Appearance* appearance()const { return a_ppearance; } + KNConfig::ReadNewsGeneral* readNewsGeneral()const { return r_eadNewsGeneral; } + KNConfig::ReadNewsNavigation* readNewsNavigation()const { return r_eadNewsNavigation; } + KNConfig::ReadNewsViewer* readNewsViewer()const { return r_eadNewsViewer; } + KNConfig::DisplayedHeaders* displayedHeaders()const { return d_isplayedHeaders; } + KNConfig::Scoring* scoring()const { return s_coring; } + KNConfig::PostNewsTechnical* postNewsTechnical()const { return p_ostNewsTechnical; } + KNConfig::PostNewsComposer* postNewsComposer() const { return p_ostNewsCompose; } + KNConfig::Cleanup* cleanup()const { return c_leanup; } + //KNConfig::Cache* cache()const { return c_ache; } + + void configure(); + void syncConfig(); + + protected: + KNConfig::Identity *i_dentity; + KNConfig::Appearance *a_ppearance; + KNConfig::ReadNewsGeneral *r_eadNewsGeneral; + KNConfig::ReadNewsNavigation *r_eadNewsNavigation; + KNConfig::ReadNewsViewer *r_eadNewsViewer; + KNConfig::DisplayedHeaders *d_isplayedHeaders; + KNConfig::Scoring *s_coring; + KNConfig::PostNewsTechnical *p_ostNewsTechnical; + KNConfig::PostNewsComposer *p_ostNewsCompose; + KNConfig::Cleanup *c_leanup; + //KNConfig::Cache *c_ache; + + KNConfigDialog *d_ialog; + + protected slots: + void slotDialogDone(); + +}; + + +class KNConfigDialog : public KCMultiDialog { + + Q_OBJECT + + public: + KNConfigDialog(QWidget *p=0, const char *n=0); + + protected slots: + void slotConfigCommitted(); + +}; + +#endif //KNCONFIGMANAGER_H diff --git a/knode/knconfigpages.cpp b/knode/knconfigpages.cpp new file mode 100644 index 000000000..c7d0c0758 --- /dev/null +++ b/knode/knconfigpages.cpp @@ -0,0 +1,198 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2004-2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#include <qlayout.h> + +#include <kcmodule.h> +#include <kdebug.h> +#include <klocale.h> + +#include "knglobals.h" +#include "knconfig.h" +#include "knconfigmanager.h" +#include "knconfigpages.h" +#include "knconfigwidgets.h" + +#include <kdepimmacros.h> + +// +// common config page with tabs (code mostly taken from kmail) +// +KNConfig::BasePageWithTabs::BasePageWithTabs( QWidget * parent, const char * name ) + : KCModule( parent, name ) +{ + QVBoxLayout *vlay = new QVBoxLayout( this, 0, KDialog::spacingHint() ); + mTabWidget = new QTabWidget( this ); + vlay->addWidget( mTabWidget ); +} + +void KNConfig::BasePageWithTabs::addTab( KCModule* tab, const QString & title ) { + mTabWidget->addTab( tab, title ); + connect( tab, SIGNAL(changed( bool )), this, SIGNAL(changed( bool )) ); +} + +void KNConfig::BasePageWithTabs::load() { + for ( int i = 0 ; i < mTabWidget->count() ; ++i ) { + KCModule *tab = (KCModule*) mTabWidget->page(i); + if ( tab ) + tab->load(); + } +} + +void KNConfig::BasePageWithTabs::save() { + for ( int i = 0 ; i < mTabWidget->count() ; ++i ) { + KCModule *tab = (KCModule*) mTabWidget->page(i); + if ( tab ) + tab->save(); + } +} + +void KNConfig::BasePageWithTabs::defaults() { + for ( int i = 0 ; i < mTabWidget->count() ; ++i ) { + KCModule *tab = (KCModule*) mTabWidget->page(i); + if ( tab ) + tab->defaults(); + } +} + + + +// +// identity page +// +extern "C" +{ + KDE_EXPORT KCModule *create_knode_config_identity( QWidget *parent, const char * ) + { + KNConfig::IdentityWidget *page = new KNConfig::IdentityWidget( + knGlobals.configManager()->identity(), + parent, + "kcmknode_config_identity" ); + return page; + } +} + + + +// +// accounts page +// +extern "C" +{ + KCModule *create_knode_config_accounts( QWidget *parent, const char * ) + { + KNConfig::AccountsPage *page = new KNConfig::AccountsPage( parent, "kcmknode_config_accounts" ); + return page; + } +} + +KNConfig::AccountsPage::AccountsPage(QWidget *parent, const char *name) + : BasePageWithTabs(parent, name) { + + addTab(new KNConfig::NntpAccountListWidget(this), i18n("Newsgroup Servers")); + addTab(new KNConfig::SmtpAccountWidget(this), i18n("Mail Server (SMTP)")); +} + + + +// +// appearance page +// +extern "C" +{ + KCModule *create_knode_config_appearance( QWidget *parent, const char * ) + { + KNConfig::AppearanceWidget *page = new KNConfig::AppearanceWidget( parent, "kcmknode_config_appearance" ); + return page; + } +} + + + +// +// read news page +// +extern "C" +{ + KCModule *create_knode_config_read_news( QWidget *parent, const char * ) + { + KNConfig::ReadNewsPage *page = new KNConfig::ReadNewsPage( parent, "kcmknode_config_read_news" ); + return page; + } +} + +KNConfig::ReadNewsPage::ReadNewsPage(QWidget *parent, const char *name) + : BasePageWithTabs(parent, name) { + + KNConfigManager *cfgMgr = knGlobals.configManager(); + addTab(new KNConfig::ReadNewsGeneralWidget(cfgMgr->readNewsGeneral(), this), i18n("General")); + addTab(new KNConfig::ReadNewsNavigationWidget(cfgMgr->readNewsNavigation(), this), i18n("Navigation")); + addTab(new KNConfig::ScoringWidget(cfgMgr->scoring(), this), i18n("Scoring")); + addTab(new KNConfig::FilterListWidget(this), i18n("Filters")); + addTab(new KNConfig::DisplayedHeadersWidget(cfgMgr->displayedHeaders(), this), i18n("Headers")); + addTab(new KNConfig::ReadNewsViewerWidget(cfgMgr->readNewsViewer(), this), i18n("Viewer")); +} + + + +// +// post news page +// +extern "C" +{ + KCModule *create_knode_config_post_news( QWidget *parent, const char * ) + { + KNConfig::PostNewsPage *page = new KNConfig::PostNewsPage( parent, "kcmknode_config_post_news" ); + return page; + } +} + +KNConfig::PostNewsPage::PostNewsPage(QWidget *parent, const char *name) + : BasePageWithTabs(parent, name) { + + KNConfigManager *cfgMgr = knGlobals.configManager(); + addTab(new KNConfig::PostNewsTechnicalWidget(cfgMgr->postNewsTechnical(), this), i18n("Technical")); + addTab(new KNConfig::PostNewsComposerWidget(cfgMgr->postNewsComposer(), this), i18n("Composer")); + addTab(new KNConfig::PostNewsSpellingWidget(this), i18n("Spelling")); +} + + + +// +// privacy page +// +extern "C" +{ + KCModule *create_knode_config_privacy( QWidget *parent, const char * ) + { + KNConfig::PrivacyWidget *page = new KNConfig::PrivacyWidget( parent, "kcmknode_config_privacy" ); + return page; + } +} + + + +// +// cleanup page +// +extern "C" +{ + KCModule *create_knode_config_cleanup( QWidget *parent, const char * ) + { + KNConfig::CleanupWidget *page = new KNConfig::CleanupWidget( parent, "kcmknode_config_cleanup" ); + return page; + } +} + + +#include "knconfigpages.moc" diff --git a/knode/knconfigpages.h b/knode/knconfigpages.h new file mode 100644 index 000000000..c76432b78 --- /dev/null +++ b/knode/knconfigpages.h @@ -0,0 +1,79 @@ +/* + knconfigpages.h + + KNode, the KDE newsreader + Copyright (c) 2004 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ + +#ifndef KNCONFIGPAGES_H +#define KNCONFIGPAGES_H + + +#include <kcmodule.h> + +class BaseWidget; +class IdentityWidget; + +namespace KNConfig { + +/* + * BasePageWithTabs represents a kcm with several tabs. + * It simply forwards load and save operations to all tabs. + * Code mostly taken from kmail. + */ +class KDE_EXPORT BasePageWithTabs : public KCModule { + Q_OBJECT + public: + BasePageWithTabs( QWidget * parent=0, const char * name=0 ); + ~BasePageWithTabs() {}; + + virtual void load(); + virtual void save(); + virtual void defaults(); + + protected: + void addTab( KCModule* tab, const QString & title ); + + private: + QTabWidget *mTabWidget; + +}; + + +// accounts page +class AccountsPage : public BasePageWithTabs { + Q_OBJECT + + public: + AccountsPage(QWidget *parent = 0, const char *name = 0); +}; + + +// read news page +class KDE_EXPORT ReadNewsPage : public BasePageWithTabs { + Q_OBJECT + + public: + ReadNewsPage(QWidget *parent = 0, const char *name = 0); +}; + +// post news page +class KDE_EXPORT PostNewsPage : public BasePageWithTabs { + Q_OBJECT + + public: + PostNewsPage(QWidget *parent = 0, const char *name = 0); +}; + + +} //KNConfig + +#endif //KNCONFIGPAGES_H diff --git a/knode/knconfigwidgets.cpp b/knode/knconfigwidgets.cpp new file mode 100644 index 000000000..72d8f366c --- /dev/null +++ b/knode/knconfigwidgets.cpp @@ -0,0 +1,2627 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + + +#include <qvbox.h> +#include <qpainter.h> +#include <qwhatsthis.h> +#include <qlabel.h> + +#include <klocale.h> +#include <knumvalidator.h> +#include <kmessagebox.h> +#include <kcolordialog.h> +#include <kfontdialog.h> +#include <kfiledialog.h> +#include <kuserprofile.h> +#include <kopenwith.h> +#include <kscoringeditor.h> +#include <kspell.h> +#include <kcombobox.h> +#include <kpgpui.h> +#include <kurlcompletion.h> +#include <kiconloader.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "knaccountmanager.h" +#include "knconfig.h" +#include "knconfigwidgets.h" +#include "knconfigmanager.h" +#include "kndisplayedheader.h" +#include "kngroupmanager.h" +#include "knglobals.h" +#include "knnntpaccount.h" +#include "utilities.h" +#include "knfiltermanager.h" +#include "knarticlefilter.h" +#include "knscoring.h" +#include <kpgp.h> + + +KNConfig::IdentityWidget::IdentityWidget( Identity *d, QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( d ) +{ + QString msg; + + QGridLayout *topL=new QGridLayout(this, 11, 3, 5,5); + + n_ame=new KLineEdit(this); + QLabel *l=new QLabel(n_ame, i18n("&Name:"), this); + topL->addWidget(l, 0,0); + topL->addMultiCellWidget(n_ame, 0,0, 1,2); + msg = i18n("<qt><p>Your name as it will appear to others reading your articles.</p>" + "<p>Ex: <b>John Stuart Masterson III</b>.</p></qt>"); + QWhatsThis::add( n_ame, msg ); + QWhatsThis::add( l, msg ); + connect( n_ame, SIGNAL(textChanged(const QString&)), SLOT(changed()) ); + + o_rga=new KLineEdit(this); + l=new QLabel(o_rga, i18n("Organi&zation:"), this); + topL->addWidget(l, 1,0); + topL->addMultiCellWidget(o_rga, 1,1, 1,2); + msg = i18n( "<qt><p>The name of the organization you work for.</p>" + "<p>Ex: <b>KNode, Inc</b>.</p></qt>" ); + QWhatsThis::add( o_rga, msg ); + QWhatsThis::add( l, msg ); + connect( o_rga, SIGNAL(textChanged(const QString&)), SLOT(changed()) ); + + e_mail=new KLineEdit(this); + l=new QLabel(e_mail, i18n("Email a&ddress:"), this); + topL->addWidget(l, 2,0); + topL->addMultiCellWidget(e_mail, 2,2, 1,2); + msg = i18n( "<qt><p>Your email address as it will appear to others " + "reading your articles</p><p>Ex: <b>nospam@please.com</b>.</qt>" ); + QWhatsThis::add( l, msg ); + QWhatsThis::add( e_mail, msg ); + connect( e_mail, SIGNAL(textChanged(const QString&)), SLOT(changed()) ); + + r_eplyTo=new KLineEdit(this); + l=new QLabel(r_eplyTo, i18n("&Reply-to address:"), this); + topL->addWidget(l, 3,0); + topL->addMultiCellWidget(r_eplyTo, 3,3, 1,2); + msg = i18n( "<qt><p>When someone reply to your article by email, this is the address the message " + "will be sent. If you fill in this field, please do it with a real " + "email address.</p><p>Ex: <b>john@example.com</b>.</p></qt>" ); + QWhatsThis::add( l, msg ); + QWhatsThis::add( r_eplyTo, msg ); + connect( r_eplyTo, SIGNAL(textChanged(const QString&)), SLOT(changed()) ); + + m_ailCopiesTo=new KLineEdit(this); + l=new QLabel(m_ailCopiesTo, i18n("&Mail-copies-to:"), this); + topL->addWidget(l, 4,0); + topL->addMultiCellWidget(m_ailCopiesTo, 4,4, 1,2); + connect( m_ailCopiesTo, SIGNAL(textChanged(const QString&)), SLOT(changed()) ); + + s_igningKey = new Kpgp::SecretKeyRequester(this); + s_igningKey->dialogButton()->setText(i18n("Chan&ge...")); + s_igningKey->setDialogCaption(i18n("Your OpenPGP Key")); + s_igningKey->setDialogMessage(i18n("Select the OpenPGP key which should be " + "used for signing articles.")); + l=new QLabel(s_igningKey, i18n("Signing ke&y:"), this); + topL->addWidget(l, 5,0); + topL->addMultiCellWidget(s_igningKey, 5,5, 1,2); + msg = i18n("<qt><p>The OpenPGP key you choose here will be " + "used to sign your articles.</p></qt>"); + QWhatsThis::add( l, msg ); + QWhatsThis::add( s_igningKey, msg ); + connect( s_igningKey, SIGNAL(changed()), SLOT(changed()) ); + + b_uttonGroup = new QButtonGroup(this); + connect( b_uttonGroup, SIGNAL(clicked(int)), + this, SLOT(slotSignatureType(int)) ); + b_uttonGroup->setExclusive(true); + b_uttonGroup->hide(); + + s_igFile = new QRadioButton( i18n("&Use a signature from file"), this ); + b_uttonGroup->insert(s_igFile, 0); + topL->addMultiCellWidget(s_igFile, 6, 6, 0, 2); + QWhatsThis::add( s_igFile, + i18n( "<qt><p>Mark this to let KNode read the signature from a file.</p></qt>" ) ); + s_ig = new KLineEdit(this); + + f_ileName = new QLabel(s_ig, i18n("Signature &file:"), this); + topL->addWidget(f_ileName, 7, 0 ); + topL->addWidget(s_ig, 7, 1 ); + c_ompletion = new KURLCompletion(); + s_ig->setCompletionObject(c_ompletion); + msg = i18n( "<qt><p>The file from which the signature will be read.</p>" + "<p>Ex: <b>/home/robt/.sig</b>.</p></qt>" ); + QWhatsThis::add( f_ileName, msg ); + QWhatsThis::add( s_ig, msg ); + + c_hooseBtn = new QPushButton( i18n("Choo&se..."), this); + connect(c_hooseBtn, SIGNAL(clicked()), + this, SLOT(slotSignatureChoose())); + topL->addWidget(c_hooseBtn, 7, 2 ); + e_ditBtn = new QPushButton( i18n("&Edit File"), this); + connect(e_ditBtn, SIGNAL(clicked()), + this, SLOT(slotSignatureEdit())); + topL->addWidget(e_ditBtn, 8, 2); + + s_igGenerator = new QCheckBox(i18n("&The file is a program"), this); + topL->addMultiCellWidget(s_igGenerator, 8, 8, 0, 1); + msg = i18n( "<qt><p>Mark this option if the signature will be generated by a program</p>" + "<p>Ex: <b>/home/robt/gensig.sh</b>.</p></qt>" ); + QWhatsThis::add( s_igGenerator, msg ); + connect( s_igGenerator, SIGNAL(toggled(bool)), SLOT(changed()) ); + + s_igEdit = new QRadioButton( i18n("Specify signature &below"), this); + b_uttonGroup->insert(s_igEdit, 1); + topL->addMultiCellWidget(s_igEdit, 9, 9, 0, 2); + + s_igEditor = new QTextEdit(this); + s_igEditor->setTextFormat(Qt::PlainText); + topL->addMultiCellWidget(s_igEditor, 10, 10, 0, 2); + connect( s_igEditor, SIGNAL(textChanged()), SLOT(changed()) ); + + topL->setColStretch(1,1); + topL->setRowStretch(7,1); + topL->setResizeMode(QLayout::Minimum); + connect(s_ig,SIGNAL(textChanged ( const QString & )), + this,SLOT(textFileNameChanged(const QString &))); + + load(); +} + + +KNConfig::IdentityWidget::~IdentityWidget() +{ + delete c_ompletion; +} + +void KNConfig::IdentityWidget::textFileNameChanged(const QString &text) +{ + e_ditBtn->setEnabled(!text.isEmpty()); + emit changed( true ); +} + +void KNConfig::IdentityWidget::load() +{ + kdDebug() << "void KNConfig::IdentityWidget::load()" << endl; + n_ame->setText(d_ata->n_ame); + o_rga->setText(d_ata->o_rga); + e_mail->setText(d_ata->e_mail); + r_eplyTo->setText(d_ata->r_eplyTo); + m_ailCopiesTo->setText(d_ata->m_ailCopiesTo); + s_igningKey->setKeyIDs(Kpgp::KeyIDList() << d_ata->s_igningKey); + s_ig->setText(d_ata->s_igPath); + s_igGenerator->setChecked(d_ata->useSigGenerator()); + s_igEditor->setText(d_ata->s_igText); + slotSignatureType(d_ata->useSigFile()? 0:1); +} + +void KNConfig::IdentityWidget::save() +{ + d_ata->n_ame=n_ame->text(); + d_ata->o_rga=o_rga->text(); + d_ata->e_mail=e_mail->text(); + d_ata->r_eplyTo=r_eplyTo->text(); + d_ata->m_ailCopiesTo=m_ailCopiesTo->text(); + d_ata->s_igningKey = s_igningKey->keyIDs().first(); + d_ata->u_seSigFile=s_igFile->isChecked(); + d_ata->u_seSigGenerator=s_igGenerator->isChecked(); + d_ata->s_igPath=c_ompletion->replacedPath(s_ig->text()); + d_ata->s_igText=s_igEditor->text(); + + if(d_ata->isGlobal()) + d_ata->save(); +} + +void KNConfig::IdentityWidget::slotSignatureType(int type) +{ + bool sigFromFile = (type==0); + + b_uttonGroup->setButton(type); + f_ileName->setEnabled(sigFromFile); + s_ig->setEnabled(sigFromFile); + c_hooseBtn->setEnabled(sigFromFile); + e_ditBtn->setEnabled(sigFromFile && !s_ig->text().isEmpty()); + s_igGenerator->setEnabled(sigFromFile); + s_igEditor->setEnabled(!sigFromFile); + + if (sigFromFile) + f_ileName->setFocus(); + else + s_igEditor->setFocus(); + emit changed( true ); +} + + +void KNConfig::IdentityWidget::slotSignatureChoose() +{ + QString tmp=KFileDialog::getOpenFileName(c_ompletion->replacedPath(s_ig->text()),QString::null,this,i18n("Choose Signature")); + if(!tmp.isEmpty()) s_ig->setText(tmp); + emit changed( true ); +} + + +void KNConfig::IdentityWidget::slotSignatureEdit() +{ + QString fileName = c_ompletion->replacedPath(s_ig->text()).stripWhiteSpace(); + + if (fileName.isEmpty()) { + KMessageBox::sorry(this, i18n("You must specify a filename.")); + return; + } + + QFileInfo fileInfo( fileName ); + if (fileInfo.isDir()) { + KMessageBox::sorry(this, i18n("You have specified a folder.")); + return; + } + + KService::Ptr offer = KServiceTypeProfile::preferredService("text/plain", "Application"); + KURL u(fileName); + + if (offer) + KRun::run(*offer, u); + else + KRun::displayOpenWithDialog(u); + emit changed( true ); +} + + + +//========================================================================================== + +//BEGIN: NNTP account configuration widgets ---------------------------------- + +KNConfig::NntpAccountListWidget::NntpAccountListWidget(QWidget *p, const char *n) : + KCModule( p, n ), + a_ccManager( knGlobals.accountManager() ) +{ + p_ixmap = SmallIcon("server"); + + QGridLayout *topL=new QGridLayout(this, 6,2, 5,5); + + // account listbox + l_box=new KNDialogListBox(false, this); + connect(l_box, SIGNAL(selected(int)), this, SLOT(slotItemSelected(int))); + connect(l_box, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); + topL->addMultiCellWidget(l_box, 0,4, 0,0); + + // info box + QGroupBox *gb = new QGroupBox(2,Qt::Vertical,QString::null,this); + topL->addWidget(gb,5,0); + + s_erverInfo = new QLabel(gb); + p_ortInfo = new QLabel(gb); + + // buttons + a_ddBtn=new QPushButton(i18n("&Add..."), this); + connect(a_ddBtn, SIGNAL(clicked()), this, SLOT(slotAddBtnClicked())); + topL->addWidget(a_ddBtn, 0,1); + + e_ditBtn=new QPushButton(i18n("modify something","&Edit..."), this); + connect(e_ditBtn, SIGNAL(clicked()), this, SLOT(slotEditBtnClicked())); + topL->addWidget(e_ditBtn, 1,1); + + d_elBtn=new QPushButton(i18n("&Delete"), this); + connect(d_elBtn, SIGNAL(clicked()), this, SLOT(slotDelBtnClicked())); + topL->addWidget(d_elBtn, 2,1); + + s_ubBtn=new QPushButton(i18n("&Subscribe..."), this); + connect(s_ubBtn, SIGNAL(clicked()), this, SLOT(slotSubBtnClicked())); + topL->addWidget(s_ubBtn, 3,1); + + topL->setRowStretch(4,1); // stretch the server listbox + + load(); + + // the settings dialog is non-modal, so we have to react to changes + // made outside of the dialog + connect(a_ccManager, SIGNAL(accountAdded(KNNntpAccount*)), this, SLOT(slotAddItem(KNNntpAccount*))); + connect(a_ccManager, SIGNAL(accountRemoved(KNNntpAccount*)), this, SLOT(slotRemoveItem(KNNntpAccount*))); + connect(a_ccManager, SIGNAL(accountModified(KNNntpAccount*)), this, SLOT(slotUpdateItem(KNNntpAccount*))); + + slotSelectionChanged(); // disable Delete & Edit initially +} + + +KNConfig::NntpAccountListWidget::~NntpAccountListWidget() +{ +} + + +void KNConfig::NntpAccountListWidget::load() +{ + l_box->clear(); + QValueList<KNNntpAccount*>::Iterator it; + for ( it = a_ccManager->begin(); it != a_ccManager->end(); ++it ) + slotAddItem( *it ); +} + + +void KNConfig::NntpAccountListWidget::slotAddItem(KNNntpAccount *a) +{ + LBoxItem *it; + it=new LBoxItem(a, a->name(), &p_ixmap); + l_box->insertItem(it); + emit changed(true); +} + + +void KNConfig::NntpAccountListWidget::slotRemoveItem(KNNntpAccount *a) +{ + LBoxItem *it; + for(uint i=0; i<l_box->count(); i++) { + it=static_cast<LBoxItem*>(l_box->item(i)); + if(it && it->account==a) { + l_box->removeItem(i); + break; + } + } + slotSelectionChanged(); + emit changed(true); +} + + +void KNConfig::NntpAccountListWidget::slotUpdateItem(KNNntpAccount *a) +{ + LBoxItem *it; + for(uint i=0; i<l_box->count(); i++) { + it=static_cast<LBoxItem*>(l_box->item(i)); + if(it && it->account==a) { + it=new LBoxItem(a, a->name(), &p_ixmap); + l_box->changeItem(it, i); + break; + } + } + slotSelectionChanged(); + emit changed(true); +} + + + +void KNConfig::NntpAccountListWidget::slotSelectionChanged() +{ + int curr=l_box->currentItem(); + d_elBtn->setEnabled(curr!=-1); + e_ditBtn->setEnabled(curr!=-1); + s_ubBtn->setEnabled(curr!=-1); + + LBoxItem *it = static_cast<LBoxItem*>(l_box->item(curr)); + if(it) { + s_erverInfo->setText(i18n("Server: %1").arg(it->account->server())); + p_ortInfo->setText(i18n("Port: %1").arg(it->account->port())); + } + else { + s_erverInfo->setText(i18n("Server: ")); + p_ortInfo->setText(i18n("Port: ")); + } +} + + + +void KNConfig::NntpAccountListWidget::slotItemSelected(int) +{ + slotEditBtnClicked(); +} + + + +void KNConfig::NntpAccountListWidget::slotAddBtnClicked() +{ + KNNntpAccount *acc = new KNNntpAccount(); + + if(acc->editProperties(this)) { + if (a_ccManager->newAccount(acc)) + acc->saveInfo(); + } + else + delete acc; +} + + + +void KNConfig::NntpAccountListWidget::slotDelBtnClicked() +{ + LBoxItem *it = static_cast<LBoxItem*>(l_box->item(l_box->currentItem())); + + if(it) + a_ccManager->removeAccount(it->account); +} + + + +void KNConfig::NntpAccountListWidget::slotEditBtnClicked() +{ + LBoxItem *it = static_cast<LBoxItem*>(l_box->item(l_box->currentItem())); + + if(it) { + it->account->editProperties(this); + slotUpdateItem(it->account); + } +} + + +void KNConfig::NntpAccountListWidget::slotSubBtnClicked() +{ + LBoxItem *it = static_cast<LBoxItem*>(l_box->item(l_box->currentItem())); + + if(it) + knGlobals.groupManager()->showGroupDialog(it->account, this); +} + + +//======================================================================================= + + +KNConfig::NntpAccountConfDialog::NntpAccountConfDialog(KNNntpAccount *a, QWidget *p, const char *n) + : KDialogBase(Tabbed, (a->id()!=-1)? i18n("Properties of %1").arg(a->name()):i18n("New Account"), + Ok|Cancel|Help, Ok, p, n), + a_ccount(a) +{ + QFrame* page=addPage(i18n("Ser&ver")); + QGridLayout *topL=new QGridLayout(page, 11, 3, 5); + + n_ame=new KLineEdit(page); + QLabel *l=new QLabel(n_ame,i18n("&Name:"),page); + topL->addWidget(l, 0,0); + n_ame->setText(a->name()); + topL->addMultiCellWidget(n_ame, 0, 0, 1, 2); + + s_erver=new KLineEdit(page); + l=new QLabel(s_erver,i18n("&Server:"), page); + s_erver->setText(a->server()); + topL->addWidget(l, 1,0); + topL->addMultiCellWidget(s_erver, 1, 1, 1, 2); + + p_ort=new KLineEdit(page); + l=new QLabel(p_ort, i18n("&Port:"), page); + p_ort->setValidator(new KIntValidator(0,65536,this)); + p_ort->setText(QString::number(a->port())); + topL->addWidget(l, 2,0); + topL->addWidget(p_ort, 2,1); + + h_old = new KIntSpinBox(5,1800,5,5,10,page); + l = new QLabel(h_old,i18n("Hol&d connection for:"), page); + h_old->setSuffix(i18n(" sec")); + h_old->setValue(a->hold()); + topL->addWidget(l,3,0); + topL->addWidget(h_old,3,1); + + t_imeout = new KIntSpinBox(15,600,5,15,10,page); + l = new QLabel(t_imeout, i18n("&Timeout:"), page); + t_imeout->setValue(a->timeout()); + t_imeout->setSuffix(i18n(" sec")); + topL->addWidget(l,4,0); + topL->addWidget(t_imeout,4,1); + + f_etchDes=new QCheckBox(i18n("&Fetch group descriptions"), page); + f_etchDes->setChecked(a->fetchDescriptions()); + topL->addMultiCellWidget(f_etchDes, 5,5, 0,3); + + /*u_seDiskCache=new QCheckBox(i18n("&Cache articles on disk"), page); + u_seDiskCache->setChecked(a->useDiskCache()); + topL->addMultiCellWidget(u_seDiskCache, 6,6, 0,3);*/ + + a_uth=new QCheckBox(i18n("Server requires &authentication"), page); + connect(a_uth, SIGNAL(toggled(bool)), this, SLOT(slotAuthChecked(bool))); + topL->addMultiCellWidget(a_uth, 6,6, 0,3); + + u_ser=new KLineEdit(page); + u_serLabel=new QLabel(u_ser,i18n("&User:"), page); + u_ser->setText(a->user()); + topL->addWidget(u_serLabel, 7,0); + topL->addMultiCellWidget(u_ser, 7,7, 1,2); + + p_ass=new KLineEdit(page); + p_assLabel=new QLabel(p_ass, i18n("Pass&word:"), page); + p_ass->setEchoMode(KLineEdit::Password); + if ( a->readyForLogin() ) + p_ass->setText(a->pass()); + else + if ( a->needsLogon() ) + knGlobals.accountManager()->loadPasswordsAsync(); + topL->addWidget(p_assLabel, 8,0); + topL->addMultiCellWidget(p_ass, 8,8, 1,2); + + i_nterval=new QCheckBox(i18n("Enable &interval news checking"), page); + connect(i_nterval, SIGNAL(toggled(bool)), this, SLOT(slotIntervalChecked(bool))); + topL->addMultiCellWidget(i_nterval, 9,9, 0,3); + + c_heckInterval=new KIntSpinBox(1,10000,1,1,10,page); + c_heckIntervalLabel=new QLabel(c_heckInterval, i18n("Check inter&val:"), page); + c_heckInterval->setSuffix(i18n(" min") ); + c_heckInterval->setValue(a->checkInterval()); + c_heckIntervalLabel->setBuddy(c_heckInterval); + topL->addWidget(c_heckIntervalLabel, 10,0); + topL->addMultiCellWidget(c_heckInterval, 10,10, 1,2); + + slotAuthChecked(a->needsLogon()); + slotIntervalChecked(a->intervalChecking()); + + topL->setColStretch(1, 1); + topL->setColStretch(2, 1); + + // Specfic Identity tab ========================================= + i_dWidget=new KNConfig::IdentityWidget(a->identity(), addVBoxPage(i18n("&Identity"))); + + // per server cleanup configuration + QFrame* cleanupPage = addPage( i18n("&Cleanup") ); + QVBoxLayout *cleanupLayout = new QVBoxLayout( cleanupPage, KDialog::spacingHint() ); + mCleanupWidget = new GroupCleanupWidget( a->cleanupConfig(), cleanupPage ); + mCleanupWidget->load(); + cleanupLayout->addWidget( mCleanupWidget ); + cleanupLayout->addStretch( 1 ); + + connect( knGlobals.accountManager(), SIGNAL(passwordsChanged()), SLOT(slotPasswordChanged()) ); + + KNHelper::restoreWindowSize("accNewsPropDLG", this, sizeHint()); + + setHelp("anc-setting-the-news-account"); +} + + + +KNConfig::NntpAccountConfDialog::~NntpAccountConfDialog() +{ + KNHelper::saveWindowSize("accNewsPropDLG", size()); +} + + +void KNConfig::NntpAccountConfDialog::slotOk() +{ + if (n_ame->text().isEmpty() || s_erver->text().stripWhiteSpace().isEmpty()) { + KMessageBox::sorry(this, i18n("Please enter an arbitrary name for the account and the\nhostname of the news server.")); + return; + } + + a_ccount->setName(n_ame->text()); + a_ccount->setServer(s_erver->text().stripWhiteSpace()); + a_ccount->setPort(p_ort->text().toInt()); + a_ccount->setHold(h_old->value()); + a_ccount->setTimeout(t_imeout->value()); + a_ccount->setFetchDescriptions(f_etchDes->isChecked()); + //a_ccount->setUseDiskCache(u_seDiskCache->isChecked()); + a_ccount->setNeedsLogon(a_uth->isChecked()); + a_ccount->setUser(u_ser->text()); + a_ccount->setPass(p_ass->text()); + a_ccount->setIntervalChecking(i_nterval->isChecked()); + a_ccount->setCheckInterval(c_heckInterval->value()); + if (a_ccount->id() != -1) // only save if account has a valid id + a_ccount->saveInfo(); + + i_dWidget->save(); + mCleanupWidget->save(); + + accept(); +} + + +void KNConfig::NntpAccountConfDialog::slotAuthChecked(bool b) +{ + a_uth->setChecked(b); + u_ser->setEnabled(b); + u_serLabel->setEnabled(b); + p_ass->setEnabled(b); + p_assLabel->setEnabled(b); +} + +void KNConfig::NntpAccountConfDialog::slotIntervalChecked(bool b) +{ + i_nterval->setChecked(b); + c_heckInterval->setEnabled(b); + c_heckIntervalLabel->setEnabled(b); +} + +void KNConfig::NntpAccountConfDialog::slotPasswordChanged() +{ + if ( p_ass->text().isEmpty() ) + p_ass->setText( a_ccount->pass() ); +} + +//END: NNTP account configuration widgets ------------------------------------ + +//============================================================================================= + +KNConfig::SmtpAccountWidget::SmtpAccountWidget( QWidget *p, const char *n ) : + SmtpAccountWidgetBase( p, n ) +{ + mAccount = knGlobals.accountManager()->smtp(); + connect( knGlobals.accountManager(), SIGNAL(passwordsChanged()), SLOT(slotPasswordChanged()) ); + load(); +} + + +void KNConfig::SmtpAccountWidget::load() +{ + mUseExternalMailer->setChecked( knGlobals.configManager()->postNewsTechnical()->useExternalMailer() ); + useExternalMailerToggled( knGlobals.configManager()->postNewsTechnical()->useExternalMailer() ); + mServer->setText( mAccount->server() ); + mPort->setValue( mAccount->port() ); + mLogin->setChecked( mAccount->needsLogon() ); + loginToggled( mAccount->needsLogon() ); + mUser->setText( mAccount->user() ); + if ( mAccount->readyForLogin() ) + mPassword->setText( mAccount->pass() ); + else + if ( mAccount->needsLogon() ) + knGlobals.accountManager()->loadPasswordsAsync(); + switch ( mAccount->encryption() ) { + case KNServerInfo::None: + mEncNone->setChecked( true ); + break; + case KNServerInfo::SSL: + mEncSSL->setChecked( true ); + break; + case KNServerInfo::TLS: + mEncTLS->setChecked( true ); + break; + } +} + + +void KNConfig::SmtpAccountWidget::save() +{ + knGlobals.configManager()->postNewsTechnical()->u_seExternalMailer = mUseExternalMailer->isChecked(); + knGlobals.configManager()->postNewsTechnical()->setDirty(true); + + mAccount->setServer( mServer->text() ); + mAccount->setPort( mPort->value() ); + mAccount->setNeedsLogon( mLogin->isChecked() ); + if ( mAccount->needsLogon() ) { + mAccount->setUser( mUser->text() ); + mAccount->setPass( mPassword->text() ); + } + if ( mEncNone->isChecked() ) + mAccount->setEncryption( KNServerInfo::None ); + if ( mEncSSL->isChecked() ) + mAccount->setEncryption( KNServerInfo::SSL ); + if ( mEncTLS->isChecked() ) + mAccount->setEncryption( KNServerInfo::TLS ); + + KConfig *conf = knGlobals.config(); + conf->setGroup("MAILSERVER"); + mAccount->saveConf( conf ); +} + + +void KNConfig::SmtpAccountWidget::useExternalMailerToggled( bool b ) +{ + mServer->setEnabled( !b ); + mPort->setEnabled( !b ); + mServerLabel->setEnabled( !b ); + mPortLabel->setEnabled( !b ); + mLogin->setEnabled( !b ); + if ( !b ) + loginToggled( mLogin->isChecked() ); + else + loginToggled( false ); + mEncGroup->setEnabled( !b ); + emit changed( true ); +} + + +void KNConfig::SmtpAccountWidget::loginToggled( bool b ) +{ + bool canEnable = ( b && !mUseExternalMailer->isChecked() ); + mUser->setEnabled( canEnable ); + mUserLabel->setEnabled( canEnable ); + mPassword->setEnabled( canEnable ); + mPasswordLabel->setEnabled( canEnable ); + emit changed( true ); +} + + +void KNConfig::SmtpAccountWidget::slotPasswordChanged() +{ + if ( mPassword->text().isEmpty() ) + mPassword->setText( mAccount->pass() ); +} + + +//============================================================================================= + + +//=================================================================================== +// code taken from KMail, Copyright (C) 2000 Espen Sand, espen@kde.org + +KNConfig::AppearanceWidget::ColorListItem::ColorListItem( const QString &text, const QColor &color ) + : QListBoxText(text), mColor( color ) +{ +} + + +KNConfig::AppearanceWidget::ColorListItem::~ColorListItem() +{ +} + + +void KNConfig::AppearanceWidget::ColorListItem::paint( QPainter *p ) +{ + QFontMetrics fm = p->fontMetrics(); + int h = fm.height(); + + p->drawText( 30+3*2, fm.ascent() + fm.leading()/2, text() ); + + p->setPen( Qt::black ); + p->drawRect( 3, 1, 30, h-1 ); + p->fillRect( 4, 2, 28, h-3, mColor ); +} + + +int KNConfig::AppearanceWidget::ColorListItem::height(const QListBox *lb ) const +{ + return( lb->fontMetrics().lineSpacing()+1 ); +} + + +int KNConfig::AppearanceWidget::ColorListItem::width(const QListBox *lb ) const +{ + return( 30 + lb->fontMetrics().width( text() ) + 6 ); +} + + +//=================================================================================== + + +KNConfig::AppearanceWidget::FontListItem::FontListItem( const QString &name, const QFont &font ) + : QListBoxText(name), f_ont(font) +{ + fontInfo = QString("[%1 %2]").arg(f_ont.family()).arg(f_ont.pointSize()); +} + + +KNConfig::AppearanceWidget::FontListItem::~FontListItem() +{ +} + + +void KNConfig::AppearanceWidget::FontListItem::setFont(const QFont &font) +{ + f_ont = font; + fontInfo = QString("[%1 %2]").arg(f_ont.family()).arg(f_ont.pointSize()); +} + + +void KNConfig::AppearanceWidget::FontListItem::paint( QPainter *p ) +{ + QFont fnt = p->font(); + fnt.setWeight(QFont::Bold); + p->setFont(fnt); + int fontInfoWidth = p->fontMetrics().width(fontInfo); + int h = p->fontMetrics().ascent() + p->fontMetrics().leading()/2; + p->drawText(2, h, fontInfo ); + fnt.setWeight(QFont::Normal); + p->setFont(fnt); + p->drawText(5 + fontInfoWidth, h, text() ); +} + + +int KNConfig::AppearanceWidget::FontListItem::width(const QListBox *lb ) const +{ + return( lb->fontMetrics().width(fontInfo) + lb->fontMetrics().width(text()) + 20 ); +} + + +//=================================================================================== + + +KNConfig::AppearanceWidget::AppearanceWidget( QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( knGlobals.configManager()->appearance() ) +{ + QGridLayout *topL=new QGridLayout(this, 8,2, 5,5); + + //color-list + c_List = new KNDialogListBox(false, this); + topL->addMultiCellWidget(c_List,1,3,0,0); + connect(c_List, SIGNAL(selected(QListBoxItem*)),SLOT(slotColItemSelected(QListBoxItem*))); + connect(c_List, SIGNAL(selectionChanged()), SLOT(slotColSelectionChanged())); + + c_olorCB = new QCheckBox(i18n("&Use custom colors"),this); + topL->addWidget(c_olorCB,0,0); + connect(c_olorCB, SIGNAL(toggled(bool)), this, SLOT(slotColCheckBoxToggled(bool))); + + c_olChngBtn=new QPushButton(i18n("Cha&nge..."), this); + connect(c_olChngBtn, SIGNAL(clicked()), this, SLOT(slotColChangeBtnClicked())); + topL->addWidget(c_olChngBtn,1,1); + + //font-list + f_List = new KNDialogListBox(false, this); + topL->addMultiCellWidget(f_List,5,7,0,0); + connect(f_List, SIGNAL(selected(QListBoxItem*)),SLOT(slotFontItemSelected(QListBoxItem*))); + connect(f_List, SIGNAL(selectionChanged()),SLOT(slotFontSelectionChanged())); + + f_ontCB = new QCheckBox(i18n("Use custom &fonts"),this); + topL->addWidget(f_ontCB,4,0); + connect(f_ontCB, SIGNAL(toggled(bool)), this, SLOT(slotFontCheckBoxToggled(bool))); + + f_ntChngBtn=new QPushButton(i18n("Chang&e..."), this); + connect(f_ntChngBtn, SIGNAL(clicked()), this, SLOT(slotFontChangeBtnClicked())); + topL->addWidget(f_ntChngBtn,5,1); + + load(); +} + + +KNConfig::AppearanceWidget::~AppearanceWidget() +{ +} + + +void KNConfig::AppearanceWidget::load() +{ + c_olorCB->setChecked(d_ata->u_seColors); + slotColCheckBoxToggled(d_ata->u_seColors); + c_List->clear(); + for(int i=0; i < d_ata->colorCount(); i++) + c_List->insertItem(new ColorListItem(d_ata->colorName(i), d_ata->color(i))); + + f_ontCB->setChecked(d_ata->u_seFonts); + slotFontCheckBoxToggled(d_ata->u_seFonts); + f_List->clear(); + for(int i=0; i < d_ata->fontCount(); i++) + f_List->insertItem(new FontListItem(d_ata->fontName(i), d_ata->font(i))); +} + + +void KNConfig::AppearanceWidget::save() +{ + d_ata->u_seColors=c_olorCB->isChecked(); + for(int i=0; i<d_ata->colorCount(); i++) + d_ata->c_olors[i] = (static_cast<ColorListItem*>(c_List->item(i)))->color(); + + d_ata->u_seFonts=f_ontCB->isChecked(); + for(int i=0; i<d_ata->fontCount(); i++) + d_ata->f_onts[i] = (static_cast<FontListItem*>(f_List->item(i)))->font(); + + d_ata->setDirty(true); + + d_ata->recreateLVIcons(); +} + + +void KNConfig::AppearanceWidget::defaults() +{ + // default colors + ColorListItem *colorItem; + for(int i=0; i < d_ata->colorCount(); i++) { + colorItem=static_cast<ColorListItem*>(c_List->item(i)); + colorItem->setColor(d_ata->defaultColor(i)); + } + c_List->triggerUpdate(true); + c_List->repaint(true); + + // default fonts + FontListItem *fontItem; + for(int i=0; i < d_ata->fontCount(); i++) { + fontItem=static_cast<FontListItem*>(f_List->item(i)); + fontItem->setFont(d_ata->defaultFont(i)); + } + f_List->triggerUpdate(false); + + emit changed(true); +} + + +void KNConfig::AppearanceWidget::slotColCheckBoxToggled(bool b) +{ + c_List->setEnabled(b); + c_olChngBtn->setEnabled(b && (c_List->currentItem()!=-1)); + if (b) c_List->setFocus(); + emit changed(true); +} + + +// show color dialog for the entry +void KNConfig::AppearanceWidget::slotColItemSelected(QListBoxItem *it) +{ + if (it) { + ColorListItem *colorItem = static_cast<ColorListItem*>(it); + QColor col = colorItem->color(); + int result = KColorDialog::getColor(col,this); + + if (result == KColorDialog::Accepted) { + colorItem->setColor(col); + c_List->triggerUpdate(false); + } + } + emit changed(true); +} + + +void KNConfig::AppearanceWidget::slotColChangeBtnClicked() +{ + if(c_List->currentItem()!=-1) + slotColItemSelected(c_List->item(c_List->currentItem())); +} + + +void KNConfig::AppearanceWidget::slotColSelectionChanged() +{ + c_olChngBtn->setEnabled(c_List->currentItem()!=-1); +} + + +void KNConfig::AppearanceWidget::slotFontCheckBoxToggled(bool b) +{ + f_List->setEnabled(b); + f_ntChngBtn->setEnabled(b && (f_List->currentItem()!=-1)); + if (b) f_List->setFocus(); + emit changed(true); +} + + +// show font dialog for the entry +void KNConfig::AppearanceWidget::slotFontItemSelected(QListBoxItem *it) +{ + if (it) { + FontListItem *fontItem = static_cast<FontListItem*>(it); + QFont font = fontItem->font(); + int result = KFontDialog::getFont(font,false,this); + + if (result == KFontDialog::Accepted) { + fontItem->setFont(font); + f_List->triggerUpdate(false); + } + } + emit changed(true); +} + + +void KNConfig::AppearanceWidget::slotFontChangeBtnClicked() +{ + if(f_List->currentItem()!=-1) + slotFontItemSelected(f_List->item(f_List->currentItem())); +} + + +void KNConfig::AppearanceWidget::slotFontSelectionChanged() +{ + f_ntChngBtn->setEnabled(f_List->currentItem()!=-1); +} + + +//============================================================================================= + + +KNConfig::ReadNewsGeneralWidget::ReadNewsGeneralWidget( ReadNewsGeneral *d, QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( d ) +{ + QGroupBox *hgb=new QGroupBox(i18n("Article Handling"), this); + QGroupBox *lgb=new QGroupBox(i18n("Article List"), this); + QGroupBox *cgb=new QGroupBox(i18n("Memory Consumption"), this); + QLabel *l1, *l2, *l3; + + a_utoCB=new QCheckBox(i18n("Check for new articles a&utomatically"), hgb); + m_axFetch=new KIntSpinBox(0, 100000, 1, 0, 10, hgb); + l1=new QLabel(m_axFetch, i18n("&Maximum number of articles to fetch:"), hgb); + m_arkCB=new QCheckBox(i18n("Mar&k article as read after:"), hgb); + m_arkSecs=new KIntSpinBox(0, 9999, 1, 0, 10, hgb); + connect(m_arkCB, SIGNAL(toggled(bool)), m_arkSecs, SLOT(setEnabled(bool))); + m_arkSecs->setSuffix(i18n(" sec")); + m_arkCrossCB=new QCheckBox(i18n("Mark c&rossposted articles as read"), hgb); + + s_martScrollingCB=new QCheckBox(i18n("Smart scrolli&ng"), lgb); + e_xpThrCB=new QCheckBox(i18n("Show &whole thread on expanding"), lgb); + d_efaultExpandCB=new QCheckBox(i18n("Default to e&xpanded threads"), lgb); + s_coreCB=new QCheckBox(i18n("Show article &score"), lgb); + l_inesCB=new QCheckBox(i18n("Show &line count"), lgb); + u_nreadCB=new QCheckBox(i18n("Show unread count in &thread"), lgb); + + c_ollCacheSize=new KIntSpinBox(0, 99999, 1, 1, 10, cgb); + c_ollCacheSize->setSuffix(" KB"); + l2=new QLabel(c_ollCacheSize, i18n("Cach&e size for headers:"), cgb); + a_rtCacheSize=new KIntSpinBox(0, 99999, 1, 1, 10, cgb); + a_rtCacheSize->setSuffix(" KB"); + l3=new QLabel(a_rtCacheSize, i18n("Cache si&ze for articles:"), cgb); + + QVBoxLayout *topL=new QVBoxLayout(this, 5); + QGridLayout *hgbL=new QGridLayout(hgb, 5,2, 8,5); + QVBoxLayout *lgbL=new QVBoxLayout(lgb, 8, 5); + QGridLayout *cgbL=new QGridLayout(cgb, 3,2, 8,5); + + topL->addWidget(hgb); + topL->addWidget(lgb); + topL->addWidget(cgb); + topL->addStretch(1); + + hgbL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + hgbL->addMultiCellWidget(a_utoCB, 1,1, 0,1); + hgbL->addWidget(l1, 2, 0); + hgbL->addWidget(m_axFetch, 2,1); + hgbL->addWidget(m_arkCB, 3,0); + hgbL->addWidget(m_arkSecs, 3,1); + hgbL->addMultiCellWidget(m_arkCrossCB, 4,4, 0,1); + hgbL->setColStretch(0,1); + + lgbL->addSpacing(fontMetrics().lineSpacing()-4); + lgbL->addWidget(s_martScrollingCB); + lgbL->addWidget(e_xpThrCB); + lgbL->addWidget(d_efaultExpandCB); + lgbL->addWidget(s_coreCB); + lgbL->addWidget(l_inesCB); + lgbL->addWidget(u_nreadCB); + + cgbL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + cgbL->addWidget(l2, 1,0); + cgbL->addWidget(c_ollCacheSize, 1,1); + cgbL->addWidget(l3, 2,0); + cgbL->addWidget(a_rtCacheSize, 2,1); + cgbL->setColStretch(0,1); + + topL->setResizeMode(QLayout::Minimum); + + connect(a_utoCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(m_axFetch, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(m_arkCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(m_arkSecs, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(m_arkCrossCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(s_martScrollingCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(e_xpThrCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(d_efaultExpandCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(l_inesCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(s_coreCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(u_nreadCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(c_ollCacheSize, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(a_rtCacheSize, SIGNAL(valueChanged(int)), SLOT(changed())); + + load(); +} + + +KNConfig::ReadNewsGeneralWidget::~ReadNewsGeneralWidget() +{ +} + + +void KNConfig::ReadNewsGeneralWidget::load() +{ + a_utoCB->setChecked(d_ata->a_utoCheck); + m_axFetch->setValue(d_ata->m_axFetch); + m_arkCB->setChecked(d_ata->a_utoMark); + m_arkSecs->setValue(d_ata->m_arkSecs); + m_arkSecs->setEnabled(d_ata->a_utoMark); + m_arkCrossCB->setChecked(d_ata->m_arkCrossposts); + s_martScrollingCB->setChecked(d_ata->s_martScrolling); + e_xpThrCB->setChecked(d_ata->t_otalExpand); + d_efaultExpandCB->setChecked(d_ata->d_efaultExpand); + l_inesCB->setChecked(d_ata->s_howLines); + s_coreCB->setChecked(d_ata->s_howScore); + u_nreadCB->setChecked(d_ata->s_howUnread); + c_ollCacheSize->setValue(d_ata->c_ollCacheSize); + a_rtCacheSize->setValue(d_ata->a_rtCacheSize); +} + +void KNConfig::ReadNewsGeneralWidget::save() +{ + d_ata->a_utoCheck=a_utoCB->isChecked(); + d_ata->m_axFetch=m_axFetch->value(); + d_ata->a_utoMark=m_arkCB->isChecked(); + d_ata->m_arkSecs=m_arkSecs->value(); + d_ata->m_arkCrossposts=m_arkCrossCB->isChecked(); + d_ata->s_martScrolling=s_martScrollingCB->isChecked(); + d_ata->t_otalExpand=e_xpThrCB->isChecked(); + d_ata->d_efaultExpand=d_efaultExpandCB->isChecked(); + d_ata->s_howLines=l_inesCB->isChecked(); + d_ata->s_howScore=s_coreCB->isChecked(); + d_ata->s_howUnread=u_nreadCB->isChecked(); + d_ata->c_ollCacheSize=c_ollCacheSize->value(); + d_ata->a_rtCacheSize=a_rtCacheSize->value(); + + d_ata->setDirty(true); +} + +//============================================================================================= + + +KNConfig::ReadNewsNavigationWidget::ReadNewsNavigationWidget( ReadNewsNavigation *d, QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( d ) +{ + QVBoxLayout *topL=new QVBoxLayout(this, 5); + + // ==== Mark All as Read ==================================================== + + QGroupBox *gb=new QGroupBox(i18n("\"Mark All as Read\" Triggers Following Actions"), this); + QVBoxLayout *gbL=new QVBoxLayout(gb, 8, 5); + topL->addWidget(gb); + + gbL->addSpacing(fontMetrics().lineSpacing()-4); + m_arkAllReadGoNextCB=new QCheckBox(i18n("&Switch to the next group"), gb); + gbL->addWidget(m_arkAllReadGoNextCB); + + connect(m_arkAllReadGoNextCB, SIGNAL(toggled(bool)), SLOT(changed())); + + // ==== Mark Thread as Read ================================================= + + gb=new QGroupBox(i18n("\"Mark Thread as Read\" Triggers Following Actions"), this); + gbL=new QVBoxLayout(gb, 8, 5); + topL->addWidget(gb); + + gbL->addSpacing(fontMetrics().lineSpacing()-4); + m_arkThreadReadCloseThreadCB=new QCheckBox(i18n("Clos&e the current thread"), gb); + gbL->addWidget(m_arkThreadReadCloseThreadCB); + m_arkThreadReadGoNextCB=new QCheckBox(i18n("Go &to the next unread thread"), gb); + gbL->addWidget(m_arkThreadReadGoNextCB); + + connect(m_arkThreadReadCloseThreadCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(m_arkThreadReadGoNextCB, SIGNAL(toggled(bool)), SLOT(changed())); + + // ==== Ignore Thread ======================================================= + + gb=new QGroupBox(i18n("\"Ignore Thread\" Triggers Following Actions"), this); + gbL=new QVBoxLayout(gb, 8, 5); + topL->addWidget(gb); + + gbL->addSpacing(fontMetrics().lineSpacing()-4); + i_gnoreThreadCloseThreadCB=new QCheckBox(i18n("Close the cu&rrent thread"), gb); + gbL->addWidget(i_gnoreThreadCloseThreadCB); + i_gnoreThreadGoNextCB=new QCheckBox(i18n("Go to the next &unread thread"), gb); + gbL->addWidget(i_gnoreThreadGoNextCB); + + connect(i_gnoreThreadCloseThreadCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(i_gnoreThreadGoNextCB, SIGNAL(toggled(bool)), SLOT(changed())); + + topL->addStretch(1); + topL->setResizeMode(QLayout::Minimum); + + load(); +} + + +KNConfig::ReadNewsNavigationWidget::~ReadNewsNavigationWidget() +{ +} + + +void KNConfig::ReadNewsNavigationWidget::load() +{ + m_arkAllReadGoNextCB->setChecked(d_ata->m_arkAllReadGoNext); + m_arkThreadReadGoNextCB->setChecked(d_ata->m_arkThreadReadGoNext); + m_arkThreadReadCloseThreadCB->setChecked(d_ata->m_arkThreadReadCloseThread); + i_gnoreThreadGoNextCB->setChecked(d_ata->i_gnoreThreadGoNext); + i_gnoreThreadCloseThreadCB->setChecked(d_ata->i_gnoreThreadCloseThread); +} + +void KNConfig::ReadNewsNavigationWidget::save() +{ + d_ata->m_arkAllReadGoNext = m_arkAllReadGoNextCB->isChecked(); + d_ata->m_arkThreadReadGoNext = m_arkThreadReadGoNextCB->isChecked(); + d_ata->m_arkThreadReadCloseThread = m_arkThreadReadCloseThreadCB->isChecked(); + d_ata->i_gnoreThreadGoNext = i_gnoreThreadGoNextCB->isChecked(); + d_ata->i_gnoreThreadCloseThread = i_gnoreThreadCloseThreadCB->isChecked(); + + d_ata->setDirty(true); +} + + +//============================================================================================= + + +KNConfig::ReadNewsViewerWidget::ReadNewsViewerWidget( ReadNewsViewer *d, QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( d ) +{ + QGroupBox *appgb=new QGroupBox(i18n("Appearance"), this); + QGroupBox *agb=new QGroupBox(i18n("Attachments"), this); + QGroupBox *secbox = new QGroupBox( i18n("Security"), this ); + + r_ewrapCB=new QCheckBox(i18n("Re&wrap text when necessary"), appgb); + r_emoveTrailingCB=new QCheckBox(i18n("Re&move trailing empty lines"), appgb); + s_igCB=new QCheckBox(i18n("Show sig&nature"), appgb); + mShowRefBar = new QCheckBox( i18n("Show reference bar"), appgb ); + q_uoteCharacters=new KLineEdit(appgb); + QLabel *quoteCharL = new QLabel(q_uoteCharacters, i18n("Recognized q&uote characters:"), appgb); + + o_penAttCB=new QCheckBox(i18n("Open a&ttachments on click"), agb); + a_ltAttCB=new QCheckBox(i18n("Show alternati&ve contents as attachments"), agb); + + mAlwaysShowHTML = new QCheckBox( i18n("Prefer HTML to plain text"), secbox ); + + QVBoxLayout *topL=new QVBoxLayout(this, 5); + QGridLayout *appgbL=new QGridLayout(appgb, 5,2, 8,5); + QVBoxLayout *agbL=new QVBoxLayout(agb, 8, 5); + QVBoxLayout *secLayout = new QVBoxLayout( secbox, 8, 5 ); + + topL->addWidget(appgb); + topL->addWidget(agb); + topL->addWidget( secbox ); + topL->addStretch(1); + + appgbL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + appgbL->addMultiCellWidget(r_ewrapCB, 2,2, 0,1); + appgbL->addMultiCellWidget(r_emoveTrailingCB, 3,3, 0,1); + appgbL->addMultiCellWidget(s_igCB, 4,4, 0,1); + appgbL->addMultiCellWidget( mShowRefBar, 5,5, 0,1 ); + appgbL->addWidget(quoteCharL, 6,0); + appgbL->addWidget(q_uoteCharacters, 6,1); + + agbL->addSpacing(fontMetrics().lineSpacing()-4); + agbL->addWidget(o_penAttCB); + agbL->addWidget(a_ltAttCB); + + secLayout->addSpacing( fontMetrics().lineSpacing() - 4 ); + secLayout->addWidget( mAlwaysShowHTML ); + + topL->setResizeMode(QLayout::Minimum); + + connect(r_ewrapCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(r_emoveTrailingCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(s_igCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(q_uoteCharacters, SIGNAL(textChanged(const QString&)), SLOT(changed())); + connect(o_penAttCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(a_ltAttCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect( mShowRefBar, SIGNAL(toggled(bool)), SLOT(changed()) ); + connect( mAlwaysShowHTML, SIGNAL(toggled(bool)), SLOT(changed()) ); + + load(); +} + + +KNConfig::ReadNewsViewerWidget::~ReadNewsViewerWidget() +{ +} + + +void KNConfig::ReadNewsViewerWidget::load() +{ + r_ewrapCB->setChecked(d_ata->r_ewrapBody); + r_emoveTrailingCB->setChecked(d_ata->r_emoveTrailingNewlines); + s_igCB->setChecked(d_ata->s_howSig); + q_uoteCharacters->setText(d_ata->q_uoteCharacters); + o_penAttCB->setChecked(d_ata->o_penAtt); + a_ltAttCB->setChecked(d_ata->s_howAlts); + mShowRefBar->setChecked( d_ata->showRefBar() ); + mAlwaysShowHTML->setChecked( d_ata->alwaysShowHTML() ); +} + + +void KNConfig::ReadNewsViewerWidget::save() +{ + d_ata->r_ewrapBody=r_ewrapCB->isChecked(); + d_ata->r_emoveTrailingNewlines=r_emoveTrailingCB->isChecked(); + d_ata->s_howSig=s_igCB->isChecked(); + d_ata->q_uoteCharacters=q_uoteCharacters->text(); + d_ata->o_penAtt=o_penAttCB->isChecked(); + d_ata->s_howAlts=a_ltAttCB->isChecked(); + d_ata->setShowRefBar( mShowRefBar->isChecked() ); + d_ata->setAlwaysShowHTML( mAlwaysShowHTML->isChecked() ); + + d_ata->setDirty(true); +} + + +//============================================================================================= + + +KNConfig::DisplayedHeadersWidget::DisplayedHeadersWidget( DisplayedHeaders *d, QWidget *p, const char *n ) : + KCModule( p, n ), + s_ave( false ), + d_ata( d ) +{ + QGridLayout *topL=new QGridLayout(this, 7,2, 5,5); + + //listbox + l_box=new KNDialogListBox(false, this); + connect(l_box, SIGNAL(selected(int)), this, SLOT(slotItemSelected(int))); + connect(l_box, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); + topL->addMultiCellWidget(l_box, 0,6, 0,0); + + // buttons + a_ddBtn=new QPushButton(i18n("&Add..."), this); + connect(a_ddBtn, SIGNAL(clicked()), this, SLOT(slotAddBtnClicked())); + topL->addWidget(a_ddBtn, 0,1); + + d_elBtn=new QPushButton(i18n("&Delete"), this); + connect(d_elBtn, SIGNAL(clicked()), this, SLOT(slotDelBtnClicked())); + topL->addWidget(d_elBtn, 1,1); + + e_ditBtn=new QPushButton(i18n("modify something","&Edit..."), this); + connect(e_ditBtn, SIGNAL(clicked()), this, SLOT(slotEditBtnClicked())); + topL->addWidget(e_ditBtn, 2,1); + + u_pBtn=new QPushButton(i18n("&Up"), this); + connect(u_pBtn, SIGNAL(clicked()), this, SLOT(slotUpBtnClicked())); + topL->addWidget(u_pBtn, 4,1); + + d_ownBtn=new QPushButton(i18n("Do&wn"), this); + connect(d_ownBtn, SIGNAL(clicked()), this, SLOT(slotDownBtnClicked())); + topL->addWidget(d_ownBtn, 5,1); + + topL->addRowSpacing(3,20); // separate up/down buttons + topL->setRowStretch(6,1); // stretch the listbox + + slotSelectionChanged(); // disable buttons initially + + load(); +} + + + +KNConfig::DisplayedHeadersWidget::~DisplayedHeadersWidget() +{ +} + + +void KNConfig::DisplayedHeadersWidget::load() +{ + l_box->clear(); + QValueList<KNDisplayedHeader*> list = d_ata->headers(); + for ( QValueList<KNDisplayedHeader*>::Iterator it = list.begin(); it != list.end(); ++it ) + l_box->insertItem( generateItem( (*it) ) ); +} + +void KNConfig::DisplayedHeadersWidget::save() +{ + if(s_ave) { + d_ata->setDirty(true); + d_ata->save(); + } + s_ave = false; +} + + + +KNConfig::DisplayedHeadersWidget::HdrItem* KNConfig::DisplayedHeadersWidget::generateItem(KNDisplayedHeader *h) +{ + QString text; + if(h->hasName()) { + text=h->translatedName(); + text+=": <"; + } else + text="<"; + text+=h->header(); + text+=">"; + return new HdrItem(text,h); +} + + + +void KNConfig::DisplayedHeadersWidget::slotItemSelected(int) +{ + slotEditBtnClicked(); +} + + + +void KNConfig::DisplayedHeadersWidget::slotSelectionChanged() +{ + int curr = l_box->currentItem(); + d_elBtn->setEnabled(curr!=-1); + e_ditBtn->setEnabled(curr!=-1); + u_pBtn->setEnabled(curr>0); + d_ownBtn->setEnabled((curr!=-1)&&(curr+1!=(int)(l_box->count()))); +} + + + +void KNConfig::DisplayedHeadersWidget::slotAddBtnClicked() +{ + KNDisplayedHeader *h=d_ata->createNewHeader(); + + DisplayedHeaderConfDialog* dlg=new DisplayedHeaderConfDialog(h, this); + if(dlg->exec()) { + l_box->insertItem(generateItem(h)); + h->createTags(); + s_ave=true; + } else + d_ata->remove(h); + emit changed(true); +} + + + +void KNConfig::DisplayedHeadersWidget::slotDelBtnClicked() +{ + if(l_box->currentItem()==-1) + return; + + if(KMessageBox::warningContinueCancel(this, i18n("Really delete this header?"),"",KGuiItem(i18n("&Delete"),"editdelete"))==KMessageBox::Continue) { + KNDisplayedHeader *h = (static_cast<HdrItem*>(l_box->item(l_box->currentItem())))->hdr; + d_ata->remove(h); + l_box->removeItem(l_box->currentItem()); + s_ave=true; + } + emit changed(true); +} + + + +void KNConfig::DisplayedHeadersWidget::slotEditBtnClicked() +{ + if (l_box->currentItem()==-1) return; + KNDisplayedHeader *h = (static_cast<HdrItem*>(l_box->item(l_box->currentItem())))->hdr; + + DisplayedHeaderConfDialog* dlg=new DisplayedHeaderConfDialog(h, this); + if(dlg->exec()) { + l_box->changeItem(generateItem(h), l_box->currentItem()); + h->createTags(); + s_ave=true; + } + emit changed(true); +} + + + +void KNConfig::DisplayedHeadersWidget::slotUpBtnClicked() +{ + int c=l_box->currentItem(); + if(c==0 || c==-1) return; + + KNDisplayedHeader *h = (static_cast<HdrItem*>(l_box->item(c)))->hdr; + + d_ata->up(h); + l_box->insertItem(generateItem(h), c-1); + l_box->removeItem(c+1); + l_box->setCurrentItem(c-1); + s_ave=true; + emit changed(true); +} + + + +void KNConfig::DisplayedHeadersWidget::slotDownBtnClicked() +{ + int c=l_box->currentItem(); + if(c==-1 || c==(int) l_box->count()-1) return; + + KNDisplayedHeader *h = (static_cast<HdrItem*>(l_box->item(c)))->hdr; + + d_ata->down(h); + l_box->insertItem(generateItem(h), c+2); + l_box->removeItem(c); + l_box->setCurrentItem(c+1); + s_ave=true; + emit changed(true); +} + + +//============================================================================================= + + +KNConfig::DisplayedHeaderConfDialog::DisplayedHeaderConfDialog(KNDisplayedHeader *h, QWidget *p, char *n) + : KDialogBase(Plain, i18n("Header Properties"),Ok|Cancel|Help, Ok, p, n), + h_dr(h) +{ + QFrame* page=plainPage(); + QGridLayout *topL=new QGridLayout(page, 2, 2, 0, 5); + + QWidget *nameW = new QWidget(page); + QGridLayout *nameL=new QGridLayout(nameW, 2, 2, 5); + + h_drC=new KComboBox(true, nameW); + h_drC->lineEdit()->setMaxLength(64); + connect(h_drC, SIGNAL(activated(int)), this, SLOT(slotActivated(int))); + nameL->addWidget(new QLabel(h_drC, i18n("H&eader:"),nameW),0,0); + nameL->addWidget(h_drC,0,1); + + n_ameE=new KLineEdit(nameW); + + n_ameE->setMaxLength(64); + nameL->addWidget(new QLabel(n_ameE, i18n("Displayed na&me:"),nameW),1,0); + nameL->addWidget(n_ameE,1,1); + nameL->setColStretch(1,1); + + topL->addMultiCellWidget(nameW,0,0,0,1); + + QGroupBox *ngb=new QGroupBox(i18n("Name"), page); + // ### hide style settings for now, the new viewer doesn't support this yet + ngb->hide(); + QVBoxLayout *ngbL = new QVBoxLayout(ngb, 8, 5); + ngbL->setAutoAdd(true); + ngbL->addSpacing(fontMetrics().lineSpacing()-4); + n_ameCB[0]=new QCheckBox(i18n("&Large"), ngb); + n_ameCB[1]=new QCheckBox(i18n("&Bold"), ngb); + n_ameCB[2]=new QCheckBox(i18n("&Italic"), ngb); + n_ameCB[3]=new QCheckBox(i18n("&Underlined"), ngb); + topL->addWidget(ngb,1,0); + + QGroupBox *vgb=new QGroupBox(i18n("Value"), page); + // ### hide style settings for now, the new viewer doen't support this yet + vgb->hide(); + QVBoxLayout *vgbL = new QVBoxLayout(vgb, 8, 5); + vgbL->setAutoAdd(true); + vgbL->addSpacing(fontMetrics().lineSpacing()-4); + v_alueCB[0]=new QCheckBox(i18n("L&arge"), vgb); + v_alueCB[1]=new QCheckBox(i18n("Bol&d"), vgb); + v_alueCB[2]=new QCheckBox(i18n("I&talic"), vgb); + v_alueCB[3]=new QCheckBox(i18n("U&nderlined"), vgb); + topL->addWidget(vgb,1,1); + + topL->setColStretch(0,1); + topL->setColStretch(1,1); + + // preset values... + h_drC->insertStrList(KNDisplayedHeader::predefs()); + h_drC->lineEdit()->setText(h->header()); + n_ameE->setText(h->translatedName()); + for(int i=0; i<4; i++) { + n_ameCB[i]->setChecked(h->flag(i)); + v_alueCB[i]->setChecked(h->flag(i+4)); + } + + setFixedHeight(sizeHint().height()); + KNHelper::restoreWindowSize("accReadHdrPropDLG", this, sizeHint()); + + connect(n_ameE, SIGNAL(textChanged(const QString&)), SLOT(slotNameChanged(const QString&))); + + setHelp("anc-knode-headers"); + slotNameChanged( n_ameE->text() ); +} + + +KNConfig::DisplayedHeaderConfDialog::~DisplayedHeaderConfDialog() +{ + KNHelper::saveWindowSize("accReadHdrPropDLG", size()); +} + + +void KNConfig::DisplayedHeaderConfDialog::slotOk() +{ + h_dr->setHeader(h_drC->currentText()); + h_dr->setTranslatedName(n_ameE->text()); + for(int i=0; i<4; i++) { + if(h_dr->hasName()) + h_dr->setFlag(i, n_ameCB[i]->isChecked()); + else + h_dr->setFlag(i,false); + h_dr->setFlag(i+4, v_alueCB[i]->isChecked()); + } + accept(); +} + + +// the user selected one of the presets, insert the *translated* string as display name: +void KNConfig::DisplayedHeaderConfDialog::slotActivated(int pos) +{ + n_ameE->setText(i18n(h_drC->text(pos).local8Bit())); // I think it's save here, the combobox has only english defaults +} + + +// disable the name format options when the name is empty +void KNConfig::DisplayedHeaderConfDialog::slotNameChanged(const QString& str) +{ + for(int i=0; i<4; i++) + n_ameCB[i]->setEnabled(!str.isEmpty()); +} + +//============================================================================================= + + +KNConfig::ScoringWidget::ScoringWidget( Scoring *d, QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( d ) +{ + QGridLayout *topL = new QGridLayout(this,4,2, 5,5); + ksc = new KScoringEditorWidget(knGlobals.scoringManager(), this); + topL->addMultiCellWidget(ksc, 0,0, 0,1); + + topL->addRowSpacing(1, 10); + + i_gnored=new KIntSpinBox(-100000, 100000, 1, 0, 10, this); + QLabel *l=new QLabel(i_gnored, i18n("Default score for &ignored threads:"), this); + topL->addWidget(l, 2, 0); + topL->addWidget(i_gnored, 2, 1); + connect(i_gnored, SIGNAL(valueChanged(int)), SLOT(changed())); + + w_atched=new KIntSpinBox(-100000, 100000, 1, 0, 10, this); + l=new QLabel(w_atched, i18n("Default score for &watched threads:"), this); + topL->addWidget(l, 3, 0); + topL->addWidget(w_atched, 3, 1); + connect(w_atched, SIGNAL(valueChanged(int)), SLOT(changed())); + + topL->setColStretch(0, 1); + + load(); +} + + +KNConfig::ScoringWidget::~ScoringWidget() +{ +} + + +void KNConfig::ScoringWidget::load() +{ + i_gnored->setValue(d_ata->i_gnoredThreshold); + w_atched->setValue(d_ata->w_atchedThreshold); +} + +void KNConfig::ScoringWidget::save() +{ + d_ata->i_gnoredThreshold = i_gnored->value(); + d_ata->w_atchedThreshold = w_atched->value(); + + d_ata->setDirty(true); +} + + +//============================================================================================= + + +KNConfig::FilterListWidget::FilterListWidget( QWidget *p, const char *n ) : + KCModule( p, n ), + f_ilManager( knGlobals.filterManager() ) +{ + QGridLayout *topL=new QGridLayout(this, 6,2, 5,5); + + // == Filters ================================================= + + f_lb=new KNDialogListBox(false, this); + topL->addWidget(new QLabel(f_lb, i18n("&Filters:"),this),0,0); + + connect(f_lb, SIGNAL(selectionChanged()), SLOT(slotSelectionChangedFilter())); + connect(f_lb, SIGNAL(selected(int)), SLOT(slotItemSelectedFilter(int))); + topL->addMultiCellWidget(f_lb,1,5,0,0); + + a_ddBtn=new QPushButton(i18n("&Add..."), this); + connect(a_ddBtn, SIGNAL(clicked()), this, SLOT(slotAddBtnClicked())); + topL->addWidget(a_ddBtn,1,1); + + e_ditBtn=new QPushButton(i18n("modify something","&Edit..."), this); + connect(e_ditBtn, SIGNAL(clicked()), this, SLOT(slotEditBtnClicked())); + topL->addWidget(e_ditBtn,2,1); + + c_opyBtn=new QPushButton(i18n("Co&py..."), this); + connect(c_opyBtn, SIGNAL(clicked()), this, SLOT(slotCopyBtnClicked())); + topL->addWidget(c_opyBtn,3,1); + + d_elBtn=new QPushButton(i18n("&Delete"), this); + connect(d_elBtn, SIGNAL(clicked()), this, SLOT(slotDelBtnClicked())); + topL->addWidget(d_elBtn,4,1); + + // == Menu ==================================================== + + m_lb=new KNDialogListBox(false, this); + topL->addWidget(new QLabel(m_lb, i18n("&Menu:"),this),6,0); + + connect(m_lb, SIGNAL(selectionChanged()), SLOT(slotSelectionChangedMenu())); + topL->addMultiCellWidget(m_lb,7,11,0,0); + + u_pBtn=new QPushButton(i18n("&Up"), this); + connect(u_pBtn, SIGNAL(clicked()), this, SLOT(slotUpBtnClicked())); + topL->addWidget(u_pBtn,7,1); + + d_ownBtn=new QPushButton(i18n("Do&wn"), this); + connect(d_ownBtn, SIGNAL(clicked()), this, SLOT(slotDownBtnClicked())); + topL->addWidget(d_ownBtn,8,1); + + s_epAddBtn=new QPushButton(i18n("Add\n&Separator"), this); + connect(s_epAddBtn, SIGNAL(clicked()), this, SLOT(slotSepAddBtnClicked())); + topL->addWidget(s_epAddBtn,9,1); + + s_epRemBtn=new QPushButton(i18n("&Remove\nSeparator"), this); + connect(s_epRemBtn, SIGNAL(clicked()), this, SLOT(slotSepRemBtnClicked())); + topL->addWidget(s_epRemBtn,10,1); + + topL->setRowStretch(5,1); + topL->setRowStretch(11,1); + + a_ctive = SmallIcon("filter",16); + d_isabled = SmallIcon("filter",16,KIcon::DisabledState); + + load(); + + slotSelectionChangedFilter(); + slotSelectionChangedMenu(); +} + + +KNConfig::FilterListWidget::~FilterListWidget() +{ + f_ilManager->endConfig(); +} + + +void KNConfig::FilterListWidget::load() +{ + f_lb->clear(); + m_lb->clear(); + f_ilManager->startConfig(this); +} + +void KNConfig::FilterListWidget::save() +{ + f_ilManager->commitChanges(); +} + + +void KNConfig::FilterListWidget::addItem(KNArticleFilter *f) +{ + if(f->isEnabled()) + f_lb->insertItem(new LBoxItem(f, f->translatedName(), &a_ctive)); + else + f_lb->insertItem(new LBoxItem(f, f->translatedName(), &d_isabled)); + slotSelectionChangedFilter(); + emit changed(true); +} + + +void KNConfig::FilterListWidget::removeItem(KNArticleFilter *f) +{ + int i=findItem(f_lb, f); + if (i!=-1) f_lb->removeItem(i); + slotSelectionChangedFilter(); + emit changed(true); +} + + +void KNConfig::FilterListWidget::updateItem(KNArticleFilter *f) +{ + int i=findItem(f_lb, f); + + if(i!=-1) { + if(f->isEnabled()) { + f_lb->changeItem(new LBoxItem(f, f->translatedName(), &a_ctive), i); + m_lb->changeItem(new LBoxItem(f, f->translatedName()), findItem(m_lb, f)); + } else + f_lb->changeItem(new LBoxItem(f, f->translatedName(), &d_isabled), i); + } + slotSelectionChangedFilter(); + emit changed(true); +} + + +void KNConfig::FilterListWidget::addMenuItem(KNArticleFilter *f) +{ + if (f) { + if (findItem(m_lb, f)==-1) + m_lb->insertItem(new LBoxItem(f, f->translatedName())); + } else // separator + m_lb->insertItem(new LBoxItem(0, "===")); + slotSelectionChangedMenu(); + emit changed(true); +} + + +void KNConfig::FilterListWidget::removeMenuItem(KNArticleFilter *f) +{ + int i=findItem(m_lb, f); + if(i!=-1) m_lb->removeItem(i); + slotSelectionChangedMenu(); + emit changed(true); +} + + +QValueList<int> KNConfig::FilterListWidget::menuOrder() +{ + KNArticleFilter *f; + QValueList<int> lst; + + for(uint i=0; i<m_lb->count(); i++) { + f= (static_cast<LBoxItem*>(m_lb->item(i)))->filter; + if(f) + lst << f->id(); + else + lst << -1; + } + return lst; +} + + +int KNConfig::FilterListWidget::findItem(QListBox *l, KNArticleFilter *f) +{ + int idx=0; + bool found=false; + while(!found && idx < (int) l->count()) { + found=( (static_cast<LBoxItem*>(l->item(idx)))->filter==f ); + if(!found) idx++; + } + if(found) return idx; + else return -1; +} + + +void KNConfig::FilterListWidget::slotAddBtnClicked() +{ + f_ilManager->newFilter(); +} + + +void KNConfig::FilterListWidget::slotDelBtnClicked() +{ + if (f_lb->currentItem()!=-1) + f_ilManager->deleteFilter( (static_cast<LBoxItem*>(f_lb->item(f_lb->currentItem())))->filter ); +} + + +void KNConfig::FilterListWidget::slotEditBtnClicked() +{ + if (f_lb->currentItem()!=-1) + f_ilManager->editFilter( (static_cast<LBoxItem*>(f_lb->item(f_lb->currentItem())))->filter ); +} + + +void KNConfig::FilterListWidget::slotCopyBtnClicked() +{ + if (f_lb->currentItem()!=-1) + f_ilManager->copyFilter( (static_cast<LBoxItem*>(f_lb->item(f_lb->currentItem())))->filter ); +} + + +void KNConfig::FilterListWidget::slotUpBtnClicked() +{ + int c=m_lb->currentItem(); + KNArticleFilter *f=0; + + if(c==0 || c==-1) return; + f=(static_cast<LBoxItem*>(m_lb->item(c)))->filter; + if(f) + m_lb->insertItem(new LBoxItem(f, f->translatedName()), c-1); + else + m_lb->insertItem(new LBoxItem(0, "==="), c-1); + m_lb->removeItem(c+1); + m_lb->setCurrentItem(c-1); + emit changed(true); +} + + +void KNConfig::FilterListWidget::slotDownBtnClicked() +{ + int c=m_lb->currentItem(); + KNArticleFilter *f=0; + + if(c==-1 || c+1==(int)m_lb->count()) return; + f=(static_cast<LBoxItem*>(m_lb->item(c)))->filter; + if(f) + m_lb->insertItem(new LBoxItem(f, f->translatedName()), c+2); + else + m_lb->insertItem(new LBoxItem(0, "==="), c+2); + m_lb->removeItem(c); + m_lb->setCurrentItem(c+1); + emit changed(true); +} + + +void KNConfig::FilterListWidget::slotSepAddBtnClicked() +{ + m_lb->insertItem(new LBoxItem(0, "==="), m_lb->currentItem()); + slotSelectionChangedMenu(); + emit changed(true); +} + + +void KNConfig::FilterListWidget::slotSepRemBtnClicked() +{ + int c=m_lb->currentItem(); + + if( (c!=-1) && ( (static_cast<LBoxItem*>(m_lb->item(c)))->filter==0 ) ) + m_lb->removeItem(c); + slotSelectionChangedMenu(); + emit changed(true); +} + + +void KNConfig::FilterListWidget::slotItemSelectedFilter(int) +{ + slotEditBtnClicked(); +} + + +void KNConfig::FilterListWidget::slotSelectionChangedFilter() +{ + int curr = f_lb->currentItem(); + + d_elBtn->setEnabled(curr!=-1); + e_ditBtn->setEnabled(curr!=-1); + c_opyBtn->setEnabled(curr!=-1); +} + + +void KNConfig::FilterListWidget::slotSelectionChangedMenu() +{ + int curr = m_lb->currentItem(); + + u_pBtn->setEnabled(curr>0); + d_ownBtn->setEnabled((curr!=-1)&&(curr+1!=(int)m_lb->count())); + s_epRemBtn->setEnabled((curr!=-1) && ( (static_cast<LBoxItem*>(m_lb->item(curr)))->filter==0 ) ); +} + + +//============================================================================================= + + +KNConfig::PostNewsTechnicalWidget::PostNewsTechnicalWidget( PostNewsTechnical *d, QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( d ) +{ + QVBoxLayout *topL=new QVBoxLayout(this, 5); + + // ==== General ============================================================= + + QGroupBox *ggb=new QGroupBox(i18n("General"), this); + QGridLayout *ggbL=new QGridLayout(ggb, 6,2, 8,5); + topL->addWidget(ggb); + + ggbL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + c_harset=new QComboBox(ggb); + c_harset->insertStringList(d->composerCharsets()); + ggbL->addWidget(new QLabel(c_harset, i18n("Cha&rset:"), ggb), 1,0); + ggbL->addWidget(c_harset, 1,1); + connect(c_harset, SIGNAL(activated(int)), SLOT(changed())); + + e_ncoding=new QComboBox(ggb); + e_ncoding->insertItem(i18n("Allow 8-bit")); + e_ncoding->insertItem(i18n("7-bit (Quoted-Printable)")); + ggbL->addWidget(new QLabel(e_ncoding, i18n("Enco&ding:"), ggb), 2,0); + ggbL->addWidget(e_ncoding, 2,1); + connect(e_ncoding, SIGNAL(activated(int)), SLOT(changed())); + + u_seOwnCSCB=new QCheckBox(i18n("Use o&wn default charset when replying"), ggb); + ggbL->addMultiCellWidget(u_seOwnCSCB, 3,3, 0,1); + connect(u_seOwnCSCB, SIGNAL(toggled(bool)), SLOT(changed())); + + g_enMIdCB=new QCheckBox(i18n("&Generate message-id"), ggb); + connect(g_enMIdCB, SIGNAL(toggled(bool)), this, SLOT(slotGenMIdCBToggled(bool))); + ggbL->addMultiCellWidget(g_enMIdCB, 4,4, 0,1); + h_ost=new KLineEdit(ggb); + h_ost->setEnabled(false); + h_ostL=new QLabel(h_ost, i18n("Ho&st name:"), ggb); + h_ostL->setEnabled(false); + ggbL->addWidget(h_ostL, 5,0); + ggbL->addWidget(h_ost, 5,1); + ggbL->setColStretch(1,1); + connect(h_ost, SIGNAL(textChanged(const QString&)), SLOT(changed())); + + // ==== X-Headers ============================================================= + + QGroupBox *xgb=new QGroupBox(i18n("X-Headers"), this); + topL->addWidget(xgb, 1); + QGridLayout *xgbL=new QGridLayout(xgb, 7,2, 8,5); + + xgbL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + + l_box=new KNDialogListBox(false, xgb); + connect(l_box, SIGNAL(selected(int)), SLOT(slotItemSelected(int))); + connect(l_box, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged())); + xgbL->addMultiCellWidget(l_box, 1,4, 0,0); + + a_ddBtn=new QPushButton(i18n("&Add..."), xgb); + connect(a_ddBtn, SIGNAL(clicked()), SLOT(slotAddBtnClicked())); + xgbL->addWidget(a_ddBtn, 1,1); + + d_elBtn=new QPushButton(i18n("Dele&te"), xgb); + connect(d_elBtn, SIGNAL(clicked()), SLOT(slotDelBtnClicked())); + xgbL->addWidget(d_elBtn, 2,1); + + e_ditBtn=new QPushButton(i18n("modify something","&Edit..."), xgb); + connect(e_ditBtn, SIGNAL(clicked()), SLOT(slotEditBtnClicked())); + xgbL->addWidget(e_ditBtn, 3,1); + + QLabel *placeHolders = new QLabel(i18n("<qt>Placeholders for replies: <b>%NAME</b>=sender's name, <b>%EMAIL</b>=sender's address</qt>"), xgb); + xgbL->addMultiCellWidget(placeHolders, 5, 5, 0, 1); + + i_ncUaCB=new QCheckBox(i18n("Do not add the \"&User-Agent\" identification header"), xgb); + xgbL->addMultiCellWidget(i_ncUaCB, 6,6, 0,1); + connect(i_ncUaCB, SIGNAL(toggled(bool)), SLOT(changed())); + + xgbL->setRowStretch(4,1); + xgbL->setColStretch(0,1); + + load(); + + slotSelectionChanged(); +} + + +KNConfig::PostNewsTechnicalWidget::~PostNewsTechnicalWidget() +{ +} + + +void KNConfig::PostNewsTechnicalWidget::load() +{ + c_harset->setCurrentItem(d_ata->indexForCharset(d_ata->charset())); + e_ncoding->setCurrentItem(d_ata->a_llow8BitBody? 0:1); + u_seOwnCSCB->setChecked(d_ata->u_seOwnCharset); + g_enMIdCB->setChecked(d_ata->g_enerateMID); + h_ost->setText(d_ata->h_ostname); + i_ncUaCB->setChecked(d_ata->d_ontIncludeUA); + + l_box->clear(); + for(XHeaders::Iterator it=d_ata->x_headers.begin(); it!=d_ata->x_headers.end(); ++it) + l_box->insertItem((*it).header()); +} + +void KNConfig::PostNewsTechnicalWidget::save() +{ + d_ata->c_harset=c_harset->currentText().latin1(); + d_ata->a_llow8BitBody=(e_ncoding->currentItem()==0); + d_ata->u_seOwnCharset=u_seOwnCSCB->isChecked(); + d_ata->g_enerateMID=g_enMIdCB->isChecked(); + d_ata->h_ostname=h_ost->text().latin1(); + d_ata->d_ontIncludeUA=i_ncUaCB->isChecked(); + d_ata->x_headers.clear(); + for(unsigned int idx=0; idx<l_box->count(); idx++) + d_ata->x_headers.append( XHeader(l_box->text(idx)) ); + + d_ata->setDirty(true); +} + + + +void KNConfig::PostNewsTechnicalWidget::slotGenMIdCBToggled(bool b) +{ + h_ost->setEnabled(b); + h_ostL->setEnabled(b); + emit changed(true); +} + + + +void KNConfig::PostNewsTechnicalWidget::slotSelectionChanged() +{ + d_elBtn->setEnabled(l_box->currentItem()!=-1); + e_ditBtn->setEnabled(l_box->currentItem()!=-1); +} + + + +void KNConfig::PostNewsTechnicalWidget::slotItemSelected(int) +{ + slotEditBtnClicked(); +} + + + +void KNConfig::PostNewsTechnicalWidget::slotAddBtnClicked() +{ + XHeaderConfDialog *dlg=new XHeaderConfDialog(QString::null, this); + if (dlg->exec()) + l_box->insertItem(dlg->result()); + + delete dlg; + + slotSelectionChanged(); + emit changed(true); +} + + + +void KNConfig::PostNewsTechnicalWidget::slotDelBtnClicked() +{ + int c=l_box->currentItem(); + if (c == -1) + return; + + l_box->removeItem(c); + slotSelectionChanged(); + emit changed(true); +} + + + +void KNConfig::PostNewsTechnicalWidget::slotEditBtnClicked() +{ + int c=l_box->currentItem(); + if (c == -1) + return; + + XHeaderConfDialog *dlg=new XHeaderConfDialog(l_box->text(c), this); + if (dlg->exec()) + l_box->changeItem(dlg->result(),c); + + delete dlg; + + slotSelectionChanged(); + emit changed(true); +} + + +//=================================================================================================== + + +KNConfig::XHeaderConfDialog::XHeaderConfDialog(const QString &h, QWidget *p, const char *n) + : KDialogBase(Plain, i18n("X-Headers"),Ok|Cancel, Ok, p, n) +{ + QFrame* page=plainPage(); + QHBoxLayout *topL=new QHBoxLayout(page, 5,8); + topL->setAutoAdd(true); + + new QLabel("X-", page); + n_ame=new KLineEdit(page); + new QLabel(":", page); + v_alue=new KLineEdit(page); + + int pos=h.find(": ", 2); + if(pos!=-1) { + n_ame->setText(h.mid(2, pos-2)); + pos+=2; + v_alue->setText(h.mid(pos, h.length()-pos)); + } + + setFixedHeight(sizeHint().height()); + KNHelper::restoreWindowSize("XHeaderDlg", this, sizeHint()); + + n_ame->setFocus(); +} + + + +KNConfig::XHeaderConfDialog::~XHeaderConfDialog() +{ + KNHelper::saveWindowSize("XHeaderDlg", size()); +} + + + +QString KNConfig::XHeaderConfDialog::result() +{ + QString value = v_alue->text(); + // just in case someone pastes a newline + value.replace( '\n', ' ' ); + return QString( "X-%1: %2" ).arg( n_ame->text() ).arg( value ); +} + + +//=================================================================================================== + + +KNConfig::PostNewsComposerWidget::PostNewsComposerWidget( PostNewsComposer *d, QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( d ) +{ + QVBoxLayout *topL=new QVBoxLayout(this, 5); + + // === general =========================================================== + + QGroupBox *generalB=new QGroupBox(i18n("General"), this); + topL->addWidget(generalB); + QGridLayout *generalL=new QGridLayout(generalB, 3,3, 8,5); + + generalL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + + w_ordWrapCB=new QCheckBox(i18n("Word &wrap at column:"), generalB); + generalL->addWidget(w_ordWrapCB,1,0); + m_axLen=new KIntSpinBox(20, 200, 1, 20, 10, generalB); + generalL->addWidget(m_axLen,1,2); + connect(w_ordWrapCB, SIGNAL(toggled(bool)), m_axLen, SLOT(setEnabled(bool))); + connect(w_ordWrapCB, SIGNAL(toggled(bool)), SLOT(changed())); + connect(m_axLen, SIGNAL(valueChanged(int)), SLOT(changed())); + + o_wnSigCB=new QCheckBox(i18n("Appe&nd signature automatically"), generalB); + generalL->addMultiCellWidget(o_wnSigCB,2,2,0,1); + connect(o_wnSigCB, SIGNAL(toggled(bool)), SLOT(changed())); + + generalL->setColStretch(1,1); + + // === reply ============================================================= + + QGroupBox *replyB=new QGroupBox(i18n("Reply"), this); + topL->addWidget(replyB); + QGridLayout *replyL=new QGridLayout(replyB, 7,2, 8,5); + + replyL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + + i_ntro=new KLineEdit(replyB); + replyL->addMultiCellWidget(new QLabel(i_ntro,i18n("&Introduction phrase:"), replyB),1,1,0,1); + replyL->addMultiCellWidget(i_ntro, 2,2,0,1); + replyL->addMultiCellWidget(new QLabel(i18n("<qt>Placeholders: <b>%NAME</b>=sender's name, <b>%EMAIL</b>=sender's address,<br><b>%DATE</b>=date, <b>%MSID</b>=message-id, <b>%GROUP</b>=group name, <b>%L</b>=line break</qt>"), replyB),3,3,0,1); + connect(i_ntro, SIGNAL(textChanged(const QString&)), SLOT(changed())); + + r_ewrapCB=new QCheckBox(i18n("Rewrap quoted te&xt automatically"), replyB); + replyL->addMultiCellWidget(r_ewrapCB, 4,4,0,1); + connect(r_ewrapCB, SIGNAL(toggled(bool)), SLOT(changed())); + + a_uthSigCB=new QCheckBox(i18n("Include the a&uthor's signature"), replyB); + replyL->addMultiCellWidget(a_uthSigCB, 5,5,0,1); + connect(a_uthSigCB, SIGNAL(toggled(bool)), SLOT(changed())); + + c_ursorOnTopCB=new QCheckBox(i18n("Put the cursor &below the introduction phrase"), replyB); + replyL->addMultiCellWidget(c_ursorOnTopCB, 6,6,0,1); + connect(c_ursorOnTopCB, SIGNAL(toggled(bool)), SLOT(changed())); + + replyL->setColStretch(1,1); + + // === external editor ======================================================== + + QGroupBox *editorB=new QGroupBox(i18n("External Editor"), this); + topL->addWidget(editorB); + QGridLayout *editorL=new QGridLayout(editorB, 6,3, 8,5); + + editorL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + + e_ditor=new KLineEdit(editorB); + editorL->addWidget(new QLabel(e_ditor, i18n("Specify edi&tor:"), editorB),1,0); + editorL->addWidget(e_ditor,1,1); + QPushButton *btn = new QPushButton(i18n("Choo&se..."),editorB); + connect(btn, SIGNAL(clicked()), SLOT(slotChooseEditor())); + connect(e_ditor, SIGNAL(textChanged(const QString&)), SLOT(changed())); + editorL->addWidget(btn,1,2); + + editorL->addMultiCellWidget(new QLabel(i18n("%f will be replaced with the filename to edit."), editorB),2,2,0,2); + + e_xternCB=new QCheckBox(i18n("Start exte&rnal editor automatically"), editorB); + editorL->addMultiCellWidget(e_xternCB, 3,3,0,2); + connect(e_xternCB, SIGNAL(clicked()), SLOT(changed())); + + editorL->setColStretch(1,1); + + topL->addStretch(1); + + load(); +} + + +KNConfig::PostNewsComposerWidget::~PostNewsComposerWidget() +{ +} + + +void KNConfig::PostNewsComposerWidget::load() +{ + w_ordWrapCB->setChecked(d_ata->w_ordWrap); + m_axLen->setEnabled(d_ata->w_ordWrap); + a_uthSigCB->setChecked(d_ata->i_ncSig); + c_ursorOnTopCB->setChecked(d_ata->c_ursorOnTop); + e_xternCB->setChecked(d_ata->u_seExtEditor); + o_wnSigCB->setChecked(d_ata->a_ppSig); + r_ewrapCB->setChecked(d_ata->r_ewrap); + m_axLen->setValue(d_ata->m_axLen); + i_ntro->setText(d_ata->i_ntro); + e_ditor->setText(d_ata->e_xternalEditor); +} + + +void KNConfig::PostNewsComposerWidget::save() +{ + d_ata->w_ordWrap=w_ordWrapCB->isChecked(); + d_ata->m_axLen=m_axLen->value(); + d_ata->r_ewrap=r_ewrapCB->isChecked(); + d_ata->a_ppSig=o_wnSigCB->isChecked(); + d_ata->i_ntro=i_ntro->text(); + d_ata->i_ncSig=a_uthSigCB->isChecked(); + d_ata->c_ursorOnTop=c_ursorOnTopCB->isChecked(); + d_ata->e_xternalEditor=e_ditor->text(); + d_ata->u_seExtEditor=e_xternCB->isChecked(); + + d_ata->setDirty(true); +} + + +void KNConfig::PostNewsComposerWidget::slotChooseEditor() +{ + QString path=e_ditor->text().simplifyWhiteSpace(); + if (path.right(3) == " %f") + path.truncate(path.length()-3); + + path=KFileDialog::getOpenFileName(path, QString::null, this, i18n("Choose Editor")); + + if (!path.isEmpty()) + e_ditor->setText(path+" %f"); +} + + +//=================================================================================================== + + +KNConfig::PostNewsSpellingWidget::PostNewsSpellingWidget( QWidget *p, const char *n ) : + KCModule( p, n ) +{ + QVBoxLayout *topL=new QVBoxLayout(this, 5); + + c_onf = new KSpellConfig( this, "spell", 0, false ); + topL->addWidget(c_onf); + connect(c_onf, SIGNAL(configChanged()), SLOT(changed())); + + topL->addStretch(1); +} + + +KNConfig::PostNewsSpellingWidget::~PostNewsSpellingWidget() +{ +} + + +void KNConfig::PostNewsSpellingWidget::save() +{ + c_onf->writeGlobalSettings(); +} + + +//============================================================================================================== + +KNConfig::PrivacyWidget::PrivacyWidget(QWidget *p, const char *n) : + KCModule( p, n ) +{ + QBoxLayout *topLayout = new QVBoxLayout(this, 5); + c_onf = new Kpgp::Config(this,"knode pgp config",false); + topLayout->addWidget(c_onf); + connect(c_onf, SIGNAL(changed()), SLOT(changed())); + + topLayout->addStretch(1); + + load(); +} + + +KNConfig::PrivacyWidget::~PrivacyWidget() +{ +} + + +void KNConfig::PrivacyWidget::save() +{ + c_onf->applySettings(); +} + + +//============================================================================================================== + + +//BEGIN: Cleanup configuration widgets --------------------------------------- + + +KNConfig::GroupCleanupWidget::GroupCleanupWidget( Cleanup *data, QWidget *parent, const char *name ) + : QWidget( parent, name ), mData( data ) +{ + QVBoxLayout *top = new QVBoxLayout( this ); + + if (!mData->isGlobal()) { + mDefault = new QCheckBox( i18n("&Use global cleanup configuration"), this ); + connect( mDefault, SIGNAL(toggled(bool)), SLOT(slotDefaultToggled(bool)) ); + top->addWidget( mDefault ); + } + + mExpGroup = new QGroupBox( i18n("Newsgroup Cleanup Settings"), this ); + mExpGroup->setColumnLayout(0, Qt::Vertical ); + mExpGroup->layout()->setSpacing( KDialog::spacingHint() ); + mExpGroup->layout()->setMargin( KDialog::marginHint() ); + top->addWidget( mExpGroup ); + QGridLayout *grid = new QGridLayout( mExpGroup->layout(), 7, 2 ); + + grid->setRowSpacing( 0, KDialog::spacingHint() ); + + mExpEnabled = new QCheckBox( i18n("&Expire old articles automatically"), mExpGroup ); + grid->addMultiCellWidget( mExpEnabled, 1, 1, 0, 1 ); + connect( mExpEnabled, SIGNAL(toggled(bool)), SIGNAL(changed()) ); + + mExpDays = new KIntSpinBox( 0, 99999, 1, 0, 10, mExpGroup ); + QLabel *label = new QLabel( mExpDays, i18n("&Purge groups every:"), mExpGroup ); + grid->addWidget( label, 2, 0 ); + grid->addWidget( mExpDays, 2, 1, Qt::AlignRight ); + connect( mExpDays, SIGNAL(valueChanged(int)), SIGNAL(changed()) ); + connect( mExpDays, SIGNAL(valueChanged(int)), SLOT(expDaysChanged(int)) ); + connect( mExpEnabled, SIGNAL(toggled(bool)), label, SLOT(setEnabled(bool)) ); + connect( mExpEnabled, SIGNAL(toggled(bool)), mExpDays, SLOT(setEnabled(bool)) ); + + mExpReadDays = new KIntSpinBox( 0, 99999, 1, 0, 10, mExpGroup ); + label = new QLabel( mExpReadDays, i18n("&Keep read articles:"), mExpGroup ); + grid->addWidget( label, 3, 0 ); + grid->addWidget( mExpReadDays, 3, 1, Qt::AlignRight ); + connect( mExpReadDays, SIGNAL(valueChanged(int)), SIGNAL(changed()) ); + connect( mExpReadDays, SIGNAL(valueChanged(int)), SLOT(expReadDaysChanged(int)) ); + + mExpUnreadDays = new KIntSpinBox( 0, 99999, 1, 0, 10, mExpGroup ); + label = new QLabel( mExpUnreadDays, i18n("Keep u&nread articles:"), mExpGroup ); + grid->addWidget( label, 4, 0 ); + grid->addWidget( mExpUnreadDays, 4, 1, Qt::AlignRight ); + connect( mExpUnreadDays, SIGNAL(valueChanged(int)), SIGNAL(changed()) ); + connect( mExpUnreadDays, SIGNAL(valueChanged(int)), SLOT(expUnreadDaysChanged(int)) ); + + mExpUnavailable = new QCheckBox( i18n("&Remove articles that are not available on the server"), mExpGroup ); + grid->addMultiCellWidget( mExpUnavailable, 5, 5, 0, 1 ); + connect( mExpUnavailable, SIGNAL(toggled(bool)), SIGNAL(changed()) ); + + mPreserveThreads = new QCheckBox( i18n("Preser&ve threads"), mExpGroup ); + grid->addMultiCellWidget( mPreserveThreads, 6, 6, 0, 1 ); + connect( mPreserveThreads, SIGNAL(toggled(bool)), SIGNAL(changed()) ); + + grid->setColStretch(1,1); +} + +void KNConfig::GroupCleanupWidget::expDaysChanged(int value) +{ + mExpDays->setSuffix( i18n(" day", " days", value) ); +} + +void KNConfig::GroupCleanupWidget::expReadDaysChanged(int value) +{ + mExpReadDays->setSuffix( i18n(" day", " days", value) ); +} + +void KNConfig::GroupCleanupWidget::expUnreadDaysChanged(int value) +{ + mExpUnreadDays->setSuffix( i18n(" day", " days", value) ); +} + +void KNConfig::GroupCleanupWidget::load() +{ + if (!mData->isGlobal()) { + mDefault->setChecked( mData->useDefault() ); + slotDefaultToggled( mData->useDefault() ); + } + mExpEnabled->setChecked( !mData->d_oExpire ); // make sure the toggled(bool) signal is emitted at least once + mExpEnabled->setChecked( mData->d_oExpire ); + mExpDays->setValue( mData->e_xpireInterval ); + mExpReadDays->setValue( mData->maxAgeForRead() ); + mExpUnreadDays->setValue( mData->maxAgeForUnread() ); + mExpUnavailable->setChecked( mData->removeUnavailable() ); + mPreserveThreads->setChecked( mData->preserveThreads() ); +} + + +void KNConfig::GroupCleanupWidget::save() +{ + if (!mData->isGlobal()) + mData->setUseDefault( mDefault->isChecked() ); + mData->d_oExpire = mExpEnabled->isChecked(); + mData->e_xpireInterval = mExpDays->value(); + mData->r_eadMaxAge = mExpReadDays->value(); + mData->u_nreadMaxAge = mExpUnreadDays->value(); + mData->r_emoveUnavailable = mExpUnavailable->isChecked(); + mData->p_reserveThr = mPreserveThreads->isChecked(); +} + + +void KNConfig::GroupCleanupWidget::slotDefaultToggled( bool state ) +{ + mExpGroup->setEnabled( !state ); +} + + +KNConfig::CleanupWidget::CleanupWidget( QWidget *p, const char *n ) : + KCModule( p, n ), + d_ata( knGlobals.configManager()->cleanup() ) +{ + QVBoxLayout *topL=new QVBoxLayout(this, 5); + + mGroupCleanup = new GroupCleanupWidget( d_ata, this ); + topL->addWidget( mGroupCleanup ); + connect( mGroupCleanup, SIGNAL(changed()), SLOT(changed()) ); + + // === folders ========================================================= + + QGroupBox *foldersB=new QGroupBox(i18n("Folders"), this); + foldersB->setColumnLayout(0, Qt::Vertical ); + foldersB->layout()->setSpacing( KDialog::spacingHint() ); + foldersB->layout()->setMargin( KDialog::marginHint() ); + + topL->addWidget(foldersB); + QGridLayout *foldersL=new QGridLayout(foldersB->layout(), 3,2); + + foldersL->setRowSpacing( 0, KDialog::spacingHint() ); + + f_olderCB=new QCheckBox(i18n("Co&mpact folders automatically"), foldersB); + connect(f_olderCB, SIGNAL(toggled(bool)), this, SLOT(slotFolderCBtoggled(bool))); + foldersL->addMultiCellWidget(f_olderCB,1,1,0,1); + + f_olderDays=new KIntSpinBox(0, 99999, 1, 0, 10, foldersB); + f_olderDaysL=new QLabel(f_olderDays,i18n("P&urge folders every:"), foldersB); + foldersL->addWidget(f_olderDaysL,2,0); + foldersL->addWidget(f_olderDays,2,1,Qt::AlignRight); + connect(f_olderDays, SIGNAL(valueChanged(int)), SLOT(changed())); + connect(f_olderDays, SIGNAL(valueChanged(int)), SLOT(slotFolderDaysChanged(int))); + + foldersL->setColStretch(1,1); + + topL->addStretch(1); + + load(); +} + + +KNConfig::CleanupWidget::~CleanupWidget() +{ +} + + +void KNConfig::CleanupWidget::load() +{ + f_olderCB->setChecked(d_ata->d_oCompact); + slotFolderCBtoggled(d_ata->d_oCompact); + f_olderDays->setValue(d_ata->c_ompactInterval); + mGroupCleanup->load(); +} + + +void KNConfig::CleanupWidget::save() +{ + d_ata->d_oCompact=f_olderCB->isChecked(); + d_ata->c_ompactInterval=f_olderDays->value(); + + mGroupCleanup->save(); + + d_ata->setDirty(true); +} + + +void KNConfig::CleanupWidget::slotFolderCBtoggled(bool b) +{ + f_olderDaysL->setEnabled(b); + f_olderDays->setEnabled(b); + emit changed(true); +} + +void KNConfig::CleanupWidget::slotFolderDaysChanged(int value) +{ + f_olderDays->setSuffix(i18n(" day", " days", value)); +} + +//END: Cleanup configuration widgets ----------------------------------------- + +//============================================================================================================== + +/* +KNConfig::CacheWidget::CacheWidget(Cache *d, QWidget *p, const char *n) + : KCModule p, n), d_ata(d) +{ + QVBoxLayout *topL=new QVBoxLayout(this, 5); + + // memory + QGroupBox *memGB=new QGroupBox(i18n("Memory Cache"), this); + topL->addWidget(memGB); + QGridLayout *memL=new QGridLayout(memGB, 3,2, 8,5); + memL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + + memL->addWidget(new QLabel(i18n("Max articles to keep:"), memGB), 1,0); + m_emMaxArt=new KIntSpinBox(0, 99999, 1, 1, 10, memGB); + memL->addWidget(m_emMaxArt, 1,1); + + memL->addWidget(new QLabel(i18n("Max memory usage:"), memGB), 2,0); + m_emMaxKB=new KIntSpinBox(0, 99999, 1, 1, 10, memGB); + m_emMaxKB->setSuffix(" KB"); + memL->addWidget(m_emMaxKB, 2,1); + + memL->setColStretch(0,1); + + + // disk + QGroupBox *diskGB=new QGroupBox(i18n("Disk Cache"), this); + topL->addWidget(diskGB); + QGridLayout *diskL=new QGridLayout(diskGB, 3,2, 8,5); + diskL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + + d_iskMaxArtL=new QLabel(i18n("Max articles to keep:"), diskGB); + diskL->addWidget(d_iskMaxArtL, 2,0); + d_iskMaxArt=new KIntSpinBox(0, 99999, 1, 1, 10, diskGB); + diskL->addWidget(d_iskMaxArt, 2,1); + + d_iskMaxKBL=new QLabel(i18n("Max disk usage:"), diskGB); + diskL->addWidget(d_iskMaxKBL, 3,0); + d_iskMaxKB=new KIntSpinBox(0, 99999, 1, 1, 10, diskGB); + d_iskMaxKB->setSuffix(" KB"); + diskL->addWidget(d_iskMaxKB, 3,1); + + diskL->setColStretch(0,1); + 7 + + topL->addStretch(1); + + + // init + m_emMaxArt->setValue(d->memoryMaxArticles()); + m_emMaxKB->setValue(d->memoryMaxKBytes()); + d_iskMaxArt->setValue(d->diskMaxArticles()); + d_iskMaxKB->setValue(d->diskMaxKBytes()); +} + + +KNConfig::CacheWidget::~CacheWidget() +{ +} + + +void KNConfig::CacheWidget::apply() +{ + d_ata->m_emMaxArt=m_emMaxArt->value(); + d_ata->m_emMaxKB=m_emMaxKB->value(); + + d_ata->d_iskMaxArt=d_iskMaxArt->value(); + d_ata->d_iskMaxKB=d_iskMaxKB->value(); + + d_ata->setDirty(true); +} +*/ + + +//------------------------ +#include "knconfigwidgets.moc" diff --git a/knode/knconfigwidgets.h b/knode/knconfigwidgets.h new file mode 100644 index 000000000..109ea7dc7 --- /dev/null +++ b/knode/knconfigwidgets.h @@ -0,0 +1,730 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCONFIGWIDGETS_H +#define KNCONFIGWIDGETS_H + +#include <kdialogbase.h> +#include <kcmodule.h> + +#include "knwidgets.h" +#include "smtpaccountwidget_base.h" + +class QButtonGroup; +class QCheckBox; +class QGroupBox; +class QRadioButton; +class QTextEdit; + +class KScoringEditorWidget; +class KConfigBase; +class KLineEdit; +class KComboBox; +class KIntSpinBox; +class KSpellConfig; +class KURLCompletion; + +namespace Kpgp { + class Config; + class SecretKeyRequester; +} + +class KNAccountManager; +class KNArticleFilter; +class KNDisplayedHeader; +class KNFilterManager; +class KNNntpAccount; +class KNServerInfo; + +namespace KNConfig { + class Appearance; + class Cleanup; + class Identity; + class DisplayedHeaders; + class GroupCleanupWidget; + class PostNewsTechnical; + class ReadNewsGeneral; + class ReadNewsNavigation; + class PostNewsComposer; + class ReadNewsViewer; + class Scoring; +} + +namespace KNConfig { + +class KDE_EXPORT IdentityWidget : public KCModule { + + Q_OBJECT + + public: + IdentityWidget(Identity *d, QWidget *p=0, const char *n=0); + ~IdentityWidget(); + + void load(); + void save(); + + protected: + QLabel *f_ileName; + KLineEdit *n_ame, + *o_rga, + *e_mail, + *r_eplyTo, + *m_ailCopiesTo, + *s_ig; + QRadioButton *s_igFile, + *s_igEdit; + QCheckBox *s_igGenerator; + QPushButton *c_hooseBtn, + *e_ditBtn; + QTextEdit *s_igEditor; + QButtonGroup *b_uttonGroup; + Kpgp::SecretKeyRequester + *s_igningKey; + KURLCompletion *c_ompletion; + + Identity *d_ata; + + protected slots: + void slotSignatureType(int type); + void slotSignatureChoose(); + void slotSignatureEdit(); + void textFileNameChanged(const QString &); + +}; + + +class KDE_EXPORT NntpAccountListWidget : public KCModule { + + Q_OBJECT + + public: + NntpAccountListWidget(QWidget *p=0, const char *n=0); + ~NntpAccountListWidget(); + + void load(); + + protected: + class LBoxItem : public KNListBoxItem { + public: + LBoxItem(KNNntpAccount *a, const QString &t, QPixmap *p=0) + : KNListBoxItem(t, p) , account(a) {} + ~LBoxItem() {} + KNNntpAccount *account; + }; + + KNDialogListBox *l_box; + QPushButton *a_ddBtn, + *d_elBtn, + *e_ditBtn, + *s_ubBtn; + QPixmap p_ixmap; + QLabel *s_erverInfo, + *p_ortInfo; + + KNAccountManager *a_ccManager; + + + public slots: + void slotAddItem(KNNntpAccount *a); + void slotRemoveItem(KNNntpAccount *a); + void slotUpdateItem(KNNntpAccount *a); + + protected slots: + void slotSelectionChanged(); + void slotItemSelected(int id); + void slotAddBtnClicked(); + void slotDelBtnClicked(); + void slotEditBtnClicked(); + void slotSubBtnClicked(); + +}; + + +class KDE_EXPORT NntpAccountConfDialog : public KDialogBase { + + Q_OBJECT + + public: + NntpAccountConfDialog(KNNntpAccount* acc, QWidget *p=0, const char *n=0); + ~NntpAccountConfDialog(); + + protected: + KLineEdit *n_ame, + *s_erver, + *u_ser, + *p_ass, + *p_ort; + QLabel *u_serLabel, + *p_assLabel, + *c_heckIntervalLabel; + KIntSpinBox *h_old, + *t_imeout, + *c_heckInterval; + QCheckBox *f_etchDes, + *a_uth, + *u_seDiskCache, + *i_nterval; + KNConfig::IdentityWidget* i_dWidget; + + KNNntpAccount *a_ccount; + + protected slots: + void slotOk(); + void slotAuthChecked(bool b); + void slotIntervalChecked(bool b); + + private slots: + void slotPasswordChanged(); + + private: + GroupCleanupWidget *mCleanupWidget; +}; + + +class KDE_EXPORT SmtpAccountWidget : public SmtpAccountWidgetBase { + +Q_OBJECT + + public: + SmtpAccountWidget(QWidget *p=0, const char *n=0); + ~SmtpAccountWidget() {} + + virtual void load(); + virtual void save(); + + protected slots: + virtual void useExternalMailerToggled( bool b ); + virtual void loginToggled( bool b ); + void slotPasswordChanged(); + + protected: + KNServerInfo *mAccount; +}; + + +class KDE_EXPORT AppearanceWidget : public KCModule { + + Q_OBJECT + + public: + AppearanceWidget(QWidget *p=0, const char *n=0); + ~AppearanceWidget(); + + void load(); + void save(); + void defaults(); + + //=================================================================================== + // code taken from KMail, Copyright (C) 2000 Espen Sand, espen@kde.org + + class KDE_EXPORT ColorListItem : public QListBoxText { + + public: + ColorListItem( const QString &text, const QColor &color=Qt::black ); + ~ColorListItem(); + const QColor& color() { return mColor; } + void setColor( const QColor &color ) { mColor = color; } + + protected: + virtual void paint( QPainter * ); + virtual int height( const QListBox * ) const; + virtual int width( const QListBox * ) const; + + private: + QColor mColor; + }; + + //=================================================================================== + + class KDE_EXPORT FontListItem : public QListBoxText { + + public: + FontListItem( const QString &name, const QFont & ); + ~FontListItem(); + const QFont& font() { return f_ont; } + void setFont( const QFont &); + + protected: + virtual void paint( QPainter * ); + virtual int width( const QListBox * ) const; + + private: + QFont f_ont; + QString fontInfo; + }; + + //=================================================================================== + + KNDialogListBox *c_List, + *f_List; + QCheckBox *c_olorCB, + *f_ontCB; + QPushButton *c_olChngBtn, + *f_ntChngBtn; + + Appearance *d_ata; + + protected slots: + //colors + void slotColCheckBoxToggled(bool b); + void slotColItemSelected(QListBoxItem *); // show color dialog for the entry + void slotColChangeBtnClicked(); + void slotColSelectionChanged(); + + //fonts + void slotFontCheckBoxToggled(bool b); + void slotFontItemSelected(QListBoxItem *); // show font dialog for the entry + void slotFontChangeBtnClicked(); + void slotFontSelectionChanged(); + +}; + + +class KDE_EXPORT ReadNewsGeneralWidget : public KCModule { + + public: + ReadNewsGeneralWidget(ReadNewsGeneral *d, QWidget *p=0, const char *n=0); + ~ReadNewsGeneralWidget(); + + void load(); + void save(); + + protected: + QCheckBox *a_utoCB, + *m_arkCB, + *m_arkCrossCB, + *s_martScrollingCB, + *e_xpThrCB, + *d_efaultExpandCB, + *l_inesCB, + *u_nreadCB, + *s_coreCB; + KIntSpinBox *m_arkSecs, + *m_axFetch, + *c_ollCacheSize, + *a_rtCacheSize; + + ReadNewsGeneral *d_ata; + +}; + + +class KDE_EXPORT ReadNewsNavigationWidget : public KCModule { + + public: + ReadNewsNavigationWidget(ReadNewsNavigation *d, QWidget *p=0, const char *n=0); + ~ReadNewsNavigationWidget(); + + void load(); + void save(); + + protected: + QCheckBox *m_arkAllReadGoNextCB, + *m_arkThreadReadGoNextCB, + *m_arkThreadReadCloseThreadCB, + *i_gnoreThreadGoNextCB, + *i_gnoreThreadCloseThreadCB; + + ReadNewsNavigation *d_ata; + +}; + + +class KDE_EXPORT ReadNewsViewerWidget : public KCModule { + + Q_OBJECT + + public: + ReadNewsViewerWidget(ReadNewsViewer *d, QWidget *p=0, const char *n=0); + ~ReadNewsViewerWidget(); + + void load(); + void save(); + + protected: + QCheckBox *r_ewrapCB, + *r_emoveTrailingCB, + *s_igCB, + *o_penAttCB, + *a_ltAttCB, + *mShowRefBar, + *mAlwaysShowHTML; + KLineEdit *q_uoteCharacters; + + ReadNewsViewer *d_ata; + +}; + + +class KDE_EXPORT DisplayedHeadersWidget : public KCModule { + + Q_OBJECT + + public: + DisplayedHeadersWidget(DisplayedHeaders *d, QWidget *p=0, const char *n=0); + ~DisplayedHeadersWidget(); + + void load(); + void save(); + + protected: + + class HdrItem : public QListBoxText { + + public: + HdrItem( const QString &t, KNDisplayedHeader *h ) : QListBoxText(t), hdr(h) {} + ~HdrItem() {} + + KNDisplayedHeader *hdr; + }; + + HdrItem* generateItem(KNDisplayedHeader *); + + KNDialogListBox *l_box; + QPushButton *a_ddBtn, + *d_elBtn, + *e_ditBtn, + *u_pBtn, + *d_ownBtn; + bool s_ave; + + DisplayedHeaders *d_ata; + + protected slots: + void slotItemSelected(int); + void slotSelectionChanged(); + void slotAddBtnClicked(); + void slotDelBtnClicked(); + void slotEditBtnClicked(); + void slotUpBtnClicked(); + void slotDownBtnClicked(); + +}; + + +class KDE_EXPORT DisplayedHeaderConfDialog : public KDialogBase { + + Q_OBJECT + + public: + DisplayedHeaderConfDialog(KNDisplayedHeader *h, QWidget *p=0, char *n=0); + ~DisplayedHeaderConfDialog(); + + + protected: + KNDisplayedHeader *h_dr; + KComboBox *h_drC; + KLineEdit *n_ameE; + QCheckBox *n_ameCB[4], + *v_alueCB[4]; + + + protected slots: + void slotOk(); + void slotActivated(int); + void slotNameChanged(const QString&); +}; + + +class KDE_EXPORT ScoringWidget : public KCModule { + + Q_OBJECT + + public: + ScoringWidget(Scoring *d, QWidget *p=0, const char *n=0); + ~ScoringWidget(); + + void load(); + void save(); + + private: + KScoringEditorWidget *ksc; + KIntSpinBox *i_gnored, + *w_atched; + + Scoring *d_ata; +}; + + +class KDE_EXPORT FilterListWidget : public KCModule { + + Q_OBJECT + + public: + FilterListWidget(QWidget *p=0, const char *n=0); + ~FilterListWidget(); + + void load(); + void save(); + + void addItem(KNArticleFilter *f); + void removeItem(KNArticleFilter *f); + void updateItem(KNArticleFilter *f); + void addMenuItem(KNArticleFilter *f); + void removeMenuItem(KNArticleFilter *f); + QValueList<int> menuOrder(); + + + protected: + class LBoxItem : public KNListBoxItem { + public: + LBoxItem(KNArticleFilter *f, const QString &t, QPixmap *p=0) + : KNListBoxItem(t, p) , filter(f) {} + ~LBoxItem() {} + + KNArticleFilter *filter; + }; + + int findItem(QListBox *l, KNArticleFilter *f); + + KNDialogListBox *f_lb, + *m_lb; + + QPushButton *a_ddBtn, + *d_elBtn, + *e_ditBtn, + *c_opyBtn, + *u_pBtn, + *d_ownBtn, + *s_epAddBtn, + *s_epRemBtn; + + QPixmap a_ctive, + d_isabled; + + KNFilterManager *f_ilManager; + + + protected slots: + void slotAddBtnClicked(); + void slotDelBtnClicked(); + void slotEditBtnClicked(); + void slotCopyBtnClicked(); + void slotUpBtnClicked(); + void slotDownBtnClicked(); + void slotSepAddBtnClicked(); + void slotSepRemBtnClicked(); + void slotItemSelectedFilter(int); + void slotSelectionChangedFilter(); + void slotSelectionChangedMenu(); + +}; + + +class KDE_EXPORT PostNewsTechnicalWidget : public KCModule { + + Q_OBJECT + + public: + PostNewsTechnicalWidget(PostNewsTechnical *d, QWidget *p=0, const char *n=0); + ~PostNewsTechnicalWidget(); + + void load(); + void save(); + + protected: + QComboBox *c_harset, + *e_ncoding; + QCheckBox *u_seOwnCSCB, + *g_enMIdCB, + *i_ncUaCB; + KNDialogListBox *l_box; + QPushButton *a_ddBtn, + *d_elBtn, + *e_ditBtn; + KLineEdit *h_ost; + QLabel *h_ostL; + + PostNewsTechnical *d_ata; + + protected slots: + void slotGenMIdCBToggled(bool b); + void slotSelectionChanged(); + void slotItemSelected(int id); + void slotAddBtnClicked(); + void slotDelBtnClicked(); + void slotEditBtnClicked(); + +}; + + +class KDE_EXPORT XHeaderConfDialog : public KDialogBase { + + public: + XHeaderConfDialog(const QString &h=QString::null, QWidget *p=0, const char *n=0); + ~XHeaderConfDialog(); + + QString result(); + + + protected: + KLineEdit *n_ame, + *v_alue; + +}; + + +class KDE_EXPORT PostNewsComposerWidget : public KCModule { + + Q_OBJECT + + public: + PostNewsComposerWidget(PostNewsComposer *d, QWidget *p=0, const char *n=0); + ~PostNewsComposerWidget(); + + void load(); + void save(); + + protected: + KIntSpinBox *m_axLen; + QCheckBox *w_ordWrapCB, + *o_wnSigCB, + *r_ewrapCB, + *a_uthSigCB, + *c_ursorOnTopCB, + *e_xternCB; + KLineEdit *i_ntro, + *e_ditor; + + PostNewsComposer *d_ata; + + protected slots: + void slotChooseEditor(); + +}; + + +class KDE_EXPORT PostNewsSpellingWidget : public KCModule { + + public: + PostNewsSpellingWidget(QWidget *p=0, const char *n=0); + ~PostNewsSpellingWidget(); + + void save(); + + protected: + KSpellConfig *c_onf; + +}; + + + +class KDE_EXPORT PrivacyWidget : public KCModule { + + Q_OBJECT + + public: + PrivacyWidget(QWidget *p=0, const char *n=0); + ~PrivacyWidget(); + + void save(); + + protected: + Kpgp::Config *c_onf; +}; + + + +//BEGIN: Cleanup configuration ----------------------------------------------- + +/** Configuration widget for group expireration */ +class KDE_EXPORT GroupCleanupWidget : public QWidget { + + Q_OBJECT + + public: + GroupCleanupWidget( Cleanup *data, QWidget *parent = 0, const char *name = 0 ); + + void load(); + void save(); + + signals: + void changed(); + + private: + QCheckBox *mDefault, *mExpEnabled, *mExpUnavailable, *mPreserveThreads; + KIntSpinBox *mExpDays, *mExpReadDays, *mExpUnreadDays; + QGroupBox *mExpGroup; + Cleanup *mData; + + private slots: + void slotDefaultToggled( bool state ); + void expDaysChanged( int value ); + void expReadDaysChanged( int value ); + void expUnreadDaysChanged( int value ); +}; + + +/** Global cleanup configuration widget */ +class KDE_EXPORT CleanupWidget : public KCModule { + + Q_OBJECT + + public: + CleanupWidget(QWidget *p=0, const char *n=0); + ~CleanupWidget(); + + void load(); + void save(); + + protected: + QCheckBox *f_olderCB; + KIntSpinBox *f_olderDays; + QLabel *f_olderDaysL; + + Cleanup *d_ata; + + + protected slots: + void slotFolderCBtoggled(bool b); + void slotFolderDaysChanged(int value); + + private: + GroupCleanupWidget *mGroupCleanup; + +}; + +//END: Cleanup configuration ------------------------------------------------- + + +/*class CacheWidget : public KCModule { + + + Q_OBJECT + + public: + CacheWidget(Cache *d, QWidget *p=0, const char *n=0); + ~CacheWidget(); + + void apply(); + + + protected: + KIntSpinBox *m_emMaxArt, + *m_emMaxKB, + *d_iskMaxArt, + *d_iskMaxKB; + + QLabel *d_iskMaxArtL, + *d_iskMaxKBL; + + Cache *d_ata; + + +}; */ + + +} //KNConfig + +#endif //KNCONFIGWIDGETS_H diff --git a/knode/knconvert.cpp b/knode/knconvert.cpp new file mode 100644 index 000000000..40461139b --- /dev/null +++ b/knode/knconvert.cpp @@ -0,0 +1,447 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qwidgetstack.h> +#include <qlabel.h> +#include <qcheckbox.h> + +#include <klocale.h> +#include <kfiledialog.h> +#include <kseparator.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <klineedit.h> +#include <kprocess.h> +#include <kapplication.h> +#include <kpushbutton.h> + +#include <kmime_util.h> + +#include "knconvert.h" +#include "resource.h" + + +bool KNConvert::needToConvert(const QString &oldVersion) +{ + bool ret=( + (oldVersion.left(3)=="0.3") || + (oldVersion.left(3)=="0.4") + ); + + return ret; +} + + +KNConvert::KNConvert(const QString &version) + : QDialog(0,0,true), l_ogList(0), c_onversionDone(false), v_ersion(version) +{ + setCaption(kapp->makeStdCaption(i18n("Conversion"))); + QVBoxLayout *topL=new QVBoxLayout(this, 5,5); + s_tack=new QWidgetStack(this); + topL->addWidget(s_tack, 1); + topL->addWidget(new KSeparator(this)); + + QHBoxLayout *btnL=new QHBoxLayout(topL, 5); + s_tartBtn=new QPushButton(i18n("Start Conversion..."), this); + s_tartBtn->setDefault(true); + btnL->addStretch(1); + btnL->addWidget(s_tartBtn); + c_ancelBtn=new KPushButton(KStdGuiItem::cancel(), this); + btnL->addWidget(c_ancelBtn); + + connect(s_tartBtn, SIGNAL(clicked()), this, SLOT(slotStart())); + connect(c_ancelBtn, SIGNAL(clicked()), this, SLOT(reject())); + + w_1=new QWidget(s_tack); + s_tack->addWidget(w_1, 1); + QGridLayout *w1L=new QGridLayout(w_1, 5,3, 5,5); + + QLabel *l1=new QLabel(i18n( +"<b>Congratulations, you have upgraded to KNode version %1.</b><br>\ +Unfortunately this version uses a different format for some data-files, so \ +in order to keep your existing data it is necessary to convert it first. This is \ +now done automatically by KNode. If you want to, a backup of your existing data \ +will be created before the conversion starts.").arg(KNODE_VERSION), w_1); + w1L->addMultiCellWidget(l1, 0,0, 0,2); + + c_reateBkup=new QCheckBox(i18n("Create backup of old data"), w_1); + w1L->addMultiCellWidget(c_reateBkup, 2,2, 0,2); + connect(c_reateBkup, SIGNAL(toggled(bool)), this, SLOT(slotCreateBkupToggled(bool))); + + b_ackupPathLabel=new QLabel(i18n("Save backup in:"), w_1); + w1L->addWidget(b_ackupPathLabel, 3,0); + + b_ackupPath=new KLineEdit(QDir::homeDirPath()+QString("/knodedata-")+v_ersion+".tar.gz", w_1); + w1L->addWidget(b_ackupPath, 3,1); + + b_rowseBtn= new QPushButton(i18n("Browse..."), w_1); + connect(b_rowseBtn, SIGNAL(clicked()), this, SLOT(slotBrowse())); + w1L->addWidget(b_rowseBtn, 3,2); + w1L->setColStretch(1,1); + w1L->addRowSpacing(1,15); + w1L->setRowStretch(4,1); + w1L->addRowSpacing(4,15); + + w_2=new QLabel(s_tack); + w_2->setText(i18n("<b>Converting, please wait...</b>")); + w_2->setAlignment(AlignCenter); + s_tack->addWidget(w_2, 2); + + w_3=new QWidget(s_tack); + s_tack->addWidget(w_3, 3); + QVBoxLayout *w3L=new QVBoxLayout(w_3, 5,5); + + r_esultLabel=new QLabel(w_3); + w3L->addWidget(r_esultLabel); + QLabel *l2=new QLabel(i18n("Processed tasks:"), w_3); + l_ogList=new QListBox(w_3); + w3L->addSpacing(15); + w3L->addWidget(l2); + w3L->addWidget(l_ogList, 1); + + s_tack->raiseWidget(w_1); + slotCreateBkupToggled(false); +} + + +KNConvert::~KNConvert() +{ + for ( QValueList<Converter*>::Iterator it = mConverters.begin(); it != mConverters.end(); ++it ) + delete (*it); +} + + +void KNConvert::convert() +{ + int errors=0; + for ( QValueList<Converter*>::Iterator it = mConverters.begin(); it != mConverters.end(); ++it ) + if( !(*it)->doConvert() ) + errors++; + + if(errors>0) + r_esultLabel->setText(i18n( +"<b>Some errors occurred during the conversion.</b>\ +<br>You should now examine the log to find out what went wrong.")); + else + r_esultLabel->setText(i18n( +"<b>The conversion was successful.</b>\ +<br>Have a lot of fun with this new version of KNode. ;-)")); + + s_tartBtn->setText(i18n("Start KNode")); + s_tartBtn->setEnabled(true); + c_ancelBtn->setEnabled(true); + l_ogList->insertStringList(l_og); + s_tack->raiseWidget(w_3); + + c_onversionDone=true; +} + + +void KNConvert::slotStart() +{ + if(c_onversionDone) { + accept(); + return; + } + + s_tartBtn->setEnabled(false); + c_ancelBtn->setEnabled(false); + s_tack->raiseWidget(w_2); + + if(v_ersion.left(3)=="0.3" || v_ersion.left(7)=="0.4beta") { + //Version 0.4 + mConverters.append( new Converter04( &l_og ) ); + } + + //create backup of old data using "tar" + if(c_reateBkup->isChecked()) { + if(b_ackupPath->text().isEmpty()) { + KMessageBox::error(this, i18n("Please select a valid backup path.")); + return; + } + + QString dataDir=locateLocal("data","knode/"); + t_ar=new KProcess; + *t_ar << "tar"; + *t_ar << "-cz" << dataDir + << "-f" << b_ackupPath->text(); + connect(t_ar, SIGNAL(processExited(KProcess*)), this, SLOT(slotTarExited(KProcess*))); + if(!t_ar->start()) { + delete t_ar; + t_ar = 0; + slotTarExited(0); + } + } + else + convert(); //convert files without backup +} + + +void KNConvert::slotCreateBkupToggled(bool b) +{ + b_ackupPathLabel->setEnabled(b); + b_ackupPath->setEnabled(b); + b_rowseBtn->setEnabled(b); +} + + +void KNConvert::slotBrowse() +{ + QString newPath=KFileDialog::getSaveFileName(b_ackupPath->text()); + + if(!newPath.isEmpty()) + b_ackupPath->setText(newPath); +} + + +void KNConvert::slotTarExited(KProcess *proc) +{ + bool success=true; + + if(!proc || !proc->normalExit() || proc->exitStatus()!=0) { + success=false; + if(KMessageBox::Cancel==KMessageBox::warningContinueCancel(this, i18n("<b>The backup failed</b>; do you want to continue anyway?"))) { + + delete t_ar; + t_ar = 0; + reject(); + return; + } + } + + delete t_ar; + t_ar = 0; + if(success) + l_og.append(i18n("created backup of the old data-files in %1").arg(b_ackupPath->text())); + else + l_og.append(i18n("backup failed.")); + + // now we actually convert the files + convert(); +} + + + +//============================================================================================ + + + +bool KNConvert::Converter04::doConvert() +{ + QString dir=locateLocal("data","knode/")+"folders/"; + int num; + bool error=false; + + //Drafts + if(QFile::exists(dir+"folder1.idx")) { + num=convertFolder(dir+"folder1", dir+"drafts_1"); + if(num==-1) { + error=true; + l_og->append(i18n("conversion of folder \"Drafts\" to version 0.4 failed.")); + } + else { + l_og->append(i18n("converted folder \"Drafts\" to version 0.4")); + } + } + else + l_og->append(i18n("nothing to be done for folder \"Drafts\"")); + + //Outbox + if(QFile::exists(dir+"folder2.idx")) { + num=convertFolder(dir+"folder2", dir+"outbox_2"); + if(num==-1) { + error=true; + l_og->append(i18n("conversion of folder \"Outbox\" to version 0.4 failed.")); + } + else { + l_og->append(i18n("converted folder \"Outbox\" to version 0.4")); + } + } + else + l_og->append(i18n("nothing to be done for folder \"Outbox\"")); + + //Sent + if(QFile::exists(dir+"folder3.idx")) { + num=convertFolder(dir+"folder3", dir+"sent_3"); + if(num==-1) { + error=true; + l_og->append(i18n("conversion of folder \"Sent\" to version 0.4 failed.")); + } + else { + l_og->append(i18n("converted folder \"Sent\" to version 0.4")); + } + } + else + l_og->append(i18n("nothing to be done for folder \"Sent\"")); + + //remove old info-files + QFile::remove(dir+"standard.info"); + QFile::remove(dir+".standard.info"); + return (!error); +} + + +int KNConvert::Converter04::convertFolder(QString srcPrefix, QString dstPrefix) +{ + QFile srcMBox(srcPrefix+".mbox"), + srcIdx(srcPrefix+".idx"), + dstMBox(dstPrefix+".mbox"), + dstIdx(dstPrefix+".idx"); + QTextStream ts(&dstMBox); + ts.setEncoding(QTextStream::Latin1); + + OldFolderIndex oldIdx; + NewFolderIndex newIdx; + int lastId=0; + bool filesOpen; + + //open files + filesOpen=srcMBox.open(IO_ReadOnly); + filesOpen=filesOpen && srcIdx.open(IO_ReadOnly); + + if(dstIdx.exists() && dstIdx.size()>0) { //we are converting from 0.4beta* + if( (filesOpen=filesOpen && dstIdx.open(IO_ReadOnly)) ) { + dstIdx.at( dstIdx.size()-sizeof(NewFolderIndex) ); //set filepointer to last entry + dstIdx.readBlock( (char*)(&newIdx), sizeof(NewFolderIndex) ); + lastId=newIdx.id; + dstIdx.close(); + } + } + + filesOpen=filesOpen && dstMBox.open(IO_WriteOnly | IO_Append); + filesOpen=filesOpen && dstIdx.open(IO_WriteOnly | IO_Append); + + if(!filesOpen) { + srcMBox.close(); + srcIdx.close(); + dstMBox.close(); + dstIdx.close(); + return -1; + } + + //conversion starts here + while(!srcIdx.atEnd()) { + + //read index data + srcIdx.readBlock( (char*)(&oldIdx), sizeof(OldFolderIndex)); + newIdx.id=++lastId; + newIdx.sId=oldIdx.sId; + newIdx.ti=oldIdx.ti; + + switch(oldIdx.status) { + case 0: //AStoPost + newIdx.flags[0]=false; //doMail() + newIdx.flags[1]=false; //mailed() + newIdx.flags[2]=true; //doPost() + newIdx.flags[3]=false; //posted() + newIdx.flags[4]=false; //canceled() + newIdx.flags[5]=false; //editDisabled() + break; + + case 1: //AStoMail + newIdx.flags[0]=true; //doMail() + newIdx.flags[1]=false; //mailed() + newIdx.flags[2]=false; //doPost() + newIdx.flags[3]=false; //posted() + newIdx.flags[4]=false; //canceled() + newIdx.flags[5]=false; //editDisabled() + break; + + case 2: //ASposted + newIdx.flags[0]=false; //doMail() + newIdx.flags[1]=false; //mailed() + newIdx.flags[2]=true; //doPost() + newIdx.flags[3]=true; //posted() + newIdx.flags[4]=false; //canceled() + newIdx.flags[5]=true; //editDisabled() + break; + + case 3: //ASmailed + newIdx.flags[0]=true; //doMail() + newIdx.flags[1]=true; //mailed() + newIdx.flags[2]=false; //doPost() + newIdx.flags[3]=false; //posted() + newIdx.flags[4]=false; //canceled() + newIdx.flags[5]=true; //editDisabled() + break; + + case 6: //AScanceled + newIdx.flags[0]=false; //doMail() + newIdx.flags[1]=false; //mailed() + newIdx.flags[2]=true; //doPost() + newIdx.flags[3]=true; //posted() + newIdx.flags[4]=true; //canceled() + newIdx.flags[5]=true; //editDisabled() + break; + + default: //what the .. + newIdx.flags[0]=false; //doMail() + newIdx.flags[1]=false; //mailed() + newIdx.flags[2]=false; //doPost() + newIdx.flags[3]=false; //posted() + newIdx.flags[4]=false; //canceled() + newIdx.flags[5]=false; //editDisabled() + break; + } + + + //read mbox-data + unsigned int size=oldIdx.eo-oldIdx.so; + QCString buff(size+10); + srcMBox.at(oldIdx.so); + int readBytes=srcMBox.readBlock(buff.data(), size); + buff.at(readBytes)='\0'; //terminate string; + + //remove "X-KNode-Overview" + int pos=buff.find('\n'); + if(pos>-1) + buff.remove(0, pos+1); + + //write mbox-data + ts << "From aaa@aaa Mon Jan 01 00:00:00 1997\n"; + newIdx.so=dstMBox.at(); //save start-offset + ts << "X-KNode-Overview: "; + ts << KMime::extractHeader(buff, "Subject") << '\t'; + ts << KMime::extractHeader(buff, "Newsgroups") << '\t'; + ts << KMime::extractHeader(buff, "To") << '\t'; + ts << KMime::extractHeader(buff, "Lines") << '\n'; + ts << buff; + newIdx.eo=dstMBox.at(); //save end-offset + ts << '\n'; + + //write index-data + dstIdx.writeBlock((char*)(&newIdx), sizeof(NewFolderIndex)); + } + + //close/remove files and return number of articles in the new folder + srcMBox.remove(); + srcIdx.remove(); + dstMBox.close(); + dstIdx.close(); + return ( dstIdx.size()/sizeof(NewFolderIndex) ); +} + + + + +//----------------------------- +#include "knconvert.moc" + + + + + + + diff --git a/knode/knconvert.h b/knode/knconvert.h new file mode 100644 index 000000000..c75e0da00 --- /dev/null +++ b/knode/knconvert.h @@ -0,0 +1,126 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNCONVERT_H +#define KNCONVERT_H + +#include <time.h> + +#include <qdialog.h> + +#include <qglobal.h> +#include <qvaluelist.h> +#include <kdepimmacros.h> + +class QListBox; +class QLabel; +class QWidgetStack; +class QCheckBox; + +class KLineEdit; +class KProcess; + + +class KDE_EXPORT KNConvert : public QDialog { + + Q_OBJECT + + public: + static bool needToConvert(const QString &oldVersion); + + KNConvert(const QString &version); + ~KNConvert(); + bool conversionDone()const { return c_onversionDone; } + + + protected: + + //------------ <Converter-classes> --------------- + + //Base class for all converters + class Converter { + + public: + Converter(QStringList *log) { l_og=log; } + virtual ~Converter() {} + virtual bool doConvert()=0; + + protected: + QStringList *l_og; + }; + + + //Converter for version 0.4 + class Converter04 : public Converter { + + public: + Converter04(QStringList *log) : Converter(log) {} + ~Converter04() {} + bool doConvert(); + + protected: + int convertFolder(QString srcPrefix, QString dstPrefix); + + struct OldFolderIndex { + int id, + status, + so, + eo, + sId; + time_t ti; + }; + + struct NewFolderIndex { + int id, + so, + eo, + sId; + time_t ti; + bool flags[6]; + }; + }; + + //------------ </Converter-classes> -------------- + + QWidgetStack *s_tack; + QWidget *w_1, + *w_3; + QCheckBox *c_reateBkup; + QLabel *b_ackupPathLabel, + *w_2, + *r_esultLabel; + KLineEdit *b_ackupPath; + QPushButton *b_rowseBtn, + *s_tartBtn, + *c_ancelBtn; + QListBox *l_ogList; + + QValueList<Converter*> mConverters; + QStringList l_og; + bool c_onversionDone; + QString v_ersion; + KProcess *t_ar; + + void convert(); + + protected slots: + void slotStart(); + void slotCreateBkupToggled(bool b); + void slotBrowse(); + void slotTarExited(KProcess *proc); + +}; + + +#endif // KNCONVERT_H diff --git a/knode/kndisplayedheader.cpp b/knode/kndisplayedheader.cpp new file mode 100644 index 000000000..589bd0027 --- /dev/null +++ b/knode/kndisplayedheader.cpp @@ -0,0 +1,175 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <klocale.h> + +#include "kndisplayedheader.h" + + +// some standard headers +static const char *predef[] = { "Approved","Content-Transfer-Encoding","Content-Type","Control","Date","Distribution", + "Expires","Followup-To","From","Lines","Mail-Copies-To","Message-ID","Mime-Version","NNTP-Posting-Host", + "Newsgroups","Organization","Path","References","Reply-To", "Sender","Subject", + "Supersedes","To", "User-Agent","X-Mailer","X-Newsreader","X-No-Archive","XRef",0 }; + +// default display names KNode uses +static const char *disp[] = { "Groups", 0 }; + +void dummyHeader() +{ + i18n("collection of article headers","Approved"); + i18n("collection of article headers","Content-Transfer-Encoding"); + i18n("collection of article headers","Content-Type"); + i18n("collection of article headers","Control"); + i18n("collection of article headers","Date"); + i18n("collection of article headers","Distribution"); + i18n("collection of article headers","Expires"); + i18n("collection of article headers","Followup-To"); + i18n("collection of article headers","From"); + i18n("collection of article headers","Lines"); + i18n("collection of article headers","Mail-Copies-To"); + i18n("collection of article headers","Message-ID"); + i18n("collection of article headers","Mime-Version"); + i18n("collection of article headers","NNTP-Posting-Host"); + i18n("collection of article headers","Newsgroups"); + i18n("collection of article headers","Organization"); + i18n("collection of article headers","Path"); + i18n("collection of article headers","References"); + i18n("collection of article headers","Reply-To"); + i18n("collection of article headers","Sender"); + i18n("collection of article headers","Subject"); + i18n("collection of article headers","Supersedes"); + i18n("collection of article headers","To"); + i18n("collection of article headers","User-Agent"); + i18n("collection of article headers","X-Mailer"); + i18n("collection of article headers","X-Newsreader"); + i18n("collection of article headers","X-No-Archive"); + i18n("collection of article headers","XRef"); + + i18n("collection of article headers","Groups"); +} + + +//============================================================================================================= + + +KNDisplayedHeader::KNDisplayedHeader() + : t_ranslateName(true) +{ + f_lags.fill(false, 8); + f_lags[1] = true; // header name bold by default +} + + +KNDisplayedHeader::~KNDisplayedHeader() +{ +} + + +// some common headers +const char** KNDisplayedHeader::predefs() +{ + return predef; +} + + +// *tries* to translate the name +QString KNDisplayedHeader::translatedName() +{ + if (t_ranslateName) { + // major hack alert !!! + if (!n_ame.isEmpty()) { + if (i18n("collection of article headers",n_ame.local8Bit())!=n_ame.local8Bit().data()) // try to guess if this english or not + return i18n("collection of article headers",n_ame.local8Bit()); + else + return n_ame; + } else + return QString::null; + } else + return n_ame; +} + + +// *tries* to retranslate the name to english +void KNDisplayedHeader::setTranslatedName(const QString &s) +{ + bool retranslated = false; + for (const char **c=predef;(*c)!=0;c++) { // ok, first the standard header names + if (s==i18n("collection of article headers",*c)) { + n_ame = QString::fromLatin1(*c); + retranslated = true; + break; + } + } + + if (!retranslated) { + for (const char **c=disp;(*c)!=0;c++) // now our standard display names + if (s==i18n("collection of article headers",*c)) { + n_ame = QString::fromLatin1(*c); + retranslated = true; + break; + } + } + + if (!retranslated) { // ok, we give up and store the maybe non-english string + n_ame = s; + t_ranslateName = false; // and don't try to translate it, so a german user *can* use the original english name + } else + t_ranslateName = true; +} + + +void KNDisplayedHeader::createTags() +{ + const char *tokens[] = { "<big>","</big>","<b>","</b>", + "<i>","</i>","<u>","</u>" }; + + for(int i=0; i<4; i++) t_ags[i]=QString::null; + + if(f_lags.at(0)) { // <big> + t_ags[0]=tokens[0]; + t_ags[1]=tokens[1]; + } + if(f_lags.at(4)) { + t_ags[2]=tokens[0]; + t_ags[3]=tokens[1]; + } + + if(f_lags.at(1)) { // <b> + t_ags[0]+=(tokens[2]); + t_ags[1].prepend(tokens[3]); + } + if(f_lags.at(5)) { + t_ags[2]+=tokens[2]; + t_ags[3].prepend(tokens[3]); + } + + if(f_lags.at(2)) { // <i> + t_ags[0]+=tokens[4]; + t_ags[1].prepend(tokens[5]); + } + if(f_lags.at(6)) { + t_ags[2]+=tokens[4]; + t_ags[3].prepend(tokens[5]); + } + + if(f_lags.at(3)) { // <u> + t_ags[0]+=tokens[6]; + t_ags[1].prepend(tokens[7]); + } + if(f_lags.at(7)) { + t_ags[2]+=tokens[6]; + t_ags[3].prepend(tokens[7]); + } +} diff --git a/knode/kndisplayedheader.h b/knode/kndisplayedheader.h new file mode 100644 index 000000000..84f260e58 --- /dev/null +++ b/knode/kndisplayedheader.h @@ -0,0 +1,63 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNDISPLAYEDHEADER_H +#define KNDISPLAYEDHEADER_H + +#include <qbitarray.h> + + +class KNDisplayedHeader { + + public: + KNDisplayedHeader(); + ~KNDisplayedHeader(); + + //some common headers + static const char** predefs(); + + //name + const QString& name() { return n_ame; } + void setName(const QString &s) { n_ame = s; } + bool hasName() const { return !n_ame.isEmpty(); } + + //translated name + QString translatedName(); // *tries* to translate the name + void setTranslatedName(const QString &s); // *tries* to retranslate the name to english + void setTranslateName(bool b) { t_ranslateName=b; } + bool translateName() const { return t_ranslateName; } + + //header + const QString& header() { return h_eader; } + void setHeader(const QString &s) { h_eader = s; } + + //flags + bool flag(int i) { return f_lags.at(i); } + void setFlag(int i, bool b) { f_lags.setBit(i, b); } + + //HTML-tags + void createTags(); + const QString& nameOpenTag() { return t_ags[0]; } + const QString& nameCloseTag() { return t_ags[1]; } + const QString& headerOpenTag() { return t_ags[2]; } + const QString& headerCloseTag() { return t_ags[3]; } + + protected: + bool t_ranslateName; + QString n_ame, h_eader, t_ags[4]; + QBitArray f_lags; + +}; + +#endif diff --git a/knode/knewsservice.protocol b/knode/knewsservice.protocol new file mode 100644 index 000000000..6adc5e958 --- /dev/null +++ b/knode/knewsservice.protocol @@ -0,0 +1,13 @@ +[Protocol] +exec=knode %u +protocol=news +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false +DocPath=kioslave/news.html +Icon=knode diff --git a/knode/knfilterconfigwidget.cpp b/knode/knfilterconfigwidget.cpp new file mode 100644 index 000000000..2a7a8cc47 --- /dev/null +++ b/knode/knfilterconfigwidget.cpp @@ -0,0 +1,93 @@ +/* + knfilterconfigwidget.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qlabel.h> + +#include <klocale.h> + +#include "knstringfilter.h" +#include "knstatusfilter.h" +#include "knrangefilter.h" +#include "knfilterconfigwidget.h" + + +KNFilterConfigWidget::KNFilterConfigWidget(QWidget *parent, const char *name ) : + QTabWidget(parent,name) +{ + QWidget *sf, *idW, *add; + sf=new QWidget(this); + QVBoxLayout *sfL=new QVBoxLayout(sf, 8,5); + subject=new KNStringFilterWidget(i18n("Subject"), sf); + sfL->addWidget(subject); + from=new KNStringFilterWidget(i18n("From"), sf); + sfL->addWidget(from); + QLabel *l = new QLabel(i18n("The following placeholders are supported:\n%MYNAME=own name, %MYEMAIL=own email address"),sf); + sfL->addWidget(l); + sfL->addStretch(1); + addTab(sf, i18n("Subject && &From")); + + idW=new QWidget(this); + QVBoxLayout *idL=new QVBoxLayout(idW, 8,5); + messageId=new KNStringFilterWidget(i18n("Message-ID"), idW); + idL->addWidget(messageId); + references=new KNStringFilterWidget(i18n("References"), idW); + idL->addWidget(references); + idL->addStretch(1); + addTab(idW, i18n("M&essage-IDs")); + + status=new KNStatusFilterWidget(this); + addTab(status, i18n("&Status")); + + add=new QWidget(this); + QVBoxLayout *addL=new QVBoxLayout(add, 8,5); + score=new KNRangeFilterWidget(i18n("Score"), -99999, 99999, add); + addL->addWidget(score); + age=new KNRangeFilterWidget(i18n("Age"), 0, 999, add, i18n(" days")); + addL->addWidget(age); + lines=new KNRangeFilterWidget(i18n("Lines"), 0, 99999, add); + addL->addWidget(lines); + addL->addStretch(1); + addTab(add, i18n("&Additional")); +} + + +KNFilterConfigWidget::~KNFilterConfigWidget() +{ +} + + +void KNFilterConfigWidget::reset() +{ + from->clear(); + subject->clear(); + messageId->clear(); + references->clear(); + age->clear(); + lines->clear(); + score->clear(); + status->clear(); +} + + +void KNFilterConfigWidget::setStartFocus() +{ + subject->setStartFocus(); +} + +// ----------------------------------------------------------------------------- + +#include "knfilterconfigwidget.moc" diff --git a/knode/knfilterconfigwidget.h b/knode/knfilterconfigwidget.h new file mode 100644 index 000000000..7cafa18db --- /dev/null +++ b/knode/knfilterconfigwidget.h @@ -0,0 +1,53 @@ +/* + knfilterconfigwidget.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNFILTERCONFIGWIDGET_H +#define KNFILTERCONFIGWIDGET_H + +#include <qtabwidget.h> + +class KNStatusFilterWidget; +class KNStringFilterWidget; +class KNRangeFilterWidget; + + +class KNFilterConfigWidget : public QTabWidget { + + Q_OBJECT + + friend class KNFilterDialog; + friend class KNSearchDialog; + + public: + KNFilterConfigWidget(QWidget *parent=0, const char *name=0); + ~KNFilterConfigWidget(); + + void reset(); + + void setStartFocus(); // useablity hack for the search dialog + + protected: + KNStatusFilterWidget *status; + KNStringFilterWidget *subject; + KNStringFilterWidget *from; + KNStringFilterWidget *messageId; + KNStringFilterWidget *references; + KNRangeFilterWidget *age; + KNRangeFilterWidget *lines; + KNRangeFilterWidget *score; +}; + +#endif diff --git a/knode/knfilterdialog.cpp b/knode/knfilterdialog.cpp new file mode 100644 index 000000000..e363e6ad0 --- /dev/null +++ b/knode/knfilterdialog.cpp @@ -0,0 +1,125 @@ +/* + knfilterdialog.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlabel.h> +#include <qcheckbox.h> +#include <qlayout.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <klineedit.h> + +#include "knglobals.h" +#include "knfiltermanager.h" +#include "knfilterconfigwidget.h" +#include "knarticlefilter.h" +#include "utilities.h" +#include "knfilterdialog.h" + + +KNFilterDialog::KNFilterDialog(KNArticleFilter *f, QWidget *parent, const char *name) + : KDialogBase(Plain, (f->id()==-1)? i18n("New Filter"):i18n("Properties of %1").arg(f->name()), + Ok|Cancel|Help, Ok, parent, name), + fltr(f) +{ + QFrame* page=plainPage(); + + QGroupBox *gb=new QGroupBox(page); + fname=new KLineEdit(gb); + QLabel *l1=new QLabel(fname, i18n("Na&me:"), gb); + apon=new QComboBox(gb); + apon->insertItem(i18n("Single Articles")); + apon->insertItem(i18n("Whole Threads")); + QLabel *l2=new QLabel(apon, i18n("Apply o&n:"), gb); + enabled=new QCheckBox(i18n("Sho&w in menu"), gb); + + fw=new KNFilterConfigWidget(page); + + QGridLayout *gbL=new QGridLayout(gb, 2,4,8,5); + gbL->addWidget(l1, 0,0); + gbL->addMultiCellWidget(fname, 0,0,1,3); + gbL->addWidget(enabled, 1,0); + gbL->addWidget(l2, 1,2); + gbL->addWidget(apon, 1,3); + gbL->setColStretch(1,1); + + QVBoxLayout *topL=new QVBoxLayout(page,0,5); + + topL->addWidget(gb); + topL->addWidget(fw,1); + + enabled->setChecked(f->isEnabled()); + apon->setCurrentItem((int) f->applyOn()); + fname->setText(f->translatedName()); + + fw->status->setFilter(f->status); + fw->lines->setFilter(f->lines); + fw->age->setFilter(f->age); + fw->score->setFilter(f->score); + fw->subject->setFilter(f->subject); + fw->from->setFilter(f->from); + fw->messageId->setFilter(f->messageId); + fw->references->setFilter(f->references); + + setFixedHeight(sizeHint().height()); + KNHelper::restoreWindowSize("filterDLG", this, sizeHint()); + + setHelp("anc-using-filters"); + connect( fname, SIGNAL( textChanged ( const QString & )), this, SLOT( slotTextChanged( const QString & ))); + slotTextChanged( fname->text() ); +} + + + +KNFilterDialog::~KNFilterDialog() +{ + KNHelper::saveWindowSize("filterDLG", size()); +} + +void KNFilterDialog::slotTextChanged( const QString &_text ) +{ + enableButtonOK( !_text.isEmpty() ); +} + +void KNFilterDialog::slotOk() +{ + if (fname->text().isEmpty()) + KMessageBox::sorry(this, i18n("Please provide a name for this filter.")); + else + if (!knGlobals.filterManager()->newNameIsOK(fltr,fname->text())) + KMessageBox::sorry(this, i18n("A filter with this name exists already.\nPlease choose a different name.")); + else { + fltr->setTranslatedName(fname->text()); + fltr->setEnabled(enabled->isChecked()); + fltr->status=fw->status->filter(); + fltr->score=fw->score->filter(); + fltr->age=fw->age->filter(); + fltr->lines=fw->lines->filter(); + fltr->subject=fw->subject->filter(); + fltr->from=fw->from->filter(); + fltr->messageId=fw->messageId->filter(); + fltr->references=fw->references->filter(); + fltr->setApplyOn(apon->currentItem()); + + accept(); + } +} + + + +//-------------------------------- + +#include "knfilterdialog.moc" diff --git a/knode/knfilterdialog.h b/knode/knfilterdialog.h new file mode 100644 index 000000000..a7dcbfebf --- /dev/null +++ b/knode/knfilterdialog.h @@ -0,0 +1,54 @@ +/* + knfilterdialog.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNFILTERDIALOG_H +#define KNFILTERDIALOG_H + +#include <kdialogbase.h> + +class KNFilterConfigWidget; +class KNArticleFilter; +class KLineEdit; +class QComboBox; +class QCheckBox; + + +class KNFilterDialog : public KDialogBase { + + Q_OBJECT + + friend class KNFilterManager; + + public: + KNFilterDialog(KNArticleFilter *f=0, QWidget *parent=0, const char *name=0); + ~KNFilterDialog(); + + KNArticleFilter* filter() { return fltr; } + + protected: + KNFilterConfigWidget *fw; + KLineEdit *fname; + QComboBox *apon; + QCheckBox *enabled; + + KNArticleFilter *fltr; + + protected slots: + void slotOk(); + void slotTextChanged( const QString & ); +}; + +#endif diff --git a/knode/knfiltermanager.cpp b/knode/knfiltermanager.cpp new file mode 100644 index 000000000..ce8469512 --- /dev/null +++ b/knode/knfiltermanager.cpp @@ -0,0 +1,396 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <stdlib.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kpopupmenu.h> +#include <kstandarddirs.h> +#include <ksimpleconfig.h> + +#include "utilities.h" +#include "knglobals.h" +#include "knarticlefilter.h" +#include "knfilterdialog.h" +#include "knfiltermanager.h" +#include "knconfig.h" +#include "knconfigwidgets.h" + + +KNFilterSelectAction::KNFilterSelectAction( const QString& text, const QString& pix, + QObject* parent, const char *name ) + : KActionMenu(text,pix,parent,name), currentItem(-42) +{ + popupMenu()->setCheckable(true); + connect(popupMenu(),SIGNAL(activated(int)),this,SLOT(slotMenuActivated(int))); + setDelayed(false); +} + + + +KNFilterSelectAction::~KNFilterSelectAction() +{ +} + +void KNFilterSelectAction::setCurrentItem(int id) +{ + popupMenu()->setItemChecked(currentItem, false); + popupMenu()->setItemChecked(id, true); + currentItem = id; +} + + +void KNFilterSelectAction::slotMenuActivated(int id) +{ + setCurrentItem(id); + emit(activated(id)); +} + + +//============================================================================== + +KNFilterManager::KNFilterManager(QObject * parent, const char * name) + : QObject(parent,name), fset(0), currFilter(0), a_ctFilter(0) +{ + loadFilters(); + + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS"); + setFilter(conf->readNumEntry("lastFilterID", 1)); +} + + + +KNFilterManager::~KNFilterManager() +{ + for ( QValueList<KNArticleFilter*>::Iterator it = mFilterList.begin(); it != mFilterList.end(); ++it ) + delete (*it); +} + + + +void KNFilterManager::readOptions() +{ +} + + + +void KNFilterManager::saveOptions() +{ +} + + +void KNFilterManager::prepareShutdown() +{ + if (currFilter) { + KConfig *conf=knGlobals.config(); + conf->setGroup("READNEWS"); + conf->writeEntry("lastFilterID", currFilter->id()); + } +} + + +void KNFilterManager::loadFilters() +{ + QString fname(locate("data","knode/filters/filters.rc") ); + + if (!fname.isNull()) { + KSimpleConfig conf(fname,true); + + QValueList<int> activeFilters = conf.readIntListEntry("Active"); + menuOrder = conf.readIntListEntry("Menu"); + + QValueList<int>::Iterator it = activeFilters.begin(); + while (it != activeFilters.end()) { + KNArticleFilter *f=new KNArticleFilter((*it)); + if (f->loadInfo()) + addFilter(f); + else + delete f; + it++; + } + } + updateMenu(); +} + + + +void KNFilterManager::saveFilterLists() +{ + QString dir(locateLocal("data","knode/")+"filters/"); + + if (dir.isNull()) { + KNHelper::displayInternalFileError(); + return; + } + KSimpleConfig conf(dir+"filters.rc"); + QValueList<int> activeFilters; + for ( QValueList<KNArticleFilter*>::Iterator it = mFilterList.begin(); it != mFilterList.end(); ++it ) + activeFilters << (*it)->id(); + + conf.writeEntry("Active",activeFilters); + conf.writeEntry("Menu",menuOrder); +} + + + +void KNFilterManager::startConfig(KNConfig::FilterListWidget *fs) +{ + fset=fs; + commitNeeded = false; + + for ( QValueList<KNArticleFilter*>::Iterator it = mFilterList.begin(); it != mFilterList.end(); ++it ) + fset->addItem( (*it) ); + + QValueList<int>::Iterator it = menuOrder.begin(); + while (it != menuOrder.end()) { + if ((*it)!=-1) + fset->addMenuItem(byID((*it))); + else + fset->addMenuItem(0); + ++it; + } +} + + + +void KNFilterManager::endConfig() +{ + fset=0; +} + + + +void KNFilterManager::commitChanges() +{ + menuOrder = fset->menuOrder(); + saveFilterLists(); + + if(currFilter) + if(!currFilter->isEnabled()) currFilter=0; + + updateMenu(); + + if (commitNeeded) + emit filterChanged(currFilter); +} + + + +void KNFilterManager::newFilter() +{ + KNArticleFilter *f=new KNArticleFilter(); + editFilter(f); +} + + + +void KNFilterManager::addFilter(KNArticleFilter *f) +{ + if ( f->id() == -1 ) { // new filter, find suitable ID + QValueList<int> activeFilters; + // ok, this is a ugly hack: we want to reuse old id's, so we try to find the first unused id + for ( QValueList<KNArticleFilter*>::Iterator it = mFilterList.begin(); it != mFilterList.end(); ++it ) + activeFilters << (*it)->id(); + int newId = 1; + while ( activeFilters.contains( newId ) > 0 ) + newId++; + f->setId( newId ); + } + mFilterList.append( f ); +} + + + +void KNFilterManager::editFilter(KNArticleFilter *f) +{ + if (!f->loaded() && f->id()!=-1) + f->load(); + + KNFilterDialog *fdlg=new KNFilterDialog(f,(fset)? fset:knGlobals.topWidget); + + if (fdlg->exec()) { + commitNeeded = true; + if(f->id()==-1) { // new filter + addFilter(f); + f->setLoaded(true); + if(fset) { // updating settings tab + fset->addItem(f); + if(f->isEnabled()) + fset->addMenuItem(f); + } + } else { + if(fset) { // updating settings tab + if(f->isEnabled()) + fset->addMenuItem(f); + else + fset->removeMenuItem(f); + fset->updateItem(f); + } + } + f->save(); + } else { + if(f->id()==-1) // new filter + delete f; + } + + delete fdlg; +} + + +void KNFilterManager::copyFilter(KNArticleFilter *f) +{ + if (!f->loaded()) + f->load(); + KNArticleFilter *newf=new KNArticleFilter(*f); + editFilter(newf); +} + + +void KNFilterManager::deleteFilter(KNArticleFilter *f) +{ + if ( KMessageBox::warningContinueCancel( fset ? fset : knGlobals.topWidget, + i18n("Do you really want to delete this filter?"), QString::null, KGuiItem( i18n("&Delete"), "editdelete" ) ) + == KMessageBox::Continue ) { + if ( mFilterList.remove( f ) ) { // does not delete surplus config files + if ( fset ) { // we reuse ids to reduce the number of dead files + fset->removeItem( f ); + fset->removeMenuItem( f ); + } + if ( currFilter == f ) { + currFilter = 0; + emit filterChanged( currFilter ); + } + } + } +} + + +bool KNFilterManager::newNameIsOK(KNArticleFilter *f, const QString &newName) +{ + for ( QValueList<KNArticleFilter*>::Iterator it = mFilterList.begin(); it != mFilterList.end(); ++it ) + if ( (*it) != f && newName == (*it)->translatedName() ) + return false; + + return true; +} + + + +KNArticleFilter* KNFilterManager::setFilter(const int id) +{ + KNArticleFilter *bak=currFilter; + + currFilter=byID(id); + + if(currFilter) { + if(a_ctFilter) + a_ctFilter->setCurrentItem(currFilter->id()); + emit(filterChanged(currFilter)); + } else + currFilter=bak; + + return currFilter; +} + + + +KNArticleFilter* KNFilterManager::byID(int id) +{ + for ( QValueList<KNArticleFilter*>::Iterator it = mFilterList.begin(); it != mFilterList.end(); ++it ) + if ( (*it)->id() == id ) + return (*it); + + return 0; +} + + + +void KNFilterManager::updateMenu() +{ + if(!a_ctFilter) + return; + + a_ctFilter->popupMenu()->clear(); + KNArticleFilter *f=0; + + QValueList<int>::Iterator it = menuOrder.begin(); + while (it != menuOrder.end()) { + if ((*it)!=-1) { + if ((f=byID((*it)))) + a_ctFilter->popupMenu()->insertItem(f->translatedName(), f->id()); + } + else a_ctFilter->popupMenu()->insertSeparator(); + ++it; + } + + if(currFilter) + a_ctFilter->setCurrentItem(currFilter->id()); +} + + + +void KNFilterManager::slotMenuActivated(int id) +{ + KNArticleFilter *f=setFilter(id); + + if (!f) + KMessageBox::error(knGlobals.topWidget, i18n("ERROR: no such filter.")); +} + + +void KNFilterManager::slotShowFilterChooser() +{ + KNArticleFilter *f=0; + QStringList items; + QValueList<int> ids; + + QValueList<int>::Iterator it = menuOrder.begin(); + while (it != menuOrder.end()) { + if ((*it)!=-1) + if ((f=byID((*it)))) { + items.append(f->translatedName()); + ids.append(*it); + } + ++it; + } + + int currentItem=0; + if (currFilter) + currentItem=ids.findIndex(currFilter->id()); + if (currentItem==-1) + currentItem=0; + + int newFilter = KNHelper::selectDialog(knGlobals.topWidget, i18n("Select Filter"), items, currentItem); + if (newFilter != -1) + setFilter(ids[newFilter]); +} + + +void KNFilterManager::setMenuAction(KNFilterSelectAction *a, KAction *keybA) +{ + if(a) { + a_ctFilter = a; + connect(a_ctFilter, SIGNAL(activated(int)), this, SLOT(slotMenuActivated(int))); + } + if(keybA) + connect(keybA, SIGNAL(activated()), this, SLOT(slotShowFilterChooser())); + + updateMenu(); +} + +//-------------------------------- + +#include "knfiltermanager.moc" diff --git a/knode/knfiltermanager.h b/knode/knfiltermanager.h new file mode 100644 index 000000000..ca76bcb75 --- /dev/null +++ b/knode/knfiltermanager.h @@ -0,0 +1,106 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNFILTERMANAGER_H +#define KNFILTERMANAGER_H + +#include <qglobal.h> +#include <qvaluelist.h> + +#include <kaction.h> + +namespace KNConfig { +class FilterListWidget; +} + +class KNArticleFilter; +class KNFilterDialog; + + +class KNFilterSelectAction : public KActionMenu +{ + Q_OBJECT + + public: + KNFilterSelectAction( const QString& text, const QString& pix, + QObject* parent, const char *name ); + ~KNFilterSelectAction(); + + void setCurrentItem(int id); + + protected slots: + void slotMenuActivated(int id); + + signals: + void activated(int id); + + private: + int currentItem; +}; + + +class KNFilterManager : public QObject +{ + Q_OBJECT + + public: + KNFilterManager(QObject * parent = 0, const char * name = 0); + ~KNFilterManager(); + + void readOptions(); + void saveOptions(); + + void prepareShutdown(); + + KNArticleFilter* currentFilter() { return currFilter; } + + void startConfig(KNConfig::FilterListWidget *fs); + void endConfig(); + void commitChanges(); + void newFilter(); + void editFilter(KNArticleFilter *f); + void copyFilter(KNArticleFilter *f); + void addFilter(KNArticleFilter *f); + void deleteFilter(KNArticleFilter *f); + bool newNameIsOK(KNArticleFilter *f, const QString &newName); + + // Allow to delay the setup of UI elements, since the knode part may not + // be available when the config dialog is called + void setMenuAction(KNFilterSelectAction *a, KAction *keybA); + + protected: + void loadFilters(); + void saveFilterLists(); + KNArticleFilter* setFilter(const int id); + KNArticleFilter* byID(int id); + void updateMenu(); + + QValueList<KNArticleFilter*> mFilterList; + KNConfig::FilterListWidget *fset; + KNArticleFilter *currFilter; + KNFilterSelectAction *a_ctFilter; + QValueList<int> menuOrder; + bool commitNeeded; + + protected slots: + void slotMenuActivated(int id); + void slotShowFilterChooser(); + + signals: + void filterChanged(KNArticleFilter *f); + +}; + +#endif + diff --git a/knode/knfolder.cpp b/knode/knfolder.cpp new file mode 100644 index 000000000..9cdb2f054 --- /dev/null +++ b/knode/knfolder.cpp @@ -0,0 +1,601 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qfileinfo.h> + +#include <ksimpleconfig.h> +#include <kstandarddirs.h> +#include <kdebug.h> +#include <klocale.h> + +#include <kqcstringsplitter.h> + +#include "articlewidget.h" +#include "knarticlemanager.h" +#include "kncollectionviewitem.h" +#include "knhdrviewitem.h" +#include "utilities.h" +#include "knglobals.h" +#include "knarticlefactory.h" +#include "knfolder.h" +#include "knarticlewindow.h" +#include "knmainwidget.h" + +using namespace KNode; + + +KNFolder::KNFolder() + : KNArticleCollection(0), i_d(-1), p_arentId(-1), i_ndexDirty(false), w_asOpen(true) +{ +} + + +KNFolder::KNFolder(int id, const QString &name, KNFolder *parent) + : KNArticleCollection(parent), i_d(id), i_ndexDirty(false), w_asOpen(true) +{ + QString fname=path()+QString("custom_%1").arg(i_d); + + n_ame = name; + m_boxFile.setName(fname+".mbox"); + i_ndexFile.setName(fname+".idx"); + i_nfoPath=fname+".info"; + + p_arentId=parent?parent->id():-1; + + if(i_ndexFile.exists()) + c_ount=i_ndexFile.size()/sizeof(DynData); + else + c_ount=0; +} + + +KNFolder::KNFolder(int id, const QString &name, const QString &prefix, KNFolder *parent) + : KNArticleCollection(parent), i_d(id), i_ndexDirty(false), w_asOpen(true) +{ + QString fname=path()+QString("%1_%2").arg(prefix).arg(i_d); + + n_ame = name; + m_boxFile.setName(fname+".mbox"); + i_ndexFile.setName(fname+".idx"); + i_nfoPath=fname+".info"; + + p_arentId=parent?parent->id():-1; + + if(i_ndexFile.exists()) + c_ount=i_ndexFile.size()/sizeof(DynData); + else + c_ount=0; +} + + +KNFolder::~KNFolder() +{ + closeFiles(); +} + + +void KNFolder::updateListItem() +{ + if(l_istItem) { + l_istItem->setText(0, n_ame); + if (!isRootFolder()) + l_istItem->setTotalCount( c_ount ); + } +} + + +QString KNFolder::path() +{ + QString dir(locateLocal("data","knode/")+"folders/"); + /*if (dir.isNull()) + KNHelper::displayInternalFileError();*/ + return dir; +} + + +bool KNFolder::readInfo(const QString &infoPath) +{ + if(infoPath.isEmpty()) + return false; + + i_nfoPath=infoPath; + + KSimpleConfig info(i_nfoPath); + if (!isRootFolder() && !isStandardFolder()) { + n_ame=info.readEntry("name"); + i_d=info.readNumEntry("id", -1); + p_arentId=info.readNumEntry("parentId", -1); + } + w_asOpen=info.readBoolEntry("wasOpen", true); + + if(i_d>-1) { + QFileInfo fi(infoPath); + QString fname=fi.dirPath(true)+"/"+fi.baseName(); + closeFiles(); + clear(); + + m_boxFile.setName(fname+".mbox"); + i_ndexFile.setName(fname+".idx"); + c_ount=i_ndexFile.exists() ? (i_ndexFile.size()/sizeof(DynData)) : 0; + } + + return (i_d!=-1); +} + + +bool KNFolder::readInfo() +{ + return readInfo(i_nfoPath); +} + + +void KNFolder::saveInfo() +{ + if(!i_nfoPath.isEmpty()) { + KSimpleConfig info(i_nfoPath); + if (!isRootFolder() && !isStandardFolder()) { + info.writeEntry("name", n_ame); + info.writeEntry("id", i_d); + info.writeEntry("parentId", p_arentId); + } + if(l_istItem) + info.writeEntry("wasOpen", l_istItem->isOpen()); + } +} + + +void KNFolder::setParent(KNCollection *p) +{ + p_arent = p; + p_arentId = p ? (static_cast<KNFolder*>(p))->id() : -1; +} + + +bool KNFolder::loadHdrs() +{ + if(isLoaded()) { + kdDebug(5003) << "KNFolder::loadHdrs() : already loaded" << endl; + return true; + } + + if(!i_ndexFile.open(IO_ReadOnly)) { + kdError(5003) << "KNFolder::loadHdrs() : cannot open index-file!" << endl; + closeFiles(); + return false; + } + + if(!m_boxFile.open(IO_ReadOnly)) { + kdError(5003) << "KNFolder::loadHdrs() : cannot open mbox-file!" << endl; + closeFiles(); + return false; + } + + if(!resize(c_ount)) { + closeFiles(); + return false; + } + + QCString tmp; + KQCStringSplitter split; + KNLocalArticle *art; + DynData dynamic; + int pos1=0, pos2=0, cnt=0, byteCount; + + knGlobals.top->setCursorBusy(true); + knGlobals.setStatusMsg(i18n(" Loading folder...")); + knGlobals.top->secureProcessEvents(); + + while(!i_ndexFile.atEnd()) { + + //read index-data + byteCount=i_ndexFile.readBlock((char*)(&dynamic), sizeof(DynData)); + if(byteCount!=sizeof(DynData)) + if(i_ndexFile.status() == IO_Ok) { + kdWarning(5003) << "KNFolder::loadHeaders() : found broken entry in index-file: Ignored!" << endl; + continue; + } + else { + kdError(5003) << "KNFolder::loadHeaders() : corrupted index-file, IO-error!" << endl; + closeFiles(); + clear(); + knGlobals.top->setCursorBusy( false ); + return false; + } + + art=new KNLocalArticle(this); + + //set index-data + dynamic.getData(art); + + //read overview + if(!m_boxFile.at(art->startOffset())) { + kdError(5003) << "KNFolder::loadHdrs() : cannot set mbox file-pointer!" << endl; + closeFiles(); + clear(); + knGlobals.top->setCursorBusy( false ); + return false; + } + tmp=m_boxFile.readLine(); //KNFile::readLine() + if(tmp.isEmpty()) { + if(m_boxFile.status() == IO_Ok) { + kdWarning(5003) << "found broken entry in mbox-file: Ignored!" << endl; + delete art; + continue; + } + else { + kdError(5003) << "KNFolder::loadHdrs() : corrupted mbox-file, IO-error!"<< endl; + closeFiles(); + clear(); + knGlobals.top->setCursorBusy( false ); + return false; + } + } + + //set overview + bool end=false; + pos1=tmp.find(' ')+1; + pos2=tmp.find('\t', pos1); + if (pos2 == -1) { + pos2=tmp.length(); + end=true; + } + art->subject()->from7BitString(tmp.mid(pos1, pos2-pos1)); + + if (!end) { + pos1=pos2+1; + pos2=tmp.find('\t', pos1); + if (pos2 == -1) { + pos2=tmp.length(); + end=true; + } + art->newsgroups()->from7BitString(tmp.mid(pos1, pos2-pos1)); + } + + if (!end) { + pos1=pos2+1; + pos2=tmp.find('\t', pos1); + if (pos2 == -1) { + pos2=tmp.length(); + end=true; + } + art->to()->from7BitString(tmp.mid(pos1,pos2-pos1)); + } + + if (!end) { + pos1=pos2+1; + pos2=tmp.length(); + art->lines()->from7BitString(tmp.mid(pos1,pos2-pos1)); + } + + if(!append(art)) { + kdError(5003) << "KNFolder::loadHdrs() : cannot append article!"<< endl; + delete art; + clear(); + closeFiles(); + + knGlobals.setStatusMsg(QString::null); + knGlobals.top->setCursorBusy(false); + return false; + } + + cnt++; + } + + closeFiles(); + setLastID(); + c_ount=cnt; + updateListItem(); + + knGlobals.setStatusMsg(QString::null); + knGlobals.top->setCursorBusy(false); + + return true; +} + + +bool KNFolder::unloadHdrs(bool force) +{ + if(l_ockedArticles>0) + return false; + + if (!force && isNotUnloadable()) + return false; + + KNLocalArticle *a; + for(int idx=0; idx<length(); idx++) { + a=at(idx); + if (a->hasContent() && !knGlobals.articleManager()->unloadArticle(a, force)) + return false; + } + syncIndex(); + clear(); + + return true; +} + +bool KNFolder::loadArticle(KNLocalArticle *a) +{ + if(a->hasContent()) + return true; + + closeFiles(); + if(!m_boxFile.open(IO_ReadOnly)) { + kdError(5003) << "KNFolder::loadArticle(KNLocalArticle *a) : cannot open mbox file: " + << m_boxFile.name() << endl; + return false; + } + + //set file-pointer + if(!m_boxFile.at(a->startOffset())) { + kdError(5003) << "KNFolder::loadArticle(KNLocalArticle *a) : cannot set mbox file-pointer!" << endl; + closeFiles(); + return false; + } + + //read content + m_boxFile.readLine(); //skip X-KNode-Overview + + unsigned int size=a->endOffset()-m_boxFile.at()-1; + QCString buff(size+10); + int readBytes=m_boxFile.readBlock(buff.data(), size); + closeFiles(); + if(readBytes < (int)(size) && m_boxFile.status() != IO_Ok) { //cannot read file + kdError(5003) << "KNFolder::loadArticle(KNLocalArticle *a) : corrupted mbox file, IO-error!" << endl; + return false; + } + + //set content + buff.at(readBytes)='\0'; //terminate string + a->setContent(buff); + a->parse(); + + return true; +} + + +bool KNFolder::saveArticles( KNLocalArticle::List &l ) +{ + if(!isLoaded()) // loading should not be done here - keep the StorageManager in sync !! + return false; + + if(!m_boxFile.open(IO_WriteOnly | IO_Append)) { + kdError(5003) << "KNFolder::saveArticles() : cannot open mbox-file!" << endl; + closeFiles(); + return false; + } + + int addCnt=0; + bool ret=true; + bool clear=false; + QTextStream ts(&m_boxFile); + ts.setEncoding(QTextStream::Latin1); + + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it ) { + + clear=false; + if ( (*it)->id() == -1 || (*it)->collection() != this ) { + if ( (*it)->id() != -1 ) { + KNFolder *oldFolder = static_cast<KNFolder*>( (*it)->collection() ); + if ( !(*it)->hasContent() ) + if( !( clear = oldFolder->loadArticle( (*it) ) ) ) { + ret = false; + continue; + } + + KNLocalArticle::List l; + l.append( (*it) ); + oldFolder->removeArticles( l, false ); + } + if ( !append( (*it) ) ) { + kdError(5003) << "KNFolder::saveArticle(KNLocalArticle::List *l) : cannot append article!" << endl; + ret = false; + continue; + (*it)->setCollection(0); + } + else { + (*it)->setCollection(this); + addCnt++; + } + } + + if ( byId( (*it)->id() ) == (*it) ) { + + //MBox + ts << "From aaa@aaa Mon Jan 01 00:00:00 1997\n"; + (*it)->setStartOffset(m_boxFile.at()); //save offset + + //write overview information + ts << "X-KNode-Overview: "; + ts << (*it)->subject()->as7BitString(false) << '\t'; + + KMime::Headers::Base* h; + if( ( h = (*it)->newsgroups( false ) ) !=0 ) + ts << h->as7BitString(false); + ts << '\t'; + + if( (h = (*it)->to( false ) ) != 0 ) + ts << h->as7BitString(false); + ts << '\t'; + + ts << (*it)->lines()->as7BitString(false) << '\n'; + + //write article + (*it)->toStream( ts ); + ts << "\n"; + + (*it)->setEndOffset( m_boxFile.at() ); //save offset + + //update + ArticleWidget::articleChanged( (*it) ); + i_ndexDirty=true; + + } + else { + kdError(5003) << "KNFolder::saveArticle() : article not in folder!" << endl; + ret=false; + } + + if ( clear ) + (*it)->KMime::Content::clear(); + } + + closeFiles(); + syncIndex(); + + if(addCnt>0) { + c_ount=length(); + updateListItem(); + knGlobals.articleManager()->updateViewForCollection(this); + } + + return ret; +} + + +void KNFolder::removeArticles( KNLocalArticle::List &l, bool del ) +{ + if( !isLoaded() || l.isEmpty() ) + return; + + int idx = 0, delCnt = 0, *positions; + positions = new int[l.count()]; + KNLocalArticle *a = 0; + + for ( KNLocalArticle::List::Iterator it = l.begin(); it != l.end(); ++it, ++idx ) { + if ( (*it)->isLocked() ) + positions[idx] = -1; + else + positions[idx] = a_rticles.indexForId( (*it)->id() ); + } + + for ( idx = 0; idx < (int)(l.count()); ++idx ) { + if(positions[idx]==-1) + continue; + + a=at(positions[idx]); + + //update + knGlobals.artFactory->deleteComposerForArticle(a); + KNArticleWindow::closeAllWindowsForArticle(a); + ArticleWidget::articleRemoved( a ); + delete a->listItem(); + + //delete article + a_rticles.remove(positions[idx], del, false); + delCnt++; + if(!del) + a->setId(-1); + } + + if(delCnt>0) { + compact(); + c_ount-=delCnt; + updateListItem(); + i_ndexDirty=true; + } + delete[] positions; +} + + +void KNFolder::deleteAll() +{ + if(l_ockedArticles>0) + return; + + if (!unloadHdrs(true)) + return; + + clear(); + c_ount=0; + syncIndex(true); + updateListItem(); +} + + +void KNFolder::deleteFiles() +{ + m_boxFile.remove(); + i_ndexFile.remove(); + QFile::remove(i_nfoPath); +} + + +void KNFolder::syncIndex(bool force) +{ + if(!i_ndexDirty && !force) + return; + + if(!i_ndexFile.open(IO_WriteOnly)) { + kdError(5003) << "KNFolder::syncIndex(bool force) : cannot open index-file!" << endl; + closeFiles(); + return; + } + + KNLocalArticle *a; + DynData d; + for(int idx=0; idx<length(); idx++) { + a=at(idx); + d.setData(a); + i_ndexFile.writeBlock((char*)(&d), sizeof(DynData)); + } + closeFiles(); + + i_ndexDirty=false; +} + + +void KNFolder::closeFiles() +{ + if(m_boxFile.isOpen()) + m_boxFile.close(); + if(i_ndexFile.isOpen()) + i_ndexFile.close(); +} + + +//============================================================================== + + +void KNFolder::DynData::setData(KNLocalArticle *a) +{ + id=a->id(); + so=a->startOffset(); + eo=a->endOffset(); + sId=a->serverId(); + ti=a->date()->unixTime(); + + flags[0]=a->doMail(); + flags[1]=a->mailed(); + flags[2]=a->doPost(); + flags[3]=a->posted(); + flags[4]=a->canceled(); + flags[5]=a->editDisabled(); +} + + +void KNFolder::DynData::getData(KNLocalArticle *a) +{ + a->setId(id); + a->date()->setUnixTime(ti); + a->setStartOffset(so); + a->setEndOffset(eo); + a->setServerId(sId); + a->setDoMail(flags[0]); + a->setMailed(flags[1]); + a->setDoPost(flags[2]); + a->setPosted(flags[3]); + a->setCanceled(flags[4]); + a->setEditDisabled(flags[5]); +} + diff --git a/knode/knfolder.h b/knode/knfolder.h new file mode 100644 index 000000000..0e2bfafb9 --- /dev/null +++ b/knode/knfolder.h @@ -0,0 +1,103 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNFOLDER_H +#define KNFOLDER_H + +#include <time.h> + +#include "utilities.h" +#include "knarticle.h" +#include "knarticlecollection.h" + + +class KNFolder : public KNArticleCollection { + + friend class KNCleanUp; + + public: + KNFolder(); + KNFolder(int id, const QString &name, KNFolder *parent=0); + KNFolder(int id, const QString &name, const QString &prefix, KNFolder *parent=0); + ~KNFolder(); + + //type + collectionType type() { return CTfolder; } + + //id + int id() const { return i_d; } + void setId(int i) { i_d=i; } + int parentId() const { return p_arentId; } + bool isStandardFolder() { return (i_d > 0) && (i_d <=3); } + bool isRootFolder() { return i_d==0; } + + //list item handling + void updateListItem(); + bool wasOpen()const { return w_asOpen; } + + //info + QString path(); + bool readInfo(const QString &confPath); + bool readInfo(); + void saveInfo(); + + //article access + KNLocalArticle* at(int i) { return static_cast<KNLocalArticle*>(KNArticleCollection::at(i)); } + KNLocalArticle* byId(int id) { return static_cast<KNLocalArticle*>(KNArticleCollection::byId(id)); } + KNLocalArticle* byMessageId(const QCString &mid) + { return static_cast<KNLocalArticle*>(KNArticleCollection::byMessageId(mid)); } + + //parent + void setParent(KNCollection *p); + + //load, save and delete + bool loadHdrs(); + bool unloadHdrs(bool force=true); + bool loadArticle(KNLocalArticle *a); + bool saveArticles( KNLocalArticle::List &l ); + void removeArticles( KNLocalArticle::List &l, bool del = true ); + void deleteAll(); + void deleteFiles(); + + //index synchronization + void syncIndex(bool force=false); + + protected: + void closeFiles(); + int i_d; // unique id: 0: root folder 1-3: standard folders + int p_arentId; // -1 for the root folder + bool i_ndexDirty; // do we need to sync? + bool w_asOpen; // was this folder open in the listview on the last shutdown? + KNFile m_boxFile; + QFile i_ndexFile; + QString i_nfoPath; + + /* helper-class: stores index-data of an article */ + class DynData { + public: + DynData() {} + ~DynData() {} + void setData(KNLocalArticle *a); + void getData(KNLocalArticle *a); + + int id, + so, + eo, + sId; + time_t ti; + bool flags[6]; + }; +}; + +#endif diff --git a/knode/knfoldermanager.cpp b/knode/knfoldermanager.cpp new file mode 100644 index 000000000..5acb41866 --- /dev/null +++ b/knode/knfoldermanager.cpp @@ -0,0 +1,482 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qdir.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kdebug.h> + +#include "knglobals.h" +#include "knconfigmanager.h" +#include "knfolder.h" +#include "utilities.h" +#include "knfoldermanager.h" +#include "knarticlemanager.h" +#include "kncleanup.h" +#include "knmemorymanager.h" +#include "knmainwidget.h" + + +KNFolderManager::KNFolderManager(KNArticleManager *a) : a_rtManager(a) +{ + //standard folders + QString dir(locateLocal("data","knode/")+"folders/"); + if (dir.isNull()) { + KNHelper::displayInternalFileError(); + return; + } + + KNFolder *f; + + f=new KNFolder(0, i18n("Local Folders"), "root"); + mFolderList.append( f ); + f->readInfo(); + + f=new KNFolder(1, i18n("Drafts"), "drafts", root()); + mFolderList.append( f ); + f->readInfo(); + + f=new KNFolder(2, i18n("Outbox"), "outbox", root()); + mFolderList.append( f ); + f->readInfo(); + + f=new KNFolder(3, i18n("Sent"), "sent", root()); + mFolderList.append( f ); + f->readInfo(); + + l_astId=3; + + //custom folders + loadCustomFolders(); + + setCurrentFolder(0); +} + + +KNFolderManager::~KNFolderManager() +{ + for ( QValueList<KNFolder*>::Iterator it = mFolderList.begin(); it != mFolderList.end(); ++it ) + delete (*it); +} + + +void KNFolderManager::setCurrentFolder(KNFolder *f) +{ + c_urrentFolder=f; + a_rtManager->setFolder(f); + + kdDebug(5003) << "KNFolderManager::setCurrentFolder() : folder changed" << endl; + + if(f && !f->isRootFolder()) { + if( loadHeaders(f) ) + a_rtManager->showHdrs(); + else + KMessageBox::error(knGlobals.topWidget, i18n("Cannot load index-file.")); + } +} + + +bool KNFolderManager::loadHeaders(KNFolder *f) +{ + if( !f || f->isRootFolder() ) + return false; + + if (f->isLoaded()) + return true; + + // we want to delete old stuff first => reduce vm fragmentation + knGlobals.memoryManager()->prepareLoad(f); + + if (f->loadHdrs()) { + knGlobals.memoryManager()->updateCacheEntry( f ); + return true; + } + + return false; +} + + +bool KNFolderManager::unloadHeaders(KNFolder *f, bool force) +{ + if(!f || !f->isLoaded()) + return false; + + if (!force && (c_urrentFolder == f)) + return false; + + if (f->unloadHdrs(force)) + knGlobals.memoryManager()->removeCacheEntry(f); + else + return false; + + return true; +} + + +KNFolder* KNFolderManager::folder(int i) +{ + for ( QValueList<KNFolder*>::Iterator it = mFolderList.begin(); it != mFolderList.end(); ++it ) + if ( (*it)->id() == i ) + return (*it); + return 0; +} + + +KNFolder* KNFolderManager::newFolder(KNFolder *p) +{ + if (!p) + p = root(); + KNFolder *f=new KNFolder(++l_astId, i18n("New folder"), p); + mFolderList.append( f ); + emit folderAdded(f); + return f; +} + + +bool KNFolderManager::deleteFolder(KNFolder *f) +{ + if(!f || f->isRootFolder() || f->isStandardFolder() || f->lockedArticles()>0) + return false; + + QValueList<KNFolder*> del; + KNCollection *p; + + // find all subfolders of the folder we want to delete + for ( QValueList<KNFolder*>::Iterator it = mFolderList.begin(); it != mFolderList.end(); ++it ) { + p = (*it)->parent(); + while ( p ) { + if ( p == f ) { + if ( (*it)->lockedArticles() > 0 ) + return false; + del.append( (*it) ); + break; + } + p = p->parent(); + } + } + + emit folderRemoved(f); + + del.append(f); + for ( QValueList<KNFolder*>::Iterator it = del.begin(); it != del.end(); ++it ) { + if ( c_urrentFolder == (*it) ) + c_urrentFolder = 0; + + if ( unloadHeaders( (*it), true ) ) { + (*it)->deleteFiles(); + mFolderList.remove( (*it) ); + delete (*it); + } else + return false; + } + + return true; +} + + +void KNFolderManager::emptyFolder(KNFolder *f) +{ + if(!f || f->isRootFolder()) + return; + knGlobals.memoryManager()->removeCacheEntry(f); + f->deleteAll(); +} + + +bool KNFolderManager::moveFolder(KNFolder *f, KNFolder *p) +{ + if(!f || p==f->parent()) // nothing to be done + return true; + + // is "p" a child of "f" ? + KNCollection *p2=p?p->parent():0; + while(p2) { + if(p2==f) + break; + p2=p2->parent(); + } + + if( (p2 && p2==f) || f==p || f->isStandardFolder() || f->isRootFolder()) // no way ;-) + return false; + + emit folderRemoved(f); + + // reparent + f->setParent(p); + f->saveInfo(); + + emit folderAdded(f); + + if(c_urrentFolder==f) + emit folderActivated(f); + + return true; +} + + +int KNFolderManager::unsentForAccount(int accId) +{ + int cnt=0; + + for ( QValueList<KNFolder*>::Iterator it = mFolderList.begin(); it != mFolderList.end(); ++it ) { + for ( int idx = 0; idx < (*it)->length(); ++idx ) { + KNLocalArticle *a = (*it)->at( idx ); + if ( a->serverId() == accId && a->doPost() && !a->posted() ) + cnt++; + } + } + + return cnt; +} + + +void KNFolderManager::compactFolder(KNFolder *f) +{ + if(!f || f->isRootFolder()) + return; + + KNCleanUp cup; + cup.compactFolder(f); +} + + +void KNFolderManager::compactAll(KNCleanUp *cup) +{ + for ( QValueList<KNFolder*>::Iterator it = mFolderList.begin(); it != mFolderList.end(); ++it ) + if ( !(*it)->isRootFolder() && (*it)->lockedArticles() == 0 ) + cup->appendCollection( (*it) ); +} + + +void KNFolderManager::compactAll() +{ + KNCleanUp *cup = new KNCleanUp(); + compactAll( cup ); + cup->start(); + + knGlobals.configManager()->cleanup()->setLastCompactDate(); + delete cup; +} + + +void KNFolderManager::importFromMBox(KNFolder *f) +{ + if(!f || f->isRootFolder()) + return; + + f->setNotUnloadable(true); + + if (!f->isLoaded() && !loadHeaders(f)) { + f->setNotUnloadable(false); + return; + } + + KNLoadHelper helper(knGlobals.topWidget); + KNFile *file = helper.getFile(i18n("Import MBox Folder")); + KNLocalArticle::List list; + KNLocalArticle *art; + QString s; + int artStart=0, artEnd=0; + bool done=true; + + if (file) { + knGlobals.top->setCursorBusy(true); + knGlobals.setStatusMsg(i18n(" Importing articles...")); + knGlobals.top->secureProcessEvents(); + + if (!file->atEnd()) { // search for the first article... + s = file->readLine(); + if (s.left(5) == "From ") { + artStart = file->at(); + done = false; + } else { + artStart = file->findString("\n\nFrom "); + if (artStart != -1) { + file->at(artStart+1); + s = file->readLine(); + artStart = file->at(); + done = false; + } + } + } + + knGlobals.top->secureProcessEvents(); + + if (!done) { + while (!file->atEnd()) { + artEnd = file->findString("\n\nFrom "); + + if (artEnd != -1) { + file->at(artStart); // seek the first character of the article + int size=artEnd-artStart; + QCString buff(size+10); + int readBytes=file->readBlock(buff.data(), size); + + if (readBytes != -1) { + buff.at(readBytes)='\0'; //terminate string + art = new KNLocalArticle(0); + art->setEditDisabled(true); + art->setContent(buff); + art->parse(); + list.append(art); + } + + file->at(artEnd+1); + s = file->readLine(); + artStart = file->at(); + } else { + if ((int)file->size() > artStart) { + file->at(artStart); // seek the first character of the article + int size=file->size()-artStart; + QCString buff(size+10); + int readBytes=file->readBlock(buff.data(), size); + + if (readBytes != -1) { + buff.at(readBytes)='\0'; //terminate string + art = new KNLocalArticle(0); + art->setEditDisabled(true); + art->setContent(buff); + art->parse(); + list.append(art); + } + } + } + + if (list.count()%75 == 0) + knGlobals.top->secureProcessEvents(); + } + } + + knGlobals.setStatusMsg(i18n(" Storing articles...")); + knGlobals.top->secureProcessEvents(); + + if (!list.isEmpty()) + knGlobals.articleManager()->moveIntoFolder(list, f); + + knGlobals.setStatusMsg(QString::null); + knGlobals.top->setCursorBusy(false); + } + + f->setNotUnloadable(false); +} + + +void KNFolderManager::exportToMBox(KNFolder *f) +{ + if(!f || f->isRootFolder()) + return; + + f->setNotUnloadable(true); + + if (!f->isLoaded() && !loadHeaders(f)) { + f->setNotUnloadable(false); + return; + } + + KNSaveHelper helper(f->name()+".mbox", knGlobals.topWidget); + QFile *file = helper.getFile(i18n("Export Folder")); + + if (file) { + knGlobals.top->setCursorBusy(true); + knGlobals.setStatusMsg(i18n(" Exporting articles...")); + knGlobals.top->secureProcessEvents(); + + QTextStream ts(file); + ts.setEncoding(QTextStream::Latin1); + KNLocalArticle *a; + + for(int idx=0; idx<f->length(); idx++) { + a=f->at(idx); + + a->setNotUnloadable(true); + + if (a->hasContent() || knGlobals.articleManager()->loadArticle(a)) { + ts << "From aaa@aaa Mon Jan 01 00:00:00 1997\n"; + a->toStream(ts, true); + ts << "\n"; + } + + a->setNotUnloadable(false); + + if (idx%75 == 0) + knGlobals.top->secureProcessEvents(); + } + + knGlobals.setStatusMsg(QString::null); + knGlobals.top->setCursorBusy(false); + } +} + + +void KNFolderManager::syncFolders() +{ + QString dir(locateLocal("data","knode/")+"folders/"); + if (dir.isNull()) { + KNHelper::displayInternalFileError(); + return; + } + + //sync + for ( QValueList<KNFolder*>::Iterator it = mFolderList.begin(); it != mFolderList.end(); ++it ) { + if ( !(*it)->isRootFolder() ) + (*it)->syncIndex(); + (*it)->saveInfo(); + } +} + + +int KNFolderManager::loadCustomFolders() +{ + int cnt=0; + QString dir(locateLocal("data","knode/")+"folders/"); + KNFolder *f; + + if (dir.isNull()) { + KNHelper::displayInternalFileError(); + return 0; + } + + QDir d(dir); + QStringList entries(d.entryList("custom_*.info")); // ignore info files of standard folders + for(QStringList::Iterator it=entries.begin(); it != entries.end(); ++it) { + f=new KNFolder(); + if(f->readInfo(d.absFilePath(*it))) { + if(f->id()>l_astId) + l_astId=f->id(); + mFolderList.append( f ); + cnt++; + } + else + delete f; + } + + // set parents + if(cnt>0) { + for ( QValueList<KNFolder*>::Iterator it = mFolderList.begin(); it != mFolderList.end(); ++it ) { + if ( !(*it)->isRootFolder() ) { // the root folder has no parent + KNFolder *par = folder( (*it)->parentId() ); + if ( !par ) + par = root(); + (*it)->setParent( par ); + } + } + } + + return cnt; +} + + +#include "knfoldermanager.moc" diff --git a/knode/knfoldermanager.h b/knode/knfoldermanager.h new file mode 100644 index 000000000..9bc8254e8 --- /dev/null +++ b/knode/knfoldermanager.h @@ -0,0 +1,91 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNFOLDERMANAGER_H +#define KNFOLDERMANAGER_H + +#include <qobject.h> +#include <qvaluelist.h> + +class KNFolder; +class KNArticleManager; +class KNCleanUp; + + +class KNFolderManager : public QObject +{ + Q_OBJECT + + public: + KNFolderManager(KNArticleManager *a); + ~KNFolderManager(); + + //folder access + void setCurrentFolder(KNFolder *f); + KNFolder* currentFolder() const { return c_urrentFolder; } + bool hasCurrentFolder() const { return (c_urrentFolder!=0); } + KNFolder* folder(int id); + QValueList<KNFolder*> folders() const { return mFolderList; } + + //standard folders + KNFolder* root() const { return mFolderList[0]; } + KNFolder* drafts() const { return mFolderList[1]; } + KNFolder* outbox() const { return mFolderList[2]; } + KNFolder* sent() const { return mFolderList[3]; } + + //header loading + bool loadHeaders(KNFolder *f); + bool unloadHeaders(KNFolder *f, bool force=true); + bool loadDrafts() { return loadHeaders(drafts()); } + bool loadOutbox() { return loadHeaders(outbox()); } + bool loadSent() { return loadHeaders(sent()); } + + // returns the new folder + KNFolder* newFolder(KNFolder *p); + bool deleteFolder(KNFolder *f); + void emptyFolder(KNFolder *f); + bool moveFolder(KNFolder *f, KNFolder *p); + + //unsent articles + int unsentForAccount(int accId); + + //compacting + void compactFolder(KNFolder *f); + void compactAll(KNCleanUp *cup); + void compactAll(); + + // import + export + void importFromMBox(KNFolder *f); + void exportToMBox(KNFolder *f); + + //synchronization + void syncFolders(); + + signals: + // signals for the collection tree to update the UI + void folderAdded(KNFolder *f); + void folderRemoved(KNFolder *f); + void folderActivated(KNFolder *f); + + protected: + int loadCustomFolders(); + + KNFolder *c_urrentFolder; + QValueList<KNFolder*> mFolderList; + int l_astId; + KNArticleManager *a_rtManager; + +}; + +#endif diff --git a/knode/knglobals.cpp b/knode/knglobals.cpp new file mode 100644 index 000000000..5238bebec --- /dev/null +++ b/knode/knglobals.cpp @@ -0,0 +1,114 @@ +/* + knglobals.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include "knglobals.h" + + +#include <kconfig.h> +#include <kstaticdeleter.h> + +#include "knconfigmanager.h" +#include "knnetaccess.h" +#include "knaccountmanager.h" +#include "kngroupmanager.h" +#include "knarticlemanager.h" +#include "knfiltermanager.h" +#include "knfoldermanager.h" +#include "knscoring.h" +#include "knmemorymanager.h" +#include "knmainwidget.h" +#include "knwidgets.h" + +KConfig* KNGlobals::config() +{ + if (!c_onfig) { + c_onfig = KSharedConfig::openConfig( "knoderc" ); + } + return c_onfig; +} + +KNConfigManager* KNGlobals::configManager() +{ + if (!mCfgManager) + mCfgManager = new KNConfigManager(); + return mCfgManager; +} + +KNNetAccess* KNGlobals::netAccess() +{ + if(!mNetAccess) + mNetAccess = new KNNetAccess(); + return mNetAccess; +} + +KNAccountManager* KNGlobals::accountManager() +{ + if(!mAccManager) + mAccManager = new KNAccountManager(groupManager()); + return mAccManager; +} + +KNGroupManager* KNGlobals::groupManager() +{ + if(!mGrpManager) + mGrpManager = new KNGroupManager(); + return mGrpManager; +} + +KNArticleManager* KNGlobals::articleManager() +{ + if(!mArtManager) + mArtManager = new KNArticleManager(); + return mArtManager; +} + +KNFilterManager* KNGlobals::filterManager() +{ + if (!mFilManager) + mFilManager = new KNFilterManager(); + return mFilManager; +} + +KNFolderManager* KNGlobals::folderManager() +{ + if(!mFolManager) + mFolManager = new KNFolderManager(articleManager()); + return mFolManager; +} + +KNScoringManager* KNGlobals::mScoreManager = 0; + +KNScoringManager* KNGlobals::scoringManager() +{ + static KStaticDeleter<KNScoringManager> sd; + if (!mScoreManager) + sd.setObject(mScoreManager, new KNScoringManager()); + return mScoreManager; +} + +KNMemoryManager* KNGlobals::memoryManager() +{ + if(!mMemManager) + mMemManager = new KNMemoryManager(); + return mMemManager; +} + + +void KNGlobals::setStatusMsg(const QString &text, int id) +{ + if(top) + top->setStatusMsg(text, id); +} diff --git a/knode/knglobals.h b/knode/knglobals.h new file mode 100644 index 000000000..12b934dae --- /dev/null +++ b/knode/knglobals.h @@ -0,0 +1,95 @@ +/* + knglobals.h + + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNGLOBALS_H +#define KNGLOBALS_H + +#include <kconfig.h> +#include "resource.h" + +#include <kdepimmacros.h> + +class KInstance; +class KNConfigManager; +class KNNetAccess; +class KNProgress; +class KNAccountManager; +class KNGroupManager; +class KNArticleManager; +class KNArticleFactory; +class KNFolderManager; +class QWidget; +class KNFilterManager; +class KNMainWidget; +class KNScoringManager; +class KNMemoryManager; +class KXMLGUIClient; +namespace Kpgp { + class Module; +} +namespace KNode { + class ArticleWidget; +} + + +/** idea: Previously the manager classes were available + via KNodeApp. Now they can be accessed directly, + this removes many header dependencies. + (knode.h isn't include everywhere) */ +class KDE_EXPORT KNGlobals { + public: + /** topWidget == top, used for message boxes, */ + QWidget *topWidget; + /** no need to include knode.h everywhere */ + KNMainWidget *top; + KXMLGUIClient *guiClient; + KNode::ArticleWidget *artWidget; + KNArticleFactory *artFactory; + Kpgp::Module *pgp; + KConfig *config(); + KInstance *instance; + + KNConfigManager *configManager(); + KNNetAccess *netAccess(); + KNAccountManager *accountManager(); + KNGroupManager *groupManager(); + KNArticleManager *articleManager(); + KNFilterManager *filterManager(); + KNFolderManager *folderManager(); + KNScoringManager *scoringManager(); + KNMemoryManager *memoryManager(); + + /** forwarded to top->setStatusMsg() if available */ + void setStatusMsg(const QString& text = QString::null, int id = SB_MAIN); + +private: + KSharedConfig::Ptr c_onfig; + + KNNetAccess *mNetAccess; + KNConfigManager *mCfgManager; + KNAccountManager *mAccManager; + KNGroupManager *mGrpManager; + KNArticleManager *mArtManager; + KNFilterManager *mFilManager; + KNFolderManager *mFolManager; + static KNScoringManager *mScoreManager; + KNMemoryManager *mMemManager; +}; + + +extern KNGlobals knGlobals KDE_EXPORT; + +#endif diff --git a/knode/kngroup.cpp b/knode/kngroup.cpp new file mode 100644 index 000000000..1c8bdfbf1 --- /dev/null +++ b/knode/kngroup.cpp @@ -0,0 +1,1112 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + + +#include <ksimpleconfig.h> +#include <klocale.h> +#include <kdebug.h> + +#include <kqcstringsplitter.h> + +#include "knprotocolclient.h" +#include "knglobals.h" +#include "kncollectionviewitem.h" +#include "kngrouppropdlg.h" +#include "utilities.h" +#include "knconfigmanager.h" +#include "knmainwidget.h" +#include "knscoring.h" +#include "knarticlemanager.h" +#include "kngroupmanager.h" +#include "knnntpaccount.h" +#include "headerview.h" + + +#define SORT_DEPTH 5 + +KNGroup::KNGroup(KNCollection *p) + : KNArticleCollection(p), n_ewCount(0), l_astFetchCount(0), r_eadCount(0), i_gnoreCount(0), + l_astNr(0), m_axFetch(0), d_ynDataFormat(1), f_irstNew(-1), l_ocked(false), + u_seCharset(false), s_tatus(unknown), i_dentity(0) +{ + mCleanupConf = new KNConfig::Cleanup( false ); +} + + +KNGroup::~KNGroup() +{ + delete i_dentity; + delete mCleanupConf; +} + + +QString KNGroup::path() +{ + return p_arent->path(); +} + + +const QString& KNGroup::name() +{ + static QString ret; + if(n_ame.isEmpty()) ret=g_roupname; + else ret=n_ame; + return ret; +} + + +void KNGroup::updateListItem() +{ + if(!l_istItem) return; + l_istItem->setTotalCount( c_ount ); + l_istItem->setUnreadCount( c_ount - r_eadCount - i_gnoreCount ); + l_istItem->repaint(); +} + + +bool KNGroup::readInfo(const QString &confPath) +{ + KSimpleConfig info(confPath); + + g_roupname = info.readEntry("groupname"); + d_escription = info.readEntry("description"); + n_ame = info.readEntry("name"); + c_ount = info.readNumEntry("count",0); + r_eadCount = info.readNumEntry("read",0); + if (r_eadCount > c_ount) r_eadCount = c_ount; + f_irstNr = info.readNumEntry("firstMsg",0); + l_astNr = info.readNumEntry("lastMsg",0); + d_ynDataFormat = info.readNumEntry("dynDataFormat",0); + u_seCharset = info.readBoolEntry("useCharset", false); + d_efaultChSet = info.readEntry("defaultChSet").latin1(); + QString s = info.readEntry("status","unknown"); + if (s=="readOnly") + s_tatus = readOnly; + else if (s=="postingAllowed") + s_tatus = postingAllowed; + else if (s=="moderated") + s_tatus = moderated; + else + s_tatus = unknown; + c_rosspostIDBuffer = info.readListEntry("crosspostIDBuffer"); + + i_dentity=new KNConfig::Identity(false); + i_dentity->loadConfig(&info); + if(!i_dentity->isEmpty()) { + kdDebug(5003) << "KNGroup::readInfo(const QString &confPath) : using alternative user for " << g_roupname << endl; + } + else { + delete i_dentity; + i_dentity=0; + } + + mCleanupConf->loadConfig( &info ); + + return (!g_roupname.isEmpty()); +} + + +void KNGroup::saveInfo() +{ + QString dir(path()); + + if (!dir.isNull()) { + KSimpleConfig info(dir+g_roupname+".grpinfo"); + + info.writeEntry("groupname", g_roupname); + info.writeEntry("description", d_escription); + info.writeEntry("firstMsg", f_irstNr); + info.writeEntry("lastMsg", l_astNr); + info.writeEntry("count", c_ount); + info.writeEntry("read", r_eadCount); + info.writeEntry("dynDataFormat", d_ynDataFormat); + info.writeEntry("name", n_ame); + info.writeEntry("useCharset", u_seCharset); + info.writeEntry("defaultChSet", QString::fromLatin1(d_efaultChSet)); + switch (s_tatus) { + case unknown: info.writeEntry("status","unknown"); + break; + case readOnly: info.writeEntry("status","readOnly"); + break; + case postingAllowed: info.writeEntry("status","postingAllowed"); + break; + case moderated: info.writeEntry("status","moderated"); + break; + } + info.writeEntry("crosspostIDBuffer", c_rosspostIDBuffer); + + if(i_dentity) + i_dentity->saveConfig(&info); + else if(info.hasKey("Email")) { + info.deleteEntry("Name", false); + info.deleteEntry("Email", false); + info.deleteEntry("Reply-To", false); + info.deleteEntry("Mail-Copies-To", false); + info.deleteEntry("Org", false); + info.deleteEntry("UseSigFile", false); + info.deleteEntry("UseSigGenerator", false); + info.deleteEntry("sigFile", false); + info.deleteEntry("sigText", false); + } + + mCleanupConf->saveConfig( &info ); + } +} + + +KNNntpAccount* KNGroup::account() +{ + KNCollection *p=parent(); + while(p->type()!=KNCollection::CTnntpAccount) p=p->parent(); + + return (KNNntpAccount*)p_arent; +} + + +bool KNGroup::loadHdrs() +{ + if(isLoaded()) { + kdDebug(5003) << "KNGroup::loadHdrs() : nothing to load" << endl; + return true; + } + + kdDebug(5003) << "KNGroup::loadHdrs() : loading headers" << endl; + QCString buff, hdrValue; + KNFile f; + KQCStringSplitter split; + int cnt=0, id, lines, fileFormatVersion, artNumber; + unsigned int timeT; + KNRemoteArticle *art; + + QString dir(path()); + if (dir.isNull()) + return false; + + f.setName(dir+g_roupname+".static"); + + if(f.open(IO_ReadOnly)) { + + if(!resize(c_ount)) { + f.close(); + return false; + } + + while(!f.atEnd()) { + buff=f.readLine(); + if(buff.isEmpty()){ + if (f.status() == IO_Ok) { + kdWarning(5003) << "Found broken line in static-file: Ignored!" << endl; + continue; + } else { + kdError(5003) << "Corrupted static file, IO-error!" << endl; + clear(); + return false; + } + } + + split.init(buff, "\t"); + + art=new KNRemoteArticle(this); + + split.first(); + art->messageID()->from7BitString(split.string()); + + split.next(); + art->subject()->from7BitString(split.string()); + + split.next(); + art->from()->setEmail(split.string()); + split.next(); + if(split.string()!="0") + art->from()->setNameFrom7Bit(split.string()); + + buff=f.readLine(); + if(buff!="0") art->references()->from7BitString(buff.copy()); + + buff=f.readLine(); + if (sscanf(buff,"%d %d %u %d", &id, &lines, &timeT, &fileFormatVersion) < 4) + fileFormatVersion = 0; // KNode <= 0.4 had no version number + art->setId(id); + art->lines()->setNumberOfLines(lines); + art->date()->setUnixTime(timeT); + + if (fileFormatVersion > 0) { + buff=f.readLine(); + sscanf(buff,"%d", &artNumber); + art->setArticleNumber(artNumber); + } + + // optional headers + if (fileFormatVersion > 1) { + // first line is the number of addiotion headers + buff = f.readLine(); + // following lines contain one header per line + for (uint i = buff.toUInt(); i > 0; --i) { + buff = f.readLine(); + int pos = buff.find(':'); + QCString hdrName = buff.left( pos ); + // skip headers we already set above and which we actually never should + // find here, but however it still happens... (eg. #101355) + if ( hdrName == "Subject" || hdrName == "From" || hdrName == "Date" + || hdrName == "Message-ID" || hdrName == "References" + || hdrName == "Bytes" || hdrName == "Lines" ) + continue; + hdrValue = buff.right( buff.length() - (pos + 2) ); + if (hdrValue.length() > 0) + art->setHeader( new KMime::Headers::Generic( hdrName, art, hdrValue ) ); + } + } + + if(append(art)) cnt++; + else { + f.close(); + clear(); + return false; + } + } + + setLastID(); + f.close(); + } + else { + clear(); + return false; + } + + + f.setName(dir+g_roupname+".dynamic"); + + if (f.open(IO_ReadOnly)) { + + dynDataVer0 data0; + dynDataVer1 data1; + int readCnt=0,byteCount,dataSize; + if (d_ynDataFormat==0) + dataSize = sizeof(data0); + else + dataSize = sizeof(data1); + + while(!f.atEnd()) { + + if (d_ynDataFormat==0) + byteCount = f.readBlock((char*)(&data0), dataSize); + else + byteCount = f.readBlock((char*)(&data1), dataSize); + if ((byteCount == -1)||(byteCount!=dataSize)) + if (f.status() == IO_Ok) { + kdWarning(5003) << "Found broken entry in dynamic-file: Ignored!" << endl; + continue; + } else { + kdError(5003) << "Corrupted dynamic file, IO-error!" << endl; + clear(); + return false; + } + + if (d_ynDataFormat==0) + art=byId(data0.id); + else + art=byId(data1.id); + + if(art) { + if (d_ynDataFormat==0) + data0.getData(art); + else + data1.getData(art); + + if (art->isRead()) readCnt++; + } + + } + + f.close(); + + r_eadCount=readCnt; + + } + + else { + clear(); + return false; + } + + kdDebug(5003) << cnt << " articles read from file" << endl; + c_ount=length(); + + // convert old data files into current format: + if (d_ynDataFormat!=1) { + saveDynamicData(length(), true); + d_ynDataFormat=1; + } + + // restore "New" - flags + if( f_irstNew > -1 ) { + for( int i = f_irstNew; i < length(); i++ ) { + at(i)->setNew(true); + } + } + + updateThreadInfo(); + processXPostBuffer(false); + return true; +} + + +bool KNGroup::unloadHdrs(bool force) +{ + if(l_ockedArticles>0) + return false; + + if (!force && isNotUnloadable()) + return false; + + KNRemoteArticle *a; + for(int idx=0; idx<length(); idx++) { + a=at(idx); + if (a->hasContent() && !knGlobals.articleManager()->unloadArticle(a, force)) + return false; + } + syncDynamicData(); + clear(); + + return true; +} + + +// Attention: this method is called from the network thread! +void KNGroup::insortNewHeaders(QStrList *hdrs, QStrList *hdrfmt, KNProtocolClient *client) +{ + KNRemoteArticle *art=0, *art2=0; + QCString data, hdr, hdrName; + KQCStringSplitter split; + split.setIncludeSep(false); + int new_cnt=0, added_cnt=0, todo=hdrs->count(); + QTime timer; + + l_astFetchCount=0; + + if(!hdrs || hdrs->count()==0) + return; + + timer.start(); + + //resize the list + if(!resize(size()+hdrs->count())) return; + + // recreate msg-ID index + syncSearchIndex(); + + // remember index of first new + if(f_irstNew == -1) + f_irstNew = length(); // index of last + 1 + + for(char *line=hdrs->first(); line; line=hdrs->next()) { + split.init(line, "\t"); + + //new Header-Object + art=new KNRemoteArticle(this); + art->setNew(true); + + //Article Number + split.first(); + art->setArticleNumber(split.string().toInt()); + + //Subject + split.next(); + art->subject()->from7BitString(split.string()); + if(art->subject()->isEmpty()) + art->subject()->fromUnicodeString(i18n("no subject"), art->defaultCharset()); + + //From and Email + split.next(); + art->from()->from7BitString(split.string()); + + //Date + split.next(); + art->date()->from7BitString(split.string()); + + //Message-ID + split.next(); + art->messageID()->from7BitString(split.string().simplifyWhiteSpace()); + + //References + split.next(); + if(!split.string().isEmpty()) + art->references()->from7BitString(split.string()); //use QCString::copy() ? + + // Bytes + split.next(); + + //Lines + split.next(); + art->lines()->setNumberOfLines(split.string().toInt()); + + // optinal additional headers + mOptionalHeaders = *hdrfmt; + for (hdr = hdrfmt->first(); hdr; hdr = hdrfmt->next()) { + if (!split.next()) + break; + data = split.string(); + int pos = hdr.find(':'); + hdrName = hdr.left( pos ); + // if the header format is 'full' we have to strip the header name + if (hdr.findRev("full") == (int)(hdr.length() - 4)) + data = data.right( data.length() - (hdrName.length() + 2) ); + + // add header + art->setHeader( new KMime::Headers::Generic( hdrName, art, data ) ); + } + + // check if we have this article already in this group, + // if so mark it as new (useful with leafnodes delay-body function) + art2=byMessageId(art->messageID()->as7BitString(false)); + if(art2) { // ok, we already have this article + art2->setNew(true); + art2->setArticleNumber(art->articleNumber()); + delete art; + new_cnt++; + } + else if (append(art)) { + added_cnt++; + new_cnt++; + } + else { + delete art; + return; + } + + if (timer.elapsed() > 200) { // don't flicker + timer.restart(); + if (client) client->updatePercentage((new_cnt*30)/todo); + } + } + + // now we build the threads + syncSearchIndex(); // recreate the msgId-index so it contains the appended headers + buildThreads(added_cnt, client); + updateThreadInfo(); + + // save the new headers + saveStaticData(added_cnt); + saveDynamicData(added_cnt); + + // update group-info + c_ount=length(); + n_ewCount+=new_cnt; + l_astFetchCount=new_cnt; + updateListItem(); + saveInfo(); +} + + +int KNGroup::saveStaticData(int cnt,bool ovr) +{ + int idx, savedCnt=0, mode; + KNRemoteArticle *art; + + QString dir(path()); + if (dir.isNull()) + return 0; + + QFile f(dir+g_roupname+".static"); + + if(ovr) mode=IO_WriteOnly; + else mode=IO_WriteOnly | IO_Append; + + if(f.open(mode)) { + + QTextStream ts(&f); + ts.setEncoding(QTextStream::Latin1); + + for(idx=length()-cnt; idx<length(); idx++) { + + art=at(idx); + + if(art->isExpired()) continue; + + ts << art->messageID()->as7BitString(false) << '\t'; + ts << art->subject()->as7BitString(false) << '\t'; + ts << art->from()->email() << '\t'; + + if(art->from()->hasName()) + ts << art->from()->nameAs7Bit() << '\n'; + else + ts << "0\n"; + + if(!art->references()->isEmpty()) + ts << art->references()->as7BitString(false) << "\n"; + else + ts << "0\n"; + + ts << art->id() << ' '; + ts << art->lines()->numberOfLines() << ' '; + ts << art->date()->unixTime() << ' '; + ts << "2\n"; // version number to achieve backward compatibility easily + + ts << art->articleNumber() << '\n'; + + // optional headers + ts << mOptionalHeaders.count() << '\n'; + for (QCString hdrName = mOptionalHeaders.first(); hdrName; hdrName = mOptionalHeaders.next()) { + hdrName = hdrName.left( hdrName.find(':') ); + KMime::Headers::Base *hdr = art->getHeaderByType( hdrName ); + if ( hdr ) + ts << hdrName << ": " << hdr->asUnicodeString() << '\n'; + else + ts << hdrName << ": \n"; + } + + savedCnt++; + } + + f.close(); + } + + return savedCnt; +} + + +void KNGroup::saveDynamicData(int cnt,bool ovr) +{ + dynDataVer1 data; + int mode; + KNRemoteArticle *art; + + if(length()>0) { + QString dir(path()); + if (dir.isNull()) + return; + + QFile f(dir+g_roupname+".dynamic"); + + if(ovr) mode=IO_WriteOnly; + else mode=IO_WriteOnly | IO_Append; + + if(f.open(mode)) { + + for(int idx=length()-cnt; idx<length(); idx++) { + art=at(idx); + if(art->isExpired()) continue; + data.setData(art); + f.writeBlock((char*)(&data), sizeof(data)); + art->setChanged(false); + } + f.close(); + } + else KNHelper::displayInternalFileError(); + } +} + + +void KNGroup::syncDynamicData() +{ + dynDataVer1 data; + int cnt=0, readCnt=0, sOfData; + KNRemoteArticle *art; + + if(length()>0) { + + QString dir(path()); + if (dir.isNull()) + return; + + QFile f(dir+g_roupname+".dynamic"); + + if(f.open(IO_ReadWrite)) { + + sOfData=sizeof(data); + + for(int i=0; i<length(); i++) { + art=at(i); + + if(art->hasChanged() && !art->isExpired()) { + + data.setData(art); + f.at(i*sOfData); + f.writeBlock((char*) &data, sOfData); + cnt++; + art->setChanged(false); + } + + if(art->isRead() && !art->isExpired()) readCnt++; + } + + f.close(); + + kdDebug(5003) << g_roupname << " => updated " << cnt << " entries of dynamic data" << endl; + + r_eadCount=readCnt; + } + else KNHelper::displayInternalFileError(); + } +} + + +void KNGroup::appendXPostID(const QString &id) +{ + c_rosspostIDBuffer.append(id); +} + + +void KNGroup::processXPostBuffer(bool deleteAfterwards) +{ + QStringList remainder; + KNRemoteArticle *xp; + KNRemoteArticle::List al; + + for (QStringList::Iterator it = c_rosspostIDBuffer.begin(); it != c_rosspostIDBuffer.end(); ++it) { + if ((xp=byMessageId((*it).local8Bit()))) + al.append(xp); + else + remainder.append(*it); + } + knGlobals.articleManager()->setRead(al, true, false); + + if (!deleteAfterwards) + c_rosspostIDBuffer = remainder; + else + c_rosspostIDBuffer.clear(); +} + + +void KNGroup::buildThreads(int cnt, KNProtocolClient *client) +{ + int end=length(), + start=end-cnt, + foundCnt=0, bySubCnt=0, refCnt=0, + resortCnt=0, idx, oldRef; // idRef; + KNRemoteArticle *art, *ref; + QTime timer; + + timer.start(); + + // this method is called from the nntp-thread!!! +#ifndef NDEBUG + qDebug("knode: KNGroup::buildThreads() : start = %d end = %d",start,end); +#endif + + //resort old hdrs + if(start>0) + for(idx=0; idx<start; idx++) { + art=at(idx); + if(art->threadingLevel()>1) { + oldRef=art->idRef(); + ref=findReference(art); + if(ref) { + // this method is called from the nntp-thread!!! + #ifndef NDEBUG + qDebug("knode: %d: old %d new %d",art->id(), oldRef, art->idRef()); + #endif + resortCnt++; + art->setChanged(true); + } + } + } + + + for(idx=start; idx<end; idx++) { + + art=at(idx); + + if(art->idRef()==-1 && !art->references()->isEmpty() ){ //hdr has references + refCnt++; + if(findReference(art)) + foundCnt++; + } + else { + if(art->subject()->isReply()) { + art->setIdRef(0); //hdr has no references + art->setThreadingLevel(0); + } + else if(art->idRef()==-1) + refCnt++; + } + + if (timer.elapsed() > 200) { // don't flicker + timer.restart(); + if(client) + client->updatePercentage(30+((foundCnt)*70)/cnt); + } + } + + + if(foundCnt<refCnt) { // some references could not been found + + //try to sort by subject + KNRemoteArticle *oldest; + KNRemoteArticle::List list; + + for(idx=start; idx<end; idx++) { + + art=at(idx); + + if(art->idRef()==-1) { //for all not sorted headers + + list.clear(); + list.append(art); + + //find all headers with same subject + for(int idx2=0; idx2<length(); idx2++) + if(at(idx2)==art) continue; + else if(at(idx2)->subject()==art->subject()) + list.append(at(idx2)); + + if(list.count()==1) { + art->setIdRef(0); + art->setThreadingLevel(6); + bySubCnt++; + } + else { + + //find oldest + oldest=list.first(); + for ( KNRemoteArticle::List::Iterator it = list.begin(); it != list.end(); ++it ) + if ( (*it)->date()->unixTime() < oldest->date()->unixTime() ) + oldest = (*it); + + //oldest gets idRef 0 + if(oldest->idRef()==-1) bySubCnt++; + oldest->setIdRef(0); + oldest->setThreadingLevel(6); + + for ( KNRemoteArticle::List::Iterator it = list.begin(); it != list.end(); ++it ) { + if ( (*it) == oldest ) + continue; + if ( (*it)->idRef() == -1 || ( (*it)->idRef() != -1 && (*it)->threadingLevel() == 6 ) ) { + (*it)->setIdRef(oldest->id()); + (*it)->setThreadingLevel(6); + if ( (*it)->id() >= at(start)->id() ) + bySubCnt++; + } + } + } + } + + if (timer.elapsed() > 200) { // don't flicker + timer.restart(); + if (client) client->updatePercentage(30+((bySubCnt+foundCnt)*70)/cnt); + } + } + } + + //all not found items get refID 0 + for (int idx=start; idx<end; idx++){ + art=at(idx); + if(art->idRef()==-1) { + art->setIdRef(0); + art->setThreadingLevel(6); //was 0 !!! + } + } + + //check for loops in threads + int startId; + bool isLoop; + int iterationCount; + for (int idx=start; idx<end; idx++){ + art=at(idx); + startId=art->id(); + isLoop=false; + iterationCount=0; + while(art->idRef()!=0 && !isLoop && (iterationCount < end)) { + art=byId(art->idRef()); + isLoop=(art->id()==startId); + iterationCount++; + } + + if(isLoop) { + // this method is called from the nntp-thread!!! + #ifndef NDEBUG + qDebug("knode: Sorting : loop in %d",startId); + #endif + art=at(idx); + art->setIdRef(0); + art->setThreadingLevel(0); + } + } + + // propagate ignored/watched flags to new headers + for(int idx=start; idx<end; idx++) { + art=at(idx); + int idRef=art->idRef(); + int tmpIdRef; + + if(idRef!=0) { + while(idRef!=0) { + art=byId(idRef); + tmpIdRef=art->idRef(); + idRef = (idRef!=tmpIdRef)? tmpIdRef : 0; + + } + if (art) { + if (art->isIgnored()) { + at(idx)->setIgnored(true); + ++i_gnoreCount; + } + at(idx)->setWatched(art->isWatched()); + } + } + } + + // this method is called from the nntp-thread!!! +#ifndef NDEBUG + qDebug("knode: Sorting : %d headers resorted", resortCnt); + qDebug("knode: Sorting : %d references of %d found", foundCnt, refCnt); + qDebug("knode: Sorting : %d references of %d sorted by subject", bySubCnt, refCnt); +#endif +} + + +KNRemoteArticle* KNGroup::findReference(KNRemoteArticle *a) +{ + int found=false; + QCString ref_mid; + int ref_nr=0; + KNRemoteArticle *ref_art=0; + + ref_mid=a->references()->first(); + + while(!found && !ref_mid.isNull() && ref_nr < SORT_DEPTH) { + ref_art=byMessageId(ref_mid); + if(ref_art) { + found=true; + a->setThreadingLevel(ref_nr+1); + a->setIdRef(ref_art->id()); + } + ref_nr++; + ref_mid=a->references()->next(); + } + + return ref_art; +} + + +void KNGroup::scoreArticles(bool onlynew) +{ + kdDebug(5003) << "KNGroup::scoreArticles()" << endl; + int len=length(), + todo=(onlynew)? lastFetchCount():length(); + + if (todo) { + // reset the notify collection + delete KNScorableArticle::notifyC; + KNScorableArticle::notifyC = 0; + + kdDebug(5003) << "scoring " << newCount() << " articles" << endl; + kdDebug(5003) << "(total " << length() << " article in group)" << endl; + knGlobals.top->setCursorBusy(true); + knGlobals.setStatusMsg(i18n(" Scoring...")); + + int defScore; + KScoringManager *sm = knGlobals.scoringManager(); + sm->initCache(groupname()); + for(int idx=0; idx<todo; idx++) { + KNRemoteArticle *a = at(len-idx-1); + if ( !a ) { + kdWarning( 5003 ) << "found no article at " << len-idx-1 << endl; + continue; + } + + defScore = 0; + if (a->isIgnored()) + defScore = knGlobals.configManager()->scoring()->ignoredThreshold(); + else if (a->isWatched()) + defScore = knGlobals.configManager()->scoring()->watchedThreshold(); + + if (a->score() != defScore) { + a->setScore(defScore); + a->setChanged(true); + } + + bool read = a->isRead(); + + KNScorableArticle sa(a); + sm->applyRules(sa); + + if ( a->isRead() != read && !read ) + incReadCount(); + } + + knGlobals.setStatusMsg(QString::null); + knGlobals.top->setCursorBusy(false); + + //kdDebug(5003) << KNScorableArticle::notifyC->collection() << endl; + if (KNScorableArticle::notifyC) + KNScorableArticle::notifyC->displayCollection(knGlobals.topWidget); + } +} + + +void KNGroup::reorganize() +{ + kdDebug(5003) << "KNGroup::reorganize()" << endl; + + knGlobals.top->setCursorBusy(true); + knGlobals.setStatusMsg(i18n(" Reorganizing headers...")); + + for(int idx=0; idx<length(); idx++) { + KNRemoteArticle *a = at(idx); + Q_ASSERT( a ); + a->setId(idx+1); //new ids + a->setIdRef(-1); + a->setThreadingLevel(0); + } + + buildThreads(length()); + saveStaticData(length(), true); + saveDynamicData(length(), true); + knGlobals.top->headerView()->repaint(); + knGlobals.setStatusMsg(QString::null); + knGlobals.top->setCursorBusy(false); +} + + +void KNGroup::updateThreadInfo() +{ + KNRemoteArticle *ref; + bool brokenThread=false; + + for(int idx=0; idx<length(); idx++) { + at(idx)->setUnreadFollowUps(0); + at(idx)->setNewFollowUps(0); + } + + for(int idx=0; idx<length(); idx++) { + int idRef=at(idx)->idRef(); + int tmpIdRef; + int iterCount=1; // control iteration count to avoid infinite loops + while((idRef!=0) && (iterCount <= length())) { + ref=byId(idRef); + if(!ref) { + brokenThread=true; + break; + } + + if(!at(idx)->isRead()) { + ref->incUnreadFollowUps(); + if(at(idx)->isNew()) ref->incNewFollowUps(); + } + tmpIdRef=ref->idRef(); + idRef= (idRef!=tmpIdRef) ? ref->idRef() : 0; + iterCount++; + } + if(iterCount > length()) + brokenThread=true; + if(brokenThread) break; + } + + if(brokenThread) { + kdWarning(5003) << "KNGroup::updateThreadInfo() : Found broken threading infos! Restoring ..." << endl; + reorganize(); + updateThreadInfo(); + } +} + + +void KNGroup::showProperties() +{ + if(!i_dentity) i_dentity=new KNConfig::Identity(false); + KNGroupPropDlg *d=new KNGroupPropDlg(this, knGlobals.topWidget); + + if(d->exec()) + if(d->nickHasChanged()) + l_istItem->setText(0, name()); + + if(i_dentity->isEmpty()) { + delete i_dentity; + i_dentity=0; + } + + delete d; +} + + +int KNGroup::statThrWithNew() +{ + int cnt=0; + for(int i=0; i<length(); i++) + if( (at(i)->idRef()==0) && (at(i)->hasNewFollowUps()) ) cnt++; + return cnt; +} + + +int KNGroup::statThrWithUnread() +{ + int cnt=0; + for(int i=0; i<length(); i++) + if( (at(i)->idRef()==0) && (at(i)->hasUnreadFollowUps()) ) cnt++; + return cnt; +} + +QString KNGroup::prepareForExecution() +{ + if (knGlobals.groupManager()->loadHeaders(this)) + return QString::null; + else + return i18n("Cannot load saved headers: %1").arg(groupname()); +} + +//*************************************************************************** + +void KNGroup::dynDataVer0::setData(KNRemoteArticle *a) +{ + id=a->id(); + idRef=a->idRef(); + thrLevel=a->threadingLevel(); + read=a->getReadFlag(); + score=a->score(); +} + + +void KNGroup::dynDataVer0::getData(KNRemoteArticle *a) +{ + a->setId(id); + a->setIdRef(idRef); + a->setRead(read); + a->setThreadingLevel(thrLevel); + a->setScore(score); +} + + +void KNGroup::dynDataVer1::setData(KNRemoteArticle *a) +{ + id=a->id(); + idRef=a->idRef(); + thrLevel=a->threadingLevel(); + read=a->getReadFlag(); + score=a->score(); + ignoredWatched = 0; + if (a->isWatched()) + ignoredWatched = 1; + else if (a->isIgnored()) + ignoredWatched = 2; +} + + +void KNGroup::dynDataVer1::getData(KNRemoteArticle *a) +{ + a->setId(id); + a->setIdRef(idRef); + a->setRead(read); + a->setThreadingLevel(thrLevel); + a->setScore(score); + a->setWatched(ignoredWatched==1); + a->setIgnored(ignoredWatched==2); +} + + +KNConfig::Cleanup * KNGroup::activeCleanupConfig() +{ + if (!cleanupConfig()->useDefault()) + return cleanupConfig(); + return account()->activeCleanupConfig(); +} diff --git a/knode/kngroup.h b/knode/kngroup.h new file mode 100644 index 000000000..8158e192b --- /dev/null +++ b/knode/kngroup.h @@ -0,0 +1,200 @@ +/* + kngroup.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNGROUP_H +#define KNGROUP_H + +#include "knarticlecollection.h" +#include "knjobdata.h" +#include "knarticle.h" + +class QStrList; + +class KNProtocolClient; +class KNNntpAccount; + +namespace KNConfig { + class Identity; + class Cleanup; +} + + +class KNGroup : public KNArticleCollection , public KNJobItem { + + public: + enum Status { unknown=0, readOnly=1, postingAllowed=2, moderated=3 }; + + KNGroup(KNCollection *p=0); + ~KNGroup(); + + /** type */ + collectionType type() { return CTgroup; } + + /** list-item handling */ + void updateListItem(); + + /** info */ + QString path(); + bool readInfo(const QString &confPath); + void saveInfo(); + + /** name */ + bool hasName() const { return (!n_ame.isEmpty()); } + const QString& name(); + const QString& groupname() { return g_roupname; } + void setGroupname(const QString &s) { g_roupname=s; } + const QString& description() { return d_escription; } + void setDescription(const QString &s) { d_escription=s; } + + /** count + numbers */ + int newCount() const { return n_ewCount; } + void setNewCount(int i) { n_ewCount=i; } + void incNewCount(int i=1) { n_ewCount+=i; } + void decNewCount(int i=1) { n_ewCount-=i; } + int firstNewIndex() const { return f_irstNew; } + void setFirstNewIndex(int i) { f_irstNew=i; } + + int lastFetchCount() const { return l_astFetchCount; } + void setLastFetchCount(int i) { l_astFetchCount=i; } + + int readCount()const { return r_eadCount; } + void setReadCount(int i) { r_eadCount=i; } + void incReadCount(int i=1) { r_eadCount+=i; } + void decReadCount(int i=1) { r_eadCount-=i; } + + int firstNr() const { return f_irstNr; } + void setFirstNr(int i) { f_irstNr=i; } + int lastNr() const { return l_astNr; } + void setLastNr(int i) { l_astNr=i; } + int maxFetch() const { return m_axFetch; } + void setMaxFetch(int i) { m_axFetch=i; } + + int statThrWithNew(); + int statThrWithUnread(); + + /** article access */ + KNRemoteArticle* at(int i) { return static_cast<KNRemoteArticle*> (KNArticleCollection::at(i)); } + KNRemoteArticle* byId(int id) { return static_cast<KNRemoteArticle*> (KNArticleCollection::byId(id)); } + KNRemoteArticle* byMessageId(const QCString &mId) + { return static_cast<KNRemoteArticle*> (KNArticleCollection::byMessageId(mId)); } + /** load + save */ + bool loadHdrs(); + bool unloadHdrs(bool force=true); + void insortNewHeaders(QStrList *hdrs, QStrList *hdrfmt, KNProtocolClient *client=0); + int saveStaticData(int cnt,bool ovr=false); + void saveDynamicData(int cnt,bool ovr=false); + void syncDynamicData(); + + /** mark articles with this id as read when we later load the headers / fetch new articles */ + void appendXPostID(const QString &id); + void processXPostBuffer(bool deleteAfterwards); + + /** article handling */ + void updateThreadInfo(); + void reorganize(); + void scoreArticles(bool onlynew=true); + + /** locking */ + bool isLocked() { return l_ocked; } + void setLocked(bool l) { l_ocked=l; } + + QString prepareForExecution(); + + /** charset-handling */ + const QCString defaultCharset() { return d_efaultChSet; } + void setDefaultCharset(const QCString &s) { d_efaultChSet=s; } + bool useCharset() { return ( u_seCharset && !d_efaultChSet.isEmpty() ); } + void setUseCharset(bool b) { u_seCharset=b; } + + // misc + KNNntpAccount* account(); + KNConfig::Identity* identity()const { return i_dentity; } + void setIdentity(KNConfig::Identity *i) { i_dentity=i; } + Status status()const { return s_tatus; } + void setStatus(Status s) { s_tatus=s; } + void showProperties(); + + // cleanup configuration + KNConfig::Cleanup *cleanupConfig() const { return mCleanupConf; } + KNConfig::Cleanup *activeCleanupConfig(); + + + protected: + void buildThreads(int cnt, KNProtocolClient *client=0); + KNRemoteArticle* findReference(KNRemoteArticle *a); + + int n_ewCount, + l_astFetchCount, + r_eadCount, + i_gnoreCount, + f_irstNr, + l_astNr, + m_axFetch, + d_ynDataFormat, + f_irstNew; + + QCString d_efaultChSet; + QString g_roupname, + d_escription; + + bool l_ocked, + u_seCharset; + + Status s_tatus; + + QStringList c_rosspostIDBuffer; + + /** Optional headers provided by the XOVER command + * These headers will be saved within the static data + */ + QStrList mOptionalHeaders; + + KNConfig::Identity *i_dentity; + KNConfig::Cleanup *mCleanupConf; + + class dynDataVer0 { + + public: + dynDataVer0() { id=-1; idRef=-1; read=0; thrLevel=0; score=50; } + ~dynDataVer0() {} + void setData(KNRemoteArticle *a); + void getData(KNRemoteArticle *a); + + int id; + int idRef; + bool read; + short thrLevel, score; + }; + + class dynDataVer1 { + + public: + dynDataVer1() { id=-1; idRef=-1; read=0; thrLevel=0; score=0, ignoredWatched=0; } + void setData(KNRemoteArticle *a); + void getData(KNRemoteArticle *a); + + int id; + int idRef; + bool read; + short thrLevel, score; + char ignoredWatched; + }; + +}; + +#endif + +// kate: space-indent on; indent-width 2; diff --git a/knode/kngroupbrowser.cpp b/knode/kngroupbrowser.cpp new file mode 100644 index 000000000..17342a4be --- /dev/null +++ b/knode/kngroupbrowser.cpp @@ -0,0 +1,481 @@ +/* + kngroupbrowser.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qcheckbox.h> +#include <qtimer.h> +#include <qapplication.h> + +#include <kseparator.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kdebug.h> +#include <klineedit.h> + +#include "knnetaccess.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "knnntpaccount.h" +#include "kngroupbrowser.h" +#include <qlabel.h> +#include <qpushbutton.h> + + +KNGroupBrowser::KNGroupBrowser(QWidget *parent, const QString &caption, KNNntpAccount *a, + int buttons, bool newCBact, const QString &user1, const QString &user2) : + KDialogBase( parent, 0L, true, caption, buttons | Help | Ok | Cancel, Ok, true, user1, user2 ), + incrementalFilter(false), a_ccount(a) +{ + refilterTimer = new QTimer(); + + allList=new QSortedList<KNGroupInfo>; + allList->setAutoDelete(true); + matchList=new QSortedList<KNGroupInfo>; + matchList->setAutoDelete(false); + + //create Widgets + page=new QWidget(this); + setMainWidget(page); + + filterEdit=new KLineEdit(page); + QLabel *l=new QLabel(filterEdit,i18n("S&earch:"), page); + noTreeCB=new QCheckBox(i18n("Disable &tree view"), page); + noTreeCB->setChecked(false); + subCB=new QCheckBox(i18n("&Subscribed only"), page); + subCB->setChecked(false); + newCB=new QCheckBox(i18n("&New only"), page); + if (!newCBact) + newCB->hide(); + newCB->setChecked(false); + KSeparator *sep=new KSeparator(KSeparator::HLine, page); + + QFont fnt=font(); + fnt.setBold(true); + leftLabel=new QLabel(i18n("Loading groups..."), page); + rightLabel=new QLabel(page); + leftLabel->setFont(fnt); + rightLabel->setFont(fnt); + + pmGroup=knGlobals.configManager()->appearance()->icon(KNConfig::Appearance::group); + pmNew=knGlobals.configManager()->appearance()->icon(KNConfig::Appearance::redBall); + pmRight=BarIconSet( QApplication::reverseLayout()? "back": "forward"); + pmLeft=BarIconSet( QApplication::reverseLayout() ? "forward" : "back"); + + arrowBtn1=new QPushButton(page); + arrowBtn1->setEnabled(false); + arrowBtn2=new QPushButton(page); + arrowBtn2->setEnabled(false); + arrowBtn1->setIconSet(pmRight); + arrowBtn2->setIconSet(pmLeft); + arrowBtn1->setFixedSize(35,30); + arrowBtn2->setFixedSize(35,30); + + groupView=new QListView(page); + groupView->setRootIsDecorated(true); + groupView->addColumn(i18n("Name")); + groupView->addColumn(i18n("Description")); + groupView->setTreeStepSize(15); + + connect(groupView, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT(slotItemDoubleClicked(QListViewItem*))); + + //layout + QGridLayout *topL=new QGridLayout(page,3,1,0,5); + QHBoxLayout *filterL=new QHBoxLayout(10); + QVBoxLayout *arrL=new QVBoxLayout(10); + listL=new QGridLayout(2, 3, 5); + + topL->addLayout(filterL, 0,0); + topL->addWidget(sep,1,0); + topL->addLayout(listL, 2,0); + + filterL->addWidget(l); + filterL->addWidget(filterEdit, 1); + filterL->addWidget(noTreeCB); + filterL->addWidget(subCB); + if (newCBact) + filterL->addWidget(newCB); + + listL->addWidget(leftLabel, 0,0); + listL->addWidget(rightLabel, 0,2); + listL->addWidget(groupView, 1,0); + listL->addLayout(arrL, 1,1); + listL->setRowStretch(1,1); + listL->setColStretch(0,5); + listL->setColStretch(2,2); + + arrL->addWidget(arrowBtn1, AlignCenter); + arrL->addWidget(arrowBtn2, AlignCenter); + + //connect + connect(filterEdit, SIGNAL(textChanged(const QString&)), + SLOT(slotFilterTextChanged(const QString&))); + connect(groupView, SIGNAL(expanded(QListViewItem*)), + SLOT(slotItemExpand(QListViewItem*))); + + connect(refilterTimer, SIGNAL(timeout()), SLOT(slotRefilter())); + connect(noTreeCB, SIGNAL(clicked()), SLOT(slotTreeCBToggled())); + connect(subCB, SIGNAL(clicked()), SLOT(slotSubCBToggled())); + connect(newCB, SIGNAL(clicked()), SLOT(slotNewCBToggled())); + + enableButton(User1,false); + enableButton(User2,false); + + filterEdit->setFocus(); + + QTimer::singleShot(2, this, SLOT(slotLoadList())); +} + + +KNGroupBrowser::~KNGroupBrowser() +{ + + knGlobals.netAccess()->stopJobsNntp(KNJobData::JTLoadGroups); + knGlobals.netAccess()->stopJobsNntp(KNJobData::JTFetchGroups); + knGlobals.netAccess()->stopJobsNntp(KNJobData::JTCheckNewGroups); + + delete matchList; + delete allList; + delete refilterTimer; +} + + +void KNGroupBrowser::slotReceiveList(KNGroupListData* d) +{ + enableButton(User1,true); + enableButton(User2,true); + + if (d) { // d==0 if something has gone wrong... + delete allList; + allList = d->extractList(); + incrementalFilter=false; + slotRefilter(); + } +} + + +void KNGroupBrowser::changeItemState(const KNGroupInfo &gi, bool s) +{ + QListViewItemIterator it(groupView); + + for( ; it.current(); ++it) + if (it.current()->isSelectable() && (static_cast<CheckItem*>(it.current())->info==gi)) + static_cast<CheckItem*>(it.current())->setChecked(s); +} + + +bool KNGroupBrowser::itemInListView(QListView *view, const KNGroupInfo &gi) +{ + if(!view) return false; + QListViewItemIterator it(view); + + for( ; it.current(); ++it) + if(static_cast<GroupItem*>(it.current())->info==gi) + return true; + + return false; +} + + +void KNGroupBrowser::createListItems(QListViewItem *parent) +{ + QString prefix, tlgn, compare; + QListViewItem *it; + CheckItem *cit; + int colon; + bool expandit=false; + + if(parent) { + QListViewItem *p=parent; + while(p) { + prefix.prepend(p->text(0)); + p=p->parent(); + } + } + + for(KNGroupInfo *gn=matchList->first(); gn; gn=matchList->next()) { + + if(!prefix.isEmpty() && !gn->name.startsWith(prefix)) + if(!compare.isNull()) + break; + else + continue; + + compare=gn->name.mid(prefix.length()); + + if(!expandit || !compare.startsWith(tlgn)) { + if((colon=compare.find('.'))!=-1) { + colon++; + expandit=true; + } else { + colon=compare.length(); + expandit=false; + } + + tlgn = compare.left(colon); + + if(expandit) { + if(parent) + it=new QListViewItem(parent, tlgn); + else + it=new QListViewItem(groupView, tlgn); + + it->setSelectable(false); + it->setExpandable(true); + } + else { + if(parent) + cit=new CheckItem(parent, *gn, this); + else + cit=new CheckItem(groupView, *gn, this); + updateItemState(cit); + } + } + } +} + + +void KNGroupBrowser::removeListItem(QListView *view, const KNGroupInfo &gi) +{ + if(!view) return; + QListViewItemIterator it(view); + + for( ; it.current(); ++it) + if(static_cast<GroupItem*>(it.current())->info==gi) { + delete it.current(); + break; + } +} + + +void KNGroupBrowser::slotLoadList() +{ + emit(loadList(a_ccount)); +} + + +void KNGroupBrowser::slotItemExpand(QListViewItem *it) +{ + if(!it) return; + + if(it->childCount()) { + kdDebug(5003) << "KNGroupBrowser::slotItemExpand() : has already been expanded, returning" << endl; + return; + } + + createListItems(it); + + // center the item - smart scrolling + delayedCenter = -1; + int y = groupView->itemPos(it); + int h = it->height(); + + if ( (y+h*4+5) >= (groupView->contentsY()+groupView->visibleHeight()) ) + { + groupView->ensureVisible(groupView->contentsX(), y+h/2, 0, h/2); + delayedCenter = y+h/2; + QTimer::singleShot(300, this, SLOT(slotCenterDelayed())); + } +} + + +void KNGroupBrowser::slotCenterDelayed() +{ + if (delayedCenter != -1) + groupView->ensureVisible(groupView->contentsX(), delayedCenter, 0, groupView->visibleHeight()/2); +} + + +void KNGroupBrowser::slotItemDoubleClicked(QListViewItem *it) +{ + if (it && (it->childCount()==0)) static_cast<CheckItem*>(it)->setOn(!static_cast<CheckItem*>(it)->isOn()); +} + + +#define MIN_FOR_TREE 200 +void KNGroupBrowser::slotFilter(const QString &txt) +{ + QString filtertxt = txt.lower(); + QRegExp reg(filtertxt, false, false); + CheckItem *cit=0; + + bool notCheckSub = !subCB->isChecked(); + bool notCheckNew = !newCB->isChecked(); + bool notCheckStr = (filtertxt.isEmpty()); + + bool isRegexp = filtertxt.contains(QRegExp("[^a-z0-9\\-\\+.]")); + + bool doIncrementalUpdate = (!isRegexp && incrementalFilter && (filtertxt.left(lastFilter.length())==lastFilter)); + + if (doIncrementalUpdate) { + QSortedList<KNGroupInfo> *tempList = new QSortedList<KNGroupInfo>(); + tempList->setAutoDelete(false); + + for(KNGroupInfo *g=matchList->first(); g; g=matchList->next()) { + if ((notCheckSub||g->subscribed)&& + (notCheckNew||g->newGroup)&& + (notCheckStr||(g->name.find(filtertxt)!=-1))) + tempList->append(g); + } + + delete matchList; + matchList=tempList; + } else { + matchList->clear(); + + for(KNGroupInfo *g=allList->first(); g; g=allList->next()) { + if ((notCheckSub||g->subscribed)&& + (notCheckNew||g->newGroup)&& + (notCheckStr||(isRegexp? (reg.search(g->name,0) != -1):(g->name.find(filtertxt)!=-1)))) + matchList->append(g); + } + } + + groupView->clear(); + + if((matchList->count() < MIN_FOR_TREE) || noTreeCB->isChecked()) { + for(KNGroupInfo *g=matchList->first(); g; g=matchList->next()) { + cit=new CheckItem(groupView, *g, this); + updateItemState(cit); + } + } else { + createListItems(); + } + + lastFilter = filtertxt; + incrementalFilter = !isRegexp; + + leftLabel->setText(i18n("Groups on %1: (%2 displayed)").arg(a_ccount->name()).arg(matchList->count())); + + arrowBtn1->setEnabled(false); + arrowBtn2->setEnabled(false); +} + + +void KNGroupBrowser::slotTreeCBToggled() +{ + incrementalFilter=false; + slotRefilter(); +} + + +void KNGroupBrowser::slotSubCBToggled() +{ + incrementalFilter=subCB->isChecked(); + slotRefilter(); +} + + +void KNGroupBrowser::slotNewCBToggled() +{ + incrementalFilter=newCB->isChecked(); + slotRefilter(); +} + + +void KNGroupBrowser::slotFilterTextChanged(const QString &) +{ + if (subCB->isChecked() || newCB->isChecked()) + slotRefilter(); + else + refilterTimer->start(200,true); +} + + +void KNGroupBrowser::slotRefilter() +{ + refilterTimer->stop(); + slotFilter(filterEdit->text()); +} + + +//======================================================================================= + + +KNGroupBrowser::CheckItem::CheckItem(QListView *v, const KNGroupInfo &gi, KNGroupBrowser *b) : + QCheckListItem(v, gi.name, QCheckListItem::CheckBox), info(gi), browser(b) +{ + QString des(gi.description); + if (gi.status == KNGroup::moderated) { + setText(0,gi.name+" (m)"); + if (!des.upper().contains(i18n("moderated").upper())) + des+=i18n(" (moderated)"); + } + setText(1,des); +} + + +KNGroupBrowser::CheckItem::CheckItem(QListViewItem *i, const KNGroupInfo &gi, KNGroupBrowser *b) : + QCheckListItem(i, gi.name, QCheckListItem::CheckBox), info(gi), browser(b) +{ + QString des(gi.description); + if (gi.status == KNGroup::moderated) { + setText(0,gi.name+" (m)"); + if (!des.upper().contains(i18n("moderated").upper())) + des+=i18n(" (moderated)"); + } + setText(1,des); +} + + +KNGroupBrowser::CheckItem::~CheckItem() +{ +} + + +void KNGroupBrowser::CheckItem::setChecked(bool c) +{ + KNGroupBrowser *b=browser; + browser=0; + QCheckListItem::setOn(c); + browser=b; +} + + +void KNGroupBrowser::CheckItem::stateChange(bool s) +{ + if(browser) { + kdDebug(5003) << "KNGroupBrowser::CheckItem::stateChange()" << endl; + browser->itemChangedState(this, s); + } +} + + +//======================================================================================= + + +KNGroupBrowser::GroupItem::GroupItem(QListView *v, const KNGroupInfo &gi) + : QListViewItem(v, gi.name), info(gi) +{ + if (gi.status == KNGroup::moderated) + setText(0,gi.name+" (m)"); +} + + +KNGroupBrowser::GroupItem::GroupItem(QListViewItem *i, const KNGroupInfo &gi) + : QListViewItem(i, gi.name), info(gi) +{ +} + + +KNGroupBrowser::GroupItem::~GroupItem() +{ +} + + +//----------------------------------------- + +#include "kngroupbrowser.moc" diff --git a/knode/kngroupbrowser.h b/knode/kngroupbrowser.h new file mode 100644 index 000000000..eb633c8b9 --- /dev/null +++ b/knode/kngroupbrowser.h @@ -0,0 +1,117 @@ +/* + kngroupbrowser.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNGROUPBROWSER_H +#define KNGROUPBROWSER_H + +#include <qlistview.h> + +#include <kdialogbase.h> + +#include "kngroupmanager.h" + +class KLineEdit; +class QCheckBox; +class QLayout; +class QLabel; +class QGridLayout; + +class KNNntpAccount; + + +class KNGroupBrowser : public KDialogBase { + + Q_OBJECT + + public: + class CheckItem : public QCheckListItem { + + public: + CheckItem(QListView *v, const KNGroupInfo &gi, KNGroupBrowser *b); + CheckItem(QListViewItem *i, const KNGroupInfo &gi, KNGroupBrowser *b); + ~CheckItem(); + void setChecked(bool c); + + KNGroupInfo info; + + protected: + void stateChange(bool s); + KNGroupBrowser *browser; + }; + + class GroupItem : public QListViewItem { + + public: + GroupItem(QListView *v, const KNGroupInfo &gi); + GroupItem(QListViewItem *i, const KNGroupInfo &gi); + ~GroupItem(); + + KNGroupInfo info; + }; + + KNGroupBrowser(QWidget *parent, const QString &caption, KNNntpAccount *a, int buttons=0, + bool newCBact=false, const QString &user1=QString::null, const QString &user2=QString::null); + ~KNGroupBrowser(); + + KNNntpAccount* account()const { return a_ccount; } + virtual void itemChangedState(CheckItem *it, bool s)=0; + + public slots: + void slotReceiveList(KNGroupListData* d); + + signals: + void loadList(KNNntpAccount *a); + + protected: + virtual void updateItemState(CheckItem *it)=0; + void changeItemState(const KNGroupInfo &gi, bool s); + bool itemInListView(QListView *view, const KNGroupInfo &gi); + void removeListItem(QListView *view, const KNGroupInfo &gi); + void createListItems(QListViewItem *parent=0); + + QWidget *page; + QListView *groupView; + int delayedCenter; + KLineEdit *filterEdit; + QCheckBox *noTreeCB, *subCB, *newCB; + QPushButton *arrowBtn1, *arrowBtn2; + QPixmap pmGroup, pmNew; + QIconSet pmRight, pmLeft; + QGridLayout *listL; + QLabel *leftLabel, *rightLabel; + QTimer *refilterTimer; + QString lastFilter; + bool incrementalFilter; + + KNNntpAccount *a_ccount; + QSortedList<KNGroupInfo> *allList, *matchList; + + protected slots: + void slotLoadList(); + void slotItemExpand(QListViewItem *it); + void slotCenterDelayed(); + /** double click checks/unchecks (opens/closes) item */ + void slotItemDoubleClicked(QListViewItem *it); + void slotFilter(const QString &txt); + void slotTreeCBToggled(); + void slotSubCBToggled(); + void slotNewCBToggled(); + void slotFilterTextChanged(const QString &txt); + void slotRefilter(); + +}; + +#endif diff --git a/knode/kngroupdialog.cpp b/knode/kngroupdialog.cpp new file mode 100644 index 000000000..b983f724a --- /dev/null +++ b/knode/kngroupdialog.cpp @@ -0,0 +1,335 @@ +/* + kngroupdialog.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlabel.h> +#include <qlayout.h> +#include <qbuttongroup.h> +#include <qradiobutton.h> +#include <qcheckbox.h> + +#include <kdebug.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <klineedit.h> + +#include "utilities.h" +#include "knnntpaccount.h" +#include "kngroupdialog.h" +#include "knglobals.h" +#include <qpushbutton.h> + + +KNGroupDialog::KNGroupDialog(QWidget *parent, KNNntpAccount *a) : + KNGroupBrowser(parent, i18n("Subscribe to Newsgroups"),a, User1 | User2, true, i18n("New &List"), i18n("New &Groups...") ) +{ + rightLabel->setText(i18n("Current changes:")); + subView=new QListView(page); + subView->addColumn(i18n("Subscribe To")); + unsubView=new QListView(page); + unsubView->addColumn(i18n("Unsubscribe From")); + + QVBoxLayout *protL=new QVBoxLayout(3); + listL->addLayout(protL, 1,2); + protL->addWidget(subView); + protL->addWidget(unsubView); + + dir1=right; + dir2=left; + + connect(groupView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotItemSelected(QListViewItem*))); + connect(groupView, SIGNAL(selectionChanged()), + this, SLOT(slotSelectionChanged())); + connect(subView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotItemSelected(QListViewItem*))); + connect(unsubView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotItemSelected(QListViewItem*))); + + connect(arrowBtn1, SIGNAL(clicked()), this, SLOT(slotArrowBtn1())); + connect(arrowBtn2, SIGNAL(clicked()), this, SLOT(slotArrowBtn2())); + + KNHelper::restoreWindowSize("groupDlg", this, QSize(662,393)); // optimized for 800x600 + + setHelp("anc-fetch-group-list"); +} + + + +KNGroupDialog::~KNGroupDialog() +{ + KNHelper::saveWindowSize("groupDlg", this->size()); +} + + + +void KNGroupDialog::itemChangedState(CheckItem *it, bool s) +{ + kdDebug(5003) << "KNGroupDialog::itemChangedState()" << endl; + if(s){ + if(itemInListView(unsubView, it->info)) { + removeListItem(unsubView, it->info); + setButtonDirection(btn2, right); + arrowBtn1->setEnabled(false); + arrowBtn2->setEnabled(true); + } + else { + new GroupItem(subView, it->info); + arrowBtn1->setEnabled(false); + arrowBtn2->setEnabled(false); + } + } + else { + if(itemInListView(subView, it->info)) { + removeListItem(subView, it->info); + setButtonDirection(btn1, right); + arrowBtn1->setEnabled(true); + arrowBtn2->setEnabled(false); + } + else { + new GroupItem(unsubView, it->info); + arrowBtn1->setEnabled(false); + arrowBtn2->setEnabled(false); + } + } +} + + + +void KNGroupDialog::updateItemState(CheckItem *it) +{ + it->setChecked( (it->info.subscribed && !itemInListView(unsubView, it->info)) || + (!it->info.subscribed && itemInListView(subView, it->info)) ); + + if((it->info.subscribed || it->info.newGroup) && it->pixmap(0)==0) + it->setPixmap(0, (it->info.newGroup)? pmNew:pmGroup); +} + + + +void KNGroupDialog::toSubscribe(QSortedList<KNGroupInfo> *l) +{ + KNGroupInfo *info; + l->clear(); + l->setAutoDelete(true); + + bool moderated=false; + QListViewItemIterator it(subView); + for(; it.current(); ++it) { + info = new KNGroupInfo(); + *info = ((static_cast<GroupItem*>(it.current()))->info); + l->append(info); + if (info->status==KNGroup::moderated) + moderated=true; + } + if (moderated) // warn the user + KMessageBox::information(knGlobals.topWidget,i18n("You have subscribed to a moderated newsgroup.\nYour articles will not appear in the group immediately.\nThey have to go through a moderation process."), + QString::null,"subscribeModeratedWarning"); +} + + + +void KNGroupDialog::toUnsubscribe(QStringList *l) +{ + l->clear(); + QListViewItemIterator it(unsubView); + for(; it.current(); ++it) + l->append(((static_cast<GroupItem*>(it.current()))->info).name); +} + + + +void KNGroupDialog::setButtonDirection(arrowButton b, arrowDirection d) +{ + QPushButton *btn=0; + if(b==btn1 && dir1!=d) { + btn=arrowBtn1; + dir1=d; + } + else if(b==btn2 && dir2!=d) { + btn=arrowBtn2; + dir2=d; + } + + if(btn) { + if(d==right) + btn->setIconSet(pmRight); + else + btn->setIconSet(pmLeft); + } +} + + + +void KNGroupDialog::slotItemSelected(QListViewItem *it) +{ + const QObject *s=sender(); + + + if(s==subView) { + unsubView->clearSelection(); + groupView->clearSelection(); + arrowBtn2->setEnabled(false); + arrowBtn1->setEnabled(true); + setButtonDirection(btn1, left); + } + else if(s==unsubView) { + subView->clearSelection(); + groupView->clearSelection(); + arrowBtn1->setEnabled(false); + arrowBtn2->setEnabled(true); + setButtonDirection(btn2, left); + } + else { + CheckItem *cit; + subView->clearSelection(); + unsubView->clearSelection(); + cit=static_cast<CheckItem*>(it); + if(!cit->isOn() && !itemInListView(subView, cit->info) && !itemInListView(unsubView, cit->info)) { + arrowBtn1->setEnabled(true); + arrowBtn2->setEnabled(false); + setButtonDirection(btn1, right); + } + else if(cit->isOn() && !itemInListView(unsubView, cit->info) && !itemInListView(subView, cit->info)) { + arrowBtn2->setEnabled(true); + arrowBtn1->setEnabled(false); + setButtonDirection(btn2, right); + } + else { + arrowBtn1->setEnabled(false); + arrowBtn2->setEnabled(false); + } + } +} + + + +void KNGroupDialog::slotSelectionChanged() +{ + if (!groupView->selectedItem()) + arrowBtn1->setEnabled(false); +} + + + +void KNGroupDialog::slotArrowBtn1() +{ + if(dir1==right) { + CheckItem *it=static_cast<CheckItem*>(groupView->selectedItem()); + if (it) { + new GroupItem(subView, it->info); + it->setChecked(true); + } + } + else { + GroupItem *it=static_cast<GroupItem*>(subView->selectedItem()); + if (it) { + changeItemState(it->info, false); + delete it; + } + } + + arrowBtn1->setEnabled(false); +} + + +void KNGroupDialog::slotArrowBtn2() +{ + if(dir2==right) { + CheckItem *it=static_cast<CheckItem*>(groupView->selectedItem()); + if (it) { + new GroupItem(unsubView, it->info); + it->setChecked(false); + } + } + else { + GroupItem *it=static_cast<GroupItem*>(unsubView->selectedItem()); + if (it) { + changeItemState(it->info, true); + delete it; + } + } + + arrowBtn2->setEnabled(false); +} + + +// new list +void KNGroupDialog::slotUser1() +{ + leftLabel->setText(i18n("Downloading groups...")); + enableButton(User1,false); + enableButton(User2,false); + emit(fetchList(a_ccount)); +} + + +// new groups +void KNGroupDialog::slotUser2() +{ + QDate lastDate = a_ccount->lastNewFetch(); + KDialogBase *dlg = new KDialogBase( this, 0L, true, i18n("New Groups"), Ok | Cancel, Ok); + + QButtonGroup *btnGrp = new QButtonGroup(i18n("Check for New Groups"),dlg); + dlg->setMainWidget(btnGrp); + QGridLayout *topL = new QGridLayout(btnGrp,4,2,25,10); + + QRadioButton *takeLast = new QRadioButton( i18n("Created since last check:"), btnGrp ); + topL->addMultiCellWidget(takeLast, 0, 0, 0, 1); + + QLabel *l = new QLabel(KGlobal::locale()->formatDate(lastDate, false),btnGrp); + topL->addWidget(l, 1, 1, Qt::AlignLeft); + + connect(takeLast, SIGNAL(toggled(bool)), l, SLOT(setEnabled(bool))); + + QRadioButton *takeCustom = new QRadioButton( i18n("Created since this date:"), btnGrp ); + topL->addMultiCellWidget(takeCustom, 2, 2, 0, 1); + + KDatePicker *dateSel = new KDatePicker(btnGrp, lastDate); + dateSel->setMinimumSize(dateSel->sizeHint()); + topL->addWidget(dateSel, 3, 1, Qt::AlignLeft); + + connect(takeCustom, SIGNAL(toggled(bool)), dateSel, SLOT(setEnabled(bool))); + + takeLast->setChecked(true); + dateSel->setEnabled(false); + + topL->addColSpacing(0,30); + dlg->disableResize(); + + if (dlg->exec()) { + if (takeCustom->isChecked()) + lastDate = dateSel->date(); + a_ccount->setLastNewFetch(QDate::currentDate()); + leftLabel->setText(i18n("Checking for new groups...")); + enableButton(User1,false); + enableButton(User2,false); + filterEdit->clear(); + subCB->setChecked(false); + newCB->setChecked(true); + emit(checkNew(a_ccount,lastDate)); + incrementalFilter=false; + slotRefilter(); + } + + delete dlg; +} + + +//-------------------------------- + +#include "kngroupdialog.moc" diff --git a/knode/kngroupdialog.h b/knode/kngroupdialog.h new file mode 100644 index 000000000..306a36810 --- /dev/null +++ b/knode/kngroupdialog.h @@ -0,0 +1,60 @@ +/* + kngroupdialog.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNGROUPDIALOG_H +#define KNGROUPDIALOG_H + +#include "kngroupbrowser.h" + + +class KNGroupDialog : public KNGroupBrowser { + + Q_OBJECT + + public: + KNGroupDialog(QWidget *parent, KNNntpAccount *a); + ~KNGroupDialog(); + + void toSubscribe(QSortedList<KNGroupInfo> *l); + void toUnsubscribe(QStringList *l); + + protected: + enum arrowDirection { right, left }; + enum arrowButton { btn1, btn2 }; + void updateItemState(CheckItem *it); + void itemChangedState(CheckItem *it, bool s); + void setButtonDirection(arrowButton b, arrowDirection d); + QPushButton *newListBtn; + QListView *subView, *unsubView; + arrowDirection dir1, dir2; + + protected slots: + void slotItemSelected(QListViewItem *it); + /** deactivates the button when a root item is selected */ + void slotSelectionChanged(); + void slotArrowBtn1(); + void slotArrowBtn2(); + /** new list */ + void slotUser1(); + /** new groups */ + void slotUser2(); + + signals: + void fetchList(KNNntpAccount *a); + void checkNew(KNNntpAccount *a,QDate date); +}; + +#endif diff --git a/knode/kngroupmanager.cpp b/knode/kngroupmanager.cpp new file mode 100644 index 000000000..584896091 --- /dev/null +++ b/knode/kngroupmanager.cpp @@ -0,0 +1,704 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <qdir.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <kcharsets.h> + +#include "articlewidget.h" +#include "knmainwidget.h" +#include "knarticlemanager.h" +#include "kngroupdialog.h" +#include "knnntpaccount.h" +#include "knprotocolclient.h" +#include "kncleanup.h" +#include "knnetaccess.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "resource.h" +#include "utilities.h" +#include "knarticlewindow.h" +#include "knmemorymanager.h" + +using namespace KNode; + + +//================================================================================= + +// helper classes for the group selection dialog (getting the server's grouplist, +// getting recently created groups) + + +KNGroupInfo::KNGroupInfo() +{ +} + + +KNGroupInfo::KNGroupInfo(const QString &n_ame, const QString &d_escription, bool n_ewGroup, bool s_ubscribed, KNGroup::Status s_tatus) + : name(n_ame), description(d_escription), newGroup(n_ewGroup), subscribed(s_ubscribed), + status(s_tatus) +{ +} + + +KNGroupInfo::~KNGroupInfo() +{ +} + + +bool KNGroupInfo::operator== (const KNGroupInfo &gi2) +{ + return (name == gi2.name); +} + + +bool KNGroupInfo::operator< (const KNGroupInfo &gi2) +{ + return (name < gi2.name); +} + + +//=============================================================================== + + +KNGroupListData::KNGroupListData() + : codecForDescriptions(0) +{ + groups = new QSortedList<KNGroupInfo>; + groups->setAutoDelete(true); +} + + + +KNGroupListData::~KNGroupListData() +{ + delete groups; +} + + + +bool KNGroupListData::readIn(KNProtocolClient *client) +{ + KNFile f(path+"groups"); + QCString line; + int sepPos1,sepPos2; + QString name,description; + bool sub; + KNGroup::Status status=KNGroup::unknown; + QTime timer; + uint size=f.size()+2; + + timer.start(); + if (client) client->updatePercentage(0); + + if(f.open(IO_ReadOnly)) { + while(!f.atEnd()) { + line = f.readLine(); + sepPos1 = line.find(' '); + + if (sepPos1==-1) { // no description + name = QString::fromUtf8(line); + description = QString::null; + status = KNGroup::unknown; + } else { + name = QString::fromUtf8(line.left(sepPos1)); + + sepPos2 = line.find(' ',sepPos1+1); + if (sepPos2==-1) { // no status + description = QString::fromUtf8(line.right(line.length()-sepPos1-1)); + status = KNGroup::unknown; + } else { + description = QString::fromUtf8(line.right(line.length()-sepPos2-1)); + switch (line[sepPos1+1]) { + case 'u': status = KNGroup::unknown; + break; + case 'n': status = KNGroup::readOnly; + break; + case 'y': status = KNGroup::postingAllowed; + break; + case 'm': status = KNGroup::moderated; + break; + } + } + } + + if (subscribed.contains(name)) { + subscribed.remove(name); // group names are unique, we wont find it again anyway... + sub = true; + } else + sub = false; + + groups->append(new KNGroupInfo(name,description,false,sub,status)); + + if (timer.elapsed() > 200) { // don't flicker + timer.restart(); + if (client) client->updatePercentage((f.at()*100)/size); + } + } + + f.close(); + return true; + } else { + kdWarning(5003) << "unable to open " << f.name() << " reason " << f.status() << endl; + return false; + } +} + + + +bool KNGroupListData::writeOut() +{ + QFile f(path+"groups"); + QCString temp; + + if(f.open(IO_WriteOnly)) { + for (KNGroupInfo *i=groups->first(); i; i=groups->next()) { + temp = i->name.utf8(); + switch (i->status) { + case KNGroup::unknown: temp += " u "; + break; + case KNGroup::readOnly: temp += " n "; + break; + case KNGroup::postingAllowed: temp += " y "; + break; + case KNGroup::moderated: temp += " m "; + break; + } + temp += i->description.utf8() + "\n"; + f.writeBlock(temp.data(),temp.length()); + } + f.close(); + return true; + } else { + kdWarning(5003) << "unable to open " << f.name() << " reason " << f.status() << endl; + return false; + } +} + + + +// merge in new groups, we want to preserve the "subscribed"-flag +// of the loaded groups and the "new"-flag of the new groups. +void KNGroupListData::merge(QSortedList<KNGroupInfo>* newGroups) +{ + bool subscribed; + + for (KNGroupInfo *i=newGroups->first(); i; i=newGroups->next()) { + if (groups->find(i)>=0) { + subscribed = groups->current()->subscribed; + groups->remove(); // avoid duplicates + } else + subscribed = false; + groups->append(new KNGroupInfo(i->name,i->description,true,subscribed,i->status)); + } + + groups->sort(); +} + + +QSortedList<KNGroupInfo>* KNGroupListData::extractList() +{ + QSortedList<KNGroupInfo>* temp = groups; + groups = 0; + return temp; +} + + +//=============================================================================== + + +KNGroupManager::KNGroupManager(QObject * parent, const char * name) + : QObject(parent,name) +{ + c_urrentGroup=0; + a_rticleMgr = knGlobals.articleManager(); +} + + +KNGroupManager::~KNGroupManager() +{ + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) + delete (*it); +} + + +void KNGroupManager::syncGroups() +{ + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { + (*it)->syncDynamicData(); + (*it)->saveInfo(); + } +} + + +void KNGroupManager::loadGroups(KNNntpAccount *a) +{ + KNGroup *group; + + QString dir(a->path()); + if (dir.isNull()) + return; + QDir d(dir); + + QStringList entries(d.entryList("*.grpinfo")); + for(QStringList::Iterator it=entries.begin(); it != entries.end(); ++it) { + group=new KNGroup(a); + if (group->readInfo(dir+(*it))) { + mGroupList.append( group ); + emit groupAdded(group); + } else { + delete group; + kdError(5003) << "Unable to load " << (*it) << "!" << endl; + } + } +} + + +void KNGroupManager::getSubscribed(KNNntpAccount *a, QStringList &l) +{ + l.clear(); + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) + if ( (*it)->account() == a ) + l.append( (*it)->groupname() ); +} + + +QValueList<KNGroup*> KNGroupManager::groupsOfAccount( KNNntpAccount *a ) +{ + QValueList<KNGroup*> ret; + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) + if ( (*it)->account() == a ) + ret.append( (*it) ); + return ret; +} + + +bool KNGroupManager::loadHeaders(KNGroup *g) +{ + if (!g) + return false; + + if (g->isLoaded()) + return true; + + // we want to delete old stuff first => reduce vm fragmentation + knGlobals.memoryManager()->prepareLoad(g); + + if (g->loadHdrs()) { + knGlobals.memoryManager()->updateCacheEntry( g ); + return true; + } + + return false; +} + + +bool KNGroupManager::unloadHeaders(KNGroup *g, bool force) +{ + if(!g || g->isLocked()) + return false; + + if(!g->isLoaded()) + return true; + + if (!force && (c_urrentGroup == g)) + return false; + + if (g->unloadHdrs(force)) + knGlobals.memoryManager()->removeCacheEntry(g); + else + return false; + + return true; +} + + +KNGroup* KNGroupManager::group(const QString &gName, const KNServerInfo *s) +{ + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) + if ( (*it)->account() == s && (*it)->groupname() == gName ) + return (*it); + + return 0; +} + + +KNGroup* KNGroupManager::firstGroupOfAccount(const KNServerInfo *s) +{ + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) + if ( (*it)->account() == s ) + return (*it); + + return 0; +} + + +void KNGroupManager::expireAll(KNCleanUp *cup) +{ + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { + if( (*it)->isLocked() || (*it)->lockedArticles() > 0 ) + continue; + if ( !(*it)->activeCleanupConfig()->expireToday() ) + continue; + cup->appendCollection( *(it) ); + } +} + + +void KNGroupManager::expireAll(KNNntpAccount *a) +{ + KNCleanUp *cup = new KNCleanUp(); + + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { + if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 ) + continue; + + KNArticleWindow::closeAllWindowsForCollection( (*it) ); + cup->appendCollection( (*it) ); + } + + cup->start(); + + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { + if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 ) + continue; + + emit groupUpdated( (*it) ); + if ( (*it) == c_urrentGroup ) { + if ( loadHeaders( (*it) ) ) + a_rticleMgr->showHdrs(); + else + a_rticleMgr->setGroup(0); + } + } + + delete cup; +} + + +void KNGroupManager::showGroupDialog(KNNntpAccount *a, QWidget *parent) +{ + KNGroupDialog* gDialog=new KNGroupDialog((parent!=0)? parent:knGlobals.topWidget, a); + + connect(gDialog, SIGNAL(loadList(KNNntpAccount*)), this, SLOT(slotLoadGroupList(KNNntpAccount*))); + connect(gDialog, SIGNAL(fetchList(KNNntpAccount*)), this, SLOT(slotFetchGroupList(KNNntpAccount*))); + connect(gDialog, SIGNAL(checkNew(KNNntpAccount*,QDate)), this, SLOT(slotCheckForNewGroups(KNNntpAccount*,QDate))); + connect(this, SIGNAL(newListReady(KNGroupListData*)), gDialog, SLOT(slotReceiveList(KNGroupListData*))); + + if(gDialog->exec()) { + KNGroup *g=0; + + QStringList lst; + gDialog->toUnsubscribe(&lst); + if (lst.count()>0) { + if (KMessageBox::Yes == KMessageBox::questionYesNoList((parent!=0)? parent:knGlobals.topWidget,i18n("Do you really want to unsubscribe\nfrom these groups?"), + lst, QString::null, i18n("Unsubscribe"), KStdGuiItem::cancel())) { + for ( QStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) { + if((g=group(*it, a))) + unsubscribeGroup(g); + } + } + } + + QSortedList<KNGroupInfo> lst2; + gDialog->toSubscribe(&lst2); + for(KNGroupInfo *var=lst2.first(); var; var=lst2.next()) { + subscribeGroup(var, a); + } + } + + delete gDialog; +} + + +void KNGroupManager::subscribeGroup(const KNGroupInfo *gi, KNNntpAccount *a) +{ + KNGroup *grp; + + grp=new KNGroup(a); + grp->setGroupname(gi->name); + grp->setDescription(gi->description); + grp->setStatus(gi->status); + grp->saveInfo(); + mGroupList.append( grp ); + emit groupAdded(grp); +} + + +bool KNGroupManager::unsubscribeGroup(KNGroup *g) +{ + KNNntpAccount *acc; + if(!g) g=c_urrentGroup; + if(!g) return false; + + if((g->isLocked()) || (g->lockedArticles()>0)) { + KMessageBox::sorry(knGlobals.topWidget, i18n("The group \"%1\" is being updated currently.\nIt is not possible to unsubscribe from it at the moment.").arg(g->groupname())); + return false; + } + + KNArticleWindow::closeAllWindowsForCollection(g); + ArticleWidget::collectionRemoved( g ); + + acc=g->account(); + + QDir dir(acc->path(),g->groupname()+"*"); + if (dir.exists()) { + if (unloadHeaders(g, true)) { + if(c_urrentGroup==g) { + setCurrentGroup(0); + a_rticleMgr->updateStatusString(); + } + + const QFileInfoList *list = dir.entryInfoList(); // get list of matching files and delete all + if (list) { + QFileInfoListIterator it( *list ); + while (it.current()) { + if (it.current()->fileName() == g->groupname()+".dynamic" || + it.current()->fileName() == g->groupname()+".static" || + it.current()->fileName() == g->groupname()+".grpinfo") + dir.remove(it.current()->fileName()); + ++it; + } + } + kdDebug(5003) << "Files deleted!" << endl; + + emit groupRemoved(g); + mGroupList.remove( g ); + delete g; + + return true; + } + } + + return false; +} + + +void KNGroupManager::showGroupProperties(KNGroup *g) +{ + if(!g) g=c_urrentGroup; + if(!g) return; + g->showProperties(); +} + + +void KNGroupManager::checkGroupForNewHeaders(KNGroup *g) +{ + if(!g) g=c_urrentGroup; + if(!g) return; + if(g->isLocked()) { + kdDebug(5003) << "KNGroupManager::checkGroupForNewHeaders() : group locked - returning" << endl; + return; + } + + g->setMaxFetch(knGlobals.configManager()->readNewsGeneral()->maxToFetch()); + emitJob( new KNJobData(KNJobData::JTfetchNewHeaders, this, g->account(), g) ); +} + + +void KNGroupManager::expireGroupNow(KNGroup *g) +{ + if(!g) return; + + if((g->isLocked()) || (g->lockedArticles()>0)) { + KMessageBox::sorry(knGlobals.topWidget, + i18n("This group cannot be expired because it is currently being updated.\n Please try again later.")); + return; + } + + KNArticleWindow::closeAllWindowsForCollection(g); + + KNCleanUp cup; + cup.expireGroup(g, true); + + emit groupUpdated(g); + if(g==c_urrentGroup) { + if( loadHeaders(g) ) + a_rticleMgr->showHdrs(); + else + a_rticleMgr->setGroup(0); + } +} + + +void KNGroupManager::reorganizeGroup(KNGroup *g) +{ + if(!g) g=c_urrentGroup; + if(!g) return; + g->reorganize(); + if(g==c_urrentGroup) + a_rticleMgr->showHdrs(); +} + + +void KNGroupManager::setCurrentGroup(KNGroup *g) +{ + c_urrentGroup=g; + a_rticleMgr->setGroup(g); + kdDebug(5003) << "KNGroupManager::setCurrentGroup() : group changed" << endl; + + if(g) { + if( !loadHeaders(g) ) { + //KMessageBox::error(knGlobals.topWidget, i18n("Cannot load saved headers")); + return; + } + a_rticleMgr->showHdrs(); + if(knGlobals.configManager()->readNewsGeneral()->autoCheckGroups()) + checkGroupForNewHeaders(g); + } +} + + +void KNGroupManager::checkAll(KNNntpAccount *a, bool silent) +{ + if(!a) return; + + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { + if ( (*it)->account() == a ) { + (*it)->setMaxFetch( knGlobals.configManager()->readNewsGeneral()->maxToFetch() ); + if ( silent ) + emitJob( new KNJobData(KNJobData::JTsilentFetchNewHeaders, this, (*it)->account(), (*it) ) ); + else + emitJob( new KNJobData(KNJobData::JTfetchNewHeaders, this, (*it)->account(), (*it) ) ); + } + } +} + + +void KNGroupManager::processJob(KNJobData *j) +{ + if((j->type()==KNJobData::JTLoadGroups)||(j->type()==KNJobData::JTFetchGroups)||(j->type()==KNJobData::JTCheckNewGroups)) { + KNGroupListData *d=static_cast<KNGroupListData*>(j->data()); + + if (!j->canceled()) { + if (j->success()) { + if ((j->type()==KNJobData::JTFetchGroups)||(j->type()==KNJobData::JTCheckNewGroups)) { + // update the descriptions of the subscribed groups + for ( QValueList<KNGroup*>::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { + if ( (*it)->account() == j->account() ) { + for ( KNGroupInfo* inf = d->groups->first(); inf; inf = d->groups->next() ) + if ( inf->name == (*it)->groupname() ) { + (*it)->setDescription( inf->description ); + (*it)->setStatus( inf->status ); + break; + } + } + } + } + emit(newListReady(d)); + } else { + KMessageBox::error(knGlobals.topWidget, j->errorString()); + emit(newListReady(0)); + } + } else + emit(newListReady(0)); + + delete j; + delete d; + + + } else { //KNJobData::JTfetchNewHeaders or KNJobData::JTsilentFetchNewHeaders + KNGroup *group=static_cast<KNGroup*>(j->data()); + + if (!j->canceled()) { + if (j->success()) { + if(group->lastFetchCount()>0) { + group->scoreArticles(); + group->processXPostBuffer(true); + emit groupUpdated(group); + group->saveInfo(); + knGlobals.memoryManager()->updateCacheEntry(group); + } + } else { + // ok, hack (?): + // stop all other active fetch jobs, this prevents that + // we show multiple error dialogs if a server is unavailable + knGlobals.netAccess()->stopJobsNntp(KNJobData::JTfetchNewHeaders); + knGlobals.netAccess()->stopJobsNntp(KNJobData::JTsilentFetchNewHeaders); + if(!(j->type()==KNJobData::JTsilentFetchNewHeaders)) { + KMessageBox::error(knGlobals.topWidget, j->errorString()); + } + } + } + if(group==c_urrentGroup) + a_rticleMgr->showHdrs(false); + + delete j; + } +} + + +// load group list from disk (if this fails: ask user if we should fetch the list) +void KNGroupManager::slotLoadGroupList(KNNntpAccount *a) +{ + KNGroupListData *d = new KNGroupListData(); + d->path = a->path(); + + if(!QFileInfo(d->path+"groups").exists()) { + if (KMessageBox::Yes==KMessageBox::questionYesNo(knGlobals.topWidget,i18n("You do not have any groups for this account;\ndo you want to fetch a current list?"), QString::null, i18n("Fetch List"), i18n("Do Not Fetch"))) { + delete d; + slotFetchGroupList(a); + return; + } else { + emit(newListReady(d)); + delete d; + return; + } + } + + getSubscribed(a,d->subscribed); + d->getDescriptions = a->fetchDescriptions(); + + emitJob( new KNJobData(KNJobData::JTLoadGroups, this, a, d) ); +} + + +// fetch group list from server +void KNGroupManager::slotFetchGroupList(KNNntpAccount *a) +{ + KNGroupListData *d = new KNGroupListData(); + d->path = a->path(); + getSubscribed(a,d->subscribed); + d->getDescriptions = a->fetchDescriptions(); + d->codecForDescriptions=KGlobal::charsets()->codecForName(knGlobals.configManager()->postNewsTechnical()->charset()); + + emitJob( new KNJobData(KNJobData::JTFetchGroups, this, a, d) ); +} + + +// check for new groups (created after the given date) +void KNGroupManager::slotCheckForNewGroups(KNNntpAccount *a, QDate date) +{ + KNGroupListData *d = new KNGroupListData(); + d->path = a->path(); + getSubscribed(a,d->subscribed); + d->getDescriptions = a->fetchDescriptions(); + d->fetchSince = date; + d->codecForDescriptions=KGlobal::charsets()->codecForName(knGlobals.configManager()->postNewsTechnical()->charset()); + + emitJob( new KNJobData(KNJobData::JTCheckNewGroups, this, a, d) ); +} + + +//-------------------------------- + +#include "kngroupmanager.moc" + +// kate: space-indent on; indent-width 2; diff --git a/knode/kngroupmanager.h b/knode/kngroupmanager.h new file mode 100644 index 000000000..f3805dc81 --- /dev/null +++ b/knode/kngroupmanager.h @@ -0,0 +1,142 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNGROUPMANAGER_H +#define KNGROUPMANAGER_H + +#include <qobject.h> +#include <qsortedlist.h> + +#include "knjobdata.h" +#include "kngroup.h" + +class KNNntpAccount; +class KNProtocolClient; +class KNServerInfo; +class KNArticleManager; +class KNCleanUp; + + +//================================================================================= + +/** helper classes for the group selection dialog + contains info about a newsgroup (name, description) */ + +class KNGroupInfo { + + public: + + KNGroupInfo(); + KNGroupInfo(const QString &n_ame, const QString &d_escription, bool n_ewGroup=false, bool s_ubscribed=false, KNGroup::Status s_tatus=KNGroup::unknown ); + ~KNGroupInfo(); + + /** group names will be utf-8 encoded in the future... */ + QString name,description; + bool newGroup, subscribed; + KNGroup::Status status; + + bool operator== (const KNGroupInfo &gi2); + bool operator< (const KNGroupInfo &gi2); +}; + + +class KNGroupListData : public KNJobItem { + + public: + KNGroupListData(); + ~KNGroupListData(); + + bool readIn(KNProtocolClient *client=0); + bool writeOut(); + void merge(QSortedList<KNGroupInfo>* newGroups); + + QSortedList<KNGroupInfo>* extractList(); + + QStringList subscribed; + QString path; + QSortedList<KNGroupInfo> *groups; + QDate fetchSince; + bool getDescriptions; + QTextCodec *codecForDescriptions; + +}; + +//=============================================================================== + + +class KNGroupManager : public QObject , public KNJobConsumer { + + Q_OBJECT + + public: + + KNGroupManager(QObject * parent=0, const char * name=0); + ~KNGroupManager(); + + // group access + void loadGroups(KNNntpAccount *a); + void getSubscribed(KNNntpAccount *a, QStringList &l); + QValueList<KNGroup*> groupsOfAccount( KNNntpAccount *a ); + + bool loadHeaders(KNGroup *g); + bool unloadHeaders(KNGroup *g, bool force=true); + + KNGroup* group(const QString &gName, const KNServerInfo *s); + KNGroup* firstGroupOfAccount(const KNServerInfo *s); + KNGroup* currentGroup() const { return c_urrentGroup; } + bool hasCurrentGroup() const { return (c_urrentGroup!=0); } + void setCurrentGroup(KNGroup *g); + + // group handling + void showGroupDialog(KNNntpAccount *a, QWidget *parent=0); + void subscribeGroup(const KNGroupInfo *gi, KNNntpAccount *a); + bool unsubscribeGroup(KNGroup *g=0); + void showGroupProperties(KNGroup *g=0); + void expireGroupNow(KNGroup *g=0); + void reorganizeGroup(KNGroup *g=0); + + void checkGroupForNewHeaders(KNGroup *g=0); + void checkAll(KNNntpAccount *a, bool silent=false); + + void expireAll(KNCleanUp *cup); + void expireAll(KNNntpAccount *a); + void syncGroups(); + + public slots: + /** load group list from disk (if this fails: ask user if we should fetch the list) */ + void slotLoadGroupList(KNNntpAccount *a); + /** fetch group list from server */ + void slotFetchGroupList(KNNntpAccount *a); + /** check for new groups (created after the given date) */ + void slotCheckForNewGroups(KNNntpAccount *a, QDate date); + + protected: + /** reimplemented from @ref KNJobConsumer */ + void processJob(KNJobData *j); + QValueList<KNGroup*> mGroupList; + KNGroup *c_urrentGroup; + KNArticleManager *a_rticleMgr; + + signals: + void newListReady(KNGroupListData* d); + + void groupAdded(KNGroup* g); + void groupRemoved(KNGroup* g); + void groupUpdated(KNGroup* g); + +}; + + + +#endif diff --git a/knode/kngrouppropdlg.cpp b/knode/kngrouppropdlg.cpp new file mode 100644 index 000000000..b93669374 --- /dev/null +++ b/knode/kngrouppropdlg.cpp @@ -0,0 +1,181 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qgroupbox.h> +#include <qlayout.h> +#include <qvbox.h> +#include <qcheckbox.h> + +#include <klocale.h> +#include <klineedit.h> + +#include "knglobals.h" +#include "knconfigmanager.h" +#include "knconfigwidgets.h" +#include "utilities.h" +#include "kngroup.h" +#include "kngrouppropdlg.h" +#include <qlabel.h> + + +KNGroupPropDlg::KNGroupPropDlg(KNGroup *group, QWidget *parent, const char *name ) + : KDialogBase(Tabbed, i18n("Properties of %1").arg(group->groupname()), + Ok|Cancel|Help, Ok, parent, name), + g_rp(group), n_ickChanged(false) +{ + + // General tab =============================================== + + QWidget *page = addPage(i18n("&General")); + QVBoxLayout *pageL = new QVBoxLayout(page, 3); + + // settings + QGroupBox *gb=new QGroupBox(i18n("Settings"), page); + pageL->addWidget(gb); + QGridLayout *grpL=new QGridLayout(gb, 3, 3, 15, 5); + + grpL->addRowSpacing(0, fontMetrics().lineSpacing()-9); + + n_ick=new KLineEdit(gb); + if (g_rp->hasName()) + n_ick->setText(g_rp->name()); + QLabel *l=new QLabel(n_ick, i18n("&Nickname:"), gb); + grpL->addWidget(l,1,0); + grpL->addMultiCellWidget(n_ick,1,1,1,2); + + u_seCharset=new QCheckBox(i18n("&Use different default charset:"), gb); + u_seCharset->setChecked(g_rp->useCharset()); + grpL->addMultiCellWidget(u_seCharset,2,2,0,1); + + c_harset=new QComboBox(false, gb); + c_harset->insertStringList(knGlobals.configManager()->postNewsTechnical()->composerCharsets()); + c_harset->setCurrentItem(knGlobals.configManager()->postNewsTechnical()->indexForCharset(g_rp->defaultCharset())); + c_harset->setEnabled(g_rp->useCharset()); + connect(u_seCharset, SIGNAL(toggled(bool)), c_harset, SLOT(setEnabled(bool))); + grpL->addWidget(c_harset, 2,2); + + grpL->setColStretch(1,1); + grpL->setColStretch(2,2); + + // group name & description + gb=new QGroupBox(i18n("Description"), page); + pageL->addWidget(gb); + grpL=new QGridLayout(gb, 4, 3, 15, 5); + + grpL->addRowSpacing(0, fontMetrics().lineSpacing()-9); + + l=new QLabel(i18n("Name:"), gb); + grpL->addWidget(l,1,0); + l=new QLabel(group->groupname(),gb); + grpL->addWidget(l,1,2); + + l=new QLabel(i18n("Description:"), gb); + grpL->addWidget(l,2,0); + l=new QLabel(g_rp->description(),gb); + grpL->addWidget(l,2,2); + + l=new QLabel(i18n("Status:"), gb); + grpL->addWidget(l,3,0); + QString status; + switch (g_rp->status()) { + case KNGroup::unknown: status=i18n("unknown"); + break; + case KNGroup::readOnly: status=i18n("posting forbidden"); + break; + case KNGroup::postingAllowed: status=i18n("posting allowed"); + break; + case KNGroup::moderated: status=i18n("moderated"); + break; + } + l=new QLabel(status,gb); + grpL->addWidget(l,3,2); + + grpL->addColSpacing(1,20); + grpL->setColStretch(2,1); + + // statistics + gb=new QGroupBox(i18n("Statistics"), page); + pageL->addWidget(gb); + grpL=new QGridLayout(gb, 6, 3, 15, 5); + + grpL->addRowSpacing(0, fontMetrics().lineSpacing()-9); + + l=new QLabel(i18n("Articles:"), gb); + grpL->addWidget(l,1,0); + l=new QLabel(QString::number(g_rp->count()),gb); + grpL->addWidget(l,1,2); + + l=new QLabel(i18n("Unread articles:"), gb); + grpL->addWidget(l,2,0); + l=new QLabel(QString::number(g_rp->count()-g_rp->readCount()),gb); + grpL->addWidget(l,2,2); + + l=new QLabel(i18n("New articles:"), gb); + grpL->addWidget(l,3,0); + l=new QLabel(QString::number(g_rp->newCount()),gb); + grpL->addWidget(l,3,2); + + l=new QLabel(i18n("Threads with unread articles:"), gb); + grpL->addWidget(l,4,0); + l=new QLabel(QString::number(g_rp->statThrWithUnread()),gb); + grpL->addWidget(l,4,2); + + l=new QLabel(i18n("Threads with new articles:"), gb); + grpL->addWidget(l,5,0); + l=new QLabel(QString::number(g_rp->statThrWithNew()),gb); + grpL->addWidget(l,5,2); + + grpL->addColSpacing(1,20); + grpL->setColStretch(2,1); + + pageL->addStretch(1); + + // Specfic Identity tab ========================================= + i_dWidget=new KNConfig::IdentityWidget(g_rp->identity(), addVBoxPage(i18n("&Identity"))); + + // per server cleanup configuration + QFrame* cleanupPage = addPage( i18n("&Cleanup") ); + QVBoxLayout *cleanupLayout = new QVBoxLayout( cleanupPage, KDialog::spacingHint() ); + mCleanupWidget = new KNConfig::GroupCleanupWidget( g_rp->cleanupConfig(), cleanupPage ); + mCleanupWidget->load(); + cleanupLayout->addWidget( mCleanupWidget ); + cleanupLayout->addStretch( 1 ); + + KNHelper::restoreWindowSize("groupPropDLG", this, sizeHint()); +} + + + +KNGroupPropDlg::~KNGroupPropDlg() +{ + KNHelper::saveWindowSize("groupPropDLG", size()); +} + + + +void KNGroupPropDlg::slotOk() +{ + if( !(g_rp->name()==n_ick->text()) ) { + g_rp->setName(n_ick->text()); + n_ickChanged=true; + } + + i_dWidget->save(); + mCleanupWidget->save(); + + g_rp->setUseCharset(u_seCharset->isChecked()); + g_rp->setDefaultCharset(c_harset->currentText().latin1()); + + accept(); +} diff --git a/knode/kngrouppropdlg.h b/knode/kngrouppropdlg.h new file mode 100644 index 000000000..3b67df9dc --- /dev/null +++ b/knode/kngrouppropdlg.h @@ -0,0 +1,59 @@ +/* + kngrouppropdlg.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNGROUPPROPDLG_H +#define KNGROUPPROPDLG_H + +#include <kdialogbase.h> + +class QCheckBox; +class QComboBox; + +class KLineEdit; + +class KNGroup; + +namespace KNConfig { + class IdentityWidget; + class GroupCleanupWidget; +} + + +class KNGroupPropDlg : public KDialogBase { + + public: + KNGroupPropDlg(KNGroup *group, QWidget *parent=0, const char *name=0); + ~KNGroupPropDlg(); + + bool nickHasChanged()const { return n_ickChanged; } + + protected: + KNGroup *g_rp; + bool n_ickChanged; + KNConfig::IdentityWidget* i_dWidget; + KNConfig::GroupCleanupWidget *mCleanupWidget; + KLineEdit *n_ick; + QCheckBox *u_seCharset; + QComboBox *c_harset; + + protected slots: + void slotOk(); + +}; + +#endif + +// kate: space-indent on; indent-width 2; diff --git a/knode/kngroupselectdialog.cpp b/knode/kngroupselectdialog.cpp new file mode 100644 index 000000000..e9ac3a2dd --- /dev/null +++ b/knode/kngroupselectdialog.cpp @@ -0,0 +1,172 @@ +/* + kngroupselectdialog.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qlabel.h> +#include <qheader.h> +#include <qcheckbox.h> + +#include <klocale.h> +#include <kmessagebox.h> + +#include "utilities.h" +#include "kngroupselectdialog.h" +#include <qpushbutton.h> + + +KNGroupSelectDialog::KNGroupSelectDialog(QWidget *parent, KNNntpAccount *a, const QString &act) : + KNGroupBrowser(parent, i18n("Select Destinations"), a) +{ + selView=new QListView(page); + selView->addColumn(QString::null); + selView->header()->hide(); + listL->addWidget(selView, 1,2); + rightLabel->setText(i18n("Groups for this article:")); + subCB->setChecked(true); + + KNGroupInfo info; + QStringList actGroups = QStringList::split(',',act); + for ( QStringList::Iterator it = actGroups.begin(); it != actGroups.end(); ++it ) { + info.name = *it; + new GroupItem(selView, info); + } + + connect(selView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotItemSelected(QListViewItem*))); + connect(groupView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotItemSelected(QListViewItem*))); + connect(groupView, SIGNAL(selectionChanged()), + this, SLOT(slotSelectionChanged())); + connect(arrowBtn1, SIGNAL(clicked()), this, SLOT(slotArrowBtn1())); + connect(arrowBtn2, SIGNAL(clicked()), this, SLOT(slotArrowBtn2())); + + KNHelper::restoreWindowSize("groupSelDlg", this, QSize(659,364)); // optimized for 800x600 +} + + + +KNGroupSelectDialog::~KNGroupSelectDialog() +{ + KNHelper::saveWindowSize("groupSelDlg", this->size()); +} + + + +void KNGroupSelectDialog::itemChangedState(CheckItem *it, bool s) +{ + if(s) + new GroupItem(selView, it->info); + else + removeListItem(selView, it->info); + arrowBtn1->setEnabled(!s); +} + + + +void KNGroupSelectDialog::updateItemState(CheckItem *it) +{ + it->setChecked(itemInListView(selView, it->info)); + if(it->info.subscribed && it->pixmap(0)==0) + it->setPixmap(0, pmGroup); +} + + + +QString KNGroupSelectDialog::selectedGroups()const +{ + QString ret; + QListViewItemIterator it(selView); + bool moderated=false; + int count=0; + bool isFirst=true; + + for(; it.current(); ++it) { + if(!isFirst) + ret+=","; + ret+=(static_cast<GroupItem*>(it.current()))->info.name; + isFirst=false; + count++; + if ((static_cast<GroupItem*>(it.current()))->info.status == KNGroup::moderated) + moderated=true; + } + + if (moderated && (count>=2)) // warn the user + KMessageBox::information(parentWidget(),i18n("You are crossposting to a moderated newsgroup.\nPlease be aware that your article will not appear in any group\nuntil it has been approved by the moderators of the moderated group."), + QString::null,"crosspostModeratedWarning"); + + return ret; +} + + + +void KNGroupSelectDialog::slotItemSelected(QListViewItem *it) +{ + const QObject *s=sender(); + + if(s==groupView) { + selView->clearSelection(); + arrowBtn2->setEnabled(false); + if(it) + arrowBtn1->setEnabled(!(static_cast<CheckItem*>(it))->isOn()); + else + arrowBtn1->setEnabled(false); + } + else { + groupView->clearSelection(); + arrowBtn1->setEnabled(false); + arrowBtn2->setEnabled((it!=0)); + } +} + + + +void KNGroupSelectDialog::slotSelectionChanged() +{ + if (!groupView->selectedItem()) + arrowBtn1->setEnabled(false); +} + + + +void KNGroupSelectDialog::slotArrowBtn1() +{ + CheckItem *i=static_cast<CheckItem*>(groupView->selectedItem()); + + if(i) { + new GroupItem(selView, i->info); + arrowBtn1->setEnabled(false); + i->setChecked(true); + } +} + + + +void KNGroupSelectDialog::slotArrowBtn2() +{ + GroupItem *i=static_cast<GroupItem*>(selView->selectedItem()); + + if(i) { + changeItemState(i->info, false); + delete i; + arrowBtn2->setEnabled(false); + } +} + + +// ----------------------------------------------------------------------------- + +#include "kngroupselectdialog.moc" + diff --git a/knode/kngroupselectdialog.h b/knode/kngroupselectdialog.h new file mode 100644 index 000000000..d46826cc3 --- /dev/null +++ b/knode/kngroupselectdialog.h @@ -0,0 +1,63 @@ +/* + kngroupselectdialog.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNGROUPSELECTDIALOG_H +#define KNGROUPSELECTDIALOG_H + +#include "kngroupbrowser.h" + + +class KNGroupSelectDialog : public KNGroupBrowser { + + Q_OBJECT + + public: + KNGroupSelectDialog(QWidget *parent, KNNntpAccount *a, const QString &act); + ~KNGroupSelectDialog(); + + QString selectedGroups()const; + void itemChangedState(CheckItem *it, bool s); + + protected: + void updateItemState(CheckItem *it); + QListView *selView; + + protected slots: + void slotItemSelected(QListViewItem *it); + /** deactivates the button when a root item is selected */ + void slotSelectionChanged(); + void slotArrowBtn1(); + void slotArrowBtn2(); + +}; + + +#endif + + + + + + + + + + + + + + + diff --git a/knode/knhdrviewitem.cpp b/knode/knhdrviewitem.cpp new file mode 100644 index 000000000..93b54fc0a --- /dev/null +++ b/knode/knhdrviewitem.cpp @@ -0,0 +1,294 @@ +/* + knhdrviewitem.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qdragobject.h> +#include <qpainter.h> + +#include <kdeversion.h> +#include <kdebug.h> +#include <kstringhandler.h> + +#include "knglobals.h" +#include "knconfigmanager.h" +#include "knhdrviewitem.h" +#include "knarticle.h" +#include "headerview.h" + + +KNHdrViewItem::KNHdrViewItem( KNHeaderView *ref, KNArticle *a ) : + KListViewItem( ref ) +{ + init( a ); +} + + +KNHdrViewItem::KNHdrViewItem( KNHdrViewItem *ref, KNArticle *a ) : + KListViewItem( ref ) +{ + init( a ); +} + + +void KNHdrViewItem::init( KNArticle *a ) +{ + art = a; + mActive = false; + for ( int i = 0; i < 5; ++i) // FIXME hardcoded column count + mShowToolTip[i] = false; +} + + +KNHdrViewItem::~KNHdrViewItem() +{ + if (mActive) { + QListView *lv = listView(); + if (lv) + static_cast<KNHeaderView*>( lv )->activeRemoved(); + } + + if (art) art->setListItem( 0 ); +} + + +void KNHdrViewItem::expandChildren() +{ + QListViewItemIterator it( firstChild() ); + for ( ; it.current(); ++it) { + if (it.current()->depth() <= depth()) + break; + it.current()->setOpen( true ); + } +} + + +int KNHdrViewItem::compare( QListViewItem *i, int col, bool ) const +{ + KNArticle *otherArticle = static_cast<KNHdrViewItem*>( i )->art; + int diff = 0; + time_t date1 = 0, date2 = 0; + + switch (col) { + case 0: + case 1: + return text( col ).localeAwareCompare( i->text(col) ); + + case 2: + if (art->type() == KMime::Base::ATremote) { + diff = static_cast<KNRemoteArticle*>( art )->score() - static_cast<KNRemoteArticle*>( otherArticle )->score(); + return (diff < 0 ? -1 : diff > 0 ? 1 : 0); + } else + return 0; + + case 3: + diff = art->lines()->numberOfLines() - otherArticle->lines()->numberOfLines(); + return (diff < 0 ? -1 : diff > 0 ? 1 : 0); + + case 4: + date1 = art->date()->unixTime(); + date2 = otherArticle->date()->unixTime(); + if (art->type() == KMime::Base::ATremote && static_cast<KNHeaderView*>( listView() )->sortByThreadChangeDate()) { + if (static_cast<KNRemoteArticle*>( art )->subThreadChangeDate() > date1) + date1 = static_cast<KNRemoteArticle*>( art )->subThreadChangeDate(); + if (static_cast<KNRemoteArticle*>( otherArticle )->subThreadChangeDate() > date2) + date2 = static_cast<KNRemoteArticle*>( otherArticle )->subThreadChangeDate(); + } + diff = date1 - date2; + return (diff < 0 ? -1 : diff > 0 ? 1 : 0); + + default: + return 0; + } +} + + +void KNHdrViewItem::paintCell( QPainter *p, const QColorGroup &cg, int column, int width, int alignment ) +{ + int xText = 0, xPM = 3, yPM = 0; + QColor base; + const KPaintInfo *paintInfo = static_cast<KNHeaderView*>( listView() )->paintInfo(); + + QPen pen = p->pen(); + if (isSelected() || mActive) { + pen.setColor( cg.highlightedText() ); + base = cg.highlight(); + } else { + if (greyOut()) + pen.setColor( greyColor() ); + else + pen.setColor( normalColor() ); + base = backgroundColor( column ); + } + + p->setPen( pen ); + + p->fillRect( 0, 0, width, height(), QBrush(base) ); + + if ( column == paintInfo->subCol ) { + QFont font = p->font(); + font.setBold( firstColBold() ); + p->setFont( font ); + const QPixmap *pm; + + for (int i = 0; i < 4; i++) { + pm = pixmap( i ); + if (pm && !pm->isNull()) { + yPM = (height() - pm->height()) / 2; + p->drawPixmap( xPM, yPM, *pm ); + xPM += pm->width() + 3; + } + } + + xText = xPM; + } + + if (width - xText - 5 > 0) { + int cntWidth = 0; + QString t2; + QFont f2; + if (countUnreadInThread() > 0 && column == paintInfo->subCol && !isOpen()) { + t2 = QString( " (%1)" ).arg( countUnreadInThread() ); + f2 = p->font(); + f2.setBold( true ); + cntWidth = QFontMetrics( f2 ).width( t2, -1 ); + } + QString t = KStringHandler::rPixelSqueeze( text( column ), p->fontMetrics(), width - xText - cntWidth - 5 ); + + // show tooltip if we have to squeeze the text + if ( t != text( column ) ) + mShowToolTip[column] = true; + else + mShowToolTip[column] = false; + + p->drawText( xText, 0, width - xText - 5, height(), alignment | AlignVCenter, t ); + if (cntWidth) { + QFont orig = p->font(); + p->setFont( f2 ); + QPen pen = p->pen(); + if (isSelected() || mActive) { + pen.setColor( cg.highlightedText() ); + } else { + pen.setColor( cg.link() ); + } + p->setPen( pen ); + p->drawText( xText + QFontMetrics( orig ).width( t, -1 ), 0, width - xText - 5, height(), alignment | AlignVCenter, t2 ); + } + } +} + + +int KNHdrViewItem::width( const QFontMetrics &fm, const QListView *, int column ) +{ + int ret = fm.boundingRect( text(column) ).width(); + const KPaintInfo *paintInfo = static_cast<KNHeaderView*>( listView() )->paintInfo(); + + // all pixmaps are drawn in the first column + if ( column == paintInfo->subCol ) { + const QPixmap *pm; + for (int i = 0; i < 4; ++i) { + pm = pixmap( i ); + if (pm && !pm->isNull()) + ret += pm->width() + 3; + } + } + + return ret; +} + + +QString KNHdrViewItem::text( int col ) const +{ + if ( !art ) + return QString::null; + KNHeaderView *hv = static_cast<KNHeaderView*>( listView() ); + + if ( col == hv->paintInfo()->subCol ) { + return art->subject()->asUnicodeString(); + } + + if ( col == hv->paintInfo()->sizeCol ) { + if ( art->lines()->numberOfLines() != -1 ) + return QString::number( art->lines()->numberOfLines() ); + else + return QString::null; + } + + if ( col == hv->paintInfo()->scoreCol ) { + if ( art->type() == KMime::Base::ATremote ) + return QString::number( static_cast<KNRemoteArticle*>( art )->score() ); + else + return QString::null; + } + + if ( col == hv->paintInfo()->dateCol ) { + return hv->mDateFormatter.dateString( art->date()->qdt() ); + } else + return KListViewItem::text( col ); +} + + +QDragObject* KNHdrViewItem::dragObject() +{ + QDragObject *d = new QStoredDrag( "x-knode-drag/article" , listView()->viewport() ); + d->setPixmap( knGlobals.configManager()->appearance()->icon( KNConfig::Appearance::posting ) ); + return d; +} + + +int KNHdrViewItem::countUnreadInThread() +{ + int count = 0; + if (knGlobals.configManager()->readNewsGeneral()->showUnread()) { + if (art->type() == KMime::Base::ATremote) { + count = static_cast<KNRemoteArticle*>( art )->unreadFollowUps(); + } + } + return count; +} + + +bool KNHdrViewItem::greyOut() +{ + if (art->type() == KMime::Base::ATremote) { + return !static_cast<KNRemoteArticle*>( art )->hasUnreadFollowUps() + && static_cast<KNRemoteArticle*>( art )->isRead(); + } else + return false; +} + + +bool KNHdrViewItem::firstColBold() +{ + if(art->type() == KMime::Base::ATremote) + return static_cast<KNRemoteArticle*>( art )->isNew(); + else + return false; +} + + +QColor KNHdrViewItem::normalColor() +{ + if (art->type()==KMime::Base::ATremote) + return static_cast<KNRemoteArticle*>( art )->color(); + else + return knGlobals.configManager()->appearance()->unreadThreadColor(); +} + + +QColor KNHdrViewItem::greyColor() +{ + return knGlobals.configManager()->appearance()->readThreadColor(); +} + diff --git a/knode/knhdrviewitem.h b/knode/knhdrviewitem.h new file mode 100644 index 000000000..a080c8381 --- /dev/null +++ b/knode/knhdrviewitem.h @@ -0,0 +1,67 @@ +/* + knhdrviewitem.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNHDRVIEWITEM_H +#define KNHDRVIEWITEM_H + +#include <klistview.h> +#include "headerview.h" + +class KNArticle; +class KNHeaderView; + + +class KNHdrViewItem : public KListViewItem { + + public: + KNHdrViewItem( KNHeaderView *ref, KNArticle *a = 0 ); + KNHdrViewItem( KNHdrViewItem *ref, KNArticle *a = 0 ); + ~KNHdrViewItem(); + + virtual int compare(QListViewItem *i, int col, bool ascending) const; + + void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment); + int width(const QFontMetrics &fm, const QListView *lv, int column); + + virtual QString text( int col ) const; + + void expandChildren(); + + void setActive( bool b ) { mActive = b; } + bool isActive() const { return mActive; } + + // DND + QDragObject* dragObject(); + + KNArticle *art; + int countUnreadInThread(); + + bool showToolTip( int column ) const { return mShowToolTip[column]; } + + private: + void init( KNArticle *a ); + + bool greyOut(); + bool firstColBold(); + QColor normalColor(); + QColor greyColor(); + + bool mActive; + bool mShowToolTip[5]; // ### hardcoded column count :-( + +}; + +#endif diff --git a/knode/knjobdata.cpp b/knode/knjobdata.cpp new file mode 100644 index 000000000..7d8cb6f8f --- /dev/null +++ b/knode/knjobdata.cpp @@ -0,0 +1,147 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + + +#include <kdebug.h> +#include <klocale.h> +#include <kio/job.h> + +#include <libkdepim/progressmanager.h> + +#include "knarticle.h" +#include "knglobals.h" +#include "knnetaccess.h" +#include "knnntpaccount.h" + +#include <qstylesheet.h> + +KNJobConsumer::KNJobConsumer() +{ +} + + +KNJobConsumer::~KNJobConsumer() +{ + QValueList<KNJobData*>::Iterator it; + for ( it = mJobs.begin(); it != mJobs.end(); ++it ) + (*it)->c_onsumer = 0; +} + + +void KNJobConsumer::emitJob( KNJobData *j ) +{ + if ( j ) { + mJobs.append( j ); + knGlobals.netAccess()->addJob( j ); + } +} + + +void KNJobConsumer::jobDone( KNJobData *j ) +{ + if ( j && mJobs.remove( j ) ) + processJob( j ); +} + + +void KNJobConsumer::processJob( KNJobData *j ) +{ + delete j; +} + + +// the assingment of a_ccount may cause race conditions, check again.... (CG) +KNJobData::KNJobData(jobType t, KNJobConsumer *c, KNServerInfo *a, KNJobItem *i) + : t_ype(t), d_ata(i), a_ccount(a), c_anceled(false), a_uthError(false), c_onsumer(c), + mJob( 0 ), + mProgressItem( 0 ) +{ + d_ata->setLocked(true); +} + + + +KNJobData::~KNJobData() +{ + d_ata->setLocked(false); +} + + +void KNJobData::notifyConsumer() +{ + + if(c_onsumer) + c_onsumer->jobDone(this); + else + delete this; +} + +void KNJobData::cancel() +{ + c_anceled = true; + if ( mJob ) { + mJob->kill(); + mJob = 0; + } + if ( mProgressItem ) { + mProgressItem->setStatus( "Canceled" ); + mProgressItem->setComplete(); + mProgressItem = 0; + } +} + +void KNJobData::setJob( KIO::Job *job ) +{ + mJob = job; + if ( job ) { + connect( job, SIGNAL( percent(KIO::Job*, unsigned long) ), + SLOT( slotJobPercent(KIO::Job*, unsigned long) ) ); + connect( job, SIGNAL( infoMessage(KIO::Job*, const QString&) ), + SLOT( slotJobInfoMessage(KIO::Job*, const QString&) ) ); + } +} + +void KNJobData::createProgressItem() +{ + if ( mProgressItem ) + return; + KNNntpAccount *acc = static_cast<KNNntpAccount*>( account() ); + QString msg = i18n( "KNode" ); + if ( type() == JTmail ) + msg = i18n( "Sending message" ); + else { + if ( acc ) + msg = QStyleSheet::escape( acc->name() ); + } + bool encr = false; + if ( acc && acc->encryption() != KNServerInfo::None ) + encr = true; + mProgressItem = KPIM::ProgressManager::createProgressItem( 0, + KPIM::ProgressManager::getUniqueID(), msg, i18n( "Waiting..." ), true, encr ); +} + +void KNJobData::slotJobPercent( KIO::Job*, unsigned long percent ) +{ + kdDebug(5003) << k_funcinfo << "Progress: " << percent << endl; + setProgress( percent ); +} + +void KNJobData::slotJobInfoMessage( KIO::Job*, const QString &msg ) +{ + kdDebug(5003) << k_funcinfo << "Status: " << msg << endl; + setStatus( msg ); +} + + +#include "knjobdata.moc" diff --git a/knode/knjobdata.h b/knode/knjobdata.h new file mode 100644 index 000000000..5733a9883 --- /dev/null +++ b/knode/knjobdata.h @@ -0,0 +1,141 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNJOBDATA_H +#define KNJOBDATA_H + +#include <qobject.h> +#include <qvaluelist.h> + +#include <libkdepim/progressmanager.h> + +namespace KIO { + class Job; +} + +class KNJobData; +class KNServerInfo; + + +class KNJobConsumer { + + public: + KNJobConsumer(); + virtual ~KNJobConsumer(); + + /** Send the job to KNNetAccess and append it to the + joblist */ + void emitJob(KNJobData *j); + + /** Remove the job from the joblist and process it by + calling @ref processJob */ + void jobDone(KNJobData *j); + + /** Returns true if we are waiting for at least one job + to be completed */ + bool jobsPending() const { return !mJobs.isEmpty(); } + + protected: + /** The actual work is done here */ + virtual void processJob(KNJobData *j); + QValueList<KNJobData*> mJobs; + +}; + + +class KNJobItem { + + public: + KNJobItem() {} + virtual ~KNJobItem() {} + + virtual bool isLocked() { return false; } + virtual void setLocked(bool) { } + + virtual QString prepareForExecution() { return QString::null; } + +}; + + +class KNJobData : public QObject +{ + Q_OBJECT + + public: + + friend class KNJobConsumer; + + enum jobType { JTLoadGroups=1, + JTFetchGroups, + JTCheckNewGroups, + JTfetchNewHeaders, + JTsilentFetchNewHeaders, + JTfetchArticle, + JTpostArticle, + JTmail, + JTfetchSource }; + + KNJobData(jobType t, KNJobConsumer *c, KNServerInfo *a, KNJobItem *i); + ~KNJobData(); + + jobType type() const { return t_ype; } + + bool net() const { return (t_ype!=JTLoadGroups); } + KNServerInfo* account() const { return a_ccount; } + KNJobItem* data() const { return d_ata; } + + const QString& errorString() const { return e_rrorString; } + bool success() const { return e_rrorString.isEmpty(); } + bool canceled() const { return c_anceled; } + bool authError() const { return a_uthError; } + + void setErrorString(const QString& s) { e_rrorString=s; } + void cancel(); + void setAuthError(bool b) { a_uthError=b; } + + void prepareForExecution() { e_rrorString = d_ata->prepareForExecution(); } + void notifyConsumer(); + + KIO::Job* job() const { return mJob; } + void setJob( KIO::Job *job ); + + KPIM::ProgressItem* progressItem() const { return mProgressItem; } + void createProgressItem(); + + // safe forwards to the progress item + void setStatus( const QString &msg ) { if ( mProgressItem ) mProgressItem->setStatus( msg ); } + void setProgress( unsigned int progress ) { if ( mProgressItem ) mProgressItem->setProgress( progress ); } + void setComplete() { if ( mProgressItem ) { mProgressItem->setComplete(); mProgressItem = 0; } } + + protected: + jobType t_ype; + KNJobItem *d_ata; + KNServerInfo *a_ccount; + QString e_rrorString; + bool c_anceled; + bool a_uthError; + KNJobConsumer *c_onsumer; + + private slots: + void slotJobPercent( KIO::Job *job, unsigned long percent ); + void slotJobInfoMessage( KIO::Job *job, const QString &msg ); + + private: + KIO::Job *mJob; + KPIM::ProgressItem *mProgressItem; + +}; + + +#endif diff --git a/knode/knmainwidget.cpp b/knode/knmainwidget.cpp new file mode 100644 index 000000000..e4d91e812 --- /dev/null +++ b/knode/knmainwidget.cpp @@ -0,0 +1,2215 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2004-2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ +#include "knmainwidget.h" + +#include <qhbox.h> +#include <qlayout.h> +#include <ktoolbar.h> + +#include <kinputdialog.h> +#include <kaccel.h> +#include <kxmlguiclient.h> +#include <kconfig.h> +#include <kmessagebox.h> +#include <kedittoolbar.h> +#include <kstdaction.h> +#include <kdebug.h> +#include <kmenubar.h> +#include <kiconloader.h> +#include <kstatusbar.h> +#include <klocale.h> +#include <kapplication.h> + +#include "broadcaststatus.h" +#include "krsqueezedtextlabel.h" +#include "recentaddresses.h" +using KPIM::BroadcastStatus; +using KRecentAddress::RecentAddresses; + +//GUI +#include "knmainwidget.h" +#include "knarticlewindow.h" +#include "kncollectionview.h" +#include "kncollectionviewitem.h" +#include "knhdrviewitem.h" + +//Core +#include "articlewidget.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "knarticlemanager.h" +#include "knarticlefactory.h" +#include "kngroupmanager.h" +#include "knnntpaccount.h" +#include "knaccountmanager.h" +#include "knnetaccess.h" +#include "knfiltermanager.h" +#include "knfoldermanager.h" +#include "knfolder.h" +#include "kncleanup.h" +#include "utilities.h" +#include "knscoring.h" +#include <kpgp.h> +#include "knmemorymanager.h" +#include <kcmdlineargs.h> + +#include <klistviewsearchline.h> + +using namespace KNode; + +KNGlobals knGlobals; + +KNMainWidget::KNMainWidget( KXMLGUIClient* client, bool detachable, QWidget* parent, + const char* name ) + : DCOPObject("KNodeIface"), KDockArea( parent, name ), + b_lockui( false ), m_GUIClient( client ) +{ + knGlobals.top=this; + knGlobals.guiClient=client; + knGlobals.topWidget=this; + + //------------------------------- <CONFIG> ---------------------------------- + c_fgManager = knGlobals.configManager(); + //------------------------------- </CONFIG> ---------------------------------- + + //-------------------------------- <GUI> ------------------------------------ + QAccel *accel = new QAccel( this ); + initStatusBar(); + + //setup splitter behavior + manager()->setSplitterHighResolution(true); + manager()->setSplitterOpaqueResize(true); + + //article view + a_rtDock = createDockWidget("article_viewer", SmallIcon("contents"), 0, + kapp->makeStdCaption(i18n("Article Viewer")), i18n("Article Viewer")); + if (!detachable) { + a_rtDock->setEnableDocking(KDockWidget::DockFullSite); + } + KDockWidgetHeader *header = new KDockWidgetHeader(a_rtDock, "artDockHeader"); + a_rtDock->setHeader(header); + mArticleViewer = new ArticleWidget( a_rtDock, knGlobals.guiClient, actionCollection(), "articleViewer"); + header->setDragPanel( new KNDockWidgetHeaderDrag( mArticleViewer, header, a_rtDock ) ); + knGlobals.artWidget = mArticleViewer; + a_rtDock->setWidget( mArticleViewer ); + //setView(a_rtDock); + setMainDockWidget(a_rtDock); + + connect(a_rtDock, SIGNAL(iMBeingClosed()), SLOT(slotArticleDockHidden())); + connect(a_rtDock, SIGNAL(hasUndocked()), SLOT(slotArticleDockHidden())); + connect( mArticleViewer, SIGNAL(focusChangeRequest(QWidget*)), SLOT(slotDockWidgetFocusChangeRequest(QWidget*)) ); + + //collection view + c_olDock = createDockWidget("group_view", UserIcon("group"), 0, + kapp->makeStdCaption(i18n("Group View")), i18n("Group View")); + if (!detachable) { + c_olDock->setEnableDocking(KDockWidget::DockFullSite); + } + header = new KDockWidgetHeader(c_olDock, "colDockHeader"); + c_olDock->setHeader(header); + c_olView = new KNCollectionView(this, "collectionView"); + header->setDragPanel(new KNDockWidgetHeaderDrag(c_olView, header, c_olDock)); + c_olDock->setWidget(c_olView); + c_olDock->manualDock(a_rtDock, KDockWidget::DockLeft, 3000); + + connect(c_olDock, SIGNAL(iMBeingClosed()), SLOT(slotGroupDockHidden())); + connect(c_olDock, SIGNAL(hasUndocked()), SLOT(slotGroupDockHidden())); + connect(c_olView, SIGNAL(focusChangeRequest(QWidget *)), SLOT(slotDockWidgetFocusChangeRequest(QWidget *))); + connect(c_olView, SIGNAL(selectionChanged(QListViewItem*)), + SLOT(slotCollectionSelected(QListViewItem*))); + connect(c_olView, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + SLOT(slotCollectionRMB(KListView*, QListViewItem*, const QPoint&))); + connect(c_olView, SIGNAL(folderDrop(QDropEvent*, KNCollectionViewItem*)), + SLOT(slotCollectionViewDrop(QDropEvent*, KNCollectionViewItem*))); + connect(c_olView, SIGNAL(itemRenamed(QListViewItem*)), + SLOT(slotCollectionRenamed(QListViewItem*))); + + accel->connectItem( accel->insertItem(Key_Up), mArticleViewer, SLOT(scrollUp()) ); + accel->connectItem( accel->insertItem(Key_Down), mArticleViewer, SLOT(scrollDown()) ); + accel->connectItem( accel->insertItem(Key_Prior), mArticleViewer, SLOT(scrollPrior()) ); + accel->connectItem( accel->insertItem(Key_Next), mArticleViewer, SLOT(scrollNext()) ); + + //header view + h_drDock = createDockWidget("header_view", SmallIcon("text_block"), 0, + kapp->makeStdCaption(i18n("Header View")), i18n("Header View")); + if (!detachable) { + h_drDock->setEnableDocking(KDockWidget::DockFullSite); + } + header = new KDockWidgetHeader(h_drDock, "headerDockHeader"); + h_drDock->setHeader(header); + QWidget *dummy = new QWidget(h_drDock); + QVBoxLayout *vlay = new QVBoxLayout(dummy); + h_drView = new KNHeaderView( dummy, "hdrView" ); + header->setDragPanel(new KNDockWidgetHeaderDrag(h_drView, header, h_drDock)); + h_drDock->setWidget(dummy); + h_drDock->manualDock(a_rtDock, KDockWidget::DockTop, 5000); + + q_uicksearch = new KToolBar(dummy, "search toolbar"); + KAction *resetQuickSearch = new KAction( i18n( "Reset Quick Search" ), + QApplication::reverseLayout() + ? "clear_left" + : "locationbar_erase", + 0, actionCollection(), + "reset_quicksearch" ); + resetQuickSearch->plug( q_uicksearch ); + resetQuickSearch->setWhatsThis( i18n( "<b>Reset Quick Search</b><br>" + "Resets the quick search so that " + "all messages are shown again." ) ); + + QLabel *lbl = new QLabel(i18n("&Search:"), q_uicksearch, "kde toolbar widget"); + s_earchLineEdit = new KListViewSearchLine(q_uicksearch, h_drView, "KListViewSearchLine"); + q_uicksearch->setStretchableWidget(s_earchLineEdit); + lbl->setBuddy(s_earchLineEdit); + connect( resetQuickSearch, SIGNAL( activated() ), s_earchLineEdit, SLOT( clear() )); + + vlay->addWidget(q_uicksearch); + vlay->addWidget(h_drView); + + connect(h_drDock, SIGNAL(iMBeingClosed()), SLOT(slotHeaderDockHidden())); + connect(h_drDock, SIGNAL(hasUndocked()), SLOT(slotHeaderDockHidden())); + connect(h_drView, SIGNAL(focusChangeRequest(QWidget *)), + SLOT(slotDockWidgetFocusChangeRequest(QWidget *))); + connect(h_drView, SIGNAL(itemSelected(QListViewItem*)), + SLOT(slotArticleSelected(QListViewItem*))); + connect(h_drView, SIGNAL(selectionChanged()), + SLOT(slotArticleSelectionChanged())); + connect(h_drView, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + SLOT(slotArticleRMB(KListView*, QListViewItem*, const QPoint&))); + connect(h_drView, SIGNAL(doubleClick(QListViewItem *)), + SLOT(slotOpenArticle(QListViewItem *))); + connect(h_drView, SIGNAL(sortingChanged(int)), + SLOT(slotHdrViewSortingChanged(int))); + + //actions + initActions(); + + //-------------------------------- </GUI> ------------------------------------ + + //-------------------------------- <CORE> ------------------------------------ + + //Network + n_etAccess = knGlobals.netAccess(); + connect(n_etAccess, SIGNAL(netActive(bool)), this, SLOT(slotNetworkActive(bool))); + + //Filter Manager + f_ilManager = knGlobals.filterManager(); + f_ilManager->setMenuAction(a_ctArtFilter, a_ctArtFilterKeyb); + + //Article Manager + a_rtManager = knGlobals.articleManager(); + a_rtManager->setView(h_drView); + + //Group Manager + g_rpManager = knGlobals.groupManager(); + + //Folder Manager + f_olManager = knGlobals.folderManager(); + + //Account Manager + a_ccManager = knGlobals.accountManager(); + + //Article Factory + a_rtFactory=new KNArticleFactory(); + knGlobals.artFactory=a_rtFactory; + + // Score Manager + s_coreManager = knGlobals.scoringManager(); + //connect(s_coreManager, SIGNAL(changedRules()), SLOT(slotReScore())); + connect(s_coreManager, SIGNAL(finishedEditing()), SLOT(slotReScore())); + + // Memory Manager + m_emManager = knGlobals.memoryManager(); + + // create a global pgp instance + p_gp = new Kpgp::Module(); + knGlobals.pgp = p_gp; + + //-------------------------------- </CORE> ----------------------------------- + + //apply saved options + readOptions(); + + //apply configuration + configChanged(); + + // set the keyboard focus indicator on the first item in the Collection View + if( c_olView->firstChild() ) { + QListViewItem *i = c_olView->firstChild(); + bool open = i->isOpen(); + c_olView->setActive( i ); + i->setOpen( open ); + } + + c_olView->setFocus(); + + setStatusMsg(); + + if( firstStart() ) { // open the config dialog on the first start + show(); // the settings dialog must appear in front of the main window! + slotSettings(); + } +} + +KNMainWidget::~KNMainWidget() +{ + delete a_ccel; + + h_drView->clear(); //avoid some random crashes in KNHdrViewItem::~KNHdrViewItem() + + delete n_etAccess; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Net deleted" << endl; + + delete a_rtManager; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Article Manager deleted" << endl; + + delete a_rtFactory; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Article Factory deleted" << endl; + + delete g_rpManager; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Group Manager deleted" << endl; + + delete f_olManager; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Folder Manager deleted" << endl; + + delete f_ilManager; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Filter Manager deleted" << endl; + + delete a_ccManager; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Account Manager deleted" << endl; + + delete c_fgManager; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Config deleted" << endl; + + delete m_emManager; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : Memory Manager deleted" << endl; + + delete p_gp; + kdDebug(5003) << "KNMainWidget::~KNMainWidget() : PGP deleted" << endl; + + delete c_olDock; + delete h_drDock; + delete a_rtDock; +} + +void KNMainWidget::initStatusBar() +{ + //statusbar + KMainWindow *mainWin = dynamic_cast<KMainWindow*>(topLevelWidget()); + KStatusBar *sb = mainWin ? mainWin->statusBar() : 0; + s_tatusFilter = new KRSqueezedTextLabel( QString::null, sb ); + s_tatusFilter->setAlignment( AlignLeft | AlignVCenter ); + s_tatusGroup = new KRSqueezedTextLabel( QString::null, sb ); + s_tatusGroup->setAlignment( AlignLeft | AlignVCenter ); +} + +//================================== GUI ================================= + +void KNMainWidget::setStatusMsg(const QString& text, int id) +{ + KMainWindow *mainWin = dynamic_cast<KMainWindow*>(topLevelWidget()); + KStatusBar *bar = mainWin ? mainWin->statusBar() : 0; + if ( !bar ) + return; + bar->clear(); + if (text.isEmpty() && (id==SB_MAIN)) { + if (knGlobals.netAccess()->currentMsg().isEmpty()) + BroadcastStatus::instance()->setStatusMsg(i18n(" Ready")); + else + BroadcastStatus::instance()->setStatusMsg(knGlobals.netAccess()->currentMsg()); + } else { + switch(id) { + case SB_MAIN: + BroadcastStatus::instance()->setStatusMsg(text); break; + case SB_GROUP: + s_tatusGroup->setText(text); break; + case SB_FILTER: + s_tatusFilter->setText(text); break; + } + } +} + + +void KNMainWidget::setStatusHelpMsg(const QString& text) +{ + KMainWindow *mainWin = dynamic_cast<KMainWindow*>(topLevelWidget()); + KStatusBar *bar = mainWin ? mainWin->statusBar() : 0; + if ( bar ) + bar->message(text, 2000); +} + + +void KNMainWidget::updateCaption() +{ + QString newCaption=i18n("KDE News Reader"); + if (g_rpManager->currentGroup()) { + newCaption = g_rpManager->currentGroup()->name(); + if (g_rpManager->currentGroup()->status()==KNGroup::moderated) + newCaption += i18n(" (moderated)"); + } else if (a_ccManager->currentAccount()) { + newCaption = a_ccManager->currentAccount()->name(); + } else if (f_olManager->currentFolder()) { + newCaption = f_olManager->currentFolder()->name(); + } + emit signalCaptionChangeRequest(newCaption); +} + + +void KNMainWidget::setCursorBusy(bool b) +{ + if(b) KApplication::setOverrideCursor(waitCursor); + else KApplication::restoreOverrideCursor(); +} + + +void KNMainWidget::blockUI(bool b) +{ + b_lockui = b; + KMainWindow *mainWin = dynamic_cast<KMainWindow*>(topLevelWidget()); + KMenuBar *mbar = mainWin ? mainWin->menuBar() : 0; + if ( mbar ) + mbar->setEnabled(!b); + a_ccel->setEnabled(!b); + KAccel *naccel = mainWin ? mainWin->accel() : 0; + if ( naccel ) + naccel->setEnabled(!b); + if (b) + installEventFilter(this); + else + removeEventFilter(this); + setCursorBusy(b); +} + + +void KNMainWidget::disableAccels(bool b) +{ + a_ccel->setEnabled(!b); + KMainWindow *mainWin = dynamic_cast<KMainWindow*>(topLevelWidget()); + KAccel *naccel = mainWin ? mainWin->accel() : 0; + if ( naccel ) + naccel->setEnabled(!b); + if (b) + installEventFilter(this); + else + removeEventFilter(this); +} + + +// processEvents with some blocking +void KNMainWidget::secureProcessEvents() +{ + b_lockui = true; + KMainWindow *mainWin = dynamic_cast<KMainWindow*>(topLevelWidget()); + KMenuBar *mbar = mainWin ? mainWin->menuBar() : 0; + if ( mbar ) + mbar->setEnabled(false); + a_ccel->setEnabled(false); + KAccel *naccel = mainWin ? mainWin->accel() : 0; + if ( naccel ) + naccel->setEnabled(false); + installEventFilter(this); + + kapp->processEvents(); + + b_lockui = false; + if ( mbar ) + mbar->setEnabled(true); + a_ccel->setEnabled(true); + if ( naccel ) + naccel->setEnabled(true); + removeEventFilter(this); +} + + +QSize KNMainWidget::sizeHint() const +{ + return QSize(759,478); // default optimized for 800x600 +} + + +void KNMainWidget::openURL(const KURL &url) +{ + kdDebug(5003) << k_funcinfo << url << endl; + QString host = url.host(); + unsigned short int port = url.port(); + KNNntpAccount *acc=0; + + if (url.url().left(7) == "news://") { + + // lets see if we already have an account for this host... + QValueList<KNNntpAccount*>::Iterator it; + for ( it = a_ccManager->begin(); it != a_ccManager->end(); ++it ) { + if ( (*it)->server().lower() == host.lower() && ( port==0 || (*it)->port() == port ) ) { + acc = *it; + break; + } + } + + if(!acc) { + acc=new KNNntpAccount(); + acc->setName(host); + acc->setServer(host); + + if(port!=0) + acc->setPort(port); + + if(url.hasUser() && url.hasPass()) { + acc->setNeedsLogon(true); + acc->setUser(url.user()); + acc->setPass(url.pass()); + } + + if(!a_ccManager->newAccount(acc)) + return; + } + } else { + if (url.url().left(5) == "news:") { + // TODO: make the default server configurable + acc = a_ccManager->currentAccount(); + if ( acc == 0 ) + acc = a_ccManager->first(); + } else { + kdDebug(5003) << "KNMainWidget::openURL() URL is not a valid news URL" << endl; + } + } + + if (acc) { + bool isMID=(url.url().contains('@')==1); + + if (!isMID) { + QString groupname=url.path(-1); + while(groupname.startsWith("/")) + groupname.remove(0,1); + QListViewItem *item=0; + if(groupname.isEmpty()) + item=acc->listItem(); + else { + KNGroup *grp= g_rpManager->group(groupname, acc); + + if(!grp) { + KNGroupInfo inf(groupname, ""); + g_rpManager->subscribeGroup(&inf, acc); + grp=g_rpManager->group(groupname, acc); + if(grp) + item=grp->listItem(); + } + else + item=grp->listItem(); + } + + if (item) { + c_olView->ensureItemVisible(item); + c_olView->setActive( item ); + } + } else { + QString groupname = url.url().mid( url.protocol().length()+1 ); + KNGroup *g = g_rpManager->currentGroup(); + if (g == 0) + g = g_rpManager->firstGroupOfAccount(acc); + + if (g) { + if(!KNArticleWindow::raiseWindowForArticle(groupname.latin1())) { //article not yet opened + KNRemoteArticle *a=new KNRemoteArticle(g); + QString messageID = "<"+groupname+">"; + a->messageID()->from7BitString(messageID.latin1()); + KNArticleWindow *awin=new KNArticleWindow(a); + awin->show(); + } + } else { + // TODO: fetch without group + kdDebug(5003) << "KNMainWidget::openURL() account has no groups" << endl; + } + } + } +} + + +// update fonts and colors +void KNMainWidget::configChanged() +{ + h_drView->readConfig(); + c_olView->readConfig(); + a_rtManager->updateListViewItems(); +} + + +void KNMainWidget::initActions() +{ + a_ccel=new KAccel(this); + mArticleViewer->setCharsetKeyboardAction()->plugAccel(a_ccel); + + //navigation + a_ctNavNextArt = new KAction( KGuiItem(i18n("&Next Article"), "next", + i18n("Go to next article")), "N;Right", h_drView, + SLOT(nextArticle()), actionCollection(), "go_nextArticle" ); + a_ctNavPrevArt = new KAction( KGuiItem(i18n("&Previous Article"), "previous", + i18n("Go to previous article")), "P;Left" , h_drView, + SLOT(prevArticle()), actionCollection(), "go_prevArticle" ); + a_ctNavNextUnreadArt = new KAction(i18n("Next Unread &Article"), "1rightarrow", ALT+SHIFT+Key_Space , this, + SLOT(slotNavNextUnreadArt()), actionCollection(), "go_nextUnreadArticle"); + a_ctNavNextUnreadThread = new KAction(i18n("Next Unread &Thread"),"2rightarrow", SHIFT+Key_Space , this, + SLOT(slotNavNextUnreadThread()), actionCollection(), "go_nextUnreadThread"); + a_ctNavNextGroup = new KAction(i18n("Ne&xt Group"), "down", Key_Plus , c_olView, + SLOT(nextGroup()), actionCollection(), "go_nextGroup"); + a_ctNavPrevGroup = new KAction(i18n("Pre&vious Group"), "up", Key_Minus , c_olView, + SLOT(prevGroup()), actionCollection(), "go_prevGroup"); + a_ctNavReadThrough = new KAction(i18n("Read &Through Articles"), Key_Space , this, + SLOT(slotNavReadThrough()), actionCollection(), "go_readThrough"); + a_ctNavReadThrough->plugAccel(a_ccel); + + QAccel *accel = new QAccel( this ); + new KAction( i18n("Focus on Next Folder"), CTRL+Key_Right, c_olView, + SLOT(incCurrentFolder()), actionCollection(), "inc_current_folder" ); + accel->connectItem(accel->insertItem(CTRL+Key_Right), + c_olView, SLOT(incCurrentFolder())); + new KAction( i18n("Focus on Previous Folder"), CTRL+Key_Left, c_olView, + SLOT(decCurrentFolder()), actionCollection(), "dec_current_folder" ); + accel->connectItem(accel->insertItem(CTRL+Key_Left), + c_olView, SLOT(decCurrentFolder())); + new KAction( i18n("Select Folder with Focus"), CTRL+Key_Space, c_olView, + SLOT(selectCurrentFolder()), actionCollection(), "select_current_folder" ); + accel->connectItem(accel->insertItem(CTRL+Key_Space), + c_olView, SLOT(selectCurrentFolder())); + + new KAction( i18n("Focus on Next Article"), ALT+Key_Right, h_drView, + SLOT(incCurrentArticle()), actionCollection(), "inc_current_article" ); + accel->connectItem( accel->insertItem(ALT+Key_Right), + h_drView, SLOT(incCurrentArticle()) ); + new KAction( i18n("Focus on Previous Article"), ALT+Key_Left, h_drView, + SLOT(decCurrentArticle()), actionCollection(), "dec_current_article" ); + accel->connectItem( accel->insertItem(ALT+Key_Left), + h_drView, SLOT(decCurrentArticle()) ); + new KAction( i18n("Select Article with Focus"), ALT+Key_Space, h_drView, + SLOT(selectCurrentArticle()), actionCollection(), "select_current_article" ); + accel->connectItem( accel->insertItem(ALT+Key_Space), + h_drView, SLOT(selectCurrentArticle()) ); + + //collection-view - accounts + a_ctAccProperties = new KAction(i18n("Account &Properties"), "configure", 0, this, + SLOT(slotAccProperties()), actionCollection(), "account_properties"); + a_ctAccRename = new KAction(i18n("&Rename Account"), "text", 0, this, + SLOT(slotAccRename()), actionCollection(), "account_rename"); + a_ctAccSubscribe = new KAction(i18n("&Subscribe to Newsgroups..."), "news_subscribe", 0, this, + SLOT(slotAccSubscribe()), actionCollection(), "account_subscribe"); + a_ctAccExpireAll = new KAction(i18n("&Expire All Groups"), 0, this, + SLOT(slotAccExpireAll()), actionCollection(), "account_expire_all"); + a_ctAccGetNewHdrs = new KAction(i18n("&Get New Articles in All Groups"), "mail_get", 0, this, + SLOT(slotAccGetNewHdrs()), actionCollection(), "account_dnlHeaders"); + a_ctAccGetNewHdrsAll = new KAction(i18n("&Get New Articles in All Accounts"), "mail_get_all", 0, this, + SLOT(slotAccGetNewHdrsAll()), actionCollection(), "account_dnlAllHeaders"); + a_ctAccDelete = new KAction(i18n("&Delete Account"), "editdelete", 0, this, + SLOT(slotAccDelete()), actionCollection(), "account_delete"); + a_ctAccPostNewArticle = new KAction(i18n("&Post to Newsgroup..."), "mail_new", CTRL+Key_N, this, + SLOT(slotAccPostNewArticle()), actionCollection(), "article_postNew"); + + //collection-view - groups + a_ctGrpProperties = new KAction(i18n("Group &Properties"), "configure", 0, this, + SLOT(slotGrpProperties()), actionCollection(), "group_properties"); + a_ctGrpRename = new KAction(i18n("Rename &Group"), "text", 0, this, + SLOT(slotGrpRename()), actionCollection(), "group_rename"); + a_ctGrpGetNewHdrs = new KAction(i18n("&Get New Articles"), "mail_get" , 0, this, + SLOT(slotGrpGetNewHdrs()), actionCollection(), "group_dnlHeaders"); + a_ctGrpExpire = new KAction(i18n("E&xpire Group"), "wizard", 0, this, + SLOT(slotGrpExpire()), actionCollection(), "group_expire"); + a_ctGrpReorganize = new KAction(i18n("Re&organize Group"), 0, this, + SLOT(slotGrpReorganize()), actionCollection(), "group_reorg"); + a_ctGrpUnsubscribe = new KAction(i18n("&Unsubscribe From Group"), "news_unsubscribe", 0, this, + SLOT(slotGrpUnsubscribe()), actionCollection(), "group_unsubscribe"); + a_ctGrpSetAllRead = new KAction(i18n("Mark All as &Read"), "goto", 0, this, + SLOT(slotGrpSetAllRead()), actionCollection(), "group_allRead"); + a_ctGrpSetAllUnread = new KAction(i18n("Mark All as U&nread"), 0, this, + SLOT(slotGrpSetAllUnread()), actionCollection(), "group_allUnread"); + a_ctGrpSetUnread = new KAction(i18n("Mark Last as Unr&ead..."), 0, this, + SLOT(slotGrpSetUnread()), actionCollection(), "group_unread"); + + + + (void) new KAction( i18n("&Configure KNode..."), + "configure", 0, this, + SLOT(slotSettings()), actionCollection(), + "knode_configure_knode" ); + + //collection-view - folder + a_ctFolNew = new KAction(i18n("&New Folder"), "folder_new", 0, this, + SLOT(slotFolNew()), actionCollection(), "folder_new"); + a_ctFolNewChild = new KAction(i18n("New &Subfolder"), "folder_new", 0, this, + SLOT(slotFolNewChild()), actionCollection(), "folder_newChild"); + a_ctFolDelete = new KAction(i18n("&Delete Folder"), "editdelete", 0, this, + SLOT(slotFolDelete()), actionCollection(), "folder_delete"); + a_ctFolRename = new KAction(i18n("&Rename Folder"), "text", 0, this, + SLOT(slotFolRename()), actionCollection(), "folder_rename"); + a_ctFolCompact = new KAction(i18n("C&ompact Folder"), "wizard", 0, this, + SLOT(slotFolCompact()), actionCollection(), "folder_compact"); + a_ctFolCompactAll = new KAction(i18n("Co&mpact All Folders"), 0, this, + SLOT(slotFolCompactAll()), actionCollection(), "folder_compact_all"); + a_ctFolEmpty = new KAction(i18n("&Empty Folder"), 0, this, + SLOT(slotFolEmpty()), actionCollection(), "folder_empty"); + a_ctFolMboxImport = new KAction(i18n("&Import MBox Folder..."), 0, this, + SLOT(slotFolMBoxImport()), actionCollection(), "folder_MboxImport"); + a_ctFolMboxExport = new KAction(i18n("E&xport as MBox Folder..."), 0, this, + SLOT(slotFolMBoxExport()), actionCollection(), "folder_MboxExport"); + + //header-view - list-handling + a_ctArtSortHeaders = new KSelectAction(i18n("S&ort"), 0, actionCollection(), "view_Sort"); + QStringList items; + items += i18n("By &Subject"); + items += i18n("By S&ender"); + items += i18n("By S&core"); + items += i18n("By &Lines"); + items += i18n("By &Date"); + a_ctArtSortHeaders->setItems(items); + a_ctArtSortHeaders->setShortcutConfigurable(false); + connect(a_ctArtSortHeaders, SIGNAL(activated(int)), this, SLOT(slotArtSortHeaders(int))); + a_ctArtSortHeadersKeyb = new KAction(i18n("Sort"), QString::null, Key_F7 , this, + SLOT(slotArtSortHeadersKeyb()), actionCollection(), "view_Sort_Keyb"); + a_ctArtSortHeadersKeyb->plugAccel(a_ccel); + a_ctArtFilter = new KNFilterSelectAction(i18n("&Filter"), "filter", + actionCollection(), "view_Filter"); + a_ctArtFilter->setShortcutConfigurable(false); + a_ctArtFilterKeyb = new KAction(i18n("Filter"), Key_F6, actionCollection(), "view_Filter_Keyb"); + a_ctArtFilterKeyb->plugAccel(a_ccel); + a_ctArtSearch = new KAction(i18n("&Search Articles..."),"mail_find" , Key_F4 , this, + SLOT(slotArtSearch()), actionCollection(), "article_search"); + a_ctArtRefreshList = new KAction(i18n("&Refresh List"),"reload", KStdAccel::shortcut(KStdAccel::Reload), this, + SLOT(slotArtRefreshList()), actionCollection(), "view_Refresh"); + a_ctArtCollapseAll = new KAction(i18n("&Collapse All Threads"), 0 , this, + SLOT(slotArtCollapseAll()), actionCollection(), "view_CollapseAll"); + a_ctArtExpandAll = new KAction(i18n("E&xpand All Threads"), 0 , this, + SLOT(slotArtExpandAll()), actionCollection(), "view_ExpandAll"); + a_ctArtToggleThread = new KAction(i18n("&Toggle Subthread"), Key_T, this, + SLOT(slotArtToggleThread()), actionCollection(), "thread_toggle"); + a_ctArtToggleShowThreads = new KToggleAction(i18n("Show T&hreads"), 0 , this, + SLOT(slotArtToggleShowThreads()), actionCollection(), "view_showThreads"); + a_ctArtToggleShowThreads->setCheckedState(i18n("Hide T&hreads")); + + a_ctArtToggleShowThreads->setChecked(c_fgManager->readNewsGeneral()->showThreads()); + + //header-view - remote articles + a_ctArtSetArtRead = new KAction(i18n("Mark as &Read"), Key_D , this, + SLOT(slotArtSetArtRead()), actionCollection(), "article_read"); + a_ctArtSetArtUnread = new KAction(i18n("Mar&k as Unread"), Key_U , this, + SLOT(slotArtSetArtUnread()), actionCollection(), "article_unread"); + a_ctArtSetThreadRead = new KAction(i18n("Mark &Thread as Read"), CTRL+Key_D , this, + SLOT(slotArtSetThreadRead()), actionCollection(), "thread_read"); + a_ctArtSetThreadUnread = new KAction(i18n("Mark T&hread as Unread"), CTRL+Key_U , this, + SLOT(slotArtSetThreadUnread()), actionCollection(), "thread_unread"); + a_ctArtOpenNewWindow = new KAction(i18n("Open in Own &Window"), "window_new", Key_O , this, + SLOT(slotArtOpenNewWindow()), actionCollection(), "article_ownWindow"); + + // scoring + a_ctScoresEdit = new KAction(i18n("&Edit Scoring Rules..."), "edit", CTRL+Key_E, this, + SLOT(slotScoreEdit()), actionCollection(), "scoreedit"); + a_ctReScore = new KAction(i18n("Recalculate &Scores"), 0, this, + SLOT(slotReScore()),actionCollection(),"rescore"); + a_ctScoreLower = new KAction(i18n("&Lower Score for Author..."), CTRL+Key_L, this, + SLOT(slotScoreLower()), actionCollection(), "scorelower"); + a_ctScoreRaise = new KAction(i18n("&Raise Score for Author..."), CTRL+Key_I, this, + SLOT(slotScoreRaise()),actionCollection(),"scoreraise"); + a_ctArtToggleIgnored = new KAction(i18n("&Ignore Thread"), "bottom", Key_I , this, + SLOT(slotArtToggleIgnored()), actionCollection(), "thread_ignore"); + a_ctArtToggleWatched = new KAction(i18n("&Watch Thread"), "top", Key_W , this, + SLOT(slotArtToggleWatched()), actionCollection(), "thread_watch"); + + //header-view local articles + a_ctArtSendOutbox = new KAction(i18n("Sen&d Pending Messages"), "mail_send", 0, this, + SLOT(slotArtSendOutbox()), actionCollection(), "net_sendPending"); + a_ctArtDelete = new KAction(i18n("&Delete Article"), "editdelete", Key_Delete, this, + SLOT(slotArtDelete()), actionCollection(), "article_delete"); + a_ctArtSendNow = new KAction(i18n("Send &Now"),"mail_send", 0 , this, + SLOT(slotArtSendNow()), actionCollection(), "article_sendNow"); + a_ctArtEdit = new KAction(i18n("edit article","&Edit Article..."), "edit", Key_E , this, + SLOT(slotArtEdit()), actionCollection(), "article_edit"); + + //network + a_ctNetCancel = new KAction(i18n("Stop &Network"),"stop",0, this, + SLOT(slotNetCancel()), actionCollection(), "net_stop"); + a_ctNetCancel->setEnabled(false); + + a_ctFetchArticleWithID = new KAction(i18n("&Fetch Article with ID..."), 0, this, + SLOT(slotFetchArticleWithID()), actionCollection(), "fetch_article_with_id"); + a_ctFetchArticleWithID->setEnabled(false); + + a_ctToggleGroupView = new KToggleAction(i18n("Show &Group View"), CTRL+Key_G, this, + SLOT(slotToggleGroupView()), actionCollection(), "settings_show_groupView"); + a_ctToggleGroupView->setCheckedState(i18n("Hide &Group View")); + a_ctToggleHeaderView = new KToggleAction(i18n("Show &Header View"), CTRL+Key_H, this, + SLOT(slotToggleHeaderView()), actionCollection(), "settings_show_headerView"); + a_ctToggleHeaderView->setCheckedState(i18n("Hide &Header View")); + a_ctToggleArticleViewer = new KToggleAction(i18n("Show &Article Viewer"), CTRL+Key_J, this, + SLOT(slotToggleArticleViewer()), actionCollection(), "settings_show_articleViewer"); + a_ctToggleArticleViewer->setCheckedState(i18n("Hide &Article Viewer")); + a_ctToggleQuickSearch = new KToggleAction(i18n("Show Quick Search"), QString::null, this, + SLOT(slotToggleQuickSearch()), actionCollection(), "settings_show_quickSearch"); + a_ctToggleQuickSearch->setCheckedState(i18n("Hide Quick Search")); + a_ctSwitchToGroupView = new KAction(i18n("Switch to Group View"), Key_G , this, + SLOT(slotSwitchToGroupView()), actionCollection(), "switch_to_group_view"); + a_ctSwitchToGroupView->plugAccel(a_ccel); + a_ctSwitchToHeaderView = new KAction(i18n("Switch to Header View"), Key_H , this, + SLOT(slotSwitchToHeaderView()), actionCollection(), "switch_to_header_view"); + a_ctSwitchToHeaderView->plugAccel(a_ccel); + a_ctSwitchToArticleViewer = new KAction(i18n("Switch to Article Viewer"), Key_J , this, + SLOT(slotSwitchToArticleViewer()), actionCollection(), "switch_to_article_viewer"); + a_ctSwitchToArticleViewer->plugAccel(a_ccel); +} + +bool KNMainWidget::firstStart() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("GENERAL"); + QString ver = conf->readEntry("Version"); + if(!ver.isEmpty()) + return false; + + KConfig emailConf("emaildefaults"); + + emailConf.setGroup("Defaults"); + QString group = emailConf.readEntry("Profile","Default"); + + emailConf.setGroup(QString("PROFILE_%1").arg(group)); + KNConfig::Identity *id=knGlobals.configManager()->identity(); + id->setName(emailConf.readEntry("FullName")); + id->setEmail(emailConf.readEntry("EmailAddress").latin1()); + id->setOrga(emailConf.readEntry("Organization")); + id->setReplyTo(emailConf.readEntry("ReplyAddr")); + id->save(); + + KNServerInfo *smtp=knGlobals.accountManager()->smtp(); + smtp->setServer(emailConf.readEntry("OutgoingServer").latin1()); + smtp->setPort(25); + conf->setGroup("MAILSERVER"); + smtp->saveConf(conf); + + conf->setGroup("GENERAL"); + conf->writeEntry("Version", KNODE_VERSION); + + return true; +} + + +void KNMainWidget::readOptions() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("APPEARANCE"); + + if (conf->readBoolEntry("quicksearch", true)) + a_ctToggleQuickSearch->setChecked(true); + else + q_uicksearch->hide(); + c_olView->readConfig(); + h_drView->readConfig(); + a_ctArtSortHeaders->setCurrentItem( h_drView->sortColumn() ); + + resize(787,478); // default optimized for 800x600 + //applyMainWindowSettings(KGlobal::config(),"mainWindow_options"); + + // restore dock configuration + manager()->readConfig(knGlobals.config(),"dock_configuration"); +} + + +void KNMainWidget::saveOptions() +{ + KConfig *conf=knGlobals.config(); + conf->setGroup("APPEARANCE"); + + conf->writeEntry("quicksearch", q_uicksearch->isShown()); + //saveMainWindowSettings(KGlobal::config(),"mainWindow_options"); + + c_olView->writeConfig(); + h_drView->writeConfig(); + mArticleViewer->writeConfig(); + + // store dock configuration + manager()->writeConfig(knGlobals.config(),"dock_configuration"); +} + + +bool KNMainWidget::requestShutdown() +{ + kdDebug(5003) << "KNMainWidget::requestShutdown()" << endl; + + if( a_rtFactory->jobsPending() && + KMessageBox::No==KMessageBox::warningYesNo(this, i18n( +"KNode is currently sending articles. If you quit now you might lose these \ +articles.\nDo you want to quit anyway?"), QString::null, KStdGuiItem::quit(), KStdGuiItem::cancel()) + ) + return false; + + if(!a_rtFactory->closeComposeWindows()) + return false; + + return true; +} + + +void KNMainWidget::prepareShutdown() +{ + kdDebug(5003) << "KNMainWidget::prepareShutdown()" << endl; + + //cleanup article-views + ArticleWidget::cleanup(); + + // expire groups (if necessary) + KNCleanUp *cup = new KNCleanUp(); + g_rpManager->expireAll(cup); + cup->start(); + + // compact folders + KNConfig::Cleanup *conf=c_fgManager->cleanup(); + if (conf->compactToday()) { + cup->reset(); + f_olManager->compactAll(cup); + cup->start(); + conf->setLastCompactDate(); + } + + delete cup; + + saveOptions(); + RecentAddresses::self(knGlobals.config())->save( knGlobals.config() ); + c_fgManager->syncConfig(); + a_rtManager->deleteTempFiles(); + g_rpManager->syncGroups(); + f_olManager->syncFolders(); + f_ilManager->prepareShutdown(); + a_ccManager->prepareShutdown(); + s_coreManager->save(); +} + + +bool KNMainWidget::queryClose() +{ + if(b_lockui) + return false; + + if(!requestShutdown()) + return false; + + prepareShutdown(); + + return true; +} + + +void KNMainWidget::showEvent(QShowEvent *) +{ + slotCheckDockWidgetStatus(); +} + + +void KNMainWidget::fontChange( const QFont & ) +{ + a_rtFactory->configChanged(); + ArticleWidget::configChanged(); + configChanged(); +} + + +void KNMainWidget::paletteChange( const QPalette & ) +{ + ArticleWidget::configChanged(); + configChanged(); +} + + +bool KNMainWidget::eventFilter(QObject *o, QEvent *e) +{ + if (((e->type() == QEvent::KeyPress) || + (e->type() == QEvent::KeyRelease) || + (e->type() == QEvent::Accel) || + (e->type() == QEvent::AccelOverride)) && + b_lockui) + return true; + return KDockArea::eventFilter(o, e); +} + + +void KNMainWidget::getSelectedArticles(KNArticle::List &l) +{ + if(!g_rpManager->currentGroup() && !f_olManager->currentFolder()) + return; + + for(QListViewItem *i=h_drView->firstChild(); i; i=i->itemBelow()) + if(i->isSelected() || (static_cast<KNHdrViewItem*>(i)->isActive())) + l.append( static_cast<KNArticle*> ((static_cast<KNHdrViewItem*>(i))->art) ); +} + + +void KNMainWidget::getSelectedArticles(KNRemoteArticle::List &l) +{ + if(!g_rpManager->currentGroup()) return; + + for(QListViewItem *i=h_drView->firstChild(); i; i=i->itemBelow()) + if(i->isSelected() || (static_cast<KNHdrViewItem*>(i)->isActive())) + l.append( static_cast<KNRemoteArticle*> ((static_cast<KNHdrViewItem*>(i))->art) ); +} + + +void KNMainWidget::getSelectedThreads(KNRemoteArticle::List &l) +{ + KNRemoteArticle *art; + for(QListViewItem *i=h_drView->firstChild(); i; i=i->itemBelow()) + if(i->isSelected() || (static_cast<KNHdrViewItem*>(i)->isActive())) { + art=static_cast<KNRemoteArticle*> ((static_cast<KNHdrViewItem*>(i))->art); + // ignore the article if it is already in the list + // (multiple aritcles are selected in one thread) + if ( l.find(art) == l.end() ) + art->thread(l); + } +} + + +void KNMainWidget::getSelectedArticles( KNLocalArticle::List &l ) +{ + if(!f_olManager->currentFolder()) return; + + for(QListViewItem *i=h_drView->firstChild(); i; i=i->itemBelow()) + if(i->isSelected() || (static_cast<KNHdrViewItem*>(i)->isActive())) + l.append( static_cast<KNLocalArticle*> ((static_cast<KNHdrViewItem*>(i))->art) ); +} + + +void KNMainWidget::closeCurrentThread() +{ + QListViewItem *item = h_drView->currentItem(); + if (item) { + while (item->parent()) + item = item->parent(); + h_drView->setCurrentItem(item); + item->setOpen(false); + h_drView->ensureItemVisible(item); + } +} + +void KNMainWidget::slotArticleSelected(QListViewItem *i) +{ + kdDebug(5003) << "KNMainWidget::slotArticleSelected(QListViewItem *i)" << endl; + if(b_lockui) + return; + KNArticle *selectedArticle=0; + + if(i) + selectedArticle=(static_cast<KNHdrViewItem*>(i))->art; + + mArticleViewer->setArticle( selectedArticle ); + + //actions + bool enabled; + + enabled=( selectedArticle && selectedArticle->type()==KMime::Base::ATremote ); + if(a_ctArtSetArtRead->isEnabled() != enabled) { + a_ctArtSetArtRead->setEnabled(enabled); + a_ctArtSetArtUnread->setEnabled(enabled); + a_ctArtSetThreadRead->setEnabled(enabled); + a_ctArtSetThreadUnread->setEnabled(enabled); + a_ctArtToggleIgnored->setEnabled(enabled); + a_ctArtToggleWatched->setEnabled(enabled); + a_ctScoreLower->setEnabled(enabled); + a_ctScoreRaise->setEnabled(enabled); + } + + a_ctArtOpenNewWindow->setEnabled( selectedArticle && (f_olManager->currentFolder()!=f_olManager->outbox()) + && (f_olManager->currentFolder()!=f_olManager->drafts())); + + enabled=( selectedArticle && selectedArticle->type()==KMime::Base::ATlocal ); + a_ctArtDelete->setEnabled(enabled); + a_ctArtSendNow->setEnabled(enabled && (f_olManager->currentFolder()==f_olManager->outbox())); + a_ctArtEdit->setEnabled(enabled && ((f_olManager->currentFolder()==f_olManager->outbox())|| + (f_olManager->currentFolder()==f_olManager->drafts()))); +} + + +void KNMainWidget::slotArticleSelectionChanged() +{ + // enable all actions that work with multiple selection + + //actions + bool enabled = (g_rpManager->currentGroup()!=0); + + if(a_ctArtSetArtRead->isEnabled() != enabled) { + a_ctArtSetArtRead->setEnabled(enabled); + a_ctArtSetArtUnread->setEnabled(enabled); + a_ctArtSetThreadRead->setEnabled(enabled); + a_ctArtSetThreadUnread->setEnabled(enabled); + a_ctArtToggleIgnored->setEnabled(enabled); + a_ctArtToggleWatched->setEnabled(enabled); + a_ctScoreLower->setEnabled(enabled); + a_ctScoreRaise->setEnabled(enabled); + } + + enabled = (f_olManager->currentFolder()!=0); + a_ctArtDelete->setEnabled(enabled); + a_ctArtSendNow->setEnabled(enabled && (f_olManager->currentFolder()==f_olManager->outbox())); +} + + +void KNMainWidget::slotCollectionSelected(QListViewItem *i) +{ + kdDebug(5003) << "KNMainWidget::slotCollectionSelected(QListViewItem *i)" << endl; + if(b_lockui) + return; + KNCollection *c=0; + KNNntpAccount *selectedAccount=0; + KNGroup *selectedGroup=0; + KNFolder *selectedFolder=0; + + s_earchLineEdit->clear(); + h_drView->clear(); + slotArticleSelected(0); + + // mark all articles in current group as not new/read + if ( knGlobals.configManager()->readNewsNavigation()->leaveGroupMarkAsRead() ) + a_rtManager->setAllRead( true ); + a_rtManager->setAllNotNew(); + + if(i) { + c=(static_cast<KNCollectionViewItem*>(i))->coll; + switch(c->type()) { + case KNCollection::CTnntpAccount : + selectedAccount=static_cast<KNNntpAccount*>(c); + if(!i->isOpen()) + i->setOpen(true); + break; + case KNCollection::CTgroup : + if ( !h_drView->hasFocus() && !mArticleViewer->hasFocus() ) + h_drView->setFocus(); + selectedGroup=static_cast<KNGroup*>(c); + selectedAccount=selectedGroup->account(); + break; + + case KNCollection::CTfolder : + if ( !h_drView->hasFocus() && !mArticleViewer->hasFocus() ) + h_drView->setFocus(); + selectedFolder=static_cast<KNFolder*>(c); + break; + + default: break; + } + } + + a_ccManager->setCurrentAccount(selectedAccount); + g_rpManager->setCurrentGroup(selectedGroup); + f_olManager->setCurrentFolder(selectedFolder); + if (!selectedGroup && !selectedFolder) // called from showHeaders() otherwise + a_rtManager->updateStatusString(); + + updateCaption(); + + //actions + bool enabled; + + enabled=(selectedGroup) || (selectedFolder && !selectedFolder->isRootFolder()); + if(a_ctNavNextArt->isEnabled() != enabled) { + a_ctNavNextArt->setEnabled(enabled); + a_ctNavPrevArt->setEnabled(enabled); + } + + enabled=( selectedGroup!=0 ); + if(a_ctNavNextUnreadArt->isEnabled() != enabled) { + a_ctNavNextUnreadArt->setEnabled(enabled); + a_ctNavNextUnreadThread->setEnabled(enabled); + a_ctNavReadThrough->setEnabled(enabled); + a_ctFetchArticleWithID->setEnabled(enabled); + } + + enabled=( selectedAccount!=0 ); + if(a_ctAccProperties->isEnabled() != enabled) { + a_ctAccProperties->setEnabled(enabled); + a_ctAccRename->setEnabled(enabled); + a_ctAccSubscribe->setEnabled(enabled); + a_ctAccExpireAll->setEnabled(enabled); + a_ctAccGetNewHdrs->setEnabled(enabled); + a_ctAccGetNewHdrsAll->setEnabled(enabled); + a_ctAccDelete->setEnabled(enabled); + a_ctAccPostNewArticle->setEnabled(enabled); + } + + enabled=( selectedGroup!=0 ); + if(a_ctGrpProperties->isEnabled() != enabled) { + a_ctGrpProperties->setEnabled(enabled); + a_ctGrpRename->setEnabled(enabled); + a_ctGrpGetNewHdrs->setEnabled(enabled); + a_ctGrpExpire->setEnabled(enabled); + a_ctGrpReorganize->setEnabled(enabled); + a_ctGrpUnsubscribe->setEnabled(enabled); + a_ctGrpSetAllRead->setEnabled(enabled); + a_ctGrpSetAllUnread->setEnabled(enabled); + a_ctGrpSetUnread->setEnabled(enabled); + a_ctArtFilter->setEnabled(enabled); + a_ctArtFilterKeyb->setEnabled(enabled); + a_ctArtRefreshList->setEnabled(enabled); + a_ctArtCollapseAll->setEnabled(enabled); + a_ctArtExpandAll->setEnabled(enabled); + a_ctArtToggleShowThreads->setEnabled(enabled); + a_ctReScore->setEnabled(enabled); + } + + a_ctFolNewChild->setEnabled(selectedFolder!=0); + + enabled=( selectedFolder!=0 && !selectedFolder->isRootFolder() && !selectedFolder->isStandardFolder() ); + if(a_ctFolDelete->isEnabled() != enabled) { + a_ctFolDelete->setEnabled(enabled); + a_ctFolRename->setEnabled(enabled); + } + + enabled=( selectedFolder!=0 && !selectedFolder->isRootFolder() ); + if(a_ctFolCompact->isEnabled() != enabled) { + a_ctFolCompact->setEnabled(enabled); + a_ctFolEmpty->setEnabled(enabled); + a_ctFolMboxImport->setEnabled(enabled); + a_ctFolMboxExport->setEnabled(enabled); + } +} + + +void KNMainWidget::slotCollectionRenamed(QListViewItem *i) +{ + kdDebug(5003) << "KNMainWidget::slotCollectionRenamed(QListViewItem *i)" << endl; + + if (i) { + (static_cast<KNCollectionViewItem*>(i))->coll->setName(i->text(0)); + updateCaption(); + a_rtManager->updateStatusString(); + if ((static_cast<KNCollectionViewItem*>(i))->coll->type()==KNCollection::CTnntpAccount) + a_ccManager->accountRenamed(static_cast<KNNntpAccount*>((static_cast<KNCollectionViewItem*>(i))->coll)); + disableAccels(false); + } +} + + +void KNMainWidget::slotCollectionViewDrop(QDropEvent* e, KNCollectionViewItem* after) +{ + kdDebug(5003) << "KNMainWidget::slotCollectionViewDrop() : type = " << e->format(0) << endl; + + KNCollectionViewItem *cvi=static_cast<KNCollectionViewItem*>(after); + if (cvi && cvi->coll->type() != KNCollection::CTfolder) // safety measure... + return; + KNFolder *dest=cvi ? static_cast<KNFolder*>(cvi->coll) : 0; + + if (e->provides("x-knode-drag/folder") && f_olManager->currentFolder()) { + f_olManager->moveFolder(f_olManager->currentFolder(), dest); + } + else if(dest && e->provides("x-knode-drag/article")) { + if(f_olManager->currentFolder()) { + if (e->action() == QDropEvent::Move) { + KNLocalArticle::List l; + getSelectedArticles(l); + a_rtManager->moveIntoFolder(l, dest); + } else { + KNArticle::List l; + getSelectedArticles(l); + a_rtManager->copyIntoFolder(l, dest); + } + } + else if(g_rpManager->currentGroup()) { + KNArticle::List l; + getSelectedArticles(l); + a_rtManager->copyIntoFolder(l, dest); + } + } +} + + +void KNMainWidget::slotArticleRMB(KListView*, QListViewItem *i, const QPoint &p) +{ + if(b_lockui) + return; + + if(i) { + QPopupMenu *popup; + if( (static_cast<KNHdrViewItem*>(i))->art->type()==KMime::Base::ATremote) { + popup = static_cast<QPopupMenu *>(factory()->container("remote_popup", m_GUIClient)); + } else { + popup = static_cast<QPopupMenu *>(factory()->container("local_popup", m_GUIClient)); + } + + if ( popup ) + popup->popup(p); + } +} + + +void KNMainWidget::slotCollectionRMB(KListView*, QListViewItem *i, const QPoint &p) +{ + if(b_lockui) + return; + + if(i) { + if( (static_cast<KNCollectionViewItem*>(i))->coll->type()==KNCollection::CTgroup) { + QPopupMenu *popup = static_cast<QPopupMenu *>(factory()->container("group_popup", m_GUIClient)); + if ( popup ) + popup->popup(p); + } else if ((static_cast<KNCollectionViewItem*>(i))->coll->type()==KNCollection::CTfolder) { + if (static_cast<KNFolder*>(static_cast<KNCollectionViewItem*>(i)->coll)->isRootFolder()) { + QPopupMenu *popup = static_cast<QPopupMenu *>(factory()->container("root_folder_popup", m_GUIClient)); + if ( popup ) + popup->popup(p); + } else { + QPopupMenu *popup = static_cast<QPopupMenu *>(factory()->container("folder_popup", m_GUIClient)); + if ( popup ) + popup->popup(p); + } + } else { + QPopupMenu *popup = static_cast<QPopupMenu *>(factory()->container("account_popup", m_GUIClient)); + if ( popup ) + popup->popup( p ); + } + } +} + + +void KNMainWidget::slotOpenArticle(QListViewItem *item) +{ + if(b_lockui) + return; + + if (item) { + KNArticle *art=(static_cast<KNHdrViewItem*>(item))->art; + + if ((art->type()==KMime::Base::ATlocal) && ((f_olManager->currentFolder()==f_olManager->outbox())|| + (f_olManager->currentFolder()==f_olManager->drafts()))) { + a_rtFactory->edit( static_cast<KNLocalArticle*>(art) ); + } else { + if (!KNArticleWindow::raiseWindowForArticle(art)) { + KNArticleWindow *w=new KNArticleWindow(art); + w->show(); + } + } + } +} + + +void KNMainWidget::slotHdrViewSortingChanged(int i) +{ + a_ctArtSortHeaders->setCurrentItem(i); +} + + +void KNMainWidget::slotNetworkActive(bool b) +{ + a_ctNetCancel->setEnabled(b); +} + + +void KNMainWidget::slotCheckDockWidgetStatus() +{ + a_ctToggleGroupView->setChecked(c_olDock->isVisible()); + a_ctToggleArticleViewer->setChecked(a_rtDock->isVisible()); + a_ctToggleHeaderView->setChecked(h_drDock->isVisible()); +} + + +void KNMainWidget::slotGroupDockHidden() +{ + a_ctToggleGroupView->setChecked(false); +} + + +void KNMainWidget::slotHeaderDockHidden() +{ + a_ctToggleHeaderView->setChecked(false); +} + + +void KNMainWidget::slotArticleDockHidden() +{ + a_ctToggleArticleViewer->setChecked(false); +} + + +void KNMainWidget::slotDockWidgetFocusChangeRequest(QWidget *w) +{ + if ( w == mArticleViewer ) { + if (c_olView->isVisible()) { + c_olView->setFocus(); + if (!w->hasFocus()) // fails if the view is visible but floating + return; + } + if (h_drView->isVisible()) { + h_drView->setFocus(); + return; + } + } + if (w == c_olView) { + if (h_drView->isVisible()) { + h_drView->setFocus(); + if (!w->hasFocus()) // fails if the view is visible but floating + return; + } + if ( mArticleViewer->isVisible() ) { + mArticleViewer->setFocus(); + return; + } + } + if (w == h_drView) { + if ( mArticleViewer->isVisible() ) { + mArticleViewer->setFocus(); + if (!w->hasFocus()) // fails if the view is visible but floating + return; + } + if (c_olView->isVisible()) { + c_olView->setFocus(); + return; + } + } +} + + +//------------------------------ <Actions> -------------------------------- + + +void KNMainWidget::slotNavNextUnreadArt() +{ + if ( !h_drView->nextUnreadArticle() ) + c_olView->nextGroup(); +} + + +void KNMainWidget::slotNavNextUnreadThread() +{ + if ( !h_drView->nextUnreadThread() ) + c_olView->nextGroup(); +} + + +void KNMainWidget::slotNavReadThrough() +{ + kdDebug(5003) << "KNMainWidget::slotNavReadThrough()" << endl; + if ( !mArticleViewer->atBottom() ) + mArticleViewer->scrollNext(); + else if(g_rpManager->currentGroup() != 0) + slotNavNextUnreadArt(); +} + + +void KNMainWidget::slotAccProperties() +{ + kdDebug(5003) << "KNMainWidget::slotAccProperties()" << endl; + if(a_ccManager->currentAccount()) + a_ccManager->editProperties(a_ccManager->currentAccount()); + updateCaption(); + a_rtManager->updateStatusString(); +} + + +void KNMainWidget::slotAccRename() +{ + kdDebug(5003) << "KNMainWidget::slotAccRename()" << endl; + if(a_ccManager->currentAccount()) { + disableAccels(true); // hack: global accels break the inplace renaming + c_olView->rename(a_ccManager->currentAccount()->listItem(), 0); + } +} + + +void KNMainWidget::slotAccSubscribe() +{ + kdDebug(5003) << "KNMainWidget::slotAccSubscribe()" << endl; + if(a_ccManager->currentAccount()) + g_rpManager->showGroupDialog(a_ccManager->currentAccount()); +} + + +void KNMainWidget::slotAccExpireAll() +{ + kdDebug(5003) << "KNMainWidget::slotAccExpireAll()" << endl; + if(a_ccManager->currentAccount()) + g_rpManager->expireAll(a_ccManager->currentAccount()); +} + + +void KNMainWidget::slotAccGetNewHdrs() +{ + kdDebug(5003) << "KNMainWidget::slotAccGetNewHdrs()" << endl; + if(a_ccManager->currentAccount()) + g_rpManager->checkAll(a_ccManager->currentAccount()); +} + + + +void KNMainWidget::slotAccDelete() +{ + kdDebug(5003) << "KNMainWidget::slotAccDelete()" << endl; + if(a_ccManager->currentAccount()) { + if (a_ccManager->removeAccount(a_ccManager->currentAccount())) + slotCollectionSelected(0); + } +} + +void KNMainWidget::slotAccGetNewHdrsAll() +{ + QValueList<KNNntpAccount*>::Iterator it; + for ( it = a_ccManager->begin(); it != a_ccManager->end(); ++it ) + g_rpManager->checkAll( *it ); +} + +void KNMainWidget::slotAccPostNewArticle() +{ + kdDebug(5003) << "KNMainWidget::slotAccPostNewArticle()" << endl; + if(g_rpManager->currentGroup()) + a_rtFactory->createPosting(g_rpManager->currentGroup()); + else if(a_ccManager->currentAccount()) + a_rtFactory->createPosting(a_ccManager->currentAccount()); +} + + +void KNMainWidget::slotGrpProperties() +{ + kdDebug(5003) << "slotGrpProperties()" << endl; + if(g_rpManager->currentGroup()) + g_rpManager->showGroupProperties(g_rpManager->currentGroup()); + updateCaption(); + a_rtManager->updateStatusString(); +} + + +void KNMainWidget::slotGrpRename() +{ + kdDebug(5003) << "slotGrpRename()" << endl; + if(g_rpManager->currentGroup()) { + disableAccels(true); // hack: global accels break the inplace renaming + c_olView->rename(g_rpManager->currentGroup()->listItem(), 0); + } +} + + +void KNMainWidget::slotGrpGetNewHdrs() +{ + kdDebug(5003) << "KNMainWidget::slotGrpGetNewHdrs()" << endl; + if(g_rpManager->currentGroup()) + g_rpManager->checkGroupForNewHeaders(g_rpManager->currentGroup()); +} + + +void KNMainWidget::slotGrpExpire() +{ + kdDebug(5003) << "KNMainWidget::slotGrpExpire()" << endl; + if(g_rpManager->currentGroup()) + g_rpManager->expireGroupNow(g_rpManager->currentGroup()); +} + + +void KNMainWidget::slotGrpReorganize() +{ + kdDebug(5003) << "KNMainWidget::slotGrpReorganize()" << endl; + g_rpManager->reorganizeGroup(g_rpManager->currentGroup()); +} + + +void KNMainWidget::slotGrpUnsubscribe() +{ + kdDebug(5003) << "KNMainWidget::slotGrpUnsubscribe()" << endl; + if(g_rpManager->currentGroup()) { + if(KMessageBox::Yes==KMessageBox::questionYesNo(knGlobals.topWidget, + i18n("Do you really want to unsubscribe from %1?").arg(g_rpManager->currentGroup()->groupname()), QString::null, i18n("Unsubscribe"), KStdGuiItem::cancel())) + if (g_rpManager->unsubscribeGroup(g_rpManager->currentGroup())) + slotCollectionSelected(0); + } +} + + +void KNMainWidget::slotGrpSetAllRead() +{ + kdDebug(5003) << "KNMainWidget::slotGrpSetAllRead()" << endl; + + a_rtManager->setAllRead(true); + if (c_fgManager->readNewsNavigation()->markAllReadGoNext()) + c_olView->nextGroup(); +} + + +void KNMainWidget::slotGrpSetAllUnread() +{ + kdDebug(5003) << "KNMainWidget::slotGrpSetAllUnread()" << endl; + a_rtManager->setAllRead(false); +} + +void KNMainWidget::slotGrpSetUnread() +{ + kdDebug(5003) << "KNMainWidget::slotGrpSetUnread()" << endl; + int groupLength = g_rpManager->currentGroup()->length(); + + bool ok = false; + int res = KInputDialog::getInteger( + i18n( "Mark Last as Unread" ), + i18n( "Enter how many articles should be marked unread:" ), groupLength, 1, groupLength, 1, &ok, this ); + if ( ok ) + a_rtManager->setAllRead( false, res ); +} + +void KNMainWidget::slotFolNew() +{ + kdDebug(5003) << "KNMainWidget::slotFolNew()" << endl; + KNFolder *f = f_olManager->newFolder(0); + + if (f) { + f_olManager->setCurrentFolder(f); + c_olView->ensureItemVisible(f->listItem()); + c_olView->setActive( f->listItem() ); + slotFolRename(); + } +} + + +void KNMainWidget::slotFolNewChild() +{ + kdDebug(5003) << "KNMainWidget::slotFolNew()" << endl; + if(f_olManager->currentFolder()) { + KNFolder *f = f_olManager->newFolder(f_olManager->currentFolder()); + + if (f) { + f_olManager->setCurrentFolder(f); + c_olView->ensureItemVisible(f->listItem()); + c_olView->setActive( f->listItem() ); + slotFolRename(); + } + } +} + + +void KNMainWidget::slotFolDelete() +{ + kdDebug(5003) << "KNMainWidget::slotFolDelete()" << endl; + + if(!f_olManager->currentFolder() || f_olManager->currentFolder()->isRootFolder()) + return; + + if(f_olManager->currentFolder()->isStandardFolder()) + KMessageBox::sorry(knGlobals.topWidget, i18n("You cannot delete a standard folder.")); + + else if( KMessageBox::Continue==KMessageBox::warningContinueCancel(knGlobals.topWidget, + i18n("Do you really want to delete this folder and all its children?"),"",KGuiItem(i18n("&Delete"),"editdelete")) ) { + + if(!f_olManager->deleteFolder(f_olManager->currentFolder())) + KMessageBox::sorry(knGlobals.topWidget, + i18n("This folder cannot be deleted because some of\n its articles are currently in use.") ); + else + slotCollectionSelected(0); + } +} + + +void KNMainWidget::slotFolRename() +{ + kdDebug(5003) << "KNMainWidget::slotFolRename()" << endl; + + if(f_olManager->currentFolder() && !f_olManager->currentFolder()->isRootFolder()) { + if(f_olManager->currentFolder()->isStandardFolder()) + KMessageBox::sorry(knGlobals.topWidget, i18n("You cannot rename a standard folder.")); + else { + disableAccels(true); // hack: global accels break the inplace renaming + c_olView->rename(f_olManager->currentFolder()->listItem(), 0); + } + } +} + + +void KNMainWidget::slotFolCompact() +{ + kdDebug(5003) << "KNMainWidget::slotFolCompact()" << endl; + if(f_olManager->currentFolder() && !f_olManager->currentFolder()->isRootFolder()) + f_olManager->compactFolder(f_olManager->currentFolder()); +} + + +void KNMainWidget::slotFolCompactAll() +{ + kdDebug(5003) << "KNMainWidget::slotFolCompactAll()" << endl; + f_olManager->compactAll(); +} + + +void KNMainWidget::slotFolEmpty() +{ + kdDebug(5003) << "KNMainWidget::slotFolEmpty()" << endl; + if(f_olManager->currentFolder() && !f_olManager->currentFolder()->isRootFolder()) { + if(f_olManager->currentFolder()->lockedArticles()>0) { + KMessageBox::sorry(this, + i18n("This folder cannot be emptied at the moment\nbecause some of its articles are currently in use.") ); + return; + } + if( KMessageBox::Continue == KMessageBox::warningContinueCancel( + this, i18n("Do you really want to delete all articles in %1?").arg(f_olManager->currentFolder()->name()),"",KGuiItem(i18n("&Delete"),"editdelete")) ) + f_olManager->emptyFolder(f_olManager->currentFolder()); + } +} + + +void KNMainWidget::slotFolMBoxImport() +{ + kdDebug(5003) << "KNMainWidget::slotFolMBoxImport()" << endl; + if(f_olManager->currentFolder() && !f_olManager->currentFolder()->isRootFolder()) { + f_olManager->importFromMBox(f_olManager->currentFolder()); + } +} + + +void KNMainWidget::slotFolMBoxExport() +{ + kdDebug(5003) << "KNMainWidget::slotFolMBoxExport()" << endl; + if(f_olManager->currentFolder() && !f_olManager->currentFolder()->isRootFolder()) { + f_olManager->exportToMBox(f_olManager->currentFolder()); + } +} + + +void KNMainWidget::slotArtSortHeaders(int i) +{ + kdDebug(5003) << "KNMainWidget::slotArtSortHeaders(int i)" << endl; + h_drView->setSorting( i ); +} + + +void KNMainWidget::slotArtSortHeadersKeyb() +{ + kdDebug(5003) << "KNMainWidget::slotArtSortHeadersKeyb()" << endl; + + int newCol = KNHelper::selectDialog(this, i18n("Select Sort Column"), a_ctArtSortHeaders->items(), a_ctArtSortHeaders->currentItem()); + if (newCol != -1) + h_drView->setSorting( newCol ); +} + + +void KNMainWidget::slotArtSearch() +{ + kdDebug(5003) << "KNMainWidget::slotArtSearch()" << endl; + a_rtManager->search(); +} + + +void KNMainWidget::slotArtRefreshList() +{ + kdDebug(5003) << "KNMainWidget::slotArtRefreshList()" << endl; + a_rtManager->showHdrs(true); +} + + +void KNMainWidget::slotArtCollapseAll() +{ + kdDebug(5003) << "KNMainWidget::slotArtCollapseAll()" << endl; + + closeCurrentThread(); + a_rtManager->setAllThreadsOpen(false); + if (h_drView->currentItem()) + h_drView->ensureItemVisible(h_drView->currentItem()); +} + + +void KNMainWidget::slotArtExpandAll() +{ + kdDebug(5003) << "KNMainWidget::slotArtExpandAll()" << endl; + + a_rtManager->setAllThreadsOpen(true); + if (h_drView->currentItem()) + h_drView->ensureItemVisible(h_drView->currentItem()); +} + + +void KNMainWidget::slotArtToggleThread() +{ + kdDebug(5003) << "KNMainWidget::slotArtToggleThread()" << endl; + if( mArticleViewer->article() && mArticleViewer->article()->listItem()->isExpandable() ) { + bool o = !(mArticleViewer->article()->listItem()->isOpen()); + mArticleViewer->article()->listItem()->setOpen( o ); + } +} + + +void KNMainWidget::slotArtToggleShowThreads() +{ + kdDebug(5003) << "KNMainWidget::slotArtToggleShowThreads()" << endl; + if(g_rpManager->currentGroup()) { + c_fgManager->readNewsGeneral()->setShowThreads(!c_fgManager->readNewsGeneral()->showThreads()); + a_rtManager->showHdrs(true); + } +} + + +void KNMainWidget::slotArtSetArtRead() +{ + kdDebug(5003) << "KNMainWidget::slotArtSetArtRead()" << endl; + if(!g_rpManager->currentGroup()) + return; + + KNRemoteArticle::List l; + getSelectedArticles(l); + a_rtManager->setRead(l, true); +} + + +void KNMainWidget::slotArtSetArtUnread() +{ + kdDebug(5003) << "KNMainWidget::slotArtSetArtUnread()" << endl; + if(!g_rpManager->currentGroup()) + return; + + KNRemoteArticle::List l; + getSelectedArticles(l); + a_rtManager->setRead(l, false); +} + + +void KNMainWidget::slotArtSetThreadRead() +{ + kdDebug(5003) << "slotArtSetThreadRead()" << endl; + if( !g_rpManager->currentGroup() ) + return; + + KNRemoteArticle::List l; + getSelectedThreads(l); + a_rtManager->setRead(l, true); + + if (h_drView->currentItem()) { + if (c_fgManager->readNewsNavigation()->markThreadReadCloseThread()) + closeCurrentThread(); + if (c_fgManager->readNewsNavigation()->markThreadReadGoNext()) + slotNavNextUnreadThread(); + } +} + + +void KNMainWidget::slotArtSetThreadUnread() +{ + kdDebug(5003) << "KNMainWidget::slotArtSetThreadUnread()" << endl; + if( !g_rpManager->currentGroup() ) + return; + + KNRemoteArticle::List l; + getSelectedThreads(l); + a_rtManager->setRead(l, false); +} + + +void KNMainWidget::slotScoreEdit() +{ + kdDebug(5003) << "KNMainWidget::slotScoreEdit()" << endl; + s_coreManager->configure(); +} + + +void KNMainWidget::slotReScore() +{ + kdDebug(5003) << "KNMainWidget::slotReScore()" << endl; + if( !g_rpManager->currentGroup() ) + return; + + g_rpManager->currentGroup()->scoreArticles(false); + a_rtManager->showHdrs(true); +} + + +void KNMainWidget::slotScoreLower() +{ + kdDebug(5003) << "KNMainWidget::slotScoreLower() start" << endl; + if( !g_rpManager->currentGroup() ) + return; + + if ( mArticleViewer->article() && mArticleViewer->article()->type() == KMime::Base::ATremote ) { + KNRemoteArticle *ra = static_cast<KNRemoteArticle*>( mArticleViewer->article() ); + s_coreManager->addRule(KNScorableArticle(ra), g_rpManager->currentGroup()->groupname(), -10); + } +} + + +void KNMainWidget::slotScoreRaise() +{ + kdDebug(5003) << "KNMainWidget::slotScoreRaise() start" << endl; + if( !g_rpManager->currentGroup() ) + return; + + if ( mArticleViewer->article() && mArticleViewer->article()->type() == KMime::Base::ATremote ) { + KNRemoteArticle *ra = static_cast<KNRemoteArticle*>( mArticleViewer->article() ); + s_coreManager->addRule(KNScorableArticle(ra), g_rpManager->currentGroup()->groupname(), +10); + } +} + + +void KNMainWidget::slotArtToggleIgnored() +{ + kdDebug(5003) << "KNMainWidget::slotArtToggleIgnored()" << endl; + if( !g_rpManager->currentGroup() ) + return; + + KNRemoteArticle::List l; + getSelectedThreads(l); + bool revert = !a_rtManager->toggleIgnored(l); + a_rtManager->rescoreArticles(l); + + if (h_drView->currentItem() && !revert) { + if (c_fgManager->readNewsNavigation()->ignoreThreadCloseThread()) + closeCurrentThread(); + if (c_fgManager->readNewsNavigation()->ignoreThreadGoNext()) + slotNavNextUnreadThread(); + } +} + + +void KNMainWidget::slotArtToggleWatched() +{ + kdDebug(5003) << "KNMainWidget::slotArtToggleWatched()" << endl; + if( !g_rpManager->currentGroup() ) + return; + + KNRemoteArticle::List l; + getSelectedThreads(l); + a_rtManager->toggleWatched(l); + a_rtManager->rescoreArticles(l); +} + + +void KNMainWidget::slotArtOpenNewWindow() +{ + kdDebug(5003) << "KNMainWidget::slotArtOpenNewWindow()" << endl; + + if( mArticleViewer->article() ) { + if ( !KNArticleWindow::raiseWindowForArticle( mArticleViewer->article() )) { + KNArticleWindow *win=new KNArticleWindow( mArticleViewer->article() ); + win->show(); + } + } +} + + +void KNMainWidget::slotArtSendOutbox() +{ + kdDebug(5003) << "KNMainWidget::slotArtSendOutbox()" << endl; + a_rtFactory->sendOutbox(); +} + + +void KNMainWidget::slotArtDelete() +{ + kdDebug(5003) << "KNMainWidget::slotArtDelete()" << endl; + if (!f_olManager->currentFolder()) + return; + + KNLocalArticle::List lst; + getSelectedArticles(lst); + + if(!lst.isEmpty()) + a_rtManager->deleteArticles(lst); + + if(h_drView->currentItem()) + h_drView->setActive( h_drView->currentItem() ); +} + + +void KNMainWidget::slotArtSendNow() +{ + kdDebug(5003) << "KNMainWidget::slotArtSendNow()" << endl; + if (!f_olManager->currentFolder()) + return; + + KNLocalArticle::List lst; + getSelectedArticles(lst); + + if(!lst.isEmpty()) + a_rtFactory->sendArticles( lst, true ); +} + + +void KNMainWidget::slotArtEdit() +{ + kdDebug(5003) << "KNodeVew::slotArtEdit()" << endl; + if (!f_olManager->currentFolder()) + return; + + if ( mArticleViewer->article() && mArticleViewer->article()->type() == KMime::Base::ATlocal ) + a_rtFactory->edit( static_cast<KNLocalArticle*>( mArticleViewer->article() ) ); +} + + +void KNMainWidget::slotNetCancel() +{ + kdDebug(5003) << "KNMainWidget::slotNetCancel()" << endl; + n_etAccess->cancelAllJobs(); +} + + +void KNMainWidget::slotFetchArticleWithID() +{ + kdDebug(5003) << "KNMainWidget::slotFetchArticleWithID()" << endl; + if( !g_rpManager->currentGroup() ) + return; + + FetchArticleIdDlg *dlg = new FetchArticleIdDlg(this, "messageid" ); + + if (dlg->exec()) { + QString id = dlg->messageId().simplifyWhiteSpace(); + if (id.find(QRegExp("*@*",false,true))!=-1) { + if (id.find(QRegExp("<*>",false,true))==-1) // add "<>" when necessary + id = QString("<%1>").arg(id); + + if(!KNArticleWindow::raiseWindowForArticle(id.latin1())) { //article not yet opened + KNRemoteArticle *a=new KNRemoteArticle(g_rpManager->currentGroup()); + a->messageID()->from7BitString(id.latin1()); + KNArticleWindow *awin=new KNArticleWindow(a); + awin->show(); + } + } + } + + KNHelper::saveWindowSize("fetchArticleWithID",dlg->size()); + delete dlg; +} + +void KNMainWidget::slotToggleGroupView() +{ + c_olDock->changeHideShowState(); + slotCheckDockWidgetStatus(); +} + + +void KNMainWidget::slotToggleHeaderView() +{ + + if ( !h_drDock->isVisible() ) + if ( !h_drDock->isDockBackPossible() ) { + h_drDock->manualDock( a_rtDock, KDockWidget::DockTop ); + h_drDock->makeDockVisible(); + slotCheckDockWidgetStatus(); + return; + } + + h_drDock->changeHideShowState(); + slotCheckDockWidgetStatus(); +} + + +void KNMainWidget::slotToggleArticleViewer() +{ + a_rtDock->changeHideShowState(); + slotCheckDockWidgetStatus(); +} + +void KNMainWidget::slotToggleQuickSearch() +{ + if (q_uicksearch->isHidden()) + q_uicksearch->show(); + else + q_uicksearch->hide(); +} + +void KNMainWidget::slotSwitchToGroupView() +{ + if (!c_olView->isVisible()) + slotToggleGroupView(); + c_olView->setFocus(); +} + + +void KNMainWidget::slotSwitchToHeaderView() +{ + if (!h_drView->isVisible()) + slotToggleHeaderView(); + h_drView->setFocus(); +} + +void KNMainWidget::slotSwitchToArticleViewer() +{ + if ( !mArticleViewer->isVisible() ) + slotToggleArticleViewer(); + mArticleViewer->setFocus(); +} + + +void KNMainWidget::slotSettings() +{ + c_fgManager->configure(); +} + +KActionCollection* KNMainWidget::actionCollection() const +{ + return m_GUIClient->actionCollection(); +} + +KXMLGUIFactory* KNMainWidget::factory() const +{ + kdDebug(5003)<<"m_guiclient is "<< m_GUIClient + <<", the factory is " << m_GUIClient->factory() <<endl; + return m_GUIClient->factory(); +} + +//-------------------------------- + + +FetchArticleIdDlg::FetchArticleIdDlg(QWidget *parent, const char */*name*/ ) + :KDialogBase(parent, 0, true, i18n("Fetch Article with ID"), KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok) +{ + QHBox *page = makeHBoxMainWidget(); + + QLabel *label = new QLabel(i18n("&Message-ID:"),page); + edit = new KLineEdit(page); + label->setBuddy(edit); + edit->setFocus(); + enableButtonOK( false ); + setButtonOK( i18n("&Fetch") ); + connect( edit, SIGNAL(textChanged( const QString & )), this, SLOT(slotTextChanged(const QString & ))); + KNHelper::restoreWindowSize("fetchArticleWithID", this, QSize(325,66)); +} + +QString FetchArticleIdDlg::messageId() const +{ + return edit->text(); +} + +void FetchArticleIdDlg::slotTextChanged(const QString &_text ) +{ + enableButtonOK( !_text.isEmpty() ); +} + + +//////////////////////////////////////////////////////////////////////// +//////////////////////// DCOP implementation +// Move to the next article +void KNMainWidget::nextArticle() +{ + h_drView->nextArticle(); +} + +// Move to the previous article +void KNMainWidget::previousArticle() +{ + h_drView->prevArticle(); +} + +// Move to the next unread article +void KNMainWidget::nextUnreadArticle() +{ + slotNavNextUnreadArt(); +} + +// Move to the next unread thread +void KNMainWidget::nextUnreadThread() +{ + slotNavNextUnreadThread(); +} + +// Move to the next group +void KNMainWidget::nextGroup() +{ + c_olView->nextGroup(); +} + +// Move to the previous group +void KNMainWidget::previousGroup() +{ + c_olView->prevGroup(); +} + +void KNMainWidget::fetchHeaders() +{ + // Simply call the slot + slotAccGetNewHdrs(); +} + +void KNMainWidget::expireArticles() +{ + slotAccExpireAll(); +} + +// Open the editor to post a new article in the selected group +void KNMainWidget::postArticle() +{ + slotAccPostNewArticle(); +} + +// Fetch the new headers in the selected groups +void KNMainWidget::fetchHeadersInCurrentGroup() +{ + slotGrpGetNewHdrs(); +} + +// Expire the articles in the current group +void KNMainWidget::expireArticlesInCurrentGroup() +{ + slotGrpExpire(); +} + +// Mark all the articles in the current group as read +void KNMainWidget::markAllAsRead() +{ + slotGrpSetAllRead(); +} + +// Mark all the articles in the current group as unread +void KNMainWidget::markAllAsUnread() +{ + slotGrpSetAllUnread(); +} + +// Mark the current article as read +void KNMainWidget::markAsRead() +{ + slotArtSetArtRead(); +} + +// Mark the current article as unread +void KNMainWidget::markAsUnread() +{ + slotArtSetArtUnread(); +} + +// Mark the current thread as read +void KNMainWidget::markThreadAsRead() +{ + slotArtSetThreadRead(); +} + +// Mark the current thread as unread +void KNMainWidget::markThreadAsUnread() +{ + slotArtSetThreadUnread(); +} + +// Send the pending articles +void KNMainWidget::sendPendingMessages() +{ + slotArtSendOutbox(); +} + +// Delete the current article +void KNMainWidget::deleteArticle() +{ + slotArtDelete(); +} + +// Send the current article +void KNMainWidget::sendNow() +{ + slotArtSendNow(); +} + +// Edit the current article +void KNMainWidget::editArticle() +{ + slotArtEdit(); +} + +bool KNMainWidget::handleCommandLine() +{ + bool doneSomething = false; + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if (args->count()>0) { + KURL url=args->url(0); // we take only one URL + openURL(url); + doneSomething = true; + } + args->clear(); + return doneSomething; +} + +//////////////////////// end DCOP implementation +//////////////////////////////////////////////////////////////////////// + +#include "knmainwidget.moc" diff --git a/knode/knmainwidget.h b/knode/knmainwidget.h new file mode 100644 index 000000000..2b3660bd7 --- /dev/null +++ b/knode/knmainwidget.h @@ -0,0 +1,426 @@ +/* + KNode, the KDE newsreader + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2004-2005 Volker Krause <volker.krause@rwth-aachen.de> + + 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. + 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, US +*/ +#ifndef KNMAINWIDGET_H +#define KNMAINWIDGET_H + +#include "knodeiface.h" + +#include <kdockwidget.h> +#include <kdialogbase.h> +#include "resource.h" + +#include <qglobal.h> +#include <kdepimmacros.h> + +class QListViewItem; + +class KURL; +class KAccel; +class KAction; +class KToggleAction; +class KSelectAction; +class KRSqueezedTextLabel; +class KLineEdit; +class KXMLGUIClient; + +class KNHeaderView; +class KNCollectionView; +class KNCollectionViewItem; +class KNProgress; +class KNConfigManager; +class KNAccountManager; +class KNGroupManager; +class KNFolderManager; +class KNArticleManager; +class KNArticleFactory; +class KNFilterManager; +class KNScoringManager; +class KNMemoryManager; +class KNFilterSelectAction; +class KNNetAccess; +namespace Kpgp { + class Module; +} +namespace KNode { + class ArticleWidget; +} +class KNArticle; +class KNLocalArticle; +class KNRemoteArticle; +class KActionCollection; + +class KDE_EXPORT KNMainWidget : public KDockArea, virtual public KNodeIface +{ + Q_OBJECT +public: + KNMainWidget( KXMLGUIClient *client, bool detachable, QWidget* parent, const char* name ); + ~KNMainWidget(); + + /** exit */ + bool queryClose(); + void prepareShutdown(); + + //GUI + void setStatusMsg(const QString& = QString::null, int id=SB_MAIN); + void setStatusHelpMsg(const QString& text); + void updateCaption(); + void setCursorBusy(bool b=true); + void blockUI(bool b=true); + void disableAccels(bool b=true); + /** processEvents with some blocking */ + void secureProcessEvents(); + + /** useful default value */ + virtual QSize sizeHint() const; + + /** handle URL given as command-line argument */ + void openURL(const KURL &url); + + /** update fonts and colors */ + void configChanged(); + + /** access to GUI-elements */ + KNCollectionView* collectionView()const { return c_olView; } + KNHeaderView* headerView()const { return h_drView; } + KNode::ArticleWidget* articleViewer() const { return mArticleViewer; } + KRSqueezedTextLabel* statusBarLabelGroup() const { return s_tatusGroup; } + KRSqueezedTextLabel* statusBarLabelFilter() const { return s_tatusFilter; } + public: //The dcop interface + // Implementation of KNodeIface + /* Navigation */ + // Move to the next article + virtual void nextArticle(); + // Move to the previous article + virtual void previousArticle(); + // Move to the next unread article + virtual void nextUnreadArticle(); + // Move to the next unread thread + virtual void nextUnreadThread(); + // Move to the next group + virtual void nextGroup(); + // Move to the previous group + virtual void previousGroup(); + + /* Group options */ + // Open the editor to post a new article in the selected group + virtual void postArticle(); + // Fetch the new headers in the selected groups + virtual void fetchHeadersInCurrentGroup(); + // Expire the articles in the current group + virtual void expireArticlesInCurrentGroup(); + // Mark all the articles in the current group as read + virtual void markAllAsRead(); + // Mark all the articles in the current group as unread + virtual void markAllAsUnread(); + + /* Header view */ + // Mark the current article as read + virtual void markAsRead(); + // Mark the current article as unread + virtual void markAsUnread(); + // Mark the current thread as read + virtual void markThreadAsRead(); + // Mark the current thread as unread + virtual void markThreadAsUnread(); + + /* Articles */ + + // Send the pending articles + virtual void sendPendingMessages(); + // Delete the current article + virtual void deleteArticle(); + // Send the current article + virtual void sendNow(); + // Edit the current article + virtual void editArticle(); + /// Fetch all the new article headers + virtual void fetchHeaders(); + /// Expire articles in all groups + virtual void expireArticles(); + + /* Kontact integration */ + /// Process command-line options + virtual bool handleCommandLine(); + + //end dcop interface +signals: + void signalCaptionChangeRequest( const QString& ); + +protected: + + KActionCollection* actionCollection() const; + KXMLGUIFactory *factory() const; + + void initActions(); + void initStatusBar(); + + /** checks if run for the first time, sets some global defaults (email configuration) */ + bool firstStart(); + + void readOptions(); + void saveOptions(); + + bool requestShutdown(); + + virtual void showEvent(QShowEvent *); + + /** update appearance */ + virtual void fontChange( const QFont & ); + virtual void paletteChange ( const QPalette & ); + + bool eventFilter(QObject *, QEvent *); + + // convenience methods... + void getSelectedArticles( QValueList<KNArticle*> &l ); + void getSelectedArticles( QValueList<KNRemoteArticle*> &l ); + void getSelectedThreads( QValueList<KNRemoteArticle*> &l ); + void getSelectedArticles( QValueList<KNLocalArticle*> &l ); + void closeCurrentThread(); + + //GUI + KAccel *a_ccel; + KNProgress *p_rogBar; + KNode::ArticleWidget *mArticleViewer; + KNCollectionView *c_olView; + KNHeaderView *h_drView; + KDockWidget *c_olDock, *h_drDock, *a_rtDock; + bool b_lockui; + KToolBar *q_uicksearch; + QLineEdit *s_earchLineEdit; + + //Core + KNConfigManager *c_fgManager; + KNNetAccess *n_etAccess; + KNAccountManager *a_ccManager; + KNGroupManager *g_rpManager; + KNArticleManager *a_rtManager; + KNArticleFactory *a_rtFactory; + KNFolderManager *f_olManager; + KNFilterManager *f_ilManager; + KNScoringManager *s_coreManager; + KNMemoryManager *m_emManager; + Kpgp::Module *p_gp; + +protected slots: + //listview slots + void slotArticleSelected(QListViewItem*); + void slotArticleSelectionChanged(); + void slotCollectionSelected(QListViewItem*); + void slotCollectionRenamed(QListViewItem*); + void slotCollectionViewDrop(QDropEvent* e, KNCollectionViewItem* after); + void slotArticleRMB(KListView*, QListViewItem *i, const QPoint &p); + void slotCollectionRMB(KListView*, QListViewItem *i, const QPoint &p); + /** Open selected article in own composer/reader window */ + void slotOpenArticle(QListViewItem *item); + void slotHdrViewSortingChanged(int i); + + //network slots + void slotNetworkActive(bool b); + + //dock widget slots + void slotCheckDockWidgetStatus(); + void slotGroupDockHidden(); + void slotHeaderDockHidden(); + void slotArticleDockHidden(); + void slotDockWidgetFocusChangeRequest(QWidget *w); + + //---------------------------------- <Actions> ---------------------------------- + +protected: + + //navigation + KAction *a_ctNavNextArt, + *a_ctNavPrevArt, + *a_ctNavNextUnreadArt, + *a_ctNavNextUnreadThread, + *a_ctNavNextGroup, + *a_ctNavPrevGroup, + *a_ctNavReadThrough; + + //collection-view - accounts + KAction *a_ctAccProperties, + *a_ctAccRename, + *a_ctAccSubscribe, + *a_ctAccExpireAll, + *a_ctAccGetNewHdrs, + *a_ctAccGetNewHdrsAll, + *a_ctAccDelete, + *a_ctAccPostNewArticle; + + //collection-view - groups + KAction *a_ctGrpProperties, + *a_ctGrpRename, + *a_ctGrpGetNewHdrs, + *a_ctGrpExpire, + *a_ctGrpReorganize, + *a_ctGrpUnsubscribe, + *a_ctGrpSetAllRead, + *a_ctGrpSetAllUnread, + *a_ctGrpSetUnread; + + //collection-view - folder + KAction *a_ctFolNew, + *a_ctFolNewChild, + *a_ctFolDelete, + *a_ctFolRename, + *a_ctFolCompact, + *a_ctFolCompactAll, + *a_ctFolEmpty, + *a_ctFolMboxImport, + *a_ctFolMboxExport; + + //header-view - list-handling + KSelectAction *a_ctArtSortHeaders; + KNFilterSelectAction *a_ctArtFilter; + KAction *a_ctArtSortHeadersKeyb, + *a_ctArtFilterKeyb, + *a_ctArtSearch, + *a_ctArtRefreshList, + *a_ctArtCollapseAll, + *a_ctArtExpandAll, + *a_ctArtToggleThread; + KToggleAction *a_ctArtToggleShowThreads; + + //header-view - remote articles + KAction *a_ctArtSetArtRead, + *a_ctArtSetArtUnread, + *a_ctArtSetThreadRead, + *a_ctArtSetThreadUnread, + *a_ctArtOpenNewWindow; + + // scoring + KAction *a_ctScoresEdit, + *a_ctReScore, + *a_ctScoreLower, + *a_ctScoreRaise, + *a_ctArtToggleIgnored, + *a_ctArtToggleWatched; + + //header-view local articles + KAction *a_ctArtSendOutbox, + *a_ctArtDelete, + *a_ctArtSendNow, + *a_ctArtEdit; + + //network + KAction *a_ctNetCancel; + + KAction *a_ctFetchArticleWithID; + + // settings menu + KToggleAction *a_ctToggleGroupView, + *a_ctToggleHeaderView, + *a_ctToggleArticleViewer, + *a_ctToggleQuickSearch; + KAction *a_ctSwitchToGroupView, + *a_ctSwitchToHeaderView, + *a_ctSwitchToArticleViewer; + +protected slots: + void slotNavNextUnreadArt(); + void slotNavNextUnreadThread(); + void slotNavReadThrough(); + + void slotAccProperties(); + void slotAccRename(); + void slotAccSubscribe(); + void slotAccExpireAll(); + void slotAccGetNewHdrs(); + void slotAccGetNewHdrsAll(); + void slotAccDelete(); + void slotAccPostNewArticle(); + + void slotGrpProperties(); + void slotGrpRename(); + void slotGrpGetNewHdrs(); + void slotGrpExpire(); + void slotGrpReorganize(); + void slotGrpUnsubscribe(); + void slotGrpSetAllRead(); + void slotGrpSetAllUnread(); + void slotGrpSetUnread(); + + void slotFolNew(); + void slotFolNewChild(); + void slotFolDelete(); + void slotFolRename(); + void slotFolCompact(); + void slotFolCompactAll(); + void slotFolEmpty(); + void slotFolMBoxImport(); + void slotFolMBoxExport(); + + void slotArtSortHeaders(int i); + void slotArtSortHeadersKeyb(); + void slotArtSearch(); + void slotArtRefreshList(); + void slotArtCollapseAll(); + void slotArtExpandAll(); + void slotArtToggleThread(); + void slotArtToggleShowThreads(); + + void slotArtSetArtRead(); + void slotArtSetArtUnread(); + void slotArtSetThreadRead(); + void slotArtSetThreadUnread(); + + void slotScoreEdit(); + void slotReScore(); + void slotScoreLower(); + void slotScoreRaise(); + void slotArtToggleIgnored(); + void slotArtToggleWatched(); + + void slotArtOpenNewWindow(); + void slotArtSendOutbox(); + void slotArtDelete(); + void slotArtSendNow(); + void slotArtEdit(); + + void slotNetCancel(); + + void slotFetchArticleWithID(); + + void slotToggleGroupView(); + void slotToggleHeaderView(); + void slotToggleArticleViewer(); + void slotToggleQuickSearch(); + void slotSwitchToGroupView(); + void slotSwitchToHeaderView(); + void slotSwitchToArticleViewer(); + void slotSettings(); + + //--------------------------- </Actions> ----------------------------- + +private: + KRSqueezedTextLabel *s_tatusGroup; // widget used in the statusBar() for the group status + KRSqueezedTextLabel *s_tatusFilter; + KXMLGUIClient *m_GUIClient; +}; + + +class FetchArticleIdDlg : public KDialogBase +{ + Q_OBJECT +public: + FetchArticleIdDlg(QWidget *parent, const char */*name*/ ); + QString messageId() const; + +protected slots: + void slotTextChanged(const QString & ); +protected: + KLineEdit *edit; +}; + +#endif diff --git a/knode/knmemorymanager.cpp b/knode/knmemorymanager.cpp new file mode 100644 index 000000000..283bcf546 --- /dev/null +++ b/knode/knmemorymanager.cpp @@ -0,0 +1,217 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <kdebug.h> + +#include "knmemorymanager.h" +#include "knfolder.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "knarticlemanager.h" +#include "kngroupmanager.h" +#include "knfoldermanager.h" + + +KNMemoryManager::KNMemoryManager() + : c_ollCacheSize(0), a_rtCacheSize(0) +{ +} + + +KNMemoryManager::~KNMemoryManager() +{ + for ( QValueList<CollectionItem*>::Iterator it = mColList.begin(); it != mColList.end(); ++it ) + delete (*it); + for ( QValueList<ArticleItem*>::Iterator it = mArtList.begin(); it != mArtList.end(); ++it ) + delete (*it); +} + + +void KNMemoryManager::updateCacheEntry(KNArticleCollection *c) +{ + CollectionItem *ci; + int oldSize=0; + + if( (ci=findCacheEntry(c, true)) ) { // item is taken from the list + oldSize=ci->storageSize; + ci->sync(); + kdDebug(5003) << "KNMemoryManager::updateCacheEntry() : collection (" << c->name() << ") updated" << endl; + } + else { + ci=new CollectionItem(c); + kdDebug(5003) << "KNMemoryManager::updateCacheEntry() : collection (" << c->name() << ") added" << endl; + } + + mColList.append(ci); + c_ollCacheSize += (ci->storageSize - oldSize); + checkMemoryUsageCollections(); +} + + +void KNMemoryManager::removeCacheEntry(KNArticleCollection *c) +{ + CollectionItem *ci; + ci=findCacheEntry(c, true); + + if(ci) { + c_ollCacheSize -= ci->storageSize; + delete ci; + + kdDebug(5003) << "KNMemoryManager::removeCacheEntry() : collection removed (" << c->name() << "), " + << mColList.count() << " collections left in cache" << endl; + } +} + + +void KNMemoryManager::prepareLoad(KNArticleCollection *c) +{ + CollectionItem ci(c); + + c_ollCacheSize += ci.storageSize; + checkMemoryUsageCollections(); + c_ollCacheSize -= ci.storageSize; +} + + +void KNMemoryManager::updateCacheEntry(KNArticle *a) +{ + ArticleItem *ai; + int oldSize=0; + + if( (ai=findCacheEntry(a, true)) ) { + oldSize=ai->storageSize; + ai->sync(); + kdDebug(5003) << "KNMemoryManager::updateCacheEntry() : article updated" << endl; + } + else { + ai=new ArticleItem(a); + kdDebug(5003) << "KNMemoryManager::updateCacheEntry() : article added" << endl; + } + + mArtList.append(ai); + a_rtCacheSize += (ai->storageSize - oldSize); + checkMemoryUsageArticles(); +} + + +void KNMemoryManager::removeCacheEntry(KNArticle *a) +{ + ArticleItem *ai; + + if( (ai=findCacheEntry(a, true)) ) { + a_rtCacheSize -= ai->storageSize; + delete ai; + + kdDebug(5003) << "KNMemoryManager::removeCacheEntry() : article removed, " + << mArtList.count() << " articles left in cache" << endl; + + } +} + + +KNMemoryManager::CollectionItem* KNMemoryManager::findCacheEntry(KNArticleCollection *c, bool take) +{ + for ( QValueList<CollectionItem*>::Iterator it = mColList.begin(); it != mColList.end(); ++it ) { + if ( (*it)->col == c ) { + CollectionItem *ret = (*it); + if ( take ) + mColList.remove( it ); + return ret; + } + } + + return 0; +} + + +KNMemoryManager::ArticleItem* KNMemoryManager::findCacheEntry(KNArticle *a, bool take) +{ + for ( QValueList<ArticleItem*>::Iterator it = mArtList.begin(); it != mArtList.end(); ++it ) { + if ( (*it)->art == a ) { + ArticleItem *ret = (*it); + if ( take ) + mArtList.remove( it ); + return ret; + } + } + + return 0; +} + + +void KNMemoryManager::checkMemoryUsageCollections() +{ + int maxSize = knGlobals.configManager()->readNewsGeneral()->collCacheSize() * 1024; + KNArticleCollection *c=0; + + if (c_ollCacheSize > maxSize) { + QValueList<CollectionItem*> tempList( mColList ); // work on a copy, KNGroup-/Foldermanager will + // modify the original list + + for ( QValueList<CollectionItem*>::Iterator it = tempList.begin(); it != tempList.end(); ) { + if ( c_ollCacheSize <= maxSize ) + break; + // unloadHeaders() will remove the cache entry and thus invalidate the iterator! + c = (*it)->col; + ++it; + + if (c->type() == KNCollection::CTgroup) + knGlobals.groupManager()->unloadHeaders(static_cast<KNGroup*>(c), false); // *try* to unload + else + if (c->type() == KNCollection::CTfolder) + knGlobals.folderManager()->unloadHeaders(static_cast<KNFolder*>(c), false); // *try* to unload + } + } + + kdDebug(5003) << "KNMemoryManager::checkMemoryUsageCollections() : " + << mColList.count() << " collections in cache => Usage : " + << ( c_ollCacheSize*100.0 / maxSize ) << "%" << endl; +} + + +void KNMemoryManager::checkMemoryUsageArticles() +{ + int maxSize = knGlobals.configManager()->readNewsGeneral()->artCacheSize() * 1024; + + if (a_rtCacheSize > maxSize) { + QValueList<ArticleItem*> tempList( mArtList ); // work on a copy, KNArticlemanager will + // modify the original list + + for ( QValueList<ArticleItem*>::Iterator it = mArtList.begin(); it != mArtList.end(); ) { + if ( a_rtCacheSize <= maxSize ) + break; + // unloadArticle() will remove the cache entry and thus invalidate the iterator! + KNArticle *art = (*it)->art; + ++it; + knGlobals.articleManager()->unloadArticle( art, false ); // *try* to unload + } + } + + kdDebug(5003) << "KNMemoryManager::checkMemoryUsageArticles() : " + << mArtList.count() << " articles in cache => Usage : " + << ( a_rtCacheSize*100.0 / maxSize ) << "%" << endl; +} + + +void KNMemoryManager::ArticleItem::sync() +{ + storageSize=art->storageSize(); +} + + +void KNMemoryManager::CollectionItem::sync() +{ + storageSize=col->length()*1024; // rule of thumb : ~1k per header +} + diff --git a/knode/knmemorymanager.h b/knode/knmemorymanager.h new file mode 100644 index 000000000..a9a79d9cf --- /dev/null +++ b/knode/knmemorymanager.h @@ -0,0 +1,74 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNMEMORYMANAGER_H +#define KNMEMORYMANAGER_H + +#include <qglobal.h> +#include <qvaluelist.h> + +class KNArticle; +class KNArticleCollection; + + +class KNMemoryManager { + + public: + KNMemoryManager(); + ~KNMemoryManager(); + + /** Collection-Handling */ + void updateCacheEntry(KNArticleCollection *c); + void removeCacheEntry(KNArticleCollection *c); + /** try to free enough memory for this collection */ + void prepareLoad(KNArticleCollection *c); + + /** Article-Handling */ + void updateCacheEntry(KNArticle *a); + void removeCacheEntry(KNArticle *a); + + protected: + + class ArticleItem { + public: + ArticleItem(KNArticle *a) { art=a; sync(); } + ~ArticleItem() {} + void sync(); + + KNArticle *art; + int storageSize; + }; + + class CollectionItem { + public: + CollectionItem(KNArticleCollection *c) { col=c; sync(); } + ~CollectionItem() { } + void sync(); + + KNArticleCollection *col; + int storageSize; + }; + + CollectionItem* findCacheEntry(KNArticleCollection *c, bool take=false); + ArticleItem* findCacheEntry(KNArticle *a, bool take=false); + void checkMemoryUsageCollections(); + void checkMemoryUsageArticles(); + + QValueList<CollectionItem*> mColList; + QValueList<ArticleItem*> mArtList; + int c_ollCacheSize, a_rtCacheSize; +}; + + +#endif diff --git a/knode/knnetaccess.cpp b/knode/knnetaccess.cpp new file mode 100644 index 000000000..65b43e7a5 --- /dev/null +++ b/knode/knnetaccess.cpp @@ -0,0 +1,545 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <unistd.h> +#include <fcntl.h> + +#include <qsocketnotifier.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kdebug.h> +#include <kio/job.h> +#include <kio/passdlg.h> +#include <ksocks.h> +#include <kapplication.h> + +#include "knaccountmanager.h" +#include "knarticle.h" +#include "knmainwidget.h" +#include "knjobdata.h" +#include "knnntpclient.h" +#include "knglobals.h" +#include "knnetaccess.h" +#include "knwidgets.h" + +using KPIM::ProgressManager; + + +KNNetAccess::KNNetAccess(QObject *parent, const char *name ) + : QObject(parent,name), currentNntpJob(0), currentSmtpJob(0) +{ + if ( pipe(nntpInPipe) == -1 || pipe(nntpOutPipe) == -1 ) { + KMessageBox::error(knGlobals.topWidget, i18n("Internal error:\nFailed to open pipes for internal communication.")); + kapp->exit(1); + } + if ( fcntl( nntpInPipe[0], F_SETFL, O_NONBLOCK ) == -1 || + fcntl( nntpOutPipe[0], F_SETFL, O_NONBLOCK ) == -1 ) { + KMessageBox::error(knGlobals.topWidget, i18n("Internal error:\nFailed to open pipes for internal communication.")); + kapp->exit(1); + } + + nntpNotifier=new QSocketNotifier(nntpInPipe[0], QSocketNotifier::Read); + connect(nntpNotifier, SIGNAL(activated(int)), this, SLOT(slotThreadSignal(int))); + + // initialize the KSocks stuff in the main thread, otherwise we get + // strange effects on FreeBSD + (void) KSocks::self(); + + nntpClient=new KNNntpClient(nntpOutPipe[0],nntpInPipe[1],nntp_Mutex); + nntpClient->start(); + + connect( knGlobals.accountManager(), SIGNAL(passwordsChanged()), SLOT(slotPasswordsChanged()) ); +} + + + +KNNetAccess::~KNNetAccess() +{ + disconnect(nntpNotifier, SIGNAL(activated(int)), this, SLOT(slotThreadSignal(int))); + + nntpClient->terminateClient(); + triggerAsyncThread(nntpOutPipe[1]); + nntpClient->wait(); + + delete nntpClient; + delete nntpNotifier; + + if ( ::close(nntpInPipe[0]) == -1 || + ::close(nntpInPipe[1]) == -1 || + ::close(nntpOutPipe[0]) == -1 || + ::close(nntpOutPipe[1]) == -1 ) + kdDebug(5003) << "Can't close pipes" << endl; +} + + + +void KNNetAccess::addJob(KNJobData *job) +{ + // kdDebug(5003) << "KNNetAccess::addJob() : job queued" << endl; + if(job->account()==0) { + job->setErrorString(i18n("Internal Error: No account set for this job.")); + job->notifyConsumer(); + return; + } + + job->createProgressItem(); + connect( job->progressItem(), SIGNAL(progressItemCanceled(KPIM::ProgressItem*)), SLOT(slotCancelJob(KPIM::ProgressItem*)) ); + emit netActive( true ); + + // put jobs which are waiting for the wallet into an extra queue + if ( !job->account()->readyForLogin() ) { + mWalletQueue.append( job ); + knGlobals.accountManager()->loadPasswordsAsync(); + job->setStatus( i18n( "Waiting for KWallet..." ) ); + return; + } + + if (job->type()==KNJobData::JTmail) { + smtpJobQueue.append(job); + if (!currentSmtpJob) // no active job, start the new one + startJobSmtp(); + } else { + + /* + TODO: the following code doesn't really belong here, it should + be moved to KNGroupManager, or elsewere... + */ + + // avoid duplicate fetchNewHeader jobs... + bool duplicate = false; + if ( job->type() == KNJobData::JTfetchNewHeaders || job->type() == KNJobData::JTsilentFetchNewHeaders ) { + QValueList<KNJobData*>::ConstIterator it; + for ( it = nntpJobQueue.begin(); it != nntpJobQueue.end(); ++it ) { + if ( ( (*it)->type() == KNJobData::JTfetchNewHeaders || (*it)->type() == KNJobData::JTsilentFetchNewHeaders ) + && (*it)->data() == job->data() ) // job works on the same group... + duplicate = true; + } + } + + if (!duplicate) { + // give a lower priority to fetchNewHeaders and postArticle jobs + if ( job->type() == KNJobData::JTfetchNewHeaders + || job->type() == KNJobData::JTsilentFetchNewHeaders + || job->type() == KNJobData::JTpostArticle ) { + nntpJobQueue.append( job ); + } else { + nntpJobQueue.prepend( job ); + } + + if (!currentNntpJob) // no active job, start the new one + startJobNntp(); + } + } + updateStatus(); +} + + +void KNNetAccess::cancelCurrentNntpJob( int type ) +{ + if ((currentNntpJob && !currentNntpJob->canceled()) && ((type==0)||(currentNntpJob->type()==type))) { // stop active job + currentNntpJob->cancel(); + triggerAsyncThread(nntpOutPipe[1]); + } +} + + +void KNNetAccess::stopJobsNntp( int type ) +{ + cancelCurrentNntpJob( type ); + KNJobData *tmp = 0; + QValueList<KNJobData*>::Iterator it; + for ( it = nntpJobQueue.begin(); it != nntpJobQueue.end();) { + tmp = *it; + if ( type == 0 || tmp->type() == type ) { + it = nntpJobQueue.remove( it ); + tmp->cancel(); + tmp->notifyConsumer(); + } else + ++it; + } + for ( it = mWalletQueue.begin(); it != mWalletQueue.end();) { + tmp = *it; + if ( type == 0 || tmp->type() == type ) { + it = mWalletQueue.remove( it ); + tmp->cancel(); + tmp->notifyConsumer(); + } else + ++it; + } + updateStatus(); +} + + + +// type==0 => all jobs +void KNNetAccess::cancelCurrentSmtpJob( int type ) +{ + if ((currentSmtpJob && !currentSmtpJob->canceled()) && ((type==0)||(currentSmtpJob->type()==type))) { // stop active job + currentSmtpJob->cancel(); + threadDoneSmtp(); + } +} + + +void KNNetAccess::stopJobsSmtp( int type ) +{ + cancelCurrentSmtpJob( type ); + KNJobData *tmp = 0; + QValueList<KNJobData*>::Iterator it; + for ( it = smtpJobQueue.begin(); it != smtpJobQueue.end();) { + tmp = *it; + if ( type == 0 || tmp->type() == type ) { + it = smtpJobQueue.remove( it ); + tmp->cancel(); + tmp->notifyConsumer(); + } else + ++it; + } + updateStatus(); +} + + + +// passes a signal through the ipc-pipe to the net-thread +void KNNetAccess::triggerAsyncThread(int pipeFd) +{ + int signal=0; + + // kdDebug(5003) << "KNNetAccess::triggerAsyncThread() : sending signal to net thread" << endl; + write(pipeFd, &signal, sizeof(int)); +} + + + +void KNNetAccess::startJobNntp() +{ + if ( nntpJobQueue.isEmpty() ) + return; + + currentNntpJob = nntpJobQueue.first(); + nntpJobQueue.remove( nntpJobQueue.begin() ); + currentNntpJob->prepareForExecution(); + if (currentNntpJob->success()) { + nntpClient->insertJob(currentNntpJob); + triggerAsyncThread(nntpOutPipe[1]); + kdDebug(5003) << "KNNetAccess::startJobNntp(): job started" << endl; + } else { + threadDoneNntp(); + } +} + + + +void KNNetAccess::startJobSmtp() +{ + if ( smtpJobQueue.isEmpty() ) + return; + + currentSmtpJob = smtpJobQueue.first(); + smtpJobQueue.remove( smtpJobQueue.begin() ); + currentSmtpJob->prepareForExecution(); + if (currentSmtpJob->success()) { + KNLocalArticle *art = static_cast<KNLocalArticle*>( currentSmtpJob->data() ); + // create url query part + QString query("headers=0&from="); + query += KURL::encode_string( art->from()->email() ); + QStrList emails; + art->to()->emails( &emails ); + for ( char *e = emails.first(); e; e = emails.next() ) { + query += "&to=" + KURL::encode_string( e ); + } + // create url + KURL destination; + KNServerInfo *account = currentSmtpJob->account(); + if ( account->encryption() == KNServerInfo::SSL ) + destination.setProtocol( "smtps" ); + else + destination.setProtocol( "smtp" ); + destination.setHost( account->server() ); + destination.setPort( account->port() ); + destination.setQuery( query ); + if ( account->needsLogon() ) { + destination.setUser( account->user() ); + destination.setPass( account->pass() ); + } + KIO::Job* job = KIO::storedPut( art->encodedContent(true), destination, -1, false, false, false ); + connect( job, SIGNAL( result(KIO::Job*) ), + SLOT( slotJobResult(KIO::Job*) ) ); + if ( account->encryption() == KNServerInfo::TLS ) + job->addMetaData( "tls", "on" ); + else + job->addMetaData( "tls", "off" ); + currentSmtpJob->setJob( job ); + + kdDebug(5003) << "KNNetAccess::startJobSmtp(): job started" << endl; + } else { + threadDoneSmtp(); + } +} + + + +void KNNetAccess::threadDoneNntp() +{ + KNJobData *tmp; + if (!currentNntpJob) { + kdWarning(5003) << "KNNetAccess::threadDoneNntp(): no current job?? aborting" << endl; + return; + } + + kdDebug(5003) << "KNNetAccess::threadDoneNntp(): job done" << endl; + + tmp = currentNntpJob; + + if (!tmp->success() && tmp->authError()) { + kdDebug(5003) << "KNNetAccess::threadDoneNntp(): authentication error" << endl; + KNServerInfo *info = tmp->account(); + if (info) { + QString user = info->user(); + QString pass = info->pass(); + bool keep=false; + if (KDialog::Accepted == KIO::PasswordDialog::getNameAndPassword(user, pass, &keep, + i18n("You need to supply a username and a\npassword to access this server"), false, + kapp->makeStdCaption(i18n("Authentication Failed")),info->server(),i18n("Server:"))) { + info->setNeedsLogon(true); + info->setUser(user); + info->setPass(pass); + tmp->setAuthError(false); + tmp->setErrorString(QString::null); + + kdDebug(5003) << "KNNetAccess::threadDoneNntp(): trying again with authentication data" << endl; + + // restart job... + triggerAsyncThread(nntpOutPipe[1]); + return; + } + } + } + + nntpClient->removeJob(); + currentNntpJob = 0L; + + currMsg = QString::null; + knGlobals.setStatusMsg(); + tmp->setComplete(); + + tmp->notifyConsumer(); + + if (!nntpJobQueue.isEmpty()) + startJobNntp(); + + updateStatus(); +} + + + +void KNNetAccess::threadDoneSmtp() +{ + KNJobData *tmp; + if (!currentSmtpJob) { + kdWarning(5003) << "KNNetAccess::threadDoneSmtp(): no current job?? aborting" << endl; + return; + } + + kdDebug(5003) << "KNNetAccess::threadDoneSmtp(): job done" << endl; + + tmp = currentSmtpJob; + currentSmtpJob = 0L; + if (!currentNntpJob) { + currMsg = QString::null; + knGlobals.setStatusMsg(); + } + tmp->setComplete(); + + tmp->notifyConsumer(); + + if (!smtpJobQueue.isEmpty()) + startJobSmtp(); + + updateStatus(); +} + + +void KNNetAccess::cancelAllJobs() +{ + stopJobsNntp(0); + stopJobsSmtp(0); +} + + + +void KNNetAccess::slotThreadSignal(int i) +{ + int signal; + QString tmp; + + //kdDebug(5003) << "KNNetAccess::slotThreadSignal() : signal received from net thread" << endl; + if(read(i, &signal, sizeof(int))==-1) { + kdDebug(5003) << "KNNetAccess::slotThreadSignal() : cannot read from pipe" << endl; + return; + } + + if (i == nntpInPipe[0]) { // signal from nntp thread + switch(signal) { + case KNProtocolClient::TSworkDone: + threadDoneNntp(); + break; + case KNProtocolClient::TSconnect: + currMsg = i18n(" Connecting to server..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSloadGrouplist: + currMsg = i18n(" Loading group list from disk..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSwriteGrouplist: + currMsg = i18n(" Writing group list to disk..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSdownloadGrouplist: + currMsg = i18n(" Downloading group list..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSdownloadNewGroups: + currMsg = i18n(" Looking for new groups..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSdownloadDesc: + currMsg = i18n(" Downloading group descriptions..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSdownloadNew: + currMsg = i18n(" Downloading new headers..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSsortNew: + currMsg = i18n(" Sorting..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSdownloadArticle: + currMsg = i18n(" Downloading article..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSsendArticle: + currMsg = i18n(" Sending article..."); + knGlobals.setStatusMsg(currMsg); + currentNntpJob->setStatus(currMsg); + break; + case KNProtocolClient::TSjobStarted: + currentNntpJob->setProgress(0); + break; + case KNProtocolClient::TSprogressUpdate: + currentNntpJob->setProgress(nntpClient->getProgressValue()/10); + break; + }; + } +} + + +void KNNetAccess::slotJobResult( KIO::Job *job ) +{ + if ( job == currentSmtpJob->job() ) { + if ( job->error() ) + currentSmtpJob->setErrorString( job->errorString() ); + threadDoneSmtp(); + return; + } + if ( job == currentNntpJob->job() ) { + // TODO + return; + } + kdError(5003) << k_funcinfo << "unknown job" << endl; +} + + +void KNNetAccess::slotPasswordsChanged() +{ + QValueList<KNJobData*>::ConstIterator it; + for ( it = mWalletQueue.begin(); it != mWalletQueue.end(); ++it ) { + (*it)->setStatus( i18n("Waiting...") ); + if ( (*it)->type() == KNJobData::JTmail ) + smtpJobQueue.append( (*it) ); + else + nntpJobQueue.append( (*it) ); + } + mWalletQueue.clear(); + if ( !currentNntpJob ) + startJobNntp(); + if ( !currentSmtpJob ) + startJobSmtp(); +} + + +void KNNetAccess::slotCancelJob( KPIM::ProgressItem *item ) +{ + KNJobData *tmp = 0; + QValueList<KNJobData*>::Iterator it; + for ( it = nntpJobQueue.begin(); it != nntpJobQueue.end();) { + tmp = *it; + if ( tmp->progressItem() == item ) { + it = nntpJobQueue.remove( it ); + tmp->cancel(); + tmp->notifyConsumer(); + } else + ++it; + } + for ( it = smtpJobQueue.begin(); it != smtpJobQueue.end();) { + tmp = *it; + if ( tmp->progressItem() == item ) { + it = smtpJobQueue.remove( it ); + tmp->cancel(); + tmp->notifyConsumer(); + } else + ++it; + } + for ( it = mWalletQueue.begin(); it != mWalletQueue.end();) { + tmp = *it; + if ( tmp->progressItem() == item ) { + it = mWalletQueue.remove( it ); + tmp->cancel(); + tmp->notifyConsumer(); + } else + ++it; + } + + if ( currentNntpJob && currentNntpJob->progressItem() == item ) + cancelCurrentNntpJob(); + if ( currentSmtpJob && currentSmtpJob->progressItem() == item ) + cancelCurrentSmtpJob(); + + updateStatus(); +} + +void KNNetAccess::updateStatus( ) +{ + if ( nntpJobQueue.isEmpty() && smtpJobQueue.isEmpty() && !currentNntpJob + && !currentSmtpJob && mWalletQueue.isEmpty() ) + emit netActive( false ); + else + emit netActive( true ); +} + +//-------------------------------- + +#include "knnetaccess.moc" diff --git a/knode/knnetaccess.h b/knode/knnetaccess.h new file mode 100644 index 000000000..02450b9c7 --- /dev/null +++ b/knode/knnetaccess.h @@ -0,0 +1,102 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNNETACCESS_H +#define KNNETACCESS_H + +#include <qobject.h> +#include <qmutex.h> +#include <qvaluelist.h> + +class QSocketNotifier; + +namespace KIO { + class Job; +} + +namespace KPIM { + class ProgressItem; +} +using KPIM::ProgressItem; + +class KNJobData; +class KNNntpClient; + + +class KNNetAccess : public QObject { + + Q_OBJECT + + public: + + KNNetAccess(QObject *parent=0, const char *name=0); + ~KNNetAccess(); + + void addJob(KNJobData *job); + /** type==0 => all jobs */ + void stopJobsNntp(int type); + /** type==0 => all jobs */ + void stopJobsSmtp(int type); + void cancelAllJobs(); + + /** current statusbar message */ + QString currentMsg() const { return currMsg; } + + QMutex& nntpMutex() { return nntp_Mutex; } + + protected: + /** passes a signal through the ipc-pipe to the net-thread */ + void triggerAsyncThread(int pipeFd); + void startJobNntp(); + void startJobSmtp(); + void threadDoneNntp(); + void threadDoneSmtp(); + + /** stores the current status message, + so that it can be restored by the mainwindow */ + QString currMsg; + + KNNntpClient *nntpClient; + QValueList<KNJobData*> nntpJobQueue, smtpJobQueue; + KNJobData *currentNntpJob, *currentSmtpJob; + QMutex nntp_Mutex; + int nntpInPipe[2], nntpOutPipe[2]; + QSocketNotifier *nntpNotifier; + + protected slots: + void slotThreadSignal(int i); + + signals: + void netActive(bool); + + private: + void cancelCurrentNntpJob( int type = 0 ); + void cancelCurrentSmtpJob( int type = 0 ); + /** Update activitiy status, i.e. emit netActive signal. */ + void updateStatus(); + + private slots: + void slotJobResult( KIO::Job *job ); + + void slotCancelJob( KPIM::ProgressItem *item ); + + void slotPasswordsChanged(); + + private: + /// jobs waiting for async wallet loading + QValueList<KNJobData*> mWalletQueue; + +}; + +#endif diff --git a/knode/knnntpaccount.cpp b/knode/knnntpaccount.cpp new file mode 100644 index 000000000..22ff904b2 --- /dev/null +++ b/knode/knnntpaccount.cpp @@ -0,0 +1,229 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <ksimpleconfig.h> +#include <kstandarddirs.h> +#include <kdebug.h> + +#include "utilities.h" +#include "kncollectionviewitem.h" +#include "knnntpaccount.h" +#include "knconfig.h" +#include "knconfigmanager.h" +#include "knconfigwidgets.h" +#include "kngroupmanager.h" +#include "knglobals.h" + + +KNNntpAccountIntervalChecking::KNNntpAccountIntervalChecking(KNNntpAccount* account) : t_imer(0) { + a_ccount = account; +} + + + +KNNntpAccountIntervalChecking::~KNNntpAccountIntervalChecking() +{ + if (t_imer) deinstallTimer(); + a_ccount = 0; +} + + + +void KNNntpAccountIntervalChecking::installTimer() +{ + if (a_ccount->checkInterval() <= 0) return; + if(!t_imer) + { + t_imer = new QTimer(); + connect(t_imer,SIGNAL(timeout()),this,SLOT(slotCheckNews())); + } + else + { + t_imer->stop(); + } + t_imer->start(a_ccount->checkInterval()*60000); +} + + + +void KNNntpAccountIntervalChecking::deinstallTimer() +{ + delete t_imer; + t_imer = 0; +} + + + +void KNNntpAccountIntervalChecking::slotCheckNews() +{ + knGlobals.groupManager()->checkAll(a_ccount, true); +} + + + +KNNntpAccount::KNNntpAccount() + : KNCollection(0), KNServerInfo(), i_dentity(0), f_etchDescriptions(true), w_asOpen(false), i_ntervalChecking(false), c_heckInterval(10) +{ + l_astNewFetch = QDate::currentDate(); + a_ccountIntervalChecking = new KNNntpAccountIntervalChecking(this); + mCleanupConf = new KNConfig::Cleanup( false ); +} + + +KNNntpAccount::~KNNntpAccount() +{ + delete a_ccountIntervalChecking; + delete i_dentity; + delete mCleanupConf; +} + + +// tries to read information, returns false if it fails to do so +bool KNNntpAccount::readInfo(const QString &confPath) +{ + KSimpleConfig conf(confPath); + + n_ame = conf.readEntry("name"); + //u_nsentCount = conf.readNumEntry("unsentCnt", 0); + f_etchDescriptions = conf.readBoolEntry("fetchDescriptions", true); + l_astNewFetch = conf.readDateTimeEntry("lastNewFetch").date(); + w_asOpen = conf.readBoolEntry("listItemOpen", false); + u_seDiskCache = conf.readBoolEntry("useDiskCache", false); + i_ntervalChecking=conf.readBoolEntry("intervalChecking", false); + c_heckInterval=conf.readNumEntry("checkInterval", 10); + KNServerInfo::readConf(&conf); + + startTimer(); + + i_dentity=new KNConfig::Identity(false); + i_dentity->loadConfig(&conf); + if(!i_dentity->isEmpty()) { + kdDebug(5003) << "KNGroup::readInfo(const QString &confPath) : using alternative user for " << n_ame << endl; + } else { + delete i_dentity; + i_dentity=0; + } + + mCleanupConf->loadConfig( &conf ); + + if (n_ame.isEmpty() || s_erver.isEmpty() || i_d == -1) + return false; + else + return true; +} + + +void KNNntpAccount::saveInfo() +{ + QString dir(path()); + if (dir.isNull()) + return; + + KSimpleConfig conf(dir+"info"); + + conf.writeEntry("name", n_ame); + //conf.writeEntry("unsentCnt", u_nsentCount); + conf.writeEntry("fetchDescriptions", f_etchDescriptions); + conf.writeEntry("lastNewFetch", QDateTime(l_astNewFetch)); + if(l_istItem) + conf.writeEntry("listItemOpen", l_istItem->isOpen()); + conf.writeEntry("useDiskCache", u_seDiskCache); + conf.writeEntry("intervalChecking", i_ntervalChecking); + conf.writeEntry("checkInterval", c_heckInterval); + + KNServerInfo::saveConf(&conf); // save not KNNntpAccount specific settings + + if(i_dentity) + i_dentity->saveConfig(&conf); + else if(conf.hasKey("Email")) { + conf.deleteEntry("Name", false); + conf.deleteEntry("Email", false); + conf.deleteEntry("Reply-To", false); + conf.deleteEntry("Mail-Copies-To", false); + conf.deleteEntry("Org", false); + conf.deleteEntry("UseSigFile", false); + conf.deleteEntry("UseSigGenerator", false); + conf.deleteEntry("sigFile", false); + conf.deleteEntry("sigText", false); + } + + mCleanupConf->saveConfig( &conf ); +} + + +/*void KNNntpAccount::syncInfo() +{ + QString dir(path()); + if (dir.isNull()) + return; + KSimpleConfig conf(dir+"info"); + conf.writeEntry("unsentCnt", u_nsentCount); +}*/ + + +QString KNNntpAccount::path() +{ + QString dir(locateLocal("data","knode/")+QString("nntp.%1/").arg(i_d)); + if (dir.isNull()) + KNHelper::displayInternalFileError(); + return (dir); +} + + +bool KNNntpAccount::editProperties(QWidget *parent) +{ + if(!i_dentity) i_dentity=new KNConfig::Identity(false); + KNConfig::NntpAccountConfDialog *d = new KNConfig::NntpAccountConfDialog(this, parent); + + bool ret=false; + if (d->exec()) { + updateListItem(); + ret=true; + } + + if(i_dentity->isEmpty()) { + delete i_dentity; + i_dentity=0; + } + + delete d; + return ret; +} + +void KNNntpAccount::startTimer() +{ + if ( (i_ntervalChecking == true) && (c_heckInterval > 0) ) + { + a_ccountIntervalChecking->installTimer(); + } + else + { + a_ccountIntervalChecking->deinstallTimer(); + } +} + +void KNNntpAccount::setCheckInterval(int c) +{ + c_heckInterval = c; + startTimer(); +} + +KNConfig::Cleanup *KNNntpAccount::activeCleanupConfig() const +{ + if (cleanupConfig()->useDefault()) + return knGlobals.configManager()->cleanup(); + return cleanupConfig(); +} + +#include "knnntpaccount.moc" diff --git a/knode/knnntpaccount.h b/knode/knnntpaccount.h new file mode 100644 index 000000000..4c7694ff6 --- /dev/null +++ b/knode/knnntpaccount.h @@ -0,0 +1,117 @@ +/* + knnntpaccount.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNNNTPACCOUNT_H +#define KNNNTPACCOUNT_H + +#include <qdatetime.h> + +#include "kncollection.h" +#include "knserverinfo.h" +#include <qobject.h> +#include <qtimer.h> + +class KNNntpAccount; + +namespace KNConfig { + class Identity; + class Cleanup; +} + + +class KNNntpAccountIntervalChecking : public QObject { + + Q_OBJECT + + public: + KNNntpAccountIntervalChecking(KNNntpAccount *account); + ~KNNntpAccountIntervalChecking(); + void installTimer(); + void deinstallTimer(); + + protected: + QTimer *t_imer; + KNNntpAccount *a_ccount; + + protected slots: + void slotCheckNews(); + +}; + +class KNNntpAccount : public KNCollection , public KNServerInfo { + + public: + KNNntpAccount(); + ~KNNntpAccount(); + + collectionType type() { return CTnntpAccount; } + + /** tries to read information, returns false if it fails to do so */ + bool readInfo(const QString &confPath); + void saveInfo(); + //void syncInfo(); + QString path(); + /** returns true when the user accepted */ + bool editProperties(QWidget *parent); + + // news interval checking + void startTimer(); + + //get + bool fetchDescriptions() const { return f_etchDescriptions; } + QDate lastNewFetch() const { return l_astNewFetch; } + bool wasOpen() const { return w_asOpen; } + bool useDiskCache() const { return u_seDiskCache; } + KNConfig::Identity* identity() const { return i_dentity; } + bool intervalChecking() const { return i_ntervalChecking; } + int checkInterval() const { return c_heckInterval; } + KNConfig::Cleanup *cleanupConfig() const { return mCleanupConf; } + + /** Returns the cleanup configuration that should be used for this account */ + KNConfig::Cleanup *activeCleanupConfig() const; + + //set + void setFetchDescriptions(bool b) { f_etchDescriptions = b; } + void setLastNewFetch(QDate date) { l_astNewFetch = date; } + void setUseDiskCache(bool b) { u_seDiskCache=b; } + void setCheckInterval(int c); + void setIntervalChecking(bool b) { i_ntervalChecking=b; } + + protected: + /** server specific identity */ + KNConfig::Identity *i_dentity; + /** account specific cleanup configuration */ + KNConfig::Cleanup *mCleanupConf; + /** use an additional "list newsgroups" command to fetch the newsgroup descriptions */ + bool f_etchDescriptions; + /** last use of "newgroups" */ + QDate l_astNewFetch; + /** was the server open in the listview on the last shutdown? */ + bool w_asOpen; + /** cache fetched articles on disk */ + bool u_seDiskCache; + /** is interval checking enabled */ + bool i_ntervalChecking; + int c_heckInterval; + + /** helper class for news interval checking, manages the QTimer */ + KNNntpAccountIntervalChecking *a_ccountIntervalChecking; + +}; + +#endif + +// kate: space-indent on; indent-width 2; diff --git a/knode/knnntpclient.cpp b/knode/knnntpclient.cpp new file mode 100644 index 000000000..2c594be34 --- /dev/null +++ b/knode/knnntpclient.cpp @@ -0,0 +1,748 @@ +/* + knnntpclient.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <stdlib.h> +#include <klocale.h> +#include <qtextcodec.h> +#include <qmutex.h> + +#include "kngroupmanager.h" +#include "knnntpclient.h" +#include "utilities.h" + + +KNNntpClient::KNNntpClient(int NfdPipeIn, int NfdPipeOut, QMutex& nntpMutex) +: KNProtocolClient(NfdPipeIn,NfdPipeOut), mutex(nntpMutex) +{} + + +KNNntpClient::~KNNntpClient() +{} + + +// examines the job and calls the suitable handling method +void KNNntpClient::processJob() +{ + switch (job->type()) { + case KNJobData::JTLoadGroups : + doLoadGroups(); + break; + case KNJobData::JTFetchGroups : + doFetchGroups(); + break; + case KNJobData::JTCheckNewGroups : + doCheckNewGroups(); + break; + case KNJobData::JTfetchNewHeaders : + case KNJobData::JTsilentFetchNewHeaders : + doFetchNewHeaders(); + break; + case KNJobData::JTfetchArticle : + doFetchArticle(); + break; + case KNJobData::JTpostArticle : + doPostArticle(); + break; + case KNJobData::JTfetchSource : + doFetchSource(); + break; + default: +#ifndef NDEBUG + qDebug("knode: KNNntpClient::processJob(): mismatched job"); +#endif + break; + } +} + + +void KNNntpClient::doLoadGroups() +{ + KNGroupListData *target = static_cast<KNGroupListData *>(job->data()); + sendSignal(TSloadGrouplist); + + if (!target->readIn(this)) + job->setErrorString(i18n("Unable to read the group list file")); +} + + +void KNNntpClient::doFetchGroups() +{ + KNGroupListData *target = static_cast<KNGroupListData *>(job->data()); + + sendSignal(TSdownloadGrouplist); + errorPrefix = i18n("The group list could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = 30000; // rule of thumb ;-) + + if (!sendCommandWCheck("LIST",215)) // 215 list of newsgroups follows + return; + + char *s, *line; + QString name; + KNGroup::Status status; + bool subscribed; + + while (getNextLine()) { + line = getCurrentLine(); + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + break; // message complete + } + s = strchr(line,' '); + if(!s) { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-line - ignoring"); +#endif + } else { + s[0] = 0; // cut string + + name = QString::fromUtf8(line); + + if (target->subscribed.contains(name)) { + target->subscribed.remove(name); // group names are unique, we wont find it again anyway... + subscribed = true; + } else + subscribed = false; + + while (s[1]!=0) s++; // the last character determines the moderation status + switch (s[0]) { + case 'n' : status = KNGroup::readOnly; + break; + case 'y' : status = KNGroup::postingAllowed; + break; + case 'm' : status = KNGroup::moderated; + break; + default : status = KNGroup::unknown; + } + + target->groups->append(new KNGroupInfo(name,QString::null,false,subscribed,status)); + } + doneLines++; + } + + if (!job->success() || job->canceled()) + return; // stopped... + + QSortedVector<KNGroupInfo> tempVector; + target->groups->toVector(&tempVector); + tempVector.sort(); + + if (target->getDescriptions) { + errorPrefix = i18n("The group descriptions could not be retrieved.\nThe following error occurred:\n"); + progressValue = 100; + doneLines = 0; + predictedLines = target->groups->count(); + + sendSignal(TSdownloadDesc); + sendSignal(TSprogressUpdate); + + int rep; + if (!sendCommand("LIST NEWSGROUPS",rep)) + return; + + if (rep == 215) { // 215 informations follows + QString description; + KNGroupInfo info; + int pos; + + while (getNextLine()) { + line = getCurrentLine(); + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + break; // message complete + } + s = line; + while (*s != '\0' && *s != '\t' && *s != ' ') s++; + if (*s == '\0') { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-description - ignoring"); +#endif + } else { + s[0] = 0; // terminate groupname + s++; + while (*s == ' ' || *s == '\t') s++; // go on to the description + + name = QString::fromUtf8(line); + if (target->codecForDescriptions) // some countries use local 8 bit characters in the tag line + description = target->codecForDescriptions->toUnicode(s); + else + description = QString::fromLocal8Bit(s); + info.name = name; + + if ((pos=tempVector.bsearch(&info))!=-1) + tempVector[pos]->description = description; + } + doneLines++; + } + } + + if (!job->success() || job->canceled()) + return; // stopped... + } + + target->groups->setAutoDelete(false); + tempVector.toList(target->groups); + target->groups->setAutoDelete(true); + + sendSignal(TSwriteGrouplist); + if (!target->writeOut()) + job->setErrorString(i18n("Unable to write the group list file")); + +} + + +void KNNntpClient::doCheckNewGroups() +{ + KNGroupListData *target = static_cast<KNGroupListData *>(job->data()); + + sendSignal(TSdownloadNewGroups); + errorPrefix = i18n("New groups could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = 30; // rule of thumb ;-) + + QCString cmd; + cmd.sprintf("NEWGROUPS %.2d%.2d%.2d 000000",target->fetchSince.year()%100,target->fetchSince.month(),target->fetchSince.day()); + if (!sendCommandWCheck(cmd,231)) // 231 list of new newsgroups follows + return; + + char *s, *line; + QString name; + KNGroup::Status status; + QSortedList<KNGroupInfo> tmpList; + tmpList.setAutoDelete(true); + + while (getNextLine()) { + line = getCurrentLine(); + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + break; // message complete + } + s = strchr(line,' '); + if(!s) { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-line - ignoring"); +#endif + } else { + s[0] = 0; // cut string + name = QString::fromUtf8(line); + + while (s[1]!=0) s++; // the last character determines the moderation status + switch (s[0]) { + case 'n' : status = KNGroup::readOnly; + break; + case 'y' : status = KNGroup::postingAllowed; + break; + case 'm' : status = KNGroup::moderated; + break; + default : status = KNGroup::unknown; + } + + tmpList.append(new KNGroupInfo(name,QString::null,true,false,status)); + } + doneLines++; + } + + if (!job->success() || job->canceled()) + return; // stopped... + + if (target->getDescriptions) { + errorPrefix = i18n("The group descriptions could not be retrieved.\nThe following error occurred:\n"); + progressValue = 100; + doneLines = 0; + predictedLines = tmpList.count()*3; + + sendSignal(TSdownloadDesc); + sendSignal(TSprogressUpdate); + + cmd = "LIST NEWSGROUPS "; + QStrList desList; + char *s; + int rep; + + for (KNGroupInfo *group=tmpList.first(); group; group=tmpList.next()) { + if (!sendCommand(cmd+group->name.utf8(),rep)) + return; + if (rep != 215) // 215 informations follows + break; + desList.clear(); + if (!getMsg(desList)) + return; + + if (desList.count()>0) { // group has a description + s = desList.first(); + while (*s !=- '\0' && *s != '\t' && *s != ' ') s++; + if (*s == '\0') { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-description - ignoring"); +#endif + } else { + while (*s == ' ' || *s == '\t') s++; // go on to the description + if (target->codecForDescriptions) // some countries use local 8 bit characters in the tag line + group->description = target->codecForDescriptions->toUnicode(s); + else + group->description = QString::fromLocal8Bit(s); + } + } + } + } + + sendSignal(TSloadGrouplist); + + if (!target->readIn()) { + job->setErrorString(i18n("Unable to read the group list file")); + return; + } + target->merge(&tmpList); + sendSignal(TSwriteGrouplist); + if (!target->writeOut()) { + job->setErrorString(i18n("Unable to write the group list file")); + return; + } +} + + +void KNNntpClient::doFetchNewHeaders() +{ + KNGroup* target=static_cast<KNGroup*>(job->data()); + char* s; + int first=0, last=0, oldlast=0, toFetch=0, rep=0; + QCString cmd; + + target->setLastFetchCount(0); + + sendSignal(TSdownloadNew); + errorPrefix=i18n("No new articles could be retrieved for\n%1/%2.\nThe following error occurred:\n") + .arg(account.server()).arg(target->groupname()); + + cmd="GROUP "; + cmd+=target->groupname().utf8(); + if (!sendCommandWCheck(cmd,211)) { // 211 n f l s group selected + return; + } + + currentGroup = target->groupname(); + + progressValue = 90; + + s = strchr(getCurrentLine(),' '); + if (s) { + s++; + s = strchr(s,' '); + } + if (s) { + s++; + first=atoi(s); + target->setFirstNr(first); + s = strchr(s,' '); + } + if (s) { + last=atoi(s); + } else { + QString tmp=i18n("No new articles could be retrieved.\nThe server sent a malformatted response:\n"); + tmp+=getCurrentLine(); + job->setErrorString(tmp); + closeConnection(); + return; + } + + if(target->lastNr()==0) { //first fetch + if(first>0) + oldlast=first-1; + else + oldlast=first; + } else + oldlast=target->lastNr(); + + toFetch=last-oldlast; + //qDebug("knode: last %d oldlast %d toFetch %d\n",last,oldlast,toFetch); + + if(toFetch<=0) { + //qDebug("knode: No new Articles in group\n"); + target->setLastNr(last); // don't get stuck when the article numbers wrap + return; + } + + if(toFetch>target->maxFetch()) { + toFetch=target->maxFetch(); + //qDebug("knode: Fetching only %d articles\n",toFetch); + } + + progressValue = 100; + predictedLines = toFetch; + + // get list of additional headers provided by the XOVER command + // see RFC 2980 section 2.1.7 + QStrList headerformat; + cmd = "LIST OVERVIEW.FMT"; + if ( sendCommand( cmd, rep ) && rep == 215 ) { + QStrList tmp; + if (getMsg(tmp)) { + for(QCString s = tmp.first(); s; s = tmp.next()) { + s = s.stripWhiteSpace(); + // remove the mandatory xover header + if (s == "Subject:" || s == "From:" || s == "Date:" || s == "Message-ID:" + || s == "References:" || s == "Bytes:" || s == "Lines:") + continue; + else + headerformat.append(s); + } + } + } + + //qDebug("knode: KNNntpClient::doFetchNewHeaders() : xover %d-%d", last-toFetch+1, last); + cmd.sprintf("xover %d-%d",last-toFetch+1,last); + if (!sendCommand(cmd,rep)) + return; + + // no articles in selected range... + if (rep==420) { // 420 No article(s) selected + target->setLastNr(last); + return; + } else if (rep!=224) { // 224 success + handleErrors(); + return; + } + + QStrList headers; + if (!getMsg(headers)) { + return; + } + + progressValue = 1000; + sendSignal(TSprogressUpdate); + + sendSignal(TSsortNew); + + mutex.lock(); + target->insortNewHeaders(&headers, &headerformat, this); + target->setLastNr(last); + mutex.unlock(); +} + + +void KNNntpClient::doFetchArticle() +{ + KNRemoteArticle *target = static_cast<KNRemoteArticle*>(job->data()); + QCString cmd; + + sendSignal(TSdownloadArticle); + errorPrefix = i18n("Article could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = target->lines()->numberOfLines()+10; + + if (target->collection()) { + QString groupName = static_cast<KNGroup*>(target->collection())->groupname(); + if (currentGroup != groupName) { + cmd="GROUP "; + cmd+=groupName.utf8(); + if (!sendCommandWCheck(cmd,211)) // 211 n f l s group selected + return; + currentGroup = groupName; + } + } + + if (target->articleNumber() != -1) { + cmd.setNum(target->articleNumber()); + cmd.prepend("ARTICLE "); + } else { + cmd = "ARTICLE " + target->messageID()->as7BitString(false); + } + + if (!sendCommandWCheck(cmd,220)) { // 220 n <a> article retrieved - head and body follow + int code = atoi(getCurrentLine()); + if ( code == 430 || code == 423 ) { // 430 no such article found || 423 no such article number in this group + QString msgId = target->messageID()->as7BitString( false ); + // strip of '<' and '>' + msgId = msgId.mid( 1, msgId.length() - 2 ); + job->setErrorString( errorPrefix + getCurrentLine() + + i18n("<br><br>The article you requested is not available on your news server." + "<br>You could try to get it from <a href=\"http://groups.google.com/groups?selm=%1\">groups.google.com</a>.") + .arg( msgId ) ); + } + return; + } + + QStrList msg; + if (!getMsg(msg)) + return; + + progressValue = 1000; + sendSignal(TSprogressUpdate); + + target->setContent(&msg); + target->parse(); +} + + +void KNNntpClient::doPostArticle() +{ + KNLocalArticle *art=static_cast<KNLocalArticle*>(job->data()); + + sendSignal(TSsendArticle); + + if (art->messageID(false)!=0) { + int rep; + if (!sendCommand(QCString("STAT ")+art->messageID(false)->as7BitString(false),rep)) + return; + + if (rep==223) { // 223 n <a> article retrieved - request text separately + #ifndef NDEBUG + qDebug("knode: STAT successful, we have probably already sent this article."); + #endif + return; // the article is already on the server, lets put it silently into the send folder + } + } + + if(!sendCommandWCheck("POST", 340)) // 340 send article to be posted. End with <CR-LF>.<CR-LF> + return; + + if (art->messageID(false)==0) { // article has no message ID => search for a ID in the response + QCString s = getCurrentLine(); + int start = s.findRev(QRegExp("<[^\\s]*@[^\\s]*>")); + if (start != -1) { // post response includes a recommended id + int end = s.find('>',start); + art->messageID()->from7BitString(s.mid(start,end-start+1)); + art->assemble(); + #ifndef NDEBUG + qDebug("knode: using the message-id recommended by the server: %s",s.mid(start,end-start+1).data()); + #endif + } + } + + if (!sendMsg(art->encodedContent(true))) + return; + + if (!checkNextResponse(240)) // 240 article posted ok + return; +} + + +void KNNntpClient::doFetchSource() +{ + KNRemoteArticle *target = static_cast<KNRemoteArticle*>(job->data()); + + sendSignal(TSdownloadArticle); + errorPrefix = i18n("Article could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = target->lines()->numberOfLines()+10; + + QCString cmd = "ARTICLE " + target->messageID()->as7BitString(false); + if (!sendCommandWCheck(cmd,220)) // 220 n <a> article retrieved - head and body follow + return; + + QStrList msg; + if (!getMsg(msg)) + return; + + progressValue = 1000; + sendSignal(TSprogressUpdate); + + target->setContent(&msg); +} + + +bool KNNntpClient::openConnection() +{ + currentGroup = QString::null; + + QString oldPrefix = errorPrefix; + errorPrefix=i18n("Unable to connect.\nThe following error occurred:\n"); + + if (!KNProtocolClient::openConnection()) + return false; + + progressValue = 30; + + int rep; + if (!getNextResponse(rep)) + return false; + + if ( ( rep < 200 ) || ( rep > 299 ) ) { // RFC977: 2xx - Command ok + handleErrors(); + return false; + } + + progressValue = 50; + + if (!sendCommand("MODE READER",rep)) + return false; + + if (rep==500) { +#ifndef NDEBUG + qDebug("knode: \"MODE READER\" command not recognized."); +#endif + } else + if ( ( rep < 200 ) || ( rep > 299 ) ) { // RFC977: 2xx - Command ok + handleErrors(); + return false; + } + + progressValue = 60; + + // logon now, some newsserver send a incomplete group list otherwise + if (account.needsLogon() && !account.user().isEmpty()) { + //qDebug("knode: user: %s",account.user().latin1()); + + QCString command = "AUTHINFO USER "; + command += account.user().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + + if (rep==381) { // 381 PASS required + //qDebug("knode: Password required"); + + if (!account.pass().length()) { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.")); + job->setAuthError(true); + return false; + } + + //qDebug("knode: pass: %s",account.pass().latin1()); + + command = "AUTHINFO PASS "; + command += account.pass().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + + if (rep==281) { // 281 authorization success + #ifndef NDEBUG + qDebug("knode: Authorization successful"); + #endif + } else { + #ifndef NDEBUG + qDebug("knode: Authorization failed"); + #endif + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.\n\n%1").arg(getCurrentLine())); + job->setAuthError(true); + closeConnection(); + return false; + } + } else { + if (rep==281) { // 281 authorization success + #ifndef NDEBUG + qDebug("knode: Authorization successful"); + #endif + } else { + if ((rep==482)||(rep==500)) { //482 Authentication rejected + #ifndef NDEBUG + qDebug("knode: Authorization failed"); // we don't care, the server can refuse the info + #endif + } else { + handleErrors(); + return false; + } + } + } + } + + progressValue = 70; + + errorPrefix = oldPrefix; + return true; +} + + +// authentication on demand +bool KNNntpClient::sendCommand(const QCString &cmd, int &rep) +{ + if (!KNProtocolClient::sendCommand(cmd,rep)) + return false; + + if (rep==480) { // 480 requesting authorization + //qDebug("knode: Authorization requested"); + + if (!account.user().length()) { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.")); + job->setAuthError(true); + closeConnection(); + return false; + } + + //qDebug("knode: user: %s",account.user().data()); + + QCString command = "AUTHINFO USER "; + command += account.user().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + + if (rep==381) { // 381 PASS required + //qDebug("knode: Password required"); + + if (!account.pass().length()) { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.\n\n%1").arg(getCurrentLine())); + job->setAuthError(true); + closeConnection(); + return false; + } + + //qDebug("knode: pass: %s",account.pass().data()); + + command = "AUTHINFO PASS "; + command += account.pass().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + } + + if (rep==281) { // 281 authorization success + #ifndef NDEBUG + qDebug("knode: Authorization successful"); + #endif + if (!KNProtocolClient::sendCommand(cmd,rep)) // retry the original command + return false; + } else { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.\n\n%1").arg(getCurrentLine())); + job->setAuthError(true); + closeConnection(); + return false; + } + } + return true; +} + + +void KNNntpClient::handleErrors() +{ + if (errorPrefix.isEmpty()) + job->setErrorString(i18n("An error occurred:\n%1").arg(getCurrentLine())); + else + job->setErrorString(errorPrefix + getCurrentLine()); + + int code = atoi(getCurrentLine()); + + // close the connection only when necessary: + // 430 no such article found + // 411 no such news group + // 423 no such article number in this group + if ((code != 430)&&(code != 411)&&(code != 423)) + closeConnection(); +} + + +//-------------------------------- + diff --git a/knode/knnntpclient.h b/knode/knnntpclient.h new file mode 100644 index 000000000..274c12271 --- /dev/null +++ b/knode/knnntpclient.h @@ -0,0 +1,57 @@ +/* + knnntpclient.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNNNTPCLIENT_H +#define KNNNTPCLIENT_H + +#include <qmutex.h> + +#include <knprotocolclient.h> + + +class KNNntpClient : public KNProtocolClient { + + public: + + KNNntpClient(int NfdPipeIn, int NfdPipeOut, QMutex& nntpMutex); + ~KNNntpClient(); + + protected: + + /** examines the job and calls the suitable handling method */ + virtual void processJob(); + + void doLoadGroups(); + void doFetchGroups(); + void doCheckNewGroups(); + void doFetchNewHeaders(); + void doFetchArticle(); + void doPostArticle(); + void doFetchSource(); + + /** connect, handshake */ + virtual bool openConnection(); + /** authentication on demand */ + virtual bool sendCommand(const QCString &cmd, int &rep); + virtual void handleErrors(); + bool switchToGroup(const QString &newGroup); + + QString currentGroup; + QMutex& mutex; + +}; + +#endif diff --git a/knode/knode.cpp b/knode/knode.cpp new file mode 100644 index 000000000..0dd4e6a38 --- /dev/null +++ b/knode/knode.cpp @@ -0,0 +1,122 @@ +/* + knode.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ +#include "knode.h" +#include "knglobals.h" +#include "knwidgets.h" + +#include <kkeydialog.h> +#include <kedittoolbar.h> +#include <kstdaction.h> +#include <kdebug.h> +#include <kmenubar.h> +#include <kiconloader.h> +#include <kstatusbar.h> +#include <klocale.h> +#include <kapplication.h> + +#include "broadcaststatus.h" +#include "krsqueezedtextlabel.h" +#include "progressdialog.h" +#include "statusbarprogresswidget.h" + +//GUI +#include "knmainwidget.h" +#include "knarticlewindow.h" +#include "kncollectionviewitem.h" +#include "knhdrviewitem.h" + +KNMainWindow::KNMainWindow( QWidget* pWidget ) + : KMainWindow(pWidget,"mainWindow") +{ + //setupStatusBar(); + createStandardStatusBarAction(); + setStandardToolBarMenuEnabled(true); + + //config stuff + KStdAction::quit(kapp, SLOT(closeAllWindows()), actionCollection()); + KStdAction::configureToolbars(this, SLOT(slotConfToolbar()), actionCollection()); + KStdAction::keyBindings(this, SLOT(slotConfKeys()), actionCollection()); + + m_mainWidget = new KNMainWidget( this, true, this, 0 ); + connect( m_mainWidget, SIGNAL(signalCaptionChangeRequest(const QString&)), + SLOT( setCaption(const QString&)) ); + setCentralWidget( m_mainWidget ); + setupStatusBar(); + connect( KPIM::BroadcastStatus::instance(), SIGNAL(statusMsg(const QString&)), + this, SLOT(slotShowStatusMsg(const QString& )) ); + createGUI( "knodeui.rc" ); + knGlobals.instance = 0; + + applyMainWindowSettings(KGlobal::config(),"mainWindow_options"); +} + +KNMainWindow::~KNMainWindow() +{ + saveMainWindowSettings(knGlobals.config(),"mainWindow_options"); +} + +void KNMainWindow::openURL( const KURL& url ) +{ + m_mainWidget->openURL( url ); +} + +void KNMainWindow::slotConfToolbar() +{ + saveMainWindowSettings(knGlobals.config(),"mainWindow_options"); + KEditToolbar dlg(actionCollection(), "knodeui.rc"); + connect(&dlg,SIGNAL( newToolbarConfig() ), this, SLOT( slotNewToolbarConfig() )); + dlg.exec(); +} + +void KNMainWindow::slotNewToolbarConfig() +{ + createGUI("knodeui.rc"); + //initPopups(); + applyMainWindowSettings(knGlobals.config(),"mainWindow_options"); +} + +void KNMainWindow::slotConfKeys() +{ + KKeyDialog::configure(actionCollection(), true); +} + +bool KNMainWindow::queryClose() +{ + return m_mainWidget->queryClose(); +} + +void KNMainWindow::setupStatusBar() +{ + mProgressDialog = new KPIM::ProgressDialog( statusBar(), this ); + mProgressDialog->hide(); + + mLittleProgress = new StatusbarProgressWidget( mProgressDialog, statusBar() ); + mLittleProgress->show(); + + statusBar()->addWidget( mLittleProgress, 0 , true ); + + mStatusMsgLabel = new KRSqueezedTextLabel( QString::null, statusBar() ); + mStatusMsgLabel->setAlignment( AlignLeft | AlignVCenter ); + statusBar()->addWidget( mStatusMsgLabel, 2 ); + statusBar()->addWidget(m_mainWidget->statusBarLabelFilter(), 2); + statusBar()->addWidget(m_mainWidget->statusBarLabelGroup(), 3); +} + +void KNMainWindow::slotShowStatusMsg( const QString &msg ) { + mStatusMsgLabel->setText( msg ); +} + +#include "knode.moc" diff --git a/knode/knode.h b/knode/knode.h new file mode 100644 index 000000000..8b719a5d8 --- /dev/null +++ b/knode/knode.h @@ -0,0 +1,70 @@ +/* + knode.h + + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNODE_H +#define KNODE_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <kmainwindow.h> +#include <kdialogbase.h> +#include "resource.h" + +#include <qglobal.h> + +class KURL; + +namespace KPIM { + class StatusbarProgressWidget; + class ProgressDialog; +} +using KPIM::StatusbarProgressWidget; +using KPIM::ProgressDialog; +class KRSqueezedTextLabel; + +class KNMainWidget; +class KNHeaderView; + + +class KNMainWindow : public KMainWindow +{ + Q_OBJECT + +public: + KNMainWindow( QWidget* parentWidget=0 ); + ~KNMainWindow(); + void openURL( const KURL& ); + KNMainWidget *mainWidget() { return m_mainWidget; } + +public slots: + void slotConfToolbar(); + void slotNewToolbarConfig(); + void slotConfKeys(); +protected: + bool queryClose(); +private: + void setupStatusBar(); + KNMainWidget *m_mainWidget; + StatusbarProgressWidget *mLittleProgress; + ProgressDialog *mProgressDialog; + KRSqueezedTextLabel *mStatusMsgLabel; +private slots: + void slotShowStatusMsg( const QString& ); +}; + +#endif // KNODE_H diff --git a/knode/knode_config_accounts.desktop b/knode/knode_config_accounts.desktop new file mode 100644 index 000000000..9d0716f64 --- /dev/null +++ b/knode/knode_config_accounts.desktop @@ -0,0 +1,118 @@ +[Desktop Entry] +Icon=network +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=knode +X-KDE-FactoryName=knode_config_accounts +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=knode +X-KDE-ParentComponents=knode,kontact_knodeplugin +X-KDE-CfgDlgHierarchy=KNode + +Name=Accounts +Name[af]=Rekeninge +Name[ar]=الحسابات +Name[be]=Рахункі +Name[bg]=Сметки +Name[br]=Kontoù +Name[bs]=Računi +Name[ca]=Comptes +Name[cs]=Účty +Name[cy]=Cyfrifon +Name[da]=Konti +Name[de]=Zugänge +Name[el]=Λογαριασμοί +Name[eo]=Kontoj +Name[es]=Cuentas +Name[et]=Kontod +Name[eu]=Kontuak +Name[fa]=حسابها +Name[fi]=Tilit +Name[fr]=Comptes +Name[fy]=Akkounts +Name[ga]=Cuntais +Name[gl]=Contas +Name[he]=חשבונות +Name[hu]=Fiókok +Name[is]=Tengingar +Name[it]=Account +Name[ja]=アカウント +Name[ka]=ანგარიშები +Name[kk]=Тіркелгілері +Name[km]=គណនី +Name[lt]=Paskyros +Name[mk]=Сметки +Name[ms]=Akaun +Name[nb]=Kontoer +Name[nds]=Kontos +Name[ne]=खाता +Name[nn]=Kontoar +Name[pa]=ਖਾਤੇ +Name[pl]=Konta +Name[pt]=Contas +Name[pt_BR]=Contas +Name[ru]=Учётные записи +Name[se]=Kontut +Name[sk]=Účty +Name[sl]=Računi +Name[sr]=Налози +Name[sr@Latn]=Nalozi +Name[sv]=Konton +Name[ta]=கணக்குகள் +Name[tg]=Қайдҳои баҳисобгирӣ +Name[tr]=Hesaplar +Name[uk]=Рахунки +Name[uz]=Hisoblar +Name[uz@cyrillic]=Ҳисоблар +Name[zh_CN]=账户 +Name[zh_TW]=帳號 +Comment=Setup for Newsgroup and Mail Servers +Comment[af]=Opstel van nuus groep en e-pos bedieners +Comment[bg]=Настройки на групите и пощенските сървъри +Comment[bs]=Postavke za newsgrupe i mail servere +Comment[ca]=Configuració dels grups de notícies i servidors de correu +Comment[cs]=Nastavení diskuzní skupiny a poštovních serverů +Comment[da]=Opsætning af nyhedsgruppe og e-mail-servere +Comment[de]=Einstellungen für Newsgruppen- und Mailserver +Comment[el]=Ρύθμιση για Ομάδες Συζήτησης και Διακομιστών Αλληλογραφίας +Comment[es]=Configuración de los servidores de grupos de noticias y de correo +Comment[et]=Uudiste- ja e-posti serverite seadistus +Comment[eu]=Berri-talde eta posta zerbitzarien konfigurazioa +Comment[fa]=برپایی برای کارسازهای نامه و Newsgroup +Comment[fi]=Uutisryhmien ja sähköpostipalvelinten asetukset +Comment[fr]=Configuration les serveurs de messagerie et de groupes de discussion +Comment[fy]=Nijs- en posttsjinners opset +Comment[gl]=Configuración para Servidores de Correo e Novas +Comment[hu]=Hír- és levelezési kiszolgálók beállítása +Comment[is]=Uppsetning fyrir fréttahópa og póstþjóna +Comment[it]=Impostazioni per newsgroup e server di posta +Comment[ja]=ニュースグループとメールサーバの設定 +Comment[ka]=სიახლეთა ჯგუფებისა და საფოსტო სერვერის კონფიგურაცია +Comment[kk]=Жаңалық топтар мен Пошта серверлері +Comment[km]=រៀបចំម៉ាស៊ីនបម្រើវេទិកាព័ត៌មាន និងសំបុត្រ +Comment[lt]=Naujienų grupių ir pašto serverių nustatymai +Comment[mk]=Поставување за сервери за групи вести и за е-пошта +Comment[ms]=Setup untuk Kumpulan Berita dan Pelayan Mel +Comment[nb]=Oppsett av njusgruppe- og e-post tjenere +Comment[nds]=Narichtenkrink- und Nettpostservers instellen +Comment[ne]=समाचार समूह र पत्र सर्भरका लागि सेटअप गर्नुहोस् +Comment[nl]=Instellingen voor nieuws- en mailservers +Comment[nn]=Oppsett av Usenet- og e-post-tenarar +Comment[pl]=Ustawienia serwerów poczty i grup dyskusyjnych +Comment[pt]=Configuração de Servidores de Notícias e de E-mail +Comment[pt_BR]=Configura Grupos de Discussão e Servidores de E-mails +Comment[ru]=Учётные записи на почтовых серверах и серверах телеконференций +Comment[se]=Heivet Usenet- ja e-boastabálvváid +Comment[sk]=Nastavenie serverov pre poštu a diskusné skupiny +Comment[sl]=Nastavitve za novične in poštne strežnike +Comment[sr]=Поставке за групе новости и сервере поште +Comment[sr@Latn]=Postavke za grupe novosti i servere pošte +Comment[sv]=Inställningar för diskussionsgrupp och postservrar +Comment[ta]=செய்திக்குழு மற்றும் மின்னஞ்சல் சேவைகளுக்கான அமைப்பு +Comment[tg]=Қайдҳои баҳисобгирӣ дар серверҳои почтавӣ ва серверҳои телеконференсия +Comment[tr]=E-posta ve Haber Grubu Sunucuları için Yapılandırma +Comment[uk]=Налаштування для груп новин і серверів пошти +Comment[zh_CN]=新闻组和邮件服务器的设置 +Comment[zh_TW]=設定新聞群組及郵件伺服器 diff --git a/knode/knode_config_appearance.desktop b/knode/knode_config_appearance.desktop new file mode 100644 index 000000000..bbd2ec26d --- /dev/null +++ b/knode/knode_config_appearance.desktop @@ -0,0 +1,122 @@ +[Desktop Entry] +Icon=looknfeel +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=knode +X-KDE-FactoryName=knode_config_appearance +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=knode +X-KDE-ParentComponents=knode,kontact_knodeplugin +X-KDE-CfgDlgHierarchy=KNode + +Name=Appearance +Name[ar]=المظهر +Name[be]=Знешні выгляд +Name[bg]=Външен вид +Name[br]=Neuziadur +Name[bs]=Izgled +Name[ca]=Aparença +Name[cs]=Vzhled +Name[cy]=Golwg +Name[da]=Udseende +Name[de]=Erscheinungsbild +Name[el]=Εμφάνιση +Name[eo]=Prezentado +Name[es]=Apariencia +Name[et]=Välimus +Name[eu]=Itxura +Name[fa]=ظاهر +Name[fi]=Ulkoasu +Name[fr]=Apparence +Name[fy]=Uterlik +Name[ga]=Cuma +Name[gl]=Apariencia +Name[he]=מראה +Name[hr]=Izgled +Name[hu]=Megjelenés +Name[is]=Útlit +Name[it]=Aspetto +Name[ja]=外観 +Name[ka]=იერსახე +Name[kk]=Сыртқы көрінісі +Name[km]=រូបរាង +Name[ko]=모양 +Name[lt]=Išvaizda +Name[mk]=Изглед +Name[ms]=Rupa +Name[nb]=Utseende +Name[nds]=Utsehn +Name[ne]=मोहडा +Name[nl]=Uiterlijk +Name[nn]=Utsjånad +Name[pl]=Wygląd +Name[pt]=Aparência +Name[pt_BR]=Aparência +Name[ru]=Внешний вид +Name[rw]=Imigaragarire +Name[se]=Fárda +Name[sk]=Vzhľad +Name[sl]=Videz +Name[sr]=Изглед +Name[sr@Latn]=Izgled +Name[sv]=Uppträdande +Name[ta]=தோற்றம் +Name[tg]=Намуди зоҳирӣ +Name[tr]=Görünüm +Name[uk]=Вигляд +Name[uz]=Koʻrinishi +Name[uz@cyrillic]=Кўриниши +Name[zh_CN]=外观 +Comment=Customize Visual Appearance +Comment[af]=Pasmaak die visuele voorkoms +Comment[bg]=Настройки на външния вид +Comment[bs]=Prilagodi izgled +Comment[ca]=Personalitza l'aparença visual +Comment[cs]=Nastavení vzhledu +Comment[cy]=Addasu Golwg +Comment[da]=Brugerindstil visuelt udseende +Comment[de]=Erscheinungsbild anpassen +Comment[el]=Προσαρμογή εμφάνισης +Comment[en_GB]=Customise Visual Appearance +Comment[es]=Apariencia visual personalizada +Comment[et]=Välimuse seadistused +Comment[eu]=Pertsonalizatu itxura +Comment[fa]=سفارشی کردن ظاهر تصویری +Comment[fi]=Ulkonäköasetukset +Comment[fr]=Personnalisation de l'apparence +Comment[fy]=It uterlik oanpasse +Comment[gl]=Personalizar a apariencia visual +Comment[he]=הגדרת מראה +Comment[hu]=A grafikai megjelenés testreszabása +Comment[is]=Stilla útlit +Comment[it]=Personalizza l'aspetto +Comment[ja]=外観をカスタマイズ +Comment[ka]=ვიზუალური იერსახის დაყენება +Comment[kk]=Сыртқы көрінісін ыңғайлау +Comment[km]=ប្ដូររូបរាងមើលឃើញតាមបំណង +Comment[lt]=Derinti vizualinę išvaizdą +Comment[mk]=Приспособете ја визуелната појава +Comment[ms]=Suaikan Rupa Visual +Comment[nb]=Tilpass visuelt utseende +Comment[nds]=Utsehn topassen +Comment[ne]=दृश्यात्मक मोहडा अनुकूल गर्नुहोस् +Comment[nl]=Pas het uiterlijk aan +Comment[nn]=Tilpass utsjånad +Comment[pl]=Zmiana wyglądu +Comment[pt]=Personalizar a Aparência Visual +Comment[pt_BR]=Personalizar Aparência Visual +Comment[ru]=Настройка внешнего вида +Comment[se]=Heivet fárdda +Comment[sk]=Prispôsobenie vzhľadu +Comment[sl]=Prilagodi videz +Comment[sr]=Прилагодите визуелни приказ +Comment[sr@Latn]=Prilagodite vizuelni prikaz +Comment[sv]=Anpassa visuellt uppträdande +Comment[ta]=பார்க்கும் தோற்றத்தை தனிபயனாக்கு +Comment[tg]=Танзимоти намуди зоҳирӣ +Comment[tr]=Görsel Görünümü Özelleştir +Comment[uk]=Налаштування зовнішнього вигляду +Comment[zh_CN]=自定义视觉外观 +Comment[zh_TW]=調整視覺顯示 diff --git a/knode/knode_config_cleanup.desktop b/knode/knode_config_cleanup.desktop new file mode 100644 index 000000000..5f08bbe29 --- /dev/null +++ b/knode/knode_config_cleanup.desktop @@ -0,0 +1,113 @@ +[Desktop Entry] +Icon=wizard +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=knode +X-KDE-FactoryName=knode_config_cleanup +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=knode +X-KDE-ParentComponents=knode,kontact_knodeplugin +X-KDE-CfgDlgHierarchy=KNode + +Name=Cleanup +Name[ar]=التنظيف +Name[bg]=Изчистване +Name[bs]=Čišćenje +Name[ca]=Neteja +Name[cs]=Vyčištění +Name[da]=Oprydning +Name[de]=Aufräumen +Name[el]=Καθαρισμός +Name[eo]=Purigo +Name[es]=Limpiar +Name[et]=Puhastamine +Name[eu]=Garbiketa +Name[fa]=پاکسازی +Name[fi]=Siivoaminen +Name[fr]=Nettoyage +Name[fy]=Opskjinning +Name[ga]=Glan +Name[gl]=Limpeza +Name[he]=ניקיון +Name[hu]=Tisztítás +Name[is]=Hreinsun +Name[it]=Pulizia +Name[ja]=整理 +Name[ka]=გაწმენდა +Name[kk]=Тазалау +Name[km]=សម្អាត +Name[lt]=Išvalymas +Name[mk]=Расчистување +Name[ms]=Pembersihan +Name[nb]=Rydd opp +Name[nds]=Oprümen +Name[ne]=हटाउनुहोस् +Name[nl]=Opschoning +Name[nn]=Rydd opp +Name[pl]=Porządkowanie +Name[pt]=Limpeza +Name[pt_BR]=Limpeza +Name[ru]=Очистка диска +Name[se]=Čorge +Name[sk]=Vyčistenie +Name[sl]=Čiščenje +Name[sr]=Чишћење +Name[sr@Latn]=Čišćenje +Name[sv]=Upprensning +Name[ta]=சுத்தம் செய் +Name[tg]=Тозакунии диск +Name[tr]=Temizle +Name[uk]=Очищення диску +Name[zh_CN]=清理 +Name[zh_TW]=清除 +Comment=Preserving Disk Space +Comment[af]=Bespaar Skyf Spasie +Comment[bg]=Освобождаване на свободно място на диска +Comment[bs]=Čuvanje prostora na disku +Comment[ca]=Preservar l'espai a disc +Comment[cs]=Zachování místa na disku +Comment[da]=Bevarer diskplads +Comment[de]=Plattenplatz bewahren +Comment[el]=Διαφύλαξη χώρου στο Δίσκο +Comment[eo]=Ŝpari diskspacon +Comment[es]=Preservar el espacio de disco +Comment[et]=Kettaruumi eest hoolitsemine +Comment[eu]=Diskaren espazioa mantendu +Comment[fa]=حفظ فضای دیسک +Comment[fi]=Kiintolevytilan säästäminen +Comment[fr]=Préservation de l'espace disque +Comment[fy]=Besparring fan skiifromte +Comment[gl]=Preservar Espacio en Disco +Comment[he]=שמירת מקום בכונן +Comment[hu]=A lemezterület megőrzése +Comment[is]=Varðveita diskpláss +Comment[it]=Risparmia lo spazio su disco +Comment[ja]=ディスクスペースを維持 +Comment[ka]=სივრცის შენახვა დისკზე +Comment[kk]=Дискідегі орынды үнемдеу +Comment[km]=បង្ការទំហំថាស +Comment[lt]=Disko erdvės išsaugojimas +Comment[mk]=Зачувување простор на дискот +Comment[ms]=Memelihara Ruang Cakera +Comment[nb]=Bevarer disk plass +Comment[nds]=Platz op de Fastplaat wohren +Comment[ne]=चक्का खाली स्थान संचित गर्दैछ +Comment[nl]=Besparing van schrijfruimte +Comment[nn]=Frigjer diskplass +Comment[pl]=Kontrolowanie użytej przestrzeni dyskowej +Comment[pt]=Preservar o Espaço em Disco +Comment[pt_BR]=Preservando o Espaço em Disco +Comment[ru]=Сохранение места на диске +Comment[sk]=Úspora miesta na disku +Comment[sl]=Ohranjanje prostora na disku +Comment[sr]=Чување простора на диску +Comment[sr@Latn]=Čuvanje prostora na disku +Comment[sv]=Bevara diskutrymme +Comment[ta]=தட்டின் இடத்தை சேமித்தல் +Comment[tg]=Нигоҳ доштани ҷое дар диск +Comment[tr]=Saklama için Disk Alanı +Comment[uk]=Збереження місця на диску +Comment[zh_CN]=腾出磁盘空间 +Comment[zh_TW]=保留磁碟空間 diff --git a/knode/knode_config_identity.desktop b/knode/knode_config_identity.desktop new file mode 100644 index 000000000..2fda0f69b --- /dev/null +++ b/knode/knode_config_identity.desktop @@ -0,0 +1,124 @@ +[Desktop Entry] +Icon=identity +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=knode +X-KDE-FactoryName=knode_config_identity +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=knode +X-KDE-ParentComponents=knode,kontact_knodeplugin +X-KDE-CfgDlgHierarchy=KNode + +Name=Identity +Name[af]=Identiteit +Name[ar]=الهوية +Name[be]=Увасабленне +Name[bg]=Самоличност +Name[br]=Anvelezh +Name[bs]=Identitet +Name[ca]=Identitat +Name[cy]=Dynodiad +Name[da]=Identitet +Name[de]=Identität +Name[el]=Ταυτότητα +Name[eo]=Idento +Name[es]=Identidad +Name[et]=Identiteet +Name[eu]=Identitatea +Name[fa]=هویت +Name[fi]=Henkilöllisyys +Name[fr]=Identité +Name[fy]=Identiteit +Name[ga]=Aitheantas +Name[gl]=Identidade +Name[he]=זהות +Name[hu]=Azonosító +Name[is]=Auðkenni +Name[it]=Identità +Name[ja]=個人情報 +Name[ka]=პროფილი +Name[kk]=Іс-әлпеті +Name[km]=អត្តសញ្ញាណ +Name[lt]=Tapatybė +Name[mk]=Идентитет +Name[ms]=Identiti +Name[nb]=Identitet +Name[nds]=Persoon +Name[ne]=पहिचान +Name[nl]=Identiteit +Name[nn]=Identitet +Name[pl]=Tożsamość +Name[pt]=Identidade +Name[pt_BR]=Identidade +Name[ru]=Профиль +Name[se]=Identitehta +Name[sk]=Identita +Name[sl]=Identiteta +Name[sr]=Идентитет +Name[sr@Latn]=Identitet +Name[sv]=Identitet +Name[ta]=அடையாளம் +Name[tg]=Профил +Name[tr]=Kimlik +Name[uk]=Профіль +Name[uz]=Shaxsiyat +Name[uz@cyrillic]=Шахсият +Name[zh_CN]=身份 +Name[zh_TW]=身份 +Comment=Personal Information +Comment[af]=Persoonlike Informasie +Comment[be]=Пэрсанальная інфармацыя +Comment[bg]=Управление на личната информация +Comment[br]=Titouroù diouzhin +Comment[bs]=Lične informacije +Comment[ca]=Informació personal +Comment[cs]=Osobní informace +Comment[cy]=Gwybodaeth Bersonol +Comment[da]=Personlig information +Comment[de]=Persönliche Angaben +Comment[el]=Προσωπικές πληροφορίες +Comment[eo]=Personaj Informoj +Comment[es]=Información personal +Comment[et]=Personaalne info +Comment[eu]=Informazio pertsonala +Comment[fa]=اطلاعات شخصی +Comment[fi]=Omat tiedot +Comment[fr]=Informations personnelles +Comment[fy]=Persoanlike ynformatie +Comment[ga]=Eolas Pearsanta +Comment[gl]=Información Persoal +Comment[he]=מידע אישי +Comment[hu]=Személyi adatok +Comment[is]=Persónuupplýsingar +Comment[it]=Informazioni personali +Comment[ja]=個人の情報 +Comment[ka]=პირადი ინფორმაცია +Comment[kk]=Дербес мәліметтер +Comment[km]=ព័ត៌មានផ្ទាល់ខ្លួន +Comment[lt]=Asmeninė informacija +Comment[mk]=Лични информации +Comment[ms]=Maklumat Peribadi +Comment[nb]=Personlig informasjon +Comment[nds]=Persöönlich Informatschonen +Comment[ne]=व्यक्तिगत सूचना +Comment[nl]=Persoonlijke informatie +Comment[nn]=Personleg informasjon +Comment[pa]=ਨਿੱਜੀ ਜਾਣਕਾਰੀ +Comment[pl]=Informacje osobiste +Comment[pt]=Informação Pessoal +Comment[pt_BR]=Informações Pessoais +Comment[ru]=Персональная информация +Comment[se]=Peršuvnnalaš dieđut +Comment[sk]=Osobné informácie +Comment[sl]=Osebne informacije +Comment[sr]=Лична информација +Comment[sr@Latn]=Lična informacija +Comment[sv]=Personlig information +Comment[ta]=சொந்த தகவல் +Comment[tg]=Маълумоти шахсӣ +Comment[tr]=Kişisel Bilgi +Comment[uk]=Особиста інформація +Comment[zh_CN]=个人信息 +Comment[zh_TW]=個人資訊 diff --git a/knode/knode_config_post_news.desktop b/knode/knode_config_post_news.desktop new file mode 100644 index 000000000..784b7c39c --- /dev/null +++ b/knode/knode_config_post_news.desktop @@ -0,0 +1,62 @@ +[Desktop Entry] +Icon=mail_forward +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=knode +X-KDE-FactoryName=knode_config_post_news +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=knode +X-KDE-ParentComponents=knode,kontact_knodeplugin +X-KDE-CfgDlgHierarchy=KNode + +Name=Posting News +Name[af]=Pos Nuus +Name[bg]=Изпращане +Name[bs]=Slanje na Usenet +Name[ca]=Enviament de notícies +Name[cs]=Odesílání novinek +Name[da]=Indsender nyheder +Name[de]=Artikelversand +Name[el]=Αποστολή νέων +Name[es]=Enviar noticias +Name[et]=Uudiste postitamine +Name[eu]=Berrien argitaratzea +Name[fa]=ارسال سریع اخبار +Name[fi]=Viestien lähettäminen +Name[fr]=Postage de nouvelles +Name[fy]=Nijs poste +Name[gl]=Publicando Noticias +Name[he]=שליחת חדשות +Name[hu]=Hírek írása +Name[is]=Senda fréttir +Name[it]=Invio news +Name[ja]=ニュースを投稿 +Name[ka]=სიახლეების განთავსება +Name[kk]=Жариялау +Name[km]=ប្រកាសព័ត៌មាន +Name[lt]=Naujienų skelbimas +Name[mk]=Испраќање вести +Name[ms]=Menampal Berita +Name[nb]=Legge inn Njus +Name[nds]=Narichten sennen +Name[ne]=समाचार पोष्ट गर्दैछ +Name[nl]=Nieuws posten +Name[nn]=Send innlegg +Name[pl]=Publikowanie wiadomości +Name[pt]=Enviar Notícias +Name[pt_BR]=Enviando Notícias +Name[ru]=Размещение статей +Name[sk]=Posielanie príspevkov +Name[sl]=Pošiljanje novic +Name[sr]=Слање новости +Name[sr@Latn]=Slanje novosti +Name[sv]=Skicka inlägg +Name[ta]=செய்திகளை அனுப்புதல் +Name[tg]=Ҷойгиркунии мақола +Name[tr]=Gönderilen Haberler +Name[uk]=Розміщення новин +Name[zh_CN]=投递新闻 +Name[zh_TW]=發表文章 + diff --git a/knode/knode_config_privacy.desktop b/knode/knode_config_privacy.desktop new file mode 100644 index 000000000..21bdddb6f --- /dev/null +++ b/knode/knode_config_privacy.desktop @@ -0,0 +1,107 @@ +[Desktop Entry] +Icon=password +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=knode +X-KDE-FactoryName=knode_config_privacy +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=knode +X-KDE-ParentComponents=knode,kontact_knodeplugin +X-KDE-CfgDlgHierarchy=KNode + +Name=Signing/Verifying +Name[af]=Teken/Bevestig +Name[ar]=التوقيع/التحقق +Name[bg]=Подпис/проверка +Name[bs]=Potpisivanje/Provjera +Name[ca]=Signat/Verificació +Name[cs]=Podepisování/ověřování +Name[da]=Underskriver/Verificerer +Name[de]=Signieren/Verifizieren +Name[el]=Υπογραφή/Επαλήθευση +Name[es]=Firmar/Verificar +Name[et]=Signeerimine/kontroll +Name[eu]=Sinadura/Egiaztapena +Name[fa]=امضا/وارسی +Name[fi]=Allekirjoittaminen/tarkistaminen +Name[fr]=Signature/Vérification +Name[fy]=Undertekening/Ferifiearje +Name[gl]=Asinando/Verificando +Name[he]=חתימה/ווידוא +Name[hu]=Aláírás/ellenőrzés +Name[is]=Undirrita/staðfesta +Name[it]=Firma/verifica +Name[ja]=署名/検証 +Name[ka]=ხელმოწერა/დამოწმება +Name[kk]=Қолтаңбалау/Тексеру +Name[km]=ចុះហត្ថលេខា/ផ្ទៀងផ្ទាត់ +Name[lt]=Pasirašoma/tikrinama +Name[mk]=Потпишување/верификација +Name[ms]=Menandatangan/Menentu sah +Name[nb]=Signering/Verifisering +Name[nds]=Ünnerschrieven / Pröven +Name[ne]=साइनिङ/रुजु गर्दैछ +Name[nl]=Ondertekening/Verifiëring +Name[nn]=Signering/stadfesting +Name[pl]=Podpisywanie/Weryfikacja podpisów +Name[pt]=Assinar/Verificar +Name[pt_BR]=Assinando/Verificando +Name[ru]=Подпись и проверка +Name[sk]=Podpis/overenie +Name[sl]=Podpisovanje/Preverjanje +Name[sr]=Потписивање-проверавање +Name[sr@Latn]=Potpisivanje-proveravanje +Name[sv]=Signera och verifiera +Name[ta]=கையெழுத்து இடுதல்/சரிபார்த்தல் +Name[tg]=Имзо ва тафтиш +Name[tr]=İmzalama/Doğruluma +Name[uk]=Підписування і перевірка +Name[zh_CN]=签名/校验 +Name[zh_TW]=簽署/檢查 +Comment=Protect your privacy by signing and verifying postings +Comment[af]=Beskerm you privaatheid deur pos stukke te onderteken en te bevestig +Comment[bg]=Защита на личната кореспонденция чрез подписване и проверка на съобщенията +Comment[bs]=Zaštitite vašu privatnost potpisivanjem i provjerom postinga +Comment[ca]=Protegiu la vostra privadesa signant i verificant els missatges +Comment[cs]=Chraňte své soukromí pomocí podepisování a ověřování podpisů +Comment[da]=Beskyt dine private oplysninger ved at underskrive og verificere indsendelser +Comment[de]=Schutz der Privatsphäre durch Signieren und Verifizieren von Beiträgen. +Comment[el]=Προστατεύστε το απόρρητό σας υπογράφοντας και επαληθεύοντας τα μηνύματα +Comment[es]=Proteja su privacidad firmando y verificando sus envíos +Comment[et]=Kaitseb sinu privaatsust postitusi signeerides ja konrollides +Comment[eu]=Babestu zure pribakortasuna argitarapenak sinatu eta egiaztatuz +Comment[fa]=حفظ محرمانگی خود با امضا و وارسی ارسالهای سریع +Comment[fi]=Suojele yksityisyyttäsi allekirjoittamalla ja varmentamalla posti +Comment[fr]=Protection de votre vie privée lors de la signature et vérification des envois +Comment[fy]=Beskermje jo privacy troch jo berjochten te ûndertekenjen en te ferifiearjen +Comment[gl]=Protexe a súa intimidade asinando e verificando as entradas +Comment[hu]=Az adatok védelme az üzenetek elektronikus aláírásával, titkosításával +Comment[is]=Verndaðu einkalífið þitt með því að undirrita og staðfesta sendingar +Comment[it]=Proteggi la tua privacy firmando e verificando i messaggi +Comment[ja]=投稿の署名と検証によりあなたのプライバシーを保護します +Comment[ka]=დაიცავით თქვენი პირადულობა ხელმოწერითა და განთავსებული სტატიების დამოწმებით +Comment[kk]=Жарияланғанды қолтаңбалап/тексеріп қорғану +Comment[km]=ការពារភាពឯកជនរបស់អ្នកដោយចុះហត្ថលេខា និងផ្ទៀងផ្ទាត់ការប្រកាស +Comment[lt]=Saugokite savo privatumą pasirašydami ir patikrindami skelbimus +Comment[ms]=Melindungi ruang privasi anda dengan menandatangan dan menentusah pengeposan +Comment[nb]=Beskytt privatlivet ditt ved å signere og verifisere oppslag +Comment[nds]=Dör dat Ünnerschrieven un Överpröven vun Narichten warrt Dien Privaatrebeet schuult. +Comment[ne]=तपाईँको नीजिपना साइन गरेर र पोष्टिङ रुजु गरेर सुरक्षा गर्नुहोस् +Comment[nl]=Bescherm uw privacy door uw berichten te ondertekenen en te verifiëren +Comment[nn]=Vern om privatlivet med signerte og stadfesta innlegg +Comment[pl]=Ochrona prywatności poprzez podpisywanie i weryfikację podpisów wiadomości +Comment[pt]=Proteja a sua privacidade assinando e verificando as mensagens +Comment[pt_BR]=Protege sua privacidade através da assinatura e verificação das mensagens +Comment[ru]=Защита информации посредством подписей и проверки сообщений +Comment[sk]=Chráňte svoje súkromie digitálnymi podpismi a overovaním príspevkov +Comment[sl]=Zaščitite vašo zasebnost s podpisovanjem ali preverjanjem člankov +Comment[sr]=Заштитите вашу приватност потписивајући и проверавајући поруке +Comment[sr@Latn]=Zaštitite vašu privatnost potpisivajući i proveravajući poruke +Comment[sv]=Skyddar din integritet genom att signera och verifiera meddelanden +Comment[tg]=Муҳофизати иттилоот ба воситаи имзо ва тафтиши ахборот +Comment[tr]=Gönderilen iletilerinizi imzalayarak ve doğrulayarak güvenirliliğinizi sağlayın +Comment[uk]=Захист конфіденційності за допомогою підписування і перевірки повідомлень +Comment[zh_CN]=通过对发表的文件进行签名和校验来保护您的隐私 +Comment[zh_TW]=以簽署或檢查文章來保護您的隱私 diff --git a/knode/knode_config_read_news.desktop b/knode/knode_config_read_news.desktop new file mode 100644 index 000000000..a4ad37183 --- /dev/null +++ b/knode/knode_config_read_news.desktop @@ -0,0 +1,66 @@ +[Desktop Entry] +Icon=mail_get +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=knode +X-KDE-FactoryName=knode_config_read_news +X-KDE-HasReadOnlyMode=false +X-KDE-ParentApp=knode +X-KDE-ParentComponents=knode,kontact_knodeplugin +X-KDE-CfgDlgHierarchy=KNode + +Name=Reading News +Name[af]=Lees Nuus +Name[ar]=قراءة الأخبار +Name[be]=Чытанне навін +Name[bg]=Четене на новини +Name[bs]=Čitanje Useneta +Name[ca]=Lectura de notícies +Name[cs]=Čtení novinek +Name[da]=Læser nyheder +Name[de]=Artikel lesen +Name[el]=Ανάγνωση νέων +Name[es]=Leer noticias +Name[et]=Uudiste lugemine +Name[eu]=Berrien irakurketa +Name[fa]=خواندن اخبار +Name[fi]=Uutisryhmien lukeminen +Name[fr]=Lecture de nouvelles +Name[fy]=Nijs lêze +Name[gl]=Ler Noticias +Name[he]=קריאת חדשות +Name[hu]=Hírek olvasása +Name[is]=Lestur frétta +Name[it]=Lettura News +Name[ja]=ニュースを読む +Name[ka]=სიახლეების კითხვა +Name[kk]=Жаңалықтарды оқу +Name[km]=អានព័ត៌មាន +Name[lt]=Naujienų skaitymas +Name[mk]=Читање вести +Name[ms]=Membaca Berita +Name[nb]=Lese Njus +Name[nds]=Narichten lesen +Name[ne]=समाचार वाचन गर्दैछ +Name[nl]=Nieuws lezen +Name[nn]=Les innlegg +Name[pl]=Odczytywanie wiadomości +Name[pt]=Leitura de Notícias +Name[pt_BR]=Lendo Notícias +Name[ru]=Чтение новостей +Name[sk]=Čítanie diskusných skupín +Name[sl]=Branje novic +Name[sr]=Читање новости +Name[sr@Latn]=Čitanje novosti +Name[sv]=Läsa inlägg +Name[ta]=செய்திகளைப் படித்தல் +Name[tg]=Хондани ахборот +Name[tr]=Haber Okuma +Name[uk]=Читання новин +Name[uz]=Yangiliklarni oʻqish +Name[uz@cyrillic]=Янгиликларни ўқиш +Name[zh_CN]=阅读新闻 +Name[zh_TW]=閱讀新聞 + diff --git a/knode/knode_options.h b/knode/knode_options.h new file mode 100644 index 000000000..03c51d4d0 --- /dev/null +++ b/knode/knode_options.h @@ -0,0 +1,30 @@ +/* + main.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNODE_OPTIONS_H +#define KNODE_OPTIONS_H + +#include <kcmdlineargs.h> +#include <klocale.h> + +static KCmdLineOptions knode_options[] = +{ + { "+[url]", I18N_NOOP("A 'news://server/group' URL"), 0 }, + KCmdLineLastOption +}; + +#endif /* KNODE_OPTIONS_H */ + diff --git a/knode/knode_part.cpp b/knode/knode_part.cpp new file mode 100644 index 000000000..b1280ced5 --- /dev/null +++ b/knode/knode_part.cpp @@ -0,0 +1,125 @@ +/* + This file is part of KNode. + Copyright (c) 2003 Laurent Montel <montel@kde.org>, + Based on the work of Cornelius Schumacher <schumacher@kde.org> + + 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 "knode_part.h" +#include "knglobals.h" +#include "knmainwidget.h" +#include "aboutdata.h" +#include "kncollectionview.h" +#include "knwidgets.h" + +#include "sidebarextension.h" + +#include <kapplication.h> +#include <kparts/genericfactory.h> +#include <kparts/statusbarextension.h> +#include <knotifyclient.h> +#include <dcopclient.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <kstatusbar.h> +#include <krsqueezedtextlabel.h> + +#include <qlayout.h> + + +typedef KParts::GenericFactory< KNodePart > KNodeFactory; +K_EXPORT_COMPONENT_FACTORY( libknodepart, KNodeFactory ) + +KNodePart::KNodePart(QWidget *parentWidget, const char *widgetName, + QObject *parent, const char *name, const QStringList &) + : KParts::ReadOnlyPart(parent, name), + mParentWidget( parentWidget ) +{ + kdDebug(5003) << "KNodePart()" << endl; + kdDebug(5003) << " InstanceName: " << kapp->instanceName() << endl; + + setInstance( KNodeFactory::instance() ); + + kdDebug(5003) << "KNodePart()..." << endl; + kdDebug(5003) << " InstanceName: " << kapp->instanceName() << endl; + + KGlobal::locale()->insertCatalogue("libkdepim"); + KGlobal::locale()->insertCatalogue("libkpgp"); + kapp->dcopClient()->suspend(); // Don't handle DCOP requests yet + KGlobal::iconLoader()->addAppDir("knode"); + knGlobals.instance = KNodeFactory::instance(); + + // create a canvas to insert our widget + QWidget *canvas = new QWidget(parentWidget, widgetName); + canvas->setFocusPolicy(QWidget::ClickFocus); + setWidget(canvas); + + mainWidget = new KNMainWidget( this, false, canvas, "knode_widget" ); + QVBoxLayout *topLayout = new QVBoxLayout(canvas); + topLayout->addWidget(mainWidget); + mainWidget->setFocusPolicy(QWidget::ClickFocus); + + kapp->dcopClient()->resume(); // Ok. We are ready for DCOP requests. + + new KParts::SideBarExtension( mainWidget->collectionView(), + this, + "KNodeSidebar" ); + + KParts::StatusBarExtension* statusBar = new KParts::StatusBarExtension(this); + statusBar->addStatusBarItem(mainWidget->statusBarLabelFilter(), 10, false); + statusBar->addStatusBarItem(mainWidget->statusBarLabelGroup(), 15, false); + + setXMLFile( "knodeui.rc" ); +} + +KNodePart::~KNodePart() +{ + mainWidget->prepareShutdown(); +} + +KAboutData *KNodePart::createAboutData() +{ + return new KNode::AboutData(); +} + +bool KNodePart::openFile() +{ + kdDebug(5003) << "KNodePart:openFile()" << endl; + + mainWidget->show(); + return true; +} + +void KNodePart::guiActivateEvent(KParts::GUIActivateEvent *e) +{ + kdDebug(5003) << "KNodePart::guiActivateEvent" << endl; + KParts::ReadOnlyPart::guiActivateEvent(e); +} + + +QWidget* KNodePart::parentWidget() const +{ + return mParentWidget; +} + + + +#include "knode_part.moc" + diff --git a/knode/knode_part.h b/knode/knode_part.h new file mode 100644 index 000000000..951befc5f --- /dev/null +++ b/knode/knode_part.h @@ -0,0 +1,64 @@ +/* + This file is part of KMail. + Copyright (c) 2003 Laurent Montel <montel@kde.org>, + Based on the work of Cornelius Schumacher <schumacher@kde.org> + + 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. +*/ +#ifndef KNODE_PART_H +#define KNODE_PART_H + + +#include <kdeversion.h> +#include <kparts/browserextension.h> +#include <kparts/factory.h> +#include <kparts/event.h> +#include <kparts/part.h> + +#include <qwidget.h> + +class KInstance; +class KAboutData; + +class ActionManager; +class KNMainWidget; + +class KNodePart: public KParts::ReadOnlyPart +{ + Q_OBJECT + public: + KNodePart(QWidget *parentWidget, const char *widgetName, + QObject *parent, const char *name, const QStringList &); + virtual ~KNodePart(); + + QWidget* parentWidget() const; + + static KAboutData *createAboutData(); + + protected: + virtual bool openFile(); + virtual void guiActivateEvent(KParts::GUIActivateEvent *e); + + private: + ActionManager *mActionManager; + QWidget *mParentWidget; + KNMainWidget *mainWidget; +}; + +#endif diff --git a/knode/knodecomposeriface.h b/knode/knodecomposeriface.h new file mode 100644 index 000000000..d311541be --- /dev/null +++ b/knode/knodecomposeriface.h @@ -0,0 +1,14 @@ +#ifndef KNODECOMPOSERIFACE_H +#define KNODECOMPOSERIFACE_H + +#include <dcopobject.h> +#include <kurl.h> + +class KNodeComposerIface : virtual public DCOPObject +{ + K_DCOP + k_dcop: + virtual void initData(const QString &text) = 0; +}; + +#endif diff --git a/knode/knodeiface.h b/knode/knodeiface.h new file mode 100644 index 000000000..f92967bbb --- /dev/null +++ b/knode/knodeiface.h @@ -0,0 +1,152 @@ +/* + knodeiface.h + + KNode, the KDE newsreader + Copyright (c) 1999-2003 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ +#ifndef KNODEIFACE_H +#define KNODEIFACE_H + + +// no forward declarations - dcopidl2cpp won't work +#include <dcopobject.h> +#include <dcopref.h> +#include <kurl.h> +#include <qstringlist.h> + + +class KNodeIface : virtual public DCOPObject +{ + K_DCOP + +k_dcop: + /** + * Move to the next article + */ + virtual void nextArticle() = 0; + + /** + * Move to the previous article + */ + virtual void previousArticle() = 0; + + /** + * Move to the next unread article + */ + virtual void nextUnreadArticle() = 0; + + /** + * Move to the next unread thread + */ + virtual void nextUnreadThread() = 0; + + /** + * Move to the next group + */ + virtual void nextGroup() = 0; + + /** + * Move to the previous group + */ + virtual void previousGroup() = 0; + + /* Group options */ + + /** + * Open the editor to post a new article in the selected group + */ + virtual void postArticle() = 0; + + /** + * Fetch the new headers in the selected groups + */ + virtual void fetchHeadersInCurrentGroup() = 0; + + /** + * Expire the articles in the current group + */ + virtual void expireArticlesInCurrentGroup() = 0; + + /** + * Mark all the articles in the current group as read + */ + virtual void markAllAsRead() = 0; + + /** + * Mark all the articles in the current group as unread + */ + virtual void markAllAsUnread() = 0; + + /* Header view */ + + /** + * Mark the current article as read + */ + virtual void markAsRead() = 0; + + /** + * Mark the current article as unread + */ + virtual void markAsUnread() = 0; + + /** + * Mark the current thread as read + */ + virtual void markThreadAsRead() = 0; + + /** + * Mark the current thread as unread + */ + virtual void markThreadAsUnread() = 0; + + /* Articles */ + + /** + * Send the pending articles + */ + virtual void sendPendingMessages() = 0; + + /** + * Delete the current article + */ + virtual void deleteArticle() = 0; + + /** + * Send the current article + */ + virtual void sendNow() = 0; + + /** + * Edit the current article + */ + virtual void editArticle() = 0; + + /** + * Fetch all the new article headers + */ + virtual void fetchHeaders() = 0; + + /** + * Expire articles in all groups + */ + virtual void expireArticles() = 0; + + /* Kontact integration */ + /** + * Process command-line options + * Return true if args were handled, false if there were no args. + */ + virtual bool handleCommandLine() = 0; + +}; + +#endif diff --git a/knode/knodeui.rc b/knode/knodeui.rc new file mode 100644 index 000000000..4c020d454 --- /dev/null +++ b/knode/knodeui.rc @@ -0,0 +1,256 @@ +<!DOCTYPE kpartgui> +<kpartgui name="KNode" version="42"> + +<MenuBar> + <Menu noMerge="1" name="file"><text>&File</text> + <Action name="file_save"/> + <Action name="file_print"/> + <Separator/> + <Action name="net_sendPending"/> + <Action name="net_stop"/> + <Separator/> + <Action name="file_quit"/> + + </Menu> + <Menu noMerge="1" name="edit"><text>&Edit</text> + <Action name="edit_copy"/> + <Action name="edit_select_all"/> + <Separator/> + <Action name="article_search"/> + <Action name="find_in_article"/> + <Separator/> + <Action name="fetch_article_with_id"/> + </Menu> + <Menu noMerge="1" name="view"><text>&View</text> + <Action name="view_headers"/> + <Action name="view_attachments"/> + <Separator/> + <Action name="view_showThreads"/> + <Action name="view_ExpandAll"/> + <Action name="view_CollapseAll"/> + <Action name="thread_toggle"/> + <Separator/> + <Action name="view_Filter"/> + <Action name="view_Sort"/> + <Action name="view_Refresh"/> + <Separator/> + <Action name="view_rot13"/> + <Separator/> + <Action name="article_viewSource"/> + <Separator/> + <Action name="view_useFixedFont"/> + <Action name="view_fancyFormating"/> + <Action name="set_charset"/> + </Menu> + <Menu noMerge="1" name="go_document"><text>&Go</text> + <Action name="go_prevArticle"/> + <Action name="go_nextArticle"/> + <Separator/> + <Action name="go_nextUnreadArticle"/> + <Action name="go_nextUnreadThread"/> + <Separator/> + <Action name="go_prevGroup"/> + <Action name="go_nextGroup"/> + </Menu> + <Menu name="account"><text>A&ccount</text> + <Action name="account_dnlHeaders"/> + <Action name="account_dnlAllHeaders"/> + <Action name="account_subscribe"/> + <Action name="account_expire_all"/> + <Separator/> + <Action name="account_properties"/> + <Action name="account_delete"/> + </Menu> + <Menu name="group"><text>G&roup</text> + <Action name="group_dnlHeaders"/> + <Action name="group_expire"/> + <Separator/> + <Action name="group_reorg"/> + <Separator/> + <Action name="group_allRead"/> + <Action name="group_allUnread"/> + <Action name="group_unread"/> + <Separator/> + <Action name="group_properties"/> + <Action name="group_unsubscribe"/> + </Menu> + <Menu name="folder"><text>Fol&der</text> + <Action name="folder_new"/> + <Action name="folder_newChild"/> + <Action name="folder_rename"/> + <Separator/> + <Action name="folder_MboxImport"/> + <Action name="folder_MboxExport"/> + <Separator/> + <Action name="folder_compact"/> + <Action name="folder_compact_all"/> + <Action name="folder_empty"/> + <Separator/> + <Action name="folder_delete"/> + </Menu> + <Menu name="article"><text>&Article</text> + <Action name="article_postNew"/> + <Action name="article_postReply"/> + <Action name="article_mailReply"/> + <Action name="article_forward"/> + <Separator/> + <Action name="article_read"/> + <Action name="article_unread"/> + <Action name="thread_read"/> + <Action name="thread_unread"/> + <Separator/> + <Action name="article_cancel"/> + <Action name="article_supersede"/> + <Separator/> + <Action name="article_ownWindow"/> + <Separator/> + <Action name="article_edit"/> + <Action name="article_delete"/> + <Action name="article_sendNow"/> + </Menu> + <Menu name="scoring"><text>Sc&oring</text> + <Action name="scoreedit"/> + <Action name="rescore"/> + <Separator/> + <Action name="scorelower"/> + <Action name="scoreraise"/> + <Separator/> + <Action name="thread_watch"/> + <Action name="thread_ignore"/> + </Menu> + <Menu name="settings"><text>&Settings</text> + <Action name="settings_show_groupView" append="show_merge"/> + <Action name="settings_show_headerView" append="show_merge"/> + <Action name="settings_show_articleViewer" append="show_merge"/> + <Action name="settings_show_quickSearch" append="show_merge"/> + <Separator/> + <Action name="knode_configure_knode" append="configure_merge" group="settings_configure"/> + </Menu> +</MenuBar> + +<ToolBar noMerge="1" name="mainToolBar"><text>Main Toolbar</text> + <Action name="article_postNew"/> + <Action name="file_save"/> + <Action name="file_print"/> + <Separator/> + <Action name="account_dnlHeaders"/> + <Action name="net_stop"/> + <Separator/> + <Action name="article_postReply"/> + <Action name="article_mailReply"/> + <Action name="article_forward"/> + <Separator/> + <Action name="go_nextUnreadArticle"/> + <Action name="go_nextUnreadThread"/> + <Separator/> + <Action name="view_Filter"/> + <Action name="article_search"/> + <Action name="view_Refresh"/> + <Separator/> + <Action name="account_subscribe"/> +</ToolBar> + +<Menu name="account_popup"> + <Action name="account_dnlHeaders"/> + <Action name="account_dnlAllHeaders"/> + <Action name="account_subscribe"/> + <Action name="account_expire_all"/> + <Separator/> + <Action name="account_rename"/> + <Action name="account_properties"/> + <Action name="account_delete"/> +</Menu> + +<Menu name="group_popup"> + <Action name="group_dnlHeaders"/> + <Action name="group_expire"/> + <Action name="group_allRead"/> + <Action name="group_allUnread"/> + <Action name="group_unread"/> + <Separator/> + <Action name="group_rename"/> + <Action name="group_properties"/> + <Action name="group_unsubscribe"/> +</Menu> + +<Menu name="root_folder_popup"> + <Action name="folder_newChild"/> + <Action name="folder_compact_all"/> +</Menu> + +<Menu name="folder_popup"> + <Action name="folder_newChild"/> + <Action name="folder_rename"/> + <Separator/> + <Action name="folder_MboxImport"/> + <Action name="folder_MboxExport"/> + <Separator/> + <Action name="folder_compact"/> + <Action name="folder_empty"/> + <Separator/> + <Action name="folder_delete"/> +</Menu> + +<Menu name="remote_popup"> + <Action name="article_postReply"/> + <Action name="article_mailReply"/> + <Action name="article_forward"/> + <Separator/> + <Action name="article_cancel"/> + <Action name="article_supersede"/> + <Separator/> + <Action name="article_read"/> + <Action name="article_unread"/> + <Separator/> + <Action name="thread_read"/> + <Action name="thread_unread"/> + <Separator/> + <Action name="thread_watch"/> + <Action name="thread_ignore"/> + <Separator/> + <Action name="article_ownWindow"/> +</Menu> + +<Menu name="local_popup"> + <Action name="article_edit"/> + <Action name="article_delete"/> + <Action name="article_sendNow"/> + <Separator/> + <Action name="article_cancel"/> + <Action name="article_supersede"/> +</Menu> + +<Menu name="body_popup"> + <Action name="file_save_as"/> + <Action name="file_print"/> + <Separator/> + <Action name="edit_copy"/> + <Action name="edit_select_all"/> + <Separator/> + <Action name="article_viewSource"/> + <Separator/> + <Action name="view_rot13"/> + <Separator/> + <Action name="view_useFixedFont"/> + <Action name="view_fancyFormating"/> + <Action name="set_charset"/> +</Menu> + +<Menu name="url_popup"> + <Action name="open_url"/> + <Action name="copy_url"/> + <Action name="add_bookmark"/> +</Menu> + +<Menu name="mailto_popup"> + <Action name="add_addr_book"/> + <Action name="openin_addr_book"/> + <Action name="copy_url"/> +</Menu> + +<Menu name="attachment_popup"> + <Action name="open_attachment"/> + <Action name="save_attachment"/> +</Menu> + +</kpartgui> diff --git a/knode/knprotocolclient.cpp b/knode/knprotocolclient.cpp new file mode 100644 index 000000000..1d992d8a3 --- /dev/null +++ b/knode/knprotocolclient.cpp @@ -0,0 +1,634 @@ +/* + knprotocolclient.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdio.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> + +#include <klocale.h> +#include <kextsock.h> +#include <ksocks.h> + +#include "knjobdata.h" +#include "knprotocolclient.h" + + +KNProtocolClient::KNProtocolClient(int NfdPipeIn, int NfdPipeOut) : + job( 0 ), + inputSize( 10000 ), + fdPipeIn( NfdPipeIn ), + fdPipeOut( NfdPipeOut ), + tcpSocket( -1 ), + mTerminate( false ) +{ + input = new char[inputSize]; +} + + +KNProtocolClient::~KNProtocolClient() +{ + if (isConnected()) + closeConnection(); + delete [] input; +} + + +void KNProtocolClient::run() +{ + if (0!=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL)) + qWarning("pthread_setcancelstate failed!"); + if (0!= pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL)) + qWarning("pthread_setcanceltype failed!"); + + signal(SIGPIPE,SIG_IGN); // ignore sigpipe + waitForWork(); +} + + +void KNProtocolClient::insertJob(KNJobData *newJob) +{ + job = newJob; +} + + +void KNProtocolClient::removeJob() +{ + job = 0L; +} + + +void KNProtocolClient::updatePercentage(int percent) +{ + byteCountMode=false; + progressValue = percent*10; + sendSignal(TSprogressUpdate); +} + + +// main loop, maintains connection and waits for next job +void KNProtocolClient::waitForWork() +{ + fd_set fdsR,fdsE; + timeval tv; + int selectRet; + + while (true) { + if (isConnected()) { // we are connected, hold the connection for xx secs + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + FD_SET(tcpSocket, &fdsR); + FD_ZERO(&fdsE); + FD_SET(tcpSocket, &fdsE); + tv.tv_sec = account.hold(); + tv.tv_usec = 0; + selectRet = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv); + if ( mTerminate ) { + clearPipe(); + closeConnection(); + return; + } + // In addition to the timeout, this will also happen + // if select() returns early because of a signal + if (selectRet == 0) { +#ifndef NDEBUG + qDebug("knode: KNProtocolClient::waitForWork(): hold time elapsed, closing connection."); +#endif + closeConnection(); // nothing happend... + } else { + if (((selectRet > 0)&&(!FD_ISSET(fdPipeIn,&fdsR)))||(selectRet == -1)) { +#ifndef NDEBUG + qDebug("knode: KNProtocolClient::waitForWork(): connection broken, closing it"); +#endif + closeSocket(); + } + } + } + + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + } while (select(FD_SETSIZE, &fdsR, NULL, NULL, NULL) <= 0); // don't get tricked by signals + + clearPipe(); // remove start signal + + if (mTerminate) + return; + + timer.start(); + + sendSignal(TSjobStarted); + if (job) { + // qDebug("knode: KNProtocolClient::waitForWork(): got job"); + + if (job->net()&&!(account == *job->account())) { // server changed + account = *job->account(); + if (isConnected()) + closeConnection(); + } + + input[0] = 0; //terminate string + thisLine = input; + nextLine = input; + inputEnd = input; + progressValue = 10; + predictedLines = -1; + doneLines = 0; + byteCount = 0; + byteCountMode = true; + + if (!job->net()) // job needs no net access + processJob(); + else { + if (!isConnected()) + openConnection(); + + if (isConnected()) // connection is ready + processJob(); + } + errorPrefix = QString::null; + + clearPipe(); + } + sendSignal(TSworkDone); // emit stopped signal + } +} + + +void KNProtocolClient::processJob() +{} + + +// connect, handshake and authorization +bool KNProtocolClient::openConnection() +{ + sendSignal(TSconnect); + +#ifndef NDEBUG + qDebug("knode: KNProtocolClient::openConnection(): opening connection"); +#endif + + if (account.server().isEmpty()) { + job->setErrorString(i18n("Unable to resolve hostname")); + return false; + } + + KExtendedSocket ks; + + ks.setAddress(account.server(), account.port()); + ks.setTimeout(account.timeout()); + if (ks.connect() < 0) { + if (ks.status() == IO_LookupError) { + job->setErrorString(i18n("Unable to resolve hostname")); + } else if (ks.status() == IO_ConnectError) { + job->setErrorString(i18n("Unable to connect:\n%1").arg(KExtendedSocket::strError(ks.status(), errno))); + } else if (ks.status() == IO_TimeOutError) + job->setErrorString(i18n("A delay occurred which exceeded the\ncurrent timeout limit.")); + else + job->setErrorString(i18n("Unable to connect:\n%1").arg(KExtendedSocket::strError(ks.status(), errno))); + + closeSocket(); + return false; + } + + tcpSocket = ks.fd(); + ks.release(); + + return true; +} + + +// sends QUIT-command and closes the socket +void KNProtocolClient::closeConnection() +{ + fd_set fdsW; + timeval tv; + +#ifndef NDEBUG + qDebug("knode: KNProtocolClient::closeConnection(): closing connection"); +#endif + + FD_ZERO(&fdsW); + FD_SET(tcpSocket, &fdsW); + tv.tv_sec = 0; + tv.tv_usec = 0; + int ret = KSocks::self()->select(FD_SETSIZE, NULL, &fdsW, NULL, &tv); + + if (ret > 0) { // we can write... + QCString cmd = "QUIT\r\n"; + int todo = cmd.length(); + KSocks::self()->write(tcpSocket,&cmd.data()[0],todo); + } + closeSocket(); +} + + +// sends a command (one line), return code is written to rep +bool KNProtocolClient::sendCommand(const QCString &cmd, int &rep) +{ + if (!sendStr(cmd + "\r\n")) + return false; + if (!getNextResponse(rep)) + return false; + return true; +} + + +// checks return code and calls handleErrors() if necessary +bool KNProtocolClient::sendCommandWCheck(const QCString &cmd, int rep) +{ + int code; + + if (!sendCommand(cmd,code)) + return false; + if (code!=rep) { + handleErrors(); + return false; + } + return true; +} + + +// sends a message (multiple lines) +bool KNProtocolClient::sendMsg(const QCString &msg) +{ + const char *line = msg.data(); + const char *end; + QCString buffer; + size_t length; + char inter[10000]; + + progressValue = 100; + predictedLines = msg.length()/80; // rule of thumb + + while ((end = ::strstr(line,"\r\n"))) { + if (line[0]=='.') // expand one period to double period... + buffer.append("."); + length = end-line+2; + if ((buffer.length()>1)&&((buffer.length()+length)>1024)) { // artifical limit, because I don't want to generate too large blocks + if (!sendStr(buffer)) + return false; + buffer = ""; + } + if (length > 9500) { + job->setErrorString(i18n("Message size exceeded the size of the internal buffer.")); + closeSocket(); + return false; + } + memcpy(inter,line,length); + inter[length]=0; // terminate string + buffer += inter; + line = end+2; + doneLines++; + } + buffer += ".\r\n"; + if (!sendStr(buffer)) + return false; + + return true; +} + + +// reads next complete line of input +bool KNProtocolClient::getNextLine() +{ + thisLine = nextLine; + nextLine = strstr(thisLine,"\r\n"); + if (nextLine) { // there is another full line in the inputbuffer + nextLine[0] = 0; // terminate string + nextLine[1] = 0; + nextLine+=2; + return true; + } + unsigned int div = inputEnd-thisLine+1; // hmmm, I need to fetch more input from the server... + memmove(input,thisLine,div); // save last, incomplete line + thisLine = input; + inputEnd = input+div-1; + do { + div = inputEnd-thisLine+1; + if ((div) > inputSize-100) { + inputSize += 10000; + char *newInput = new char[inputSize]; + memmove(newInput,input,div); + delete [] input; + input = newInput; + thisLine = input; + inputEnd = input+div-1; +#ifndef NDEBUG + qDebug("knode: KNProtocolClient::getNextLine(): input buffer enlarged"); +#endif + } + if (!waitForRead()) + return false; + + int received; + do { + received = KSocks::self()->read(tcpSocket, inputEnd, inputSize-(inputEnd-input)-1); + } while ((received<0)&&(errno==EINTR)); // don't get tricked by signals + + if (received <= 0) { + job->setErrorString(i18n("The connection is broken.")); + closeSocket(); + return false; + } + + // remove null characters that some stupid servers return... + for (int i=0; i<received; i++) + if (inputEnd[i] == 0) { + memmove(inputEnd+i,inputEnd+i+1,received-i-1); + received--; + i--; + } + + inputEnd += received; + inputEnd[0] = 0; // terminate *char + + byteCount += received; + + } while (!(nextLine = strstr(thisLine,"\r\n"))); + + if (timer.elapsed()>50) { // reduce framerate to 20 f/s + timer.start(); + if (predictedLines > 0) + progressValue = 100 + (doneLines*900/predictedLines); + sendSignal(TSprogressUpdate); + } + + nextLine[0] = 0; // terminate string + nextLine[1] = 0; + nextLine+=2; + return true; +} + + +// receives a message (multiple lines) +bool KNProtocolClient::getMsg(QStrList &msg) +{ + char *line; + + while (getNextLine()) { + line = getCurrentLine(); + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + return true; // message complete + } + msg.append(line); + doneLines++; + } + + return false; // getNextLine() failed +} + + +// reads next line and returns the response code +bool KNProtocolClient::getNextResponse(int &code) +{ + if (!getNextLine()) + return false; + code = -1; + code = atoi(thisLine); + return true; +} + + +// checks return code and calls handleErrors() if necessary +bool KNProtocolClient::checkNextResponse(int code) +{ + if (!getNextLine()) + return false; + if (atoi(thisLine)!=code) { + handleErrors(); + return false; + } + return true; +} + + + +// interprets error code, generates error message and closes the connection +void KNProtocolClient::handleErrors() +{ + if (errorPrefix.isEmpty()) + job->setErrorString(i18n("An error occurred:\n%1").arg(thisLine)); + else + job->setErrorString(errorPrefix + thisLine); + + closeConnection(); +} + + +void KNProtocolClient::sendSignal(threadSignal s) +{ + int signal=(int)s; + // qDebug("knode: KNProtcolClient::sendSignal() : sending signal to main thread"); + write(fdPipeOut, &signal, sizeof(int)); +} + + +// waits until socket is readable +bool KNProtocolClient::waitForRead() +{ + fd_set fdsR,fdsE; + timeval tv; + + int ret; + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + FD_SET(tcpSocket, &fdsR); + FD_ZERO(&fdsE); + FD_SET(tcpSocket, &fdsE); + FD_SET(fdPipeIn, &fdsE); + tv.tv_sec = account.timeout(); + tv.tv_usec = 0; + ret = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv); + } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals + + if (ret == -1) { // select failed + if (job) { + QString str = i18n("Communication error:\n"); + str += strerror(errno); + job->setErrorString(str); + } + closeSocket(); + return false; + } + if (ret == 0) { // Nothing happend, timeout + if (job) + job->setErrorString(i18n("A delay occurred which exceeded the\ncurrent timeout limit.")); + closeConnection(); + return false; + } + if (ret > 0) { + if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal +#ifndef NDEBUG + qDebug("knode: KNProtocolClient::waitForRead(): got stop signal"); +#endif + closeConnection(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc + if (job) + job->setErrorString(i18n("The connection is broken.")); + closeSocket(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsR)) // all ok + return true; + } + + if (job) + job->setErrorString(i18n("Communication error")); + closeSocket(); + return false; +} + + +// used by sendBuffer() & connect() +bool KNProtocolClient::waitForWrite() +{ + fd_set fdsR,fdsW,fdsE; + timeval tv; + + int ret; + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn, &fdsR); + FD_SET(tcpSocket, &fdsR); + FD_ZERO(&fdsW); + FD_SET(tcpSocket, &fdsW); + FD_ZERO(&fdsE); + FD_SET(tcpSocket, &fdsE); + FD_SET(fdPipeIn, &fdsE); + tv.tv_sec = account.timeout(); + tv.tv_usec = 0; + ret = KSocks::self()->select(FD_SETSIZE, &fdsR, &fdsW, &fdsE, &tv); + } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals + + + if (ret == -1) { // select failed + if (job) { + QString str = i18n("Communication error:\n"); + str += strerror(errno); + job->setErrorString(str); + } + closeSocket(); + return false; + } + if (ret == 0) { // nothing happend, timeout + if (job) + job->setErrorString(i18n("A delay occurred which exceeded the\ncurrent timeout limit.")); + closeConnection(); + return false; + } + if (ret > 0) { + if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal +#ifndef NDEBUG + qDebug("knode: KNProtocolClient::waitForWrite(): got stop signal"); +#endif + closeConnection(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsR)||FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc + if (job) + job->setErrorString(i18n("The connection is broken.")); + closeSocket(); + return false; + } + if (FD_ISSET(tcpSocket,&fdsW)) // all ok + return true; + } + + if (job) + job->setErrorString(i18n("Communication error")); + closeSocket(); + return false; +} + + +void KNProtocolClient::closeSocket() +{ + if (-1 != tcpSocket) { + close(tcpSocket); + tcpSocket = -1; + } +} + + +// sends str to the server +bool KNProtocolClient::sendStr(const QCString &str) +{ + int ret; + int todo = str.length(); + int done = 0; + + while (todo > 0) { + if (!waitForWrite()) + return false; + ret = KSocks::self()->write(tcpSocket,&str.data()[done],todo); + if (ret <= 0) { + if (job) { + QString str = i18n("Communication error:\n"); + str += strerror(errno); + job->setErrorString(str); + } + closeSocket(); + return false; + } else { + done += ret; + todo -= ret; + } + byteCount += ret; + } + if (timer.elapsed()>50) { // reduce framerate to 20 f/s + timer.start(); + if (predictedLines > 0) + progressValue = 100 + (doneLines/predictedLines)*900; + sendSignal(TSprogressUpdate); + } + return true; +} + + +// removes start/stop signal +void KNProtocolClient::clearPipe() +{ + fd_set fdsR; + timeval tv; + int selectRet; + char buf; + + tv.tv_sec = 0; + tv.tv_usec = 0; + do { + FD_ZERO(&fdsR); + FD_SET(fdPipeIn,&fdsR); + if (1==(selectRet=select(FD_SETSIZE,&fdsR,NULL,NULL,&tv))) + if ( read(fdPipeIn, &buf, 1 ) == -1 ) + ::perror( "clearPipe()" ); + } while (selectRet == 1); +} + diff --git a/knode/knprotocolclient.h b/knode/knprotocolclient.h new file mode 100644 index 000000000..e531a278b --- /dev/null +++ b/knode/knprotocolclient.h @@ -0,0 +1,122 @@ +/* + knprotocolclient.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNPROTOCOLCLIENT_H +#define KNPROTOCOLCLIENT_H + +#include <qdatetime.h> +#include <qthread.h> +#include <qcstring.h> + +#include <knserverinfo.h> + +class QStrList; +class KNJobData; +struct in_addr; + + +class KNProtocolClient : public QThread { + + public: + enum threadSignal { TSworkDone=0, TSjobStarted=1, TSconnect=2, TSloadGrouplist=3, + TSwriteGrouplist=4, TSdownloadGrouplist=5, TSdownloadNew=6, + TSsortNew=7, TSdownloadArticle=8, TSsendArticle=9, TSsendMail=10, + TSprogressUpdate=11, TSdownloadDesc=12, TSdownloadNewGroups=13 }; + + KNProtocolClient(int NfdPipeIn, int NfdPipeOut); + ~KNProtocolClient(); + + virtual void run(); + + void insertJob(KNJobData *newJob); + void removeJob(); + + void updatePercentage(int percent); + + int getProgressValue() const { return progressValue; }; + /** bytes in&out for the current connection */ + int getByteCount() const { return byteCount; }; + bool isInByteCountMode() const { return byteCountMode; }; + + void terminateClient() { mTerminate = true; } + protected: + + /** main loop, maintains connection and waits for next job */ + void waitForWork(); + /** examines the job and calls the suitable handling method */ + virtual void processJob(); + + /** connect, handshake and authorization */ + virtual bool openConnection(); + bool isConnected() { return (tcpSocket!=-1); }; + /** sends QUIT-command and closes the socket */ + virtual void closeConnection(); + + /** sends a command (one line), return code is written to rep */ + virtual bool sendCommand(const QCString &cmd, int &rep); + /** checks return code and calls handleErrors() if necessary */ + bool sendCommandWCheck(const QCString &cmd, int rep); + /** sends a message (multiple lines) */ + bool sendMsg(const QCString &msg); + + /** reads next complete line of input */ + bool getNextLine(); + /** returns pointer to current line of input */ + char* getCurrentLine() { return thisLine; }; + /** receives a message (multiple lines) */ + bool getMsg(QStrList &msg); + /** reads next line and returns the response code */ + bool getNextResponse(int &rep); + /** checks return code and calls handleErrors() if necessary */ + bool checkNextResponse(int rep); + + /** interprets error code, generates error message and closes the connection */ + virtual void handleErrors(); + + void sendSignal(threadSignal s); + + KNJobData *job; + KNServerInfo account; + /** handleErrors() adds this string to the error message */ + QString errorPrefix; + int progressValue, predictedLines, doneLines; + bool byteCountMode; + + private: + /** waits until socket is readable */ + bool waitForRead(); + /** waits until socket is writeable */ + bool waitForWrite(); + void closeSocket(); + /** sends str to the server */ + bool sendStr(const QCString &str); + /** removes start/stop signal */ + void clearPipe(); + + char *input; + char *thisLine, *nextLine, *inputEnd; + unsigned int inputSize; + /** IPC-Pipes to/from async thread */ + int fdPipeIn,fdPipeOut; + int tcpSocket; + /** bytes in&out for the current connection */ + int byteCount; + QTime timer; + bool mTerminate; + +}; + +#endif diff --git a/knode/knrangefilter.cpp b/knode/knrangefilter.cpp new file mode 100644 index 000000000..bfa02110f --- /dev/null +++ b/knode/knrangefilter.cpp @@ -0,0 +1,228 @@ +/* + knrangefilter.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlabel.h> +#include <qlayout.h> +#include <qcheckbox.h> +#include <qcombobox.h> + +#include <ksimpleconfig.h> +#include <knuminput.h> + +#include "knrangefilter.h" + + +bool KNRangeFilter::doFilter(int a) +{ + bool ret=true; + if(enabled) { + switch (op1) { + case gt: + case gtoeq: + if (op2 != dis) + ret=( matchesOp(val1,op1,a) && matchesOp(a,op2,val2) ); + else + ret = matchesOp(val1,op1,a); + break; + case eq: + case lt: + case ltoeq: + ret = matchesOp(val1,op1,a); + break; + default: + ret = false; + } + } + + return ret; +} + + + +bool KNRangeFilter::matchesOp(int v1, Op o, int v2) +{ + bool ret=false; + + switch(o) { + case eq: ret=(v1==v2); break; + case gt: ret=(v1<v2); break; + case gtoeq: ret=(v1<=v2); break; + case ltoeq: ret=(v1>=v2); break; + case lt: ret=(v1>v2); break; + default: ret=false; break; + }; + + return ret; +} + + + +void KNRangeFilter::load(KSimpleConfig *conf) +{ + enabled=conf->readBoolEntry("enabled", false); + val1=conf->readNumEntry("val1",0); + op1=(Op) conf->readNumEntry("op1",0); + val2=conf->readNumEntry("val2",0); + op2=(Op) conf->readNumEntry("op2",0); +} + + + +void KNRangeFilter::save(KSimpleConfig *conf) +{ + conf->writeEntry("enabled", enabled); + conf->writeEntry("val1", val1); + conf->writeEntry("op1", (int)op1); + conf->writeEntry("op2", (int)op2); + conf->writeEntry("val2", val2); +} + + + + +//===================================================================================== +//===================================================================================== + +KNRangeFilterWidget::KNRangeFilterWidget(const QString& value, int min, int max, QWidget* parent, const QString &unit) + : QGroupBox(value, parent) +{ + enabled=new QCheckBox(this); + + val1=new KIntSpinBox(min, max, 1, min, 10, this); + val1->setSuffix(unit); + val2=new KIntSpinBox(min, max, 1, min, 10, this); + val2->setSuffix(unit); + + op1=new QComboBox(this); + op1->insertItem("<"); + op1->insertItem("<="); + op1->insertItem("="); + op1->insertItem(">="); + op1->insertItem(">"); + op2=new QComboBox(this); + op2->insertItem(""); + op2->insertItem("<"); + op2->insertItem("<="); + + des=new QLabel(value, this); + des->setAlignment(AlignCenter); + + QGridLayout *topL=new QGridLayout(this, 2,6, 8,5 ); + + topL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + topL->addWidget(enabled,1,0, Qt::AlignHCenter); + topL->addColSpacing(0, 30); + topL->addWidget(val1,1,1); + topL->addWidget(op1,1,2); + topL->addWidget(des,1,3); + topL->addColSpacing(3, 45); + topL->addWidget(op2,1,4); + topL->addWidget(val2,1,5); + topL->setColStretch(1,1); + topL->setColStretch(5,1); + + connect(op1, SIGNAL(activated(int)), SLOT(slotOp1Changed(int))); + connect(op2, SIGNAL(activated(int)), SLOT(slotOp2Changed(int))); + connect(enabled, SIGNAL(toggled(bool)), SLOT(slotEnabled(bool))); + + slotEnabled(false); +} + + + +KNRangeFilterWidget::~KNRangeFilterWidget() +{ +} + + + +KNRangeFilter KNRangeFilterWidget::filter() +{ + KNRangeFilter r; + r.val1=val1->value(); + r.val2=val2->value(); + + r.op1=(KNRangeFilter::Op) op1->currentItem(); + if (op2->currentText().isEmpty()) + r.op2=KNRangeFilter::dis; + else if (op2->currentText()=="<") + r.op2=KNRangeFilter::gt; + else if (op2->currentText()=="<=") + r.op2=KNRangeFilter::gtoeq; + + r.enabled=enabled->isChecked(); + + return r; +} + + + +void KNRangeFilterWidget::setFilter(KNRangeFilter &f) +{ + val1->setValue(f.val1); + val2->setValue(f.val2); + + op1->setCurrentItem((int)f.op1); + if (f.op2 == KNRangeFilter::dis) + op2->setCurrentItem(0); + else if (f.op2 == KNRangeFilter::gt) + op2->setCurrentItem(1); + else if (f.op2 == KNRangeFilter::gtoeq) + op2->setCurrentItem(2); + + enabled->setChecked(f.enabled); +} + + + +void KNRangeFilterWidget::clear() +{ + val1->setValue(val1->minValue()); + val2->setValue(val2->minValue()); + enabled->setChecked(false); +} + + + +void KNRangeFilterWidget::slotOp1Changed(int id) +{ + bool state = (op1->isEnabled() && (id<2)); + op2->setEnabled(state); + des->setEnabled(state); + slotOp2Changed(op2->currentItem()); +} + + + +void KNRangeFilterWidget::slotOp2Changed(int id) +{ + val2->setEnabled(op1->isEnabled() && (op1->currentItem()<2) && (id>0)); +} + + + +void KNRangeFilterWidget::slotEnabled(bool e) +{ + op1->setEnabled(e); + val1->setEnabled(e); + des->setEnabled(e); + slotOp1Changed(op1->currentItem()); +} + +// ----------------------------------------------------------------------------- + +#include "knrangefilter.moc" + diff --git a/knode/knrangefilter.h b/knode/knrangefilter.h new file mode 100644 index 000000000..702a2ca55 --- /dev/null +++ b/knode/knrangefilter.h @@ -0,0 +1,88 @@ +/* + knrangefilter.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNRANGEFILTER_H +#define KNRANGEFILTER_H + +#include <qgroupbox.h> + +class QLabel; +class KIntSpinBox; +class QComboBox; +class QCheckBox; + +class KSimpleConfig; + + +class KNRangeFilter { + + friend class KNRangeFilterWidget; + + public: + KNRangeFilter() { op1=eq; op2=dis; val1=0; val2=0; enabled=false; } + ~KNRangeFilter() {} + + KNRangeFilter& operator=(const KNRangeFilter &nr) + { val1=nr.val1; val2=nr.val2; + op1=nr.op1; op2=nr.op2; + enabled=nr.enabled; + return (*this); } + + void load(KSimpleConfig *conf); + void save(KSimpleConfig *conf); + + bool doFilter(int a); + + protected: + enum Op { gt=0, gtoeq=1, eq=2, ltoeq=3, lt=4, dis=5 }; + bool matchesOp(int v1, Op o, int v2); + + int val1, val2; + Op op1, op2; + bool enabled; + +}; + + +//================================================================================== + + +class KNRangeFilterWidget : public QGroupBox { + + Q_OBJECT + + public: + KNRangeFilterWidget(const QString& value, int min, int max, QWidget* parent, const QString &unit=QString::null); + ~KNRangeFilterWidget(); + + KNRangeFilter filter(); + void setFilter(KNRangeFilter &f); + void clear(); + + protected: + QCheckBox *enabled; + QLabel *des; + KIntSpinBox *val1, *val2; + QComboBox *op1, *op2; + + protected slots: + void slotEnabled(bool e); + void slotOp1Changed(int id); + void slotOp2Changed(int id); + +}; + +#endif diff --git a/knode/knreaderui.rc b/knode/knreaderui.rc new file mode 100644 index 000000000..4609e5b8d --- /dev/null +++ b/knode/knreaderui.rc @@ -0,0 +1,81 @@ +<!DOCTYPE kpartgui> +<kpartgui name="KNReader" version="14"> + +<MenuBar> + <Menu noMerge="1" name="file"><text>&File</text> + <Action name="file_save"/> + <Action name="file_print"/> + <Separator/> + <Action name="file_close"/> + </Menu> + <Menu noMerge="1" name="edit"><text>&Edit</text> + <Action name="edit_copy"/> + <Action name="edit_select_all"/> + <Action name="find_in_article"/> + </Menu> + <Menu noMerge="1" name="view"><text>&View</text> + <Action name="view_headers"/> + <Action name="view_attachments"/> + <Separator/> + <Action name="view_rot13"/> + <Separator/> + <Action name="article_viewSource"/> + <Separator/> + <Action name="view_useFixedFont"/> + <Action name="view_fancyFormating"/> + <Action name="set_charset"/> + </Menu> + <Menu name="article"><text>&Article</text> + <Action name="article_postReply"/> + <Action name="article_mailReply"/> + <Action name="article_forward"/> + <Separator/> + <Action name="article_cancel"/> + <Action name="article_supersede"/> + </Menu> +</MenuBar> + +<ToolBar noMerge="1" name="mainToolBar"><text>Main Toolbar</text> + <Action name="file_save"/> + <Action name="file_print"/> + <Action name="edit_copy"/> + <Separator/> + <Action name="article_postReply"/> + <Action name="article_mailReply"/> + <Action name="article_forward"/> +</ToolBar> + +<Menu name="body_popup"> + <Action name="file_save_as"/> + <Action name="file_print"/> + <Separator/> + <Action name="edit_copy"/> + <Action name="edit_select_all"/> + <Separator/> + <Action name="article_viewSource"/> + <Separator/> + <Action name="view_rot13"/> + <Separator/> + <Action name="view_useFixedFont"/> + <Action name="view_fancyFormating"/> + <Action name="set_charset"/> +</Menu> + +<Menu name="url_popup"> + <Action name="open_url"/> + <Action name="copy_url"/> + <Action name="add_bookmark"/> +</Menu> + +<Menu name="mailto_popup"> + <Action name="add_addr_book"/> + <Action name="openin_addr_book"/> + <Action name="copy_url"/> +</Menu> + +<Menu name="attachment_popup"> + <Action name="open_attachment"/> + <Action name="save_attachment"/> +</Menu> + +</kpartgui> diff --git a/knode/knscoring.cpp b/knode/knscoring.cpp new file mode 100644 index 000000000..cad44fc81 --- /dev/null +++ b/knode/knscoring.cpp @@ -0,0 +1,142 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qstring.h> + +#include <kwin.h> +#include <kscoringeditor.h> + +#include "knscoring.h" +#include "knaccountmanager.h" +#include "kngroupmanager.h" +#include "utilities.h" +#include "knglobals.h" + +//---------------------------------------------------------------------------- +NotifyCollection* KNScorableArticle::notifyC = 0; + +KNScorableArticle::KNScorableArticle(KNRemoteArticle* a) + : ScorableArticle(), _a(a) +{ +} + + +KNScorableArticle::~KNScorableArticle() +{ +} + + +void KNScorableArticle::addScore(short s) +{ + _a->setScore(_a->score()+s); + _a->setChanged(true); +} + +void KNScorableArticle::changeColor(const QColor& c) +{ + _a->setColor(c); +} + +void KNScorableArticle::displayMessage(const QString& s) +{ + if (!_a->isNew()) return; + if (!notifyC) notifyC = new NotifyCollection(); + notifyC->addNote(*this,s); +} + +QString KNScorableArticle::from() const +{ + return _a->from()->asUnicodeString(); +} + + +QString KNScorableArticle::subject() const +{ + return _a->subject()->asUnicodeString(); +} + + +QString KNScorableArticle::getHeaderByType(const QString& s) const +{ + KMime::Headers::Base *h = _a->getHeaderByType(s.latin1()); + if (!h) return ""; + QString t = _a->getHeaderByType(s.latin1())->asUnicodeString(); + Q_ASSERT( !t.isEmpty() ); + return t; +} + + +void KNScorableArticle::markAsRead() +{ + _a->setRead(); +} + +//---------------------------------------------------------------------------- + +KNScorableGroup::KNScorableGroup() +{ +} + + +KNScorableGroup::~KNScorableGroup() +{ +} + +//---------------------------------------------------------------------------- + +KNScoringManager::KNScoringManager() : KScoringManager("knode") +{ +} + + +KNScoringManager::~KNScoringManager() +{ +} + + +QStringList KNScoringManager::getGroups() const +{ + KNAccountManager *am = knGlobals.accountManager(); + QStringList res; + QValueList<KNNntpAccount*>::Iterator it; + for ( it = am->begin(); it != am->end(); ++it ) { + QStringList groups; + knGlobals.groupManager()->getSubscribed( (*it), groups); + res += groups; + } + res.sort(); + return res; +} + + +QStringList KNScoringManager::getDefaultHeaders() const +{ + QStringList l = KScoringManager::getDefaultHeaders(); + l << "Lines"; + l << "References"; + return l; +} + + +void KNScoringManager::configure() +{ + KScoringEditor *dlg = KScoringEditor::createEditor(this, knGlobals.topWidget); + + if (dlg) { + dlg->show(); + KWin::activateWindow(dlg->winId()); + } +} + +#include "knscoring.moc" diff --git a/knode/knscoring.h b/knode/knscoring.h new file mode 100644 index 000000000..77b011e22 --- /dev/null +++ b/knode/knscoring.h @@ -0,0 +1,76 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNSCORING_H +#define KNSCORING_H + +#include <kscoring.h> + +class KDialogBase; +class KNRemoteArticle; +class KNGroup; + + +class KNScorableArticle : public ScorableArticle +{ +public: + KNScorableArticle(KNRemoteArticle*); + virtual ~KNScorableArticle(); + + virtual void addScore(short s); + virtual void changeColor(const QColor&); + virtual void displayMessage(const QString&); + virtual QString from() const; + virtual QString subject() const; + virtual QString getHeaderByType(const QString&) const; + virtual void markAsRead(); + + static NotifyCollection* notifyC; + +private: + KNRemoteArticle *_a; +}; + + +class KNScorableGroup : public ScorableGroup +{ +public: + KNScorableGroup(); + virtual ~KNScorableGroup(); +}; + + +// class KNScorableServer : public ScorableServer +// { +// public: +// virtual ~KNScorableServer(); +// }; + + +class KNScoringManager : public KScoringManager +{ + Q_OBJECT + +public: + KNScoringManager(); + virtual ~KNScoringManager(); + virtual QStringList getGroups() const; + virtual QStringList getDefaultHeaders() const; + + void configure(); + bool canColors()const { return true; } + bool canMarkAsRead() const { return true; } +}; + +#endif diff --git a/knode/knsearchdialog.cpp b/knode/knsearchdialog.cpp new file mode 100644 index 000000000..113bfe733 --- /dev/null +++ b/knode/knsearchdialog.cpp @@ -0,0 +1,122 @@ +/* + knsearchdialog.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qcheckbox.h> + +#include <klocale.h> +#include <kapplication.h> +#include <kiconloader.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "knfilterconfigwidget.h" +#include "knarticlefilter.h" +#include "utilities.h" +#include "knsearchdialog.h" + + +KNSearchDialog::KNSearchDialog(searchType /*t*/, QWidget *parent) + : QDialog(parent) +{ + setCaption(kapp->makeStdCaption( i18n("Search for Articles") )); + setIcon(SmallIcon("knode")); + QGroupBox *bg=new QGroupBox(this); + + startBtn=new QPushButton(SmallIcon("mail_find"),i18n("Sea&rch"), bg); + startBtn->setDefault(true); + newBtn=new QPushButton(SmallIcon("editclear"),i18n("C&lear"), bg); + closeBtn=new KPushButton(KStdGuiItem::close(), bg); + + completeThreads=new QCheckBox(i18n("Sho&w complete threads"),this); + fcw=new KNFilterConfigWidget(this); + fcw->reset(); + + QHBoxLayout *topL=new QHBoxLayout(this, 5); + QVBoxLayout *filterL=new QVBoxLayout(this, 0, 5); + QVBoxLayout *btnL=new QVBoxLayout(bg, 8, 5); + + filterL->addWidget(completeThreads); + filterL->addWidget(fcw,1); + + btnL->addWidget(startBtn); + btnL->addWidget(newBtn); + btnL->addStretch(1); + btnL->addWidget(closeBtn); + + topL->addLayout(filterL, 1); + topL->addWidget(bg); + + connect(startBtn, SIGNAL(clicked()), this, SLOT(slotStartClicked())); + connect(newBtn, SIGNAL(clicked()), this, SLOT(slotNewClicked())); + connect(closeBtn, SIGNAL(clicked()), this, SLOT(slotCloseClicked())); + + f_ilter=new KNArticleFilter(); + f_ilter->setLoaded(true); + f_ilter->setSearchFilter(true); + + setFixedHeight(sizeHint().height()); + KNHelper::restoreWindowSize("searchDlg", this, sizeHint()); + fcw->setStartFocus(); +} + + + +KNSearchDialog::~KNSearchDialog() +{ + delete f_ilter; + KNHelper::saveWindowSize("searchDlg", size()); +} + + +void KNSearchDialog::slotStartClicked() +{ + f_ilter->status=fcw->status->filter(); + f_ilter->score=fcw->score->filter(); + f_ilter->age=fcw->age->filter(); + f_ilter->lines=fcw->lines->filter(); + f_ilter->subject=fcw->subject->filter(); + f_ilter->from=fcw->from->filter(); + f_ilter->messageId=fcw->messageId->filter(); + f_ilter->references=fcw->references->filter(); + f_ilter->setApplyOn(completeThreads->isChecked()? 1:0); + emit doSearch(f_ilter); +} + + + +void KNSearchDialog::slotNewClicked() +{ + fcw->reset(); +} + + + +void KNSearchDialog::slotCloseClicked() +{ + emit dialogDone(); +} + + +void KNSearchDialog::closeEvent( QCloseEvent * ) +{ + emit dialogDone(); +} + +//-------------------------------- + +#include "knsearchdialog.moc" + diff --git a/knode/knsearchdialog.h b/knode/knsearchdialog.h new file mode 100644 index 000000000..7c68a49fd --- /dev/null +++ b/knode/knsearchdialog.h @@ -0,0 +1,58 @@ +/* + knsearchdialog.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNSEARCHDIALOG_H +#define KNSEARCHDIALOG_H + +#include <qdialog.h> + +class QPushButton; + +class KNFilterConfigWidget; +class KNArticleFilter; + + +class KNSearchDialog : public QDialog { + + Q_OBJECT + + public: + enum searchType { STfolderSearch, STgroupSearch }; + KNSearchDialog(searchType t=STgroupSearch, QWidget *parent=0); + ~KNSearchDialog(); + + KNArticleFilter* filter() const { return f_ilter; } + + protected: + void closeEvent( QCloseEvent* e ); + + KNFilterConfigWidget *fcw; + QPushButton *startBtn, *newBtn, *closeBtn; + QCheckBox *completeThreads; + KNArticleFilter *f_ilter; + + protected slots: + void slotStartClicked(); + void slotNewClicked(); + void slotCloseClicked(); + + signals: + void doSearch(KNArticleFilter *); + void dialogDone(); + +}; + +#endif diff --git a/knode/knserverinfo.cpp b/knode/knserverinfo.cpp new file mode 100644 index 000000000..b79735a2f --- /dev/null +++ b/knode/knserverinfo.cpp @@ -0,0 +1,188 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + + +#include <kmessagebox.h> +#include <kconfig.h> +#include <klocale.h> +#include <kdebug.h> +#include <kwallet.h> +using namespace KWallet; + +#include "knglobals.h" +#include "knserverinfo.h" +#include "knaccountmanager.h" +#include "utilities.h" + +#include <qwidget.h> + +KNServerInfo::KNServerInfo() : + t_ype(STnntp), i_d(-1), p_ort(119), h_old(300), + t_imeout(60), n_eedsLogon(false), p_assDirty(false), + mPassLoaded( false ), + mEncryption( None ) +{ +} + + + +KNServerInfo::~KNServerInfo() +{ +} + + + +void KNServerInfo::readConf(KConfig *conf) +{ + s_erver=conf->readEntry("server", "localhost"); + + if(t_ype==STnntp) + p_ort=conf->readNumEntry("port", 119); + else + p_ort=conf->readNumEntry("port", 25); + + h_old=conf->readNumEntry("holdTime", 300); + + if(h_old < 0) h_old=0; + + t_imeout=conf->readNumEntry("timeout", 60); + + if(t_imeout < 15) t_imeout=15; + + if(t_ype==STnntp) + i_d=conf->readNumEntry("id", -1); + + n_eedsLogon=conf->readBoolEntry("needsLogon",false); + u_ser=conf->readEntry("user"); + p_ass = KNHelper::decryptStr(conf->readEntry("pass")); + + // migration to KWallet + if (Wallet::isEnabled() && !p_ass.isEmpty()) { + conf->deleteEntry( "pass" ); + p_assDirty = true; + } + + // if the wallet is open, no need to delay the password loading + if (Wallet::isOpen( Wallet::NetworkWallet() )) + readPassword(); + + QString encStr = conf->readEntry( "encryption", "None" ); + if ( encStr.contains( "SSL", false ) ) + mEncryption = SSL; + else if ( encStr.contains( "TLS", false ) ) + mEncryption = TLS; + else + mEncryption = None; +} + + +void KNServerInfo::saveConf(KConfig *conf) +{ + conf->writeEntry("server", s_erver); + if ( p_ort == 0 ) p_ort = 119; + conf->writeEntry("port", p_ort); + conf->writeEntry("holdTime", h_old); + conf->writeEntry("timeout", t_imeout); + if (t_ype==STnntp) + conf->writeEntry("id", i_d); + + conf->writeEntry("needsLogon", n_eedsLogon); + conf->writeEntry("user", u_ser); + // open wallet for storing only if the user actually changed the password + if (n_eedsLogon && p_assDirty) { + Wallet *wallet = KNAccountManager::wallet(); + if (!wallet || wallet->writePassword(QString::number(i_d), p_ass)) { + if ( KMessageBox::warningYesNo( 0, + i18n("KWallet is not available. It is strongly recommended to use " + "KWallet for managing your passwords.\n" + "However, KNode can store the password in its configuration " + "file instead. The password is stored in an obfuscated format, " + "but should not be considered secure from decryption efforts " + "if access to the configuration file is obtained.\n" + "Do you want to store the password for server '%1' in the " + "configuration file?").arg( server() ), + i18n("KWallet Not Available"), + KGuiItem( i18n("Store Password") ), + KGuiItem( i18n("Do Not Store Password") ) ) + == KMessageBox::Yes ) { + conf->writeEntry( "pass", KNHelper::encryptStr( p_ass ) ); + } + } + p_assDirty = false; + } + + switch ( mEncryption ) { + case SSL: + conf->writeEntry( "encryption", "SSL" ); + break; + case TLS: + conf->writeEntry( "encryption", "TLS" ); + break; + default: + conf->writeEntry( "encryption", "None" ); + } +} + + + +bool KNServerInfo::operator==(const KNServerInfo &s) +{ + return ( (t_ype==s.t_ype) && + (s_erver==s.s_erver) && + (p_ort==s.p_ort) && + (h_old==s.h_old) && + (t_imeout==s.t_imeout) && + (n_eedsLogon==s.n_eedsLogon) && + (u_ser==s.u_ser) && + (p_ass==s.p_ass) && + (mEncryption == s.mEncryption) + ); +} + + +const QString &KNServerInfo::pass() +{ + // if we need to load the password, load all of them + if (n_eedsLogon && !mPassLoaded && p_ass.isEmpty() ) + knGlobals.accountManager()->loadPasswords(); + + return p_ass; +} + +void KNServerInfo::setPass(const QString &s) +{ + if (p_ass != s) { + p_ass = s; + p_assDirty = true; + } +} + + +void KNServerInfo::readPassword() +{ + // no need to load a password if the account doesn't require auth + if (!n_eedsLogon) + return; + mPassLoaded = true; + + // check wether there is a chance to find our password at all + if (Wallet::folderDoesNotExist(Wallet::NetworkWallet(), "knode") || + Wallet::keyDoesNotExist(Wallet::NetworkWallet(), "knode", QString::number(i_d))) + return; + + // finally try to open the wallet and read the password + KWallet::Wallet *wallet = KNAccountManager::wallet(); + if ( wallet ) + wallet->readPassword( QString::number(i_d), p_ass ); +} diff --git a/knode/knserverinfo.h b/knode/knserverinfo.h new file mode 100644 index 000000000..6f564d934 --- /dev/null +++ b/knode/knserverinfo.h @@ -0,0 +1,91 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNSERVERINFO_H +#define KNSERVERINFO_H + +#include <qstring.h> + +class KConfig; +namespace KWallet { + class Wallet; +} +using KWallet::Wallet; + +class KNServerInfo { + + public: + enum serverType { STnntp, STsmtp, STpop3 }; + enum Encryption { None, SSL, TLS }; + + KNServerInfo(); + ~KNServerInfo(); + + void readConf(KConfig *conf); + void saveConf(KConfig *conf); + + //get + serverType type()const { return t_ype; } + int id()const { return i_d; } + const QString& server() { return s_erver; } + const QString& user() { return u_ser; } + const QString& pass(); + int port() const { return p_ort; } + int hold() const { return h_old; } + int timeout() const { return t_imeout; } + bool needsLogon()const { return n_eedsLogon; } + bool isEmpty()const { return s_erver.isEmpty(); } + bool readyForLogin() const { return !n_eedsLogon || mPassLoaded; } + Encryption encryption() const { return mEncryption; } + + //set + void setType(serverType t) { t_ype=t; } + void setId(int i) { i_d=i; } + void setServer(const QString &s) { s_erver=s; } + void setUser(const QString &s) { u_ser=s; } + void setPass(const QString &s); + void setPort(int p) { p_ort=p; } + void setHold(int h) { h_old=h; } + void setTimeout(int t) { t_imeout=t; } + void setNeedsLogon(bool b) { n_eedsLogon=b; } + void setEncryption( Encryption enc ) { mEncryption = enc; } + + bool operator==(const KNServerInfo &s); + + /** Loads the password from KWallet, used for on-demand password loading */ + void readPassword(); + + protected: + serverType t_ype; + + QString s_erver, + u_ser, + p_ass; + + int i_d, + p_ort, + h_old, + t_imeout; + + bool n_eedsLogon, + p_assDirty; + /** Prevent loading the password multiple times since wallet operations + from the I/O thread don't work. */ + bool mPassLoaded; + /** Encyrption method */ + Encryption mEncryption; +}; + + +#endif diff --git a/knode/knsourceviewwindow.cpp b/knode/knsourceviewwindow.cpp new file mode 100644 index 000000000..ad0eb1cf5 --- /dev/null +++ b/knode/knsourceviewwindow.cpp @@ -0,0 +1,62 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qaccel.h> + +#include <kapplication.h> + +#include "knsourceviewwindow.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "utilities.h" + + +KNSourceViewWindow::KNSourceViewWindow( const QString &text ) + : KTextBrowser(0) +{ + setWFlags(WType_TopLevel | WDestructiveClose); + QAccel *accel = new QAccel( this, "browser close-accel" ); + accel->connectItem( accel->insertItem( Qt::Key_Escape ), this , SLOT( close() )); + KNConfig::Appearance *app=knGlobals.configManager()->appearance(); + + setTextFormat( PlainText ); + + setCaption(kapp->makeStdCaption(i18n("Article Source"))); + setPaper( QBrush(app->backgroundColor()) ); + setFont( app->articleFixedFont() ); + setColor( app->textColor() ); + setWordWrap( KTextBrowser::NoWrap ); + + setText( text ); + KNHelper::restoreWindowSize("sourceWindow", this, QSize(500,300)); + show(); +} + + +void KNSourceViewWindow::setPalette( const QPalette &pal ) +{ + QPalette p = pal; + p.setColor( QColorGroup::Text, knGlobals.configManager()->appearance()->textColor() ); + p.setColor( QColorGroup::Background, knGlobals.configManager()->appearance()->backgroundColor() ); + KTextBrowser::setPalette( p ); +} + + +KNSourceViewWindow::~KNSourceViewWindow() +{ + KNHelper::saveWindowSize("sourceWindow",size()); +} + + +#include "knsourceviewwindow.moc" diff --git a/knode/knsourceviewwindow.h b/knode/knsourceviewwindow.h new file mode 100644 index 000000000..c3a7fc8ca --- /dev/null +++ b/knode/knsourceviewwindow.h @@ -0,0 +1,33 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNSOURCEVIEWWINDOW_H +#define KNSOURCEVIEWWINDOW_H + +#include <ktextbrowser.h> + + +class KNSourceViewWindow : public KTextBrowser { + + Q_OBJECT + + public: + KNSourceViewWindow( const QString &text ); + ~KNSourceViewWindow(); + virtual void setPalette( const QPalette &pal ); + +}; + + +#endif diff --git a/knode/knstatusfilter.cpp b/knode/knstatusfilter.cpp new file mode 100644 index 000000000..6389794d7 --- /dev/null +++ b/knode/knstatusfilter.cpp @@ -0,0 +1,232 @@ +/* + knstatusfilter.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qcheckbox.h> +#include <klocale.h> +#include <ksimpleconfig.h> + +#include "knarticle.h" +#include "knstatusfilter.h" + + +KNStatusFilter::KNStatusFilter() +{ + data.fill(false,8); +} + + + +KNStatusFilter::~KNStatusFilter() +{ +} + + + +void KNStatusFilter::load(KSimpleConfig *conf) +{ + data.setBit(EN_R, conf->readBoolEntry("EN_R", false)); + data.setBit(DAT_R, conf->readBoolEntry("DAT_R", false)); + + data.setBit(EN_N, conf->readBoolEntry("EN_N", false)); + data.setBit(DAT_N, conf->readBoolEntry("DAT_N", false)); + + data.setBit(EN_US, conf->readBoolEntry("EN_US", false)); + data.setBit(DAT_US, conf->readBoolEntry("DAT_US", false)); + + data.setBit(EN_NS, conf->readBoolEntry("EN_NS", false)); + data.setBit(DAT_NS, conf->readBoolEntry("DAT_NS", false)); + +} + + + +void KNStatusFilter::save(KSimpleConfig *conf) +{ + conf->writeEntry("EN_R", data.at(EN_R)); + conf->writeEntry("DAT_R", data.at(DAT_R)); + + conf->writeEntry("EN_N", data.at(EN_N)); + conf->writeEntry("DAT_N", data.at(DAT_N)); + + conf->writeEntry("EN_US", data.at(EN_US)); + conf->writeEntry("DAT_US", data.at(DAT_US)); + + conf->writeEntry("EN_NS", data.at(EN_NS)); + conf->writeEntry("DAT_NS", data.at(DAT_NS)); +} + + + +bool KNStatusFilter::doFilter(KNRemoteArticle *a) +{ + bool ret=true; + + if(data.at(EN_R) && ret) + ret=(a->isRead() == data.at(DAT_R)); + + if(data.at(EN_N) && ret) + ret=(a->isNew() == data.at(DAT_N)); + + if(data.at(EN_US) && ret) + ret=(a->hasUnreadFollowUps() == data.at(DAT_US)); + + if(data.at(EN_NS) && ret) + ret=(a->hasNewFollowUps() == data.at(DAT_NS)); + + return ret; +} + + + +//============================================================================== + +KNStatusFilterWidget::KNStatusFilterWidget(QWidget *parent) : + QButtonGroup(0, parent) +{ + setFrameStyle(NoFrame); + enR=new QCheckBox(i18n("Is read:"), this); + enN=new QCheckBox(i18n("Is new:"), this); + enUS=new QCheckBox(i18n("Has unread followups:"), this); + enNS=new QCheckBox(i18n("Has new followups:"), this); + + rCom=new TFCombo(this); + nCom=new TFCombo(this); + usCom=new TFCombo(this); + nsCom=new TFCombo(this); + + QGridLayout *topL=new QGridLayout(this, 5, 3, 15,5); + topL->addWidget(enR,0,0); topL->addWidget(rCom,0,1); + topL->addWidget(enN,1,0); topL->addWidget(nCom,1,1); + topL->addWidget(enUS,2,0); topL->addWidget(usCom,2,1); + topL->addWidget(enNS,3,0); topL->addWidget(nsCom,3,1); + topL->setColStretch(2,1); + topL->setRowStretch(4,1); + + connect(this, SIGNAL(clicked(int)), this, SLOT(slotEnabled(int))); +} + + + +KNStatusFilterWidget::~KNStatusFilterWidget() +{ +} + + + +KNStatusFilter KNStatusFilterWidget::filter() +{ + KNStatusFilter f; + + f.data.setBit(EN_R, enR->isChecked()); + f.data.setBit(DAT_R, rCom->value()); + + f.data.setBit(EN_N, enN->isChecked()); + f.data.setBit(DAT_N, nCom->value()); + + f.data.setBit(EN_US, enUS->isChecked()); + f.data.setBit(DAT_US, usCom->value()); + + f.data.setBit(EN_NS, enNS->isChecked()); + f.data.setBit(DAT_NS, nsCom->value()); + + return f; +} + + + +void KNStatusFilterWidget::setFilter(KNStatusFilter &f) +{ + enR->setChecked(f.data.at(EN_R)); + rCom->setValue(f.data.at(DAT_R)); + + enN->setChecked(f.data.at(EN_N)); + nCom->setValue(f.data.at(DAT_N)); + + enUS->setChecked(f.data.at(EN_US)); + usCom->setValue(f.data.at(DAT_US)); + + enNS->setChecked(f.data.at(EN_NS)); + nsCom->setValue(f.data.at(DAT_NS)); + + for(int i=0; i<4; i++) slotEnabled(i); +} + + +void KNStatusFilterWidget::clear() +{ + enR->setChecked(false); + enN->setChecked(false); + enUS->setChecked(false); + enNS->setChecked(false); + rCom->setValue(true); + nCom->setValue(true); + nsCom->setValue(true); + usCom->setValue(true); + + for(int i=0; i<4; i++) slotEnabled(i); +} + + + +void KNStatusFilterWidget::slotEnabled(int c) +{ + switch(c) { + + case 0: rCom->setEnabled(enR->isChecked()); break; + case 1: nCom->setEnabled(enN->isChecked()); break; + case 2: usCom->setEnabled(enUS->isChecked()); break; + case 3: nsCom->setEnabled(enNS->isChecked()); break; + }; +} + + +//============================================================================== + + +KNStatusFilterWidget::TFCombo::TFCombo(QWidget *parent) : QComboBox(parent) +{ + insertItem(i18n("True")); + insertItem(i18n("False")); +} + + + +KNStatusFilterWidget::TFCombo::~TFCombo() +{ +} + + + +//-------------------------------- + +#include "knstatusfilter.moc" + + + + + + + + + + + + + + + + diff --git a/knode/knstatusfilter.h b/knode/knstatusfilter.h new file mode 100644 index 000000000..43030de4e --- /dev/null +++ b/knode/knstatusfilter.h @@ -0,0 +1,99 @@ +/* + knstatusfilter.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNSTATUSFILTER_H +#define KNSTATUSFILTER_H + +#include <qbuttongroup.h> +#include <qcombobox.h> +#include <qbitarray.h> + +class QCheckBox; +class KSimpleConfig; +class KNRemoteArticle; + + +class KNStatusFilter { + + friend class KNStatusFilterWidget; + + public: + KNStatusFilter(); + ~KNStatusFilter(); + + KNStatusFilter& operator=(const KNStatusFilter &sf) + { for(int i=0; i<8; i++) data.setBit(i, sf.data.at(i)); return (*this); } + + void load(KSimpleConfig *conf); + void save(KSimpleConfig *conf); + + bool doFilter(KNRemoteArticle *a); + + protected: + QBitArray data; + +}; + + +//================================================================================= + + +class KNStatusFilterWidget : public QButtonGroup { + + Q_OBJECT + + public: + KNStatusFilterWidget(QWidget *parent); + ~KNStatusFilterWidget(); + + KNStatusFilter filter(); + void setFilter(KNStatusFilter &f); + void clear(); + + + protected: + + class TFCombo : public QComboBox { + + public: + TFCombo(QWidget *parent); + ~TFCombo(); + void setValue(bool b) { if(b) setCurrentItem(0); else setCurrentItem(1); } + bool value() const { return (currentItem()==0); } + }; + + + QCheckBox *enR, *enN, *enUS, *enNS; + TFCombo *rCom, *nCom, *usCom, *nsCom; + + protected slots: + void slotEnabled(int c); + +}; + + +#define EN_R 0 +#define EN_N 1 +#define EN_US 2 +#define EN_NS 3 + +#define DAT_R 4 +#define DAT_N 5 +#define DAT_US 6 +#define DAT_NS 7 + + +#endif diff --git a/knode/knstringfilter.cpp b/knode/knstringfilter.cpp new file mode 100644 index 000000000..c41b723fe --- /dev/null +++ b/knode/knstringfilter.cpp @@ -0,0 +1,165 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qcombobox.h> +#include <qlayout.h> +#include <qcheckbox.h> + +#include <klocale.h> +#include <ksimpleconfig.h> +#include <klineedit.h> + +#include "kngroup.h" +#include "knnntpaccount.h" +#include "knglobals.h" +#include "knconfigmanager.h" +#include "knstringfilter.h" + + +KNStringFilter& KNStringFilter::operator=(const KNStringFilter &sf) +{ + con=sf.con; + data=sf.data.copy(); + regExp=sf.regExp; + + return (*this); +} + + + +bool KNStringFilter::doFilter(const QString &s) +{ + bool ret=true; + + if(!expanded.isEmpty()) { + if(regExp) { + QRegExp matcher(expanded); + ret = ( matcher.search(s) >= 0 ); + } else + ret=(s.find(expanded,0,false)!=-1); + + if(!con) ret=!ret; + } + + return ret; +} + + + +// replace placeholders +void KNStringFilter::expand(KNGroup *g) +{ + KNConfig::Identity *id = (g) ? g->identity() : 0; + + if (!id) { + id = (g) ? g->account()->identity() : 0; + if (!id) + id = knGlobals.configManager()->identity(); + } + + expanded = data; + expanded.replace(QRegExp("%MYNAME"), id->name()); + expanded.replace(QRegExp("%MYEMAIL"), id->email()); +} + + + +void KNStringFilter::load(KSimpleConfig *conf) +{ + con=conf->readBoolEntry("contains", true); + data=conf->readEntry("Data"); + regExp=conf->readBoolEntry("regX", false); +} + + + +void KNStringFilter::save(KSimpleConfig *conf) +{ + conf->writeEntry("contains", con); + conf->writeEntry("Data", data); + conf->writeEntry("regX", regExp); +} + + +//=============================================================================== + +KNStringFilterWidget::KNStringFilterWidget(const QString& title, QWidget *parent) + : QGroupBox(title, parent) +{ + fType=new QComboBox(this); + fType->insertItem(i18n("Does Contain")); + fType->insertItem(i18n("Does NOT Contain")); + + fString=new KLineEdit(this); + + regExp=new QCheckBox(i18n("Regular expression"), this); + + QGridLayout *topL=new QGridLayout(this, 3,3, 8,5 ); + topL->addRowSpacing(0, fontMetrics().lineSpacing()-4); + topL->addWidget(fType, 1,0); + topL->addColSpacing(1, 10); + topL->addWidget(regExp, 1,1); + topL->addMultiCellWidget(fString, 2,2, 0,2); + topL->setColStretch(2,1); +} + + + +KNStringFilterWidget::~KNStringFilterWidget() +{ +} + + + +KNStringFilter KNStringFilterWidget::filter() +{ + KNStringFilter ret; + ret.con=(fType->currentItem()==0); + ret.data=fString->text(); + ret.regExp=regExp->isChecked(); + + return ret; +} + + + +void KNStringFilterWidget::setFilter(KNStringFilter &f) +{ + if(f.con) fType->setCurrentItem(0); + else fType->setCurrentItem(1); + fString->setText(f.data); + regExp->setChecked(f.regExp); +} + + + +void KNStringFilterWidget::clear() +{ + fString->clear(); + fType->setCurrentItem(0); + regExp->setChecked(false); +} + + +void KNStringFilterWidget::setStartFocus() +{ + fString->setFocus(); +} + + +// -----------------------------------------------------------------------------+ + +#include "knstringfilter.moc" + +// kate: space-indent on; indent-width 2; diff --git a/knode/knstringfilter.h b/knode/knstringfilter.h new file mode 100644 index 000000000..17199cbe7 --- /dev/null +++ b/knode/knstringfilter.h @@ -0,0 +1,82 @@ +/* + knstringfilter.h + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNSTRINGFILTER_H +#define KNSTRINGFILTER_H + +#include <qgroupbox.h> + +class QCheckBox; +class QComboBox; + +class KLineEdit; +class KSimpleConfig; + +class KNGroup; + + +class KNStringFilter { + + friend class KNStringFilterWidget; + + public: + KNStringFilter() { con=true; regExp=false;} + ~KNStringFilter() {} + + KNStringFilter& operator=(const KNStringFilter &sf); + /** replace placeholders */ + void expand(KNGroup *g); + + void load(KSimpleConfig *conf); + void save(KSimpleConfig *conf); + + bool doFilter(const QString &s); + + protected: + QString data, expanded; + bool con, regExp; + +}; + + +//=============================================================================== + + +class KNStringFilterWidget : public QGroupBox { + + Q_OBJECT + + public: + KNStringFilterWidget(const QString& title, QWidget *parent); + ~KNStringFilterWidget(); + + KNStringFilter filter(); + void setFilter(KNStringFilter &f); + void clear(); + + /** usablity hack for the search dialog */ + void setStartFocus(); + + protected: + QCheckBox *regExp; + QComboBox *fType; + KLineEdit *fString; + +}; + + +#endif + diff --git a/knode/knwidgets.cpp b/knode/knwidgets.cpp new file mode 100644 index 000000000..d71624eb6 --- /dev/null +++ b/knode/knwidgets.cpp @@ -0,0 +1,159 @@ +/* + knwidgets.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qpainter.h> +#include <qpixmap.h> + +#include "knwidgets.h" + + +//==================================================================================== + +KNListBoxItem::KNListBoxItem(const QString& text, QPixmap *pm) +{ + p_m=pm; + setText(text); +} + + +KNListBoxItem::~KNListBoxItem() +{ +} + + +void KNListBoxItem::paint(QPainter *p) +{ + + QFontMetrics fm = p->fontMetrics(); + + int tYPos=0, tXPos=3, pYPos=0; + + tYPos = fm.ascent() + fm.leading()/2; // vertical text position + + if(p_m) { + + tXPos=p_m->width() + 6; + + if ( p_m->height() < fm.height() ) { + //tYPos = fm.ascent() + fm.leading()/2; + pYPos = (fm.height() - p_m->height())/2;} + else { + tYPos = p_m->height()/2 - fm.height()/2 + fm.ascent(); + pYPos = 0; + } + p->drawPixmap( 3, pYPos , *p_m ); + } + + + p->drawText( tXPos, tYPos, text() ); +} + + +int KNListBoxItem::height(const QListBox *lb) const +{ + if(p_m) + return QMAX( p_m->height(), lb->fontMetrics().lineSpacing() + 1 ); + else + return (lb->fontMetrics().lineSpacing() + 1); +} + + +int KNListBoxItem::width(const QListBox *lb) const +{ + if(p_m) + return (p_m->width() + lb->fontMetrics().width( text() ) + 6); + else + return (lb->fontMetrics().width( text() ) + 6); +} + + +//==================================================================================== + +// **** listbox for dialogs ************************************************** + +KNDialogListBox::KNDialogListBox(bool alwaysIgnore, QWidget * parent, const char * name) + : QListBox(parent, name), a_lwaysIgnore(alwaysIgnore) +{ +} + + +KNDialogListBox::~KNDialogListBox() +{ +} + + +void KNDialogListBox::keyPressEvent(QKeyEvent *e) +{ + if ((a_lwaysIgnore || !(hasFocus()&&isVisible()))&&((e->key()==Key_Enter)||(e->key()==Key_Return))) + e->ignore(); + else + QListBox::keyPressEvent(e); +} + + +//==================================================================================== + + +KNDockWidgetHeaderDrag::KNDockWidgetHeaderDrag(QWidget *focusWidget, KDockWidgetAbstractHeader* parent, KDockWidget* dock, const char* name ) + : KDockWidgetHeaderDrag(parent, dock, name), f_ocus(false) +{ + connect(focusWidget, SIGNAL(focusChanged(QFocusEvent*)), SLOT(slotFocusChanged(QFocusEvent*))); +} + + +KNDockWidgetHeaderDrag::~KNDockWidgetHeaderDrag() +{ +} + + +void KNDockWidgetHeaderDrag::slotFocusChanged(QFocusEvent *e) +{ + if(e->gotFocus()) { + f_ocus = true; + } else if(e->lostFocus()) { + f_ocus = false; + } + update(); +} + + +void KNDockWidgetHeaderDrag::paintEvent(QPaintEvent* ev) +{ + if (!f_ocus) { + KDockWidgetHeaderDrag::paintEvent(ev); + return; + } + + QPixmap drawBuffer(width(), height()); + QPainter paint; + + paint.begin(&drawBuffer); + paint.fillRect(drawBuffer.rect(), QBrush(colorGroup().brush(QColorGroup::Background))); + + paint.setPen(palette().active().highlight()); + paint.drawLine(1, 2, width(), 2); + paint.drawLine(1, 3, width(), 3); + paint.drawLine(1, 5, width(), 5); + paint.drawLine(1, 6, width(), 6); + + bitBlt( this,0,0,&drawBuffer,0,0,width(),height()); + paint.end(); +} + + +//==================================================================================== + +#include "knwidgets.moc" diff --git a/knode/knwidgets.h b/knode/knwidgets.h new file mode 100644 index 000000000..af71bd5f0 --- /dev/null +++ b/knode/knwidgets.h @@ -0,0 +1,88 @@ +/* + knwidgets.h + + KNode, the KDE newsreader + Copyright (c) 1999-2004 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef KNWIDGETS_H +#define KNWIDGETS_H + +#include <qlistbox.h> +#include <qbitarray.h> + +#include <kdockwidget.h> +#include <kprogress.h> + +class QPainter; +class QPixmap; + +//==================================================================================== + + +class KNListBoxItem : public QListBoxItem { + + public: + KNListBoxItem(const QString& text, QPixmap *pm=0); + ~KNListBoxItem(); + + + protected: + virtual void paint(QPainter *); + virtual int height(const QListBox *) const; + virtual int width(const QListBox *) const; + + QPixmap *p_m; +}; + + +//==================================================================================== + + +/** a list box which ignores Enter, useful for dialogs */ +class KNDialogListBox : public QListBox +{ + public: + // alwaysIgnore==false: enter is ignored when the widget isn't visible/out of focus + KNDialogListBox(bool alwaysIgnore=false, QWidget * parent=0, const char * name=0); + ~KNDialogListBox(); + + protected: + void keyPressEvent( QKeyEvent *e ); + + bool a_lwaysIgnore; +}; + + +//==================================================================================== + + +class KNDockWidgetHeaderDrag : public KDockWidgetHeaderDrag +{ + Q_OBJECT + + public: + KNDockWidgetHeaderDrag(QWidget *focusWidget, KDockWidgetAbstractHeader* parent, KDockWidget* dock, + const char* name = 0); + virtual ~KNDockWidgetHeaderDrag(); + + protected slots: + void slotFocusChanged(QFocusEvent *e); + + protected: + virtual void paintEvent( QPaintEvent* ); + + bool f_ocus; +}; + + +#endif diff --git a/knode/main.cpp b/knode/main.cpp new file mode 100644 index 000000000..dad1963fa --- /dev/null +++ b/knode/main.cpp @@ -0,0 +1,44 @@ +/* + main.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <klocale.h> + +#include "knapplication.h" +#include "resource.h" +#include "knode.h" +#include "aboutdata.h" +#include "knode_options.h" +using KNode::AboutData; + +int main(int argc, char* argv[]) +{ + AboutData aboutData; + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( knode_options ); + KUniqueApplication::addCmdLineOptions(); + + if (!KNApplication::start()) + return 0; + + KNApplication app; + KGlobal::locale()->insertCatalogue("libkdepim"); + KGlobal::locale()->insertCatalogue("libkpgp"); + return app.exec(); +} + diff --git a/knode/pics/Makefile.am b/knode/pics/Makefile.am new file mode 100644 index 000000000..f32c60694 --- /dev/null +++ b/knode/pics/Makefile.am @@ -0,0 +1,12 @@ +pics_DATA = stat_edit.png mail.png stat_saved.png \ + ctlart.png greyball.png ignore.png \ + newsubs.png stat_sent.png \ + eyes.png greyballchk.png snderr.png \ + group.png posting.png stat_cncl.png \ + pgp-keys.png group_big.png article.png + +picsdir = $(kde_datadir)/knode/pics + +EXTRA_DIST = $(pics_DATA) +knodeicondir = $(kde_datadir)/knode/icons +knodeicon_ICON = message_reply mail_get_all diff --git a/knode/pics/article.png b/knode/pics/article.png Binary files differnew file mode 100644 index 000000000..9ebd69759 --- /dev/null +++ b/knode/pics/article.png diff --git a/knode/pics/cr16-action-mail_get_all.png b/knode/pics/cr16-action-mail_get_all.png Binary files differnew file mode 100644 index 000000000..425a23a9d --- /dev/null +++ b/knode/pics/cr16-action-mail_get_all.png diff --git a/knode/pics/cr16-action-message_reply.png b/knode/pics/cr16-action-message_reply.png Binary files differnew file mode 100644 index 000000000..af0b3b3e1 --- /dev/null +++ b/knode/pics/cr16-action-message_reply.png diff --git a/knode/pics/cr22-action-mail_get_all.png b/knode/pics/cr22-action-mail_get_all.png Binary files differnew file mode 100644 index 000000000..327045c39 --- /dev/null +++ b/knode/pics/cr22-action-mail_get_all.png diff --git a/knode/pics/cr22-action-message_reply.png b/knode/pics/cr22-action-message_reply.png Binary files differnew file mode 100644 index 000000000..27c401ad6 --- /dev/null +++ b/knode/pics/cr22-action-message_reply.png diff --git a/knode/pics/cr32-action-mail_get_all.png b/knode/pics/cr32-action-mail_get_all.png Binary files differnew file mode 100644 index 000000000..34610de6e --- /dev/null +++ b/knode/pics/cr32-action-mail_get_all.png diff --git a/knode/pics/cr32-action-message_reply.png b/knode/pics/cr32-action-message_reply.png Binary files differnew file mode 100644 index 000000000..170f2be45 --- /dev/null +++ b/knode/pics/cr32-action-message_reply.png diff --git a/knode/pics/crsc-action-mail_get_all.svg b/knode/pics/crsc-action-mail_get_all.svg new file mode 100644 index 000000000..6096ce7cc --- /dev/null +++ b/knode/pics/crsc-action-mail_get_all.svg @@ -0,0 +1,184 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" +"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<!-- Created with Sodipodi ("http://www.sodipodi.com/") --> +<svg + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + version="1.0" + x="0" + y="0" + width="40" + height="40" + id="svg602" + xml:space="preserve"><defs + id="defs604"><linearGradient + id="linearGradient660"><stop + style="stop-color:#0c000d;stop-opacity:1;" + offset="0" + id="stop662" /><stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop663" /></linearGradient><linearGradient + id="linearGradient714"><stop + style="stop-color:#0c610d;stop-opacity:1;" + offset="0" + id="stop715" /><stop + style="stop-color:#307a30;stop-opacity:1;" + offset="1" + id="stop716" /></linearGradient><linearGradient + id="linearGradient710"><stop + style="stop-color:#6eff3a;stop-opacity:1;" + offset="0" + id="stop711" /><stop + style="stop-color:#35bd3d;stop-opacity:1;" + offset="1" + id="stop712" /></linearGradient><linearGradient + id="linearGradient706"><stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop707" /><stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop708" /></linearGradient><linearGradient + id="linearGradient675"><stop + style="stop-color:#a29ac5;stop-opacity:1;" + offset="0" + id="stop676" /><stop + style="stop-color:#5e5e99;stop-opacity:1;" + offset="1" + id="stop677" /></linearGradient><linearGradient + id="linearGradient659"><stop + style="stop-color:#edeaff;stop-opacity:1;" + offset="0" + id="stop660" /><stop + style="stop-color:#9797bd;stop-opacity:1;" + offset="1" + id="stop661" /></linearGradient><linearGradient + id="linearGradient615"><stop + style="stop-color:#aba8bd;stop-opacity:1;" + offset="0" + id="stop616" /><stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="1" + id="stop617" /></linearGradient><radialGradient + cx="0.77600008" + cy="0.82812488" + r="0.57651013" + fx="0.77600008" + fy="0.82812488" + id="radialGradient618" + xlink:href="#linearGradient615" /><linearGradient + x1="0.36507928" + y1="0.68987346" + x2="0.84656072" + y2="0.94303805" + id="linearGradient658" + xlink:href="#linearGradient659" /><linearGradient + x1="0.45098045" + y1="0.386076" + x2="0.82352948" + y2="0.64556968" + id="linearGradient674" + xlink:href="#linearGradient675" /><radialGradient + cx="0.47715884" + cy="1.56241897e-2" + r="0.93336767" + fx="0.47715884" + fy="1.56241897e-2" + id="radialGradient705" + xlink:href="#linearGradient706" /><radialGradient + cx="0.51937997" + cy="0.875" + r="0.49147549" + fx="0.51937997" + fy="0.875" + id="radialGradient709" + xlink:href="#linearGradient710" /><linearGradient + x1="0.37735853" + y1="0.74050617" + x2="0.81761009" + y2="-7.59494528e-2" + id="linearGradient713" + xlink:href="#linearGradient714" /><radialGradient + cx="0.51937979" + cy="0.546875" + r="0.47348177" + fx="0.51937979" + fy="0.546875" + id="radialGradient659" + xlink:href="#linearGradient660" /></defs><g + transform="matrix(0.7543,0,0,0.7543,0.654275,0.583227)" + style="font-size:12;" + id="g650"><path + d="M 0.818516 13.53465 L 27.37481 0.620286 L 38.10647 23.72063 L 11.73206 39.27243 L 0.818516 13.53465 z " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:url(#radialGradient618);fill-rule:evenodd;" + id="path614" /><path + d="M 11.55017 38.99959 L 19.37155 21.17413 " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:#a299c0;stroke-width:0.75;" + id="path609" /><path + d="M 37.74268 23.90252 L 23.73696 19.99183 " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:#a299c0;stroke-width:0.75;" + id="path608" /><path + d="M 27.19292 0.711238 L 23.19129 22.90211 L 0.818516 13.35276 " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:url(#linearGradient658);stroke-width:1.25;" + id="path607" /><path + d="M 0.818516 13.53465 L 27.46576 0.620288 L 38.01552 23.90252 L 11.55017 39.18148 L 0.818516 13.53465 z " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:url(#linearGradient674);stroke-width:1.8762;stroke-linecap:round;stroke-linejoin:round;" + id="path606" /></g><path + d="M 7.275697 18.3548 L 15.27896 37.08972 L 37.10606 27.08564 L 28.19333 7.259362 L 7.275697 18.3548 z " + style="font-size:12;fill:url(#radialGradient659);fill-rule:evenodd;stroke-width:1;" + id="path658" /><g + transform="matrix(0.631045,0,0,0.631045,14.80571,13.06199)" + style="font-size:12;" + id="g652"><path + d="M 0.818516 13.53465 L 27.37481 0.620286 L 38.10647 23.72063 L 11.73206 39.27243 L 0.818516 13.53465 z " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:url(#radialGradient618);fill-rule:evenodd;" + id="path653" /><path + d="M 11.55017 38.99959 L 19.37155 21.17413 " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:#a299c0;stroke-width:0.75;" + id="path654" /><path + d="M 37.74268 23.90252 L 23.73696 19.99183 " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:#a299c0;stroke-width:0.75;" + id="path655" /><path + d="M 27.19292 0.711238 L 23.19129 22.90211 L 0.818516 13.35276 " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:url(#linearGradient658);stroke-width:1.25;" + id="path656" /><path + d="M 0.818516 13.53465 L 27.46576 0.620288 L 38.01552 23.90252 L 11.55017 39.18148 L 0.818516 13.53465 z " + transform="matrix(0.992683,0,0,0.992683,0.369773,0.277376)" + style="fill:none;fill-rule:evenodd;stroke:url(#linearGradient674);stroke-width:1.8762;stroke-linecap:round;stroke-linejoin:round;" + id="path657" /></g><g + transform="matrix(0.913514,0,0,0.913514,0.978449,2.68033)" + style="font-size:12;" + id="g665"><ellipse + cx="9.95861053" + cy="31.3146362" + rx="8.86725616" + ry="8.86725616" + transform="matrix(0.948718,0,0,0.948718,-0.23772,1.282547)" + style="fill:url(#radialGradient709);fill-rule:evenodd;stroke:url(#linearGradient713);" + id="path610" /><path + d="M 9.33377 26.68416 L 9.33377 34.77838 " + transform="translate(-7.812232e-2,7.8125e-2)" + style="fill:none;fill-opacity:0.4549;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.225;stroke-linecap:round;stroke-linejoin:round;" + id="path703" /><path + d="M 5.24119 31.68622 L 9.33377 35.41501 L 13.5173 31.68622 L 13.5173 31.68622 " + transform="translate(-7.812232e-2,7.8125e-2)" + style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.225;stroke-linecap:round;stroke-linejoin:round;" + id="path702" /><ellipse + cx="-14.7332878" + cy="27.6313133" + rx="5.45677233" + ry="3.54690266" + transform="matrix(1.065382,0,0,1.162882,24.94917,-4.76965)" + style="fill:url(#radialGradient705);fill-rule:evenodd;" + id="path704" /></g></svg> diff --git a/knode/pics/ctlart.png b/knode/pics/ctlart.png Binary files differnew file mode 100644 index 000000000..76836287d --- /dev/null +++ b/knode/pics/ctlart.png diff --git a/knode/pics/eyes.png b/knode/pics/eyes.png Binary files differnew file mode 100644 index 000000000..6c696eb82 --- /dev/null +++ b/knode/pics/eyes.png diff --git a/knode/pics/greyball.png b/knode/pics/greyball.png Binary files differnew file mode 100644 index 000000000..745200696 --- /dev/null +++ b/knode/pics/greyball.png diff --git a/knode/pics/greyballchk.png b/knode/pics/greyballchk.png Binary files differnew file mode 100644 index 000000000..f16a11aff --- /dev/null +++ b/knode/pics/greyballchk.png diff --git a/knode/pics/group.png b/knode/pics/group.png Binary files differnew file mode 100644 index 000000000..9ebd69759 --- /dev/null +++ b/knode/pics/group.png diff --git a/knode/pics/group_big.png b/knode/pics/group_big.png Binary files differnew file mode 100644 index 000000000..b0a5a2237 --- /dev/null +++ b/knode/pics/group_big.png diff --git a/knode/pics/ignore.png b/knode/pics/ignore.png Binary files differnew file mode 100644 index 000000000..0e06e3be2 --- /dev/null +++ b/knode/pics/ignore.png diff --git a/knode/pics/mail.png b/knode/pics/mail.png Binary files differnew file mode 100644 index 000000000..cf7160ab4 --- /dev/null +++ b/knode/pics/mail.png diff --git a/knode/pics/newsubs.png b/knode/pics/newsubs.png Binary files differnew file mode 100644 index 000000000..9b9819c61 --- /dev/null +++ b/knode/pics/newsubs.png diff --git a/knode/pics/pgp-keys.png b/knode/pics/pgp-keys.png Binary files differnew file mode 100644 index 000000000..4daed596c --- /dev/null +++ b/knode/pics/pgp-keys.png diff --git a/knode/pics/posting.png b/knode/pics/posting.png Binary files differnew file mode 100644 index 000000000..83f375218 --- /dev/null +++ b/knode/pics/posting.png diff --git a/knode/pics/snderr.png b/knode/pics/snderr.png Binary files differnew file mode 100644 index 000000000..81ca0060e --- /dev/null +++ b/knode/pics/snderr.png diff --git a/knode/pics/stat_cncl.png b/knode/pics/stat_cncl.png Binary files differnew file mode 100644 index 000000000..18ad41a64 --- /dev/null +++ b/knode/pics/stat_cncl.png diff --git a/knode/pics/stat_edit.png b/knode/pics/stat_edit.png Binary files differnew file mode 100644 index 000000000..edb4f2b4a --- /dev/null +++ b/knode/pics/stat_edit.png diff --git a/knode/pics/stat_saved.png b/knode/pics/stat_saved.png Binary files differnew file mode 100644 index 000000000..85ecf0e33 --- /dev/null +++ b/knode/pics/stat_saved.png diff --git a/knode/pics/stat_sent.png b/knode/pics/stat_sent.png Binary files differnew file mode 100644 index 000000000..1e4fd842c --- /dev/null +++ b/knode/pics/stat_sent.png diff --git a/knode/resource.h b/knode/resource.h new file mode 100644 index 000000000..3519d4437 --- /dev/null +++ b/knode/resource.h @@ -0,0 +1,43 @@ +/* + resource.h + + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef RESSOURCE_H +#define RESSOURCE_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +//========= KNode Version Information ============ + +#define KNODE_VERSION "0.10.9" + +//================= StatusBar ==================== + +#define SB_MAIN 4000005 +#define SB_GROUP 4000010 +#define SB_FILTER 4000030 + + +//================== Folders ===================== + +#define FOLD_DRAFTS 200010 +#define FOLD_SENT 200020 +#define FOLD_OUTB 200030 + + +#endif // RESOURCE_H diff --git a/knode/smtpaccountwidget_base.ui b/knode/smtpaccountwidget_base.ui new file mode 100644 index 000000000..9acad519c --- /dev/null +++ b/knode/smtpaccountwidget_base.ui @@ -0,0 +1,232 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KNConfig::SmtpAccountWidgetBase</class> +<widget class="KCModule"> + <property name="name"> + <cstring>SmtpAccountWidgetBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>306</width> + <height>320</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>mUseExternalMailer</cstring> + </property> + <property name="text"> + <string>&Use external mailer</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>mServerLabel</cstring> + </property> + <property name="text"> + <string>&Server:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>mServer</cstring> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>mPortLabel</cstring> + </property> + <property name="text"> + <string>&Port:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>mPort</cstring> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>mUserLabel</cstring> + </property> + <property name="text"> + <string>&User:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>mUser</cstring> + </property> + </widget> + <widget class="QLabel" row="5" column="0"> + <property name="name"> + <cstring>mPasswordLabel</cstring> + </property> + <property name="text"> + <string>Pass&word:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>mPassword</cstring> + </property> + </widget> + <widget class="QCheckBox" row="3" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>mLogin</cstring> + </property> + <property name="text"> + <string>Server requires &authentication</string> + </property> + </widget> + <spacer row="7" column="2"> + <property name="name"> + <cstring>mSpacer</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + <widget class="KLineEdit" row="5" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>mPassword</cstring> + </property> + <property name="echoMode"> + <enum>Password</enum> + </property> + </widget> + <widget class="KLineEdit" row="4" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>mUser</cstring> + </property> + </widget> + <widget class="KIntNumInput" row="2" column="1"> + <property name="name"> + <cstring>mPort</cstring> + </property> + <property name="value"> + <number>25</number> + </property> + <property name="minValue"> + <number>0</number> + </property> + <property name="maxValue"> + <number>65536</number> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>mServer</cstring> + </property> + </widget> + <widget class="QButtonGroup" row="6" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>mEncGroup</cstring> + </property> + <property name="title"> + <string>Encryption</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mEncNone</cstring> + </property> + <property name="text"> + <string>None</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mEncSSL</cstring> + </property> + <property name="text"> + <string>SSL</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>mEncTLS</cstring> + </property> + <property name="text"> + <string>TLS</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </vbox> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<connections> + <connection> + <sender>mServer</sender> + <signal>textChanged(const QString&)</signal> + <receiver>SmtpAccountWidgetBase</receiver> + <slot>changed()</slot> + </connection> + <connection> + <sender>mPort</sender> + <signal>valueChanged(int)</signal> + <receiver>SmtpAccountWidgetBase</receiver> + <slot>changed()</slot> + </connection> + <connection> + <sender>mUseExternalMailer</sender> + <signal>toggled(bool)</signal> + <receiver>SmtpAccountWidgetBase</receiver> + <slot>useExternalMailerToggled(bool)</slot> + </connection> + <connection> + <sender>mUser</sender> + <signal>textChanged(const QString&)</signal> + <receiver>SmtpAccountWidgetBase</receiver> + <slot>changed()</slot> + </connection> + <connection> + <sender>mPassword</sender> + <signal>textChanged(const QString&)</signal> + <receiver>SmtpAccountWidgetBase</receiver> + <slot>changed()</slot> + </connection> + <connection> + <sender>mLogin</sender> + <signal>toggled(bool)</signal> + <receiver>SmtpAccountWidgetBase</receiver> + <slot>loginToggled(bool)</slot> + </connection> + <connection> + <sender>mEncGroup</sender> + <signal>clicked(int)</signal> + <receiver>SmtpAccountWidgetBase</receiver> + <slot>changed()</slot> + </connection> +</connections> +<slots> + <slot access="protected">useExternalMailerToggled(bool)</slot> + <slot access="protected">loginToggled(bool)</slot> +</slots> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kcmodule.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>knuminput.h</includehint> +</includehints> +</UI> diff --git a/knode/utilities.cpp b/knode/utilities.cpp new file mode 100644 index 000000000..5f9c2e98c --- /dev/null +++ b/knode/utilities.cpp @@ -0,0 +1,478 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#include <qlayout.h> +#include <qregexp.h> +#include <qapplication.h> +#include <qcursor.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kglobalsettings.h> +#include <kdebug.h> +#include <kio/netaccess.h> +#include <ktempfile.h> +#include <kfiledialog.h> + +#include "knwidgets.h" +#include "knglobals.h" +#include "utilities.h" + + + +//================================================================================ + +KNFile::KNFile(const QString& fname) + : QFile(fname), filePos(0), readBytes(0) +{ + buffer.resize(512); + dataPtr=buffer.data(); + dataPtr[0]='\0'; +} + + +KNFile::~KNFile() +{ +} + + +const QCString& KNFile::readLine() +{ + filePos=at(); + readBytes=QFile::readLine(dataPtr, buffer.size()-1); + if(readBytes!=-1) { + while ((dataPtr[readBytes-1]!='\n')&&(static_cast<uint>(readBytes+2)==buffer.size())) { // don't get tricked by files without newline + at(filePos); + if (!increaseBuffer() || + (readBytes=QFile::readLine(dataPtr, buffer.size()-1))==-1) { + readBytes=1; + break; + } + } + } else + readBytes=1; + + dataPtr[readBytes-1] = '\0'; + return buffer; +} + + +const QCString& KNFile::readLineWnewLine() +{ + filePos=at(); + readBytes=QFile::readLine(dataPtr, buffer.size()-1); + if(readBytes!=-1) { + while ((dataPtr[readBytes-1]!='\n')&&(static_cast<uint>(readBytes+2)==buffer.size())) { // don't get tricked by files without newline + at(filePos); + if (!increaseBuffer() || + (readBytes=QFile::readLine(dataPtr, buffer.size()-1))==-1) { + dataPtr[0] = '\0'; + break; + } + } + } + else dataPtr[0] = '\0'; + + return buffer; +} + + +int KNFile::findString(const char *s) +{ + QCString searchBuffer; + searchBuffer.resize(2048); + char *buffPtr = searchBuffer.data(), *pos; + int readBytes, currentFilePos; + + while (!atEnd()) { + currentFilePos = at(); + readBytes = readBlock(buffPtr, 2047); + if (readBytes == -1) + return -1; + else + buffPtr[readBytes] = 0; // terminate string + + pos = strstr(buffPtr,s); + if (pos == 0) { + if (!atEnd()) + at(at()-strlen(s)); + else + return -1; + } else { + return currentFilePos + (pos-buffPtr); + } + } + + return -1; +} + + +bool KNFile::increaseBuffer() +{ + if(buffer.resize(2*buffer.size())) {; + dataPtr=buffer.data(); + dataPtr[0]='\0'; + kdDebug(5003) << "KNFile::increaseBuffer() : buffer doubled" << endl; + return true; + } + else return false; +} + + +//=============================================================================== + +QString KNSaveHelper::lastPath; + +KNSaveHelper::KNSaveHelper(QString saveName, QWidget *parent) + : p_arent(parent), s_aveName(saveName), file(0), tmpFile(0) +{ +} + + +KNSaveHelper::~KNSaveHelper() +{ + if (file) { // local filesystem, just close the file + delete file; + } else + if (tmpFile) { // network location, initiate transaction + tmpFile->close(); + if (KIO::NetAccess::upload(tmpFile->name(),url, 0) == false) + KNHelper::displayRemoteFileError(); + tmpFile->unlink(); // delete temp file + delete tmpFile; + } +} + + +QFile* KNSaveHelper::getFile(const QString &dialogTitle) +{ + url = KFileDialog::getSaveURL(lastPath + s_aveName, QString::null, p_arent, dialogTitle); + + if (url.isEmpty()) + return 0; + + lastPath = url.upURL().url(); + + if (url.isLocalFile()) { + if (QFileInfo(url.path()).exists() && + (KMessageBox::warningContinueCancel(knGlobals.topWidget, + i18n("<qt>A file named <b>%1</b> already exists.<br>Do you want to replace it?</qt>").arg(url.path()), + dialogTitle, i18n("&Replace")) != KMessageBox::Continue)) { + return 0; + } + + file = new QFile(url.path()); + if(!file->open(IO_WriteOnly)) { + KNHelper::displayExternalFileError(); + delete file; + file = 0; + } + return file; + } else { + tmpFile = new KTempFile(); + if (tmpFile->status()!=0) { + KNHelper::displayTempFileError(); + delete tmpFile; + tmpFile = 0; + return 0; + } + return tmpFile->file(); + } +} + + +//=============================================================================== + +QString KNLoadHelper::l_astPath; + +KNLoadHelper::KNLoadHelper(QWidget *parent) + : p_arent(parent), f_ile(0) +{ +} + + +KNLoadHelper::~KNLoadHelper() +{ + delete f_ile; + if (!t_empName.isEmpty()) + KIO::NetAccess::removeTempFile(t_empName); +} + + +KNFile* KNLoadHelper::getFile( const QString &dialogTitle ) +{ + if (f_ile) + return f_ile; + + KURL url = KFileDialog::getOpenURL(l_astPath,QString::null,p_arent,dialogTitle); + + if (url.isEmpty()) + return 0; + + l_astPath = url.url(-1); + l_astPath.truncate(l_astPath.length()-url.fileName().length()); + + return setURL(url); +} + + +KNFile* KNLoadHelper::setURL(KURL url) +{ + if (f_ile) + return f_ile; + + u_rl = url; + + if (u_rl.isEmpty()) + return 0; + + QString fileName; + if (!u_rl.isLocalFile()) { + if (KIO::NetAccess::download(u_rl, t_empName, 0)) + fileName = t_empName; + } else + fileName = u_rl.path(); + + if (fileName.isEmpty()) + return 0; + + f_ile = new KNFile(fileName); + if(!f_ile->open(IO_ReadOnly)) { + KNHelper::displayExternalFileError(); + delete f_ile; + f_ile = 0; + } + return f_ile; +} + + +//=============================================================================== + + +// **** keyboard selection dialog ********************************************* +int KNHelper::selectDialog(QWidget *parent, const QString &caption, const QStringList &options, int initialValue) +{ + KDialogBase *dlg=new KDialogBase(KDialogBase::Plain, caption, KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok, parent); + QFrame *page = dlg->plainPage(); + QHBoxLayout *pageL = new QHBoxLayout(page,8,5); + + KNDialogListBox *list = new KNDialogListBox(true, page); + pageL->addWidget(list); + + QString s; + for ( QStringList::ConstIterator it = options.begin(); it != options.end(); ++it ) { + s = (*it); + s.replace(QRegExp("&"),""); // remove accelerators + list->insertItem(s); + } + + list->setCurrentItem(initialValue); + list->setFocus(); + restoreWindowSize("selectBox", dlg, QSize(247,174)); + + int ret; + if (dlg->exec()) + ret = list->currentItem(); + else + ret = -1; + + saveWindowSize("selectBox", dlg->size()); + delete dlg; + return ret; +} + +// **** window geometry managing ********************************************* + +void KNHelper::saveWindowSize(const QString &name, const QSize &s) +{ + KConfig *c=knGlobals.config(); + c->setGroup("WINDOW_SIZES"); + c->writeEntry(name, s); +} + + +void KNHelper::restoreWindowSize(const QString &name, QWidget *d, const QSize &defaultSize) +{ + KConfig *c=knGlobals.config(); + c->setGroup("WINDOW_SIZES"); + + QSize s=c->readSizeEntry(name,&defaultSize); + + if(s.isValid()) { + QRect max = KGlobalSettings::desktopGeometry(QCursor::pos()); + if ( s.width() > max.width() ) s.setWidth( max.width()-5 ); + if ( s.height() > max.height() ) s.setHeight( max.height()-5 ); + d->resize(s); + } +} + +// **** scramble password strings ********************************************** + +const QString KNHelper::encryptStr(const QString& aStr) +{ + uint i,val,len = aStr.length(); + QCString result; + + for (i=0; i<len; i++) + { + val = aStr[i] - ' '; + val = (255-' ') - val; + result += (char)(val + ' '); + } + + return result; +} + + +const QString KNHelper::decryptStr(const QString& aStr) +{ + return encryptStr(aStr); +} + +// **** rot13 ******************************************************************* + +QString KNHelper::rot13(const QString &s) +{ + QString r(s); + + for (int i=0; (uint)i<r.length(); i++) { + if ( r[i] >= QChar('A') && r[i] <= QChar('M') || + r[i] >= QChar('a') && r[i] <= QChar('m') ) + r[i] = (char)((int)QChar(r[i]) + 13); + else + if ( r[i] >= QChar('N') && r[i] <= QChar('Z') || + r[i] >= QChar('n') && r[i] <= QChar('z') ) + r[i] = (char)((int)QChar(r[i]) - 13); + } + + return r; +} + +// **** text rewraping ********************************************************* + +int findBreakPos(const QString &text, int start) +{ + int i; + for(i=start;i>=0;i--) + if(text[i].isSpace()) + break; + if(i>0) + return i; + for(i=start;i<(int)text.length();i++) // ok, the line is to long + if(text[i].isSpace()) + break; + return i; +} + + +void appendTextWPrefix(QString &result, const QString &text, int wrapAt, const QString &prefix) +{ + QString txt=text; + int breakPos; + + while(!txt.isEmpty()) { + + if((int)(prefix.length()+txt.length()) > wrapAt) { + breakPos=findBreakPos(txt,wrapAt-prefix.length()); + result+=(prefix+txt.left(breakPos)+"\n"); + txt.remove(0,breakPos+1); + } else { + result+=(prefix+txt+"\n"); + txt=QString::null; + } + } +} + + +QString KNHelper::rewrapStringList(QStringList text, int wrapAt, QChar quoteChar, bool stopAtSig, bool alwaysSpace) +{ + QString quoted, lastPrefix, thisPrefix, leftover, thisLine; + int breakPos; + + for(QStringList::Iterator line=text.begin(); line!=text.end(); ++line) { + + if(stopAtSig && (*line)=="-- ") + break; + + thisLine=(*line); + if (!alwaysSpace && (thisLine[0]==quoteChar)) + thisLine.prepend(quoteChar); // second quote level without space + else + thisLine.prepend(quoteChar+' '); + + thisPrefix=QString::null; + QChar c; + for(int idx=0; idx<(int)(thisLine.length()); idx++) { + c=thisLine.at(idx); + if( (c==' ') || + (c==quoteChar) || (c=='>') ||(c=='|') || (c==':') || (c=='#') || (c=='[') || (c=='{')) + thisPrefix.append(c); + else + break; + } + + thisLine.remove(0,thisPrefix.length()); + thisLine = thisLine.stripWhiteSpace(); + + if(!leftover.isEmpty()) { // don't break paragraphs, tables and quote levels + if(thisLine.isEmpty() || (thisPrefix!=lastPrefix) || thisLine.contains(" ") || thisLine.contains('\t')) + appendTextWPrefix(quoted, leftover, wrapAt, lastPrefix); + else + thisLine.prepend(leftover+" "); + leftover=QString::null; + } + + if((int)(thisPrefix.length()+thisLine.length()) > wrapAt) { + breakPos=findBreakPos(thisLine,wrapAt-thisPrefix.length()); + if(breakPos < (int)(thisLine.length())) { + leftover=thisLine.right(thisLine.length()-breakPos-1); + thisLine.truncate(breakPos); + } + } + + quoted+=thisPrefix+thisLine+"\n"; + lastPrefix=thisPrefix; + } + + if (!leftover.isEmpty()) + appendTextWPrefix(quoted, leftover, wrapAt, lastPrefix); + + return quoted; +} + +// **** misc. message-boxes ********************************************************** + +void KNHelper::displayInternalFileError(QWidget *w) +{ + KMessageBox::error((w!=0)? w : knGlobals.topWidget, i18n("Unable to load/save configuration.\nWrong permissions on home folder?\nYou should close KNode now to avoid data loss.")); +} + + +void KNHelper::displayExternalFileError(QWidget *w) +{ + KMessageBox::error((w!=0)? w : knGlobals.topWidget, i18n("Unable to load/save file.")); +} + + +void KNHelper::displayRemoteFileError(QWidget *w) +{ + KMessageBox::error((w!=0)? w : knGlobals.topWidget, i18n("Unable to save remote file.")); +} + + +void KNHelper::displayTempFileError(QWidget *w) +{ + KMessageBox::error((w!=0)? w : knGlobals.topWidget, i18n("Unable to create temporary file.")); +} diff --git a/knode/utilities.h b/knode/utilities.h new file mode 100644 index 000000000..31f83d989 --- /dev/null +++ b/knode/utilities.h @@ -0,0 +1,166 @@ +/* + KNode, the KDE newsreader + Copyright (c) 1999-2005 the KNode authors. + See file AUTHORS for details + + 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. + 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, US +*/ + +#ifndef UTILITIES_H +#define UTILITIES_H + +#include <kurl.h> + +#include <qfile.h> + +#include <qglobal.h> +#include <qptrvector.h> +#include <qptrlist.h> + +class QWidget; +class QString; +class QChar; +class QStringList; +class QSize; + +class KTempFile; + + +//***************************************************************************** +// utility classes +//***************************************************************************** + +/** clone of QSortedList... */ +template<class type> class Q_EXPORT QSortedVector : public QPtrVector<type> +{ +public: + QSortedVector() {} + QSortedVector ( uint size ) : QPtrVector<type>(size) {} + QSortedVector( const QSortedVector<type> &l ) : QPtrVector<type>(l) {} + ~QSortedVector() { QPtrVector<type>::clear(); } + QSortedVector<type> &operator=(const QSortedVector<type> &l) + { return (QSortedVector<type>&)QPtrList<type>::operator=(l); } + + virtual int compareItems( QPtrCollection::Item s1, QPtrCollection::Item s2 ) + { if ( *((type*)s1) == *((type*)s2) ) return 0; return ( *((type*)s1) < *((type*)s2) ? -1 : 1 ); } +}; + + +//============================================================================== + + +class KNFile : public QFile { + + public: + KNFile(const QString& fname=QString::null); + ~KNFile(); + const QCString& readLine(); + const QCString& readLineWnewLine(); + /** searches for the string from the current file position + returns -1 when the string wasn't found. */ + int findString(const char *s); + + protected: + bool increaseBuffer(); + + QCString buffer; + char *dataPtr; + int filePos, readBytes; +}; + + +//======================================================================================== + + +class KNSaveHelper { + +public: + + KNSaveHelper(QString saveName, QWidget *parent); + ~KNSaveHelper(); + + /** returns a file open for writing */ + QFile* getFile(const QString &dialogTitle); + +private: + + QWidget *p_arent; + QString s_aveName; + KURL url; + QFile* file; + KTempFile* tmpFile; + static QString lastPath; + +}; + + +//======================================================================================== + + +class KNLoadHelper { + +public: + + KNLoadHelper(QWidget *parent); + ~KNLoadHelper(); + + /** opens a file dialog and returns a file open for reading */ + KNFile* getFile( const QString &dialogTitle ); + /** tries to access the file specified by the url and returns + a file open for reading */ + KNFile* setURL(KURL url); + /** returns the file after getFile(QString) of setURL(url) was called */ + KNFile* getFile()const { return f_ile; }; + KURL getURL() const { return u_rl; }; + +private: + + QWidget *p_arent; + KURL u_rl; + KNFile *f_ile; + QString t_empName; + static QString l_astPath; + +}; + + +//======================================================================================== + + +class KNHelper { + +public: + + /** list selection dialog, used instead of a popup menu + when a select action is called via the keyboard. + returns -1 when the user canceled the dialog. */ + static int selectDialog(QWidget *parent, const QString &caption, const QStringList &options, int initialValue); + + static void saveWindowSize(const QString &name, const QSize &s); + static void restoreWindowSize(const QString &name, QWidget *d, const QSize &defaultSize); + + static const QString encryptStr(const QString& aStr); + static const QString decryptStr(const QString& aStr); + static QString rot13(const QString &s); + + /** used for rewarping a text when replying to a message or inserting a file into a box */ + static QString rewrapStringList(QStringList text, int wrapAt, QChar quoteChar, bool stopAtSig, bool alwaysSpace); + + /** use this for all internal files */ + static void displayInternalFileError(QWidget *w=0); + /** use this for all external files */ + static void displayExternalFileError(QWidget *w=0); + /** use this for remote files */ + static void displayRemoteFileError(QWidget *w=0); + /** use this for error on temporary files */ + static void displayTempFileError(QWidget *w=0); + +}; + +#endif |