From 7b6b7a359d0d64a839d9b197f3a49451b3f01c5d Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Sat, 16 Apr 2005 00:25:26 +0000 Subject: [PATCH] KPDF HotNewStuff Browser! It's not yet complete (file dowload and internal notifies are missing) but the new stuff browser is running. The ui is modeled on an Aaron's mockup. NewStuff: Fetches the providers list (kpdf.kde.org/newstuff/providers.xml), lets the user select a provider and displays provider's contents in a KHMTL part (for flexibility and eye candy). Internally we use KNS' Entry and Provider structures only, doing all the transfer operations by hand via KIO::get jobs. Download will be done internally too (to give better consistancy in the interface). Network timeouts, problems or info messages are notified in a statusbar- like widget and we try to keep all information in the dialog, without using messageBoxes Part: Added the action and the dialog invocation Makefiles: Link the KNEWSTUFF and KHTML libraries. svn path=/branches/kpdf_annotations/kdegraphics/kpdf/; revision=405828 --- Makefile.am | 3 +- part.cpp | 14 +- part.h | 1 + part.rc | 2 + ui/Makefile.am | 2 +- ui/data/Makefile.am | 4 + ui/data/ghns.png | Bin 0 -> 34875 bytes ui/data/ghns_star.png | Bin 0 -> 930 bytes ui/data/ghns_star_gray.png | Bin 0 -> 772 bytes ui/data/sources/ghns.svg | 3106 ++++++++++++++++++++++++++++++++++++ ui/newstuff.cpp | 686 ++++++++ ui/newstuff.h | 52 + 12 files changed, 3867 insertions(+), 3 deletions(-) create mode 100644 ui/data/ghns.png create mode 100644 ui/data/ghns_star.png create mode 100644 ui/data/ghns_star_gray.png create mode 100644 ui/data/sources/ghns.svg create mode 100644 ui/newstuff.cpp create mode 100644 ui/newstuff.h diff --git a/Makefile.am b/Makefile.am index e34b72df2..a66a8244f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,7 +19,8 @@ libkpdfpart_la_SOURCES = dcop.skel error.cpp part.cpp libkpdfpart_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) libkpdfpart_la_LIBADD = xpdf/xpdf/libxpdf.la conf/libkpdfconf.la core/libkpdfcore.la \ ui/libkpdfui.la ui/painter_agg2/libagg2.la $(LIB_KPARTS) \ - $(LIB_KFILE) $(LIB_KDEPRINT) $(LIB_KUTILS) -lm + $(LIB_KFILE) $(LIB_KDEPRINT) $(LIB_KUTILS) $(LIB_KNEWSTUFF) \ + $(LIB_KHTML) -lm partdesktopdir = $(kde_servicesdir) partdesktop_DATA = kpdf_part.desktop diff --git a/part.cpp b/part.cpp index daa9f61ce..949b83d29 100644 --- a/part.cpp +++ b/part.cpp @@ -61,6 +61,7 @@ #include "ui/thumbnaillist.h" #include "ui/side_reviews.h" #include "ui/minibar.h" +#include "ui/newstuff.h" #include "ui/propertiesdialog.h" #include "ui/presentationwidget.h" #include "conf/preferencesdialog.h" @@ -232,12 +233,15 @@ Part::Part(QWidget *parentWidget, const char *widgetName, m_printPreview = KStdAction::printPreview( this, SLOT( slotPrintPreview() ), ac ); m_printPreview->setEnabled( false ); - m_showLeftPanel = new KToggleAction( i18n( "Show &left panel"), "show_side_panel", 0, this, SLOT( slotShowLeftPanel() ), ac, "show_leftpanel" ); + m_showLeftPanel = new KToggleAction( i18n( "Show &left panel"), "show_side_panel", 0, this, SLOT( slotShowLeftPanel() ), ac, "show_leftpanel" ); m_showLeftPanel->setShortcut( "CTRL+L" ); m_showLeftPanel->setCheckedState( i18n("Hide &left panel") ); m_showLeftPanel->setChecked( Settings::showLeftPanel() ); slotShowLeftPanel(); + KAction * ghns = new KAction(i18n("&Get new stuff"), "knewstuff", 0, this, SLOT(slotGetNewStuff()), ac, "get_new_stuff"); + ghns->setShortcut( "G" ); // TEMP, REMOVE ME! + m_showProperties = new KAction(i18n("&Properties"), "info", 0, this, SLOT(slotShowProperties()), ac, "properties"); m_showProperties->setEnabled( false ); @@ -565,6 +569,14 @@ void Part::slotSaveFileAs() } } +void Part::slotGetNewStuff() +{ + // show the modal dialog over pageview and execute it + NewStuffDialog * dialog = new NewStuffDialog( m_pageView ); + dialog->exec(); + delete dialog; +} + void Part::slotPreferences() { // an instance the dialog could be already created and could be cached, diff --git a/part.h b/part.h index da4adfa59..85b8ea9f8 100644 --- a/part.h +++ b/part.h @@ -96,6 +96,7 @@ protected slots: void slotFind(); void slotFindNext(); void slotSaveFileAs(); + void slotGetNewStuff(); void slotPreferences(); void slotNewConfig(); void slotPrintPreview(); diff --git a/part.rc b/part.rc index 2db92ac0f..95733bbfa 100644 --- a/part.rc +++ b/part.rc @@ -6,6 +6,8 @@ + + &Edit diff --git a/ui/Makefile.am b/ui/Makefile.am index 2a2e9a899..e0072343e 100644 --- a/ui/Makefile.am +++ b/ui/Makefile.am @@ -8,7 +8,7 @@ noinst_LTLIBRARIES = libkpdfui.la libkpdfui_la_SOURCES = pagepainter.cpp pageview.cpp pageviewutils.cpp \ pageviewannotator.cpp minibar.cpp thumbnaillist.cpp \ searchwidget.cpp toc.cpp propertiesdialog.cpp \ - presentationwidget.cpp side_reviews.cpp + presentationwidget.cpp side_reviews.cpp newstuff.cpp pageview.lo: ../conf/settings.h pageviewutils.lo: ../conf/settings.h diff --git a/ui/data/Makefile.am b/ui/data/Makefile.am index 07da4a779..cd4d4b03a 100644 --- a/ui/data/Makefile.am +++ b/ui/data/Makefile.am @@ -10,6 +10,10 @@ annpics_DATA = checkmark.png circle.png comment.png cross.png help.png \ rightarrow.png rightpointer.png star.png uparrow.png \ upleftarrow.png +# install misc images +miscpicsdir = $(kde_datadir)/kpdf/pics +miscpics_DATA = ghns.png ghns_star.png ghns_star_gray.png + # install annotator xml tools description xmldir = $(kde_datadir)/kpdf xml_DATA = tools.xml diff --git a/ui/data/ghns.png b/ui/data/ghns.png new file mode 100644 index 0000000000000000000000000000000000000000..16b98e1373ec6b9c1f24f709b4eef2c5c2fd49ba GIT binary patch literal 34875 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 bwu{?$?#4=8viMR+@Jmig`FXjd@%#S+_o|;j literal 0 HcmV?d00001 diff --git a/ui/data/ghns_star.png b/ui/data/ghns_star.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb01cd4e104cf92ebc1ea5e0436dfb4c31090cd GIT binary patch literal 930 zcmV;T16}-yP)N?Z120KL zK~zY`#g%VJRc9Q>Kj;26uU`M$tGioEZL*rvExd&>5p)s5EA%GD8U(Eo$>tx}8*`4m z@Re8_sWMGyRV z;GEy{<@^0Tzu)(Pr@iDyRLDjutmZo`Bd5^393cZJ0knEaN@Ik2+;zn4(kUba6u<8f+utrlb#$gh#* zHB@e<&a&D>OEfYgj7}-Ui&BVuDdOKt?Hv}&17ut73fW13CAOiHi$Q=tcu5vkWuf!{ zZd2nW>}8dyKg7CQy>4y@gQ<9op}5Yd=PZ+dV=3|!gVIF~ZalbBA*B$M05*8`_mlX%6&g7_@+P(0S|yN zU=~;c6f*!MAOH{GHTNw*8pdFZ>Bd%ZW{4IAVD>ZK@Yy*h4>UP>?(}2L4FIZ>Sbvn6 zrfL22Nj`2Frt9DOenL<-vmox4OQEMpp=E9m8B#hLs^59#NGVG-TQ~wd%L;t} z4AhXjJ()|6%1v7d0&$s`rRIQeGSS`zCOWhS)pw}xeZ*z6e*Xmq_~|XOUeBi6S)~M( zf_3C;cGQbDMml464-D|ZV3@1GLc#$FeZxbylWrv{VQHO+#<8h7VZkq}K@+;05oM3f z3xroT`6_)jNb*VCaZ1d;8{4L$uoFA5XnvRi;y@;l3K*tV5BS~WCEG|>#Xs2q*w>iO zF^Y^N>TG_K@jZx-L!=LuZvatnqysvjfqSyH=C<6M$||gG?6FWdHyG07*qoM6N<$ Eg3hCzX#fBK literal 0 HcmV?d00001 diff --git a/ui/data/ghns_star_gray.png b/ui/data/ghns_star_gray.png new file mode 100644 index 0000000000000000000000000000000000000000..698d93e48945e50b3ff3b255f0c98e170d71f989 GIT binary patch literal 772 zcmV+f1N;1mP)Q^f_>K?)aU3I#V+bLT%jNDWrP#i~oO2jsV2pw9`v5SN&1MU`9ipCV z4*@6v7zQu|;I!}i(~L1Rn@s?K@B1*uAW4!rB3cCS(>YfGP%RdV%iCbB^)~>=0UQPZ z&No2Lp-!T9Sob#Zc(%xaM zMVhAd&CShwNs_#iQm%9ruTrTDd!DyIL?;GH7h`ScO08C#X|-DKrIf$^cohJ!w6t`T zb3RW*WBtI+IT&NU*J`!D+0E`20gC_6X zzZ>-S{%nu!!Vpml4P!2F3*Qyc+nU$ou=t(#}Cc`$n;Zu%{ga{F|WcfyuPuq z@eIIMDP;@*Qp$8>WaLE@MJJ6hv&I<57#jzW>x6hafL}*Zw3sBxCjiS*${k}kIyxFD zr5*%9@SJmg8$bYH?XOi=pB0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + New + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + New + Get + Get + Exciting + Exciting + Stuff + Stuff + + + + + + + + + + + + + + + diff --git a/ui/newstuff.cpp b/ui/newstuff.cpp new file mode 100644 index 000000000..8f122d519 --- /dev/null +++ b/ui/newstuff.cpp @@ -0,0 +1,686 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros * + * * + * 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. * + ***************************************************************************/ + +// qt/kde includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// local includes +#include "newstuff.h" + +// define the providers.xml location +#define PROVIDERS_URL "http://kpdf.kde.org/newstuff/providers.xml" +// define the type of the stuff returned by providers (unused) +#define RES_TYPE "kpdf/stuff" + +/** GUI: Internal Widget that displays a ver-stretchable (only) image */ +class ExtendImageWidget : public QWidget +{ + public: + ExtendImageWidget( const QPixmap & pix, QWidget * parent ) + : QWidget( parent, "", WNoAutoErase ), m_pixmap( pix ) + { + // adjust size hint + setFixedWidth( pix.width() ); + setMinimumHeight( pix.height() ); + // paint all own pixels + setBackgroundMode( Qt::NoBackground ); + // create the tile image from last line of pixmap + m_tile.resize( pix.width(), 1 ); + copyBlt( &m_tile, 0,0, &pix, 0,pix.height()-2, pix.width(),1 ); + } + + // internal paint function + void paintEvent( QPaintEvent * e ) + { + // extend the bottom line of the image when painting + QRegion paintRegion = e->region(); + QRect pixmapRect = e->rect().intersect( m_pixmap.rect() ); + // paint the pixmap rect + QPainter p( this ); + p.drawPixmap( pixmapRect.topLeft(), m_pixmap, pixmapRect ); + // paint the tiled bottom part + QMemArray rects = paintRegion.subtract( pixmapRect ).rects(); + for ( unsigned int i = 0; i < rects.count(); i++ ) + { + const QRect & tileRect = rects[ i ]; + p.drawTiledPixmap( tileRect, m_tile, QPoint(tileRect.left(), 0) ); + } + } + + private: + QPixmap m_pixmap; + QPixmap m_tile; +}; + + +/** CORE: extend KNS::Entry adding convenience methods */ +class AvailableItem : public Entry +{ + public: + typedef QValueList< AvailableItem * > List; + AvailableItem( const QDomElement & element, const QString & providerName ) + : Entry( element ) + { + QString remoteUrl = payload().url(); + QString fileName = remoteUrl.section( '/', -1, -1 ); + QString extension = fileName.lower().section( '.', -1, -1 ); + // place books on the desktop + if ( providerName.lower().contains( "book" ) ) + m_destinationFile = KGlobalSettings::desktopPath() + "/" + fileName; + // place kpdf data on the local share/apps/kpdf/stuff + else if ( providerName.lower().contains( "kpdf" ) ) + m_destinationFile = locateLocal( "data", "kpdf/stuff/" + fileName ); + // warn about unrecognized provider + else + kdDebug() << "NewStuffDialog: AvailableItem: unrecognized provider name." << endl; + } + + // returns local destination path for the item + const QString & destinationPath() + { + return m_destinationFile; + } + + // checks if the item is already installed + bool installed() + { + return QFile::exists( m_destinationFile ); + } + + private: + QString m_destinationFile; +}; + + +/** GUI/CORE: HTML Widget to operate on AvailableItem::List */ +class ItemsView : public KHTMLPart +{ + public: + ItemsView( QWidget * parent ) + : KHTMLPart( parent, "newStuffHTMLView" ), m_sorting( 0 ) + { + // customize functionality + setJScriptEnabled( true ); + setJavaEnabled( false ); + setMetaRefreshEnabled( false ); + setPluginsEnabled( false ); + setStandardFont( QApplication::font().family() ); + // 100% is too large! less is better + setZoomFactor( 70 ); + } + + ~ItemsView() + { + clear(); + } + + void setItems( const AvailableItem::List & itemList ) + { + clear(); + m_items = itemList; + buildContents(); + } + + void setSorting( int sortType ) + { + m_sorting = sortType; + buildContents(); + } + + private: + // generate the HTML contents to be displayed by the class itself + void buildContents() + { + // try to get informations in current locale + QString preferredLanguage = KGlobal::locale()->language(); + + // write the html code to be rendered + begin(); + setTheAaronnesqueStyle(); + write( "" ); + + AvailableItem::List::iterator it = m_items.begin(), iEnd = m_items.end(); + for ( ; it != iEnd; ++it ) + { + AvailableItem * item = *it; + + // precalc the image string + QString imageString = item->preview( preferredLanguage ).url(); + if ( imageString.length() > 1 ) + imageString = "

"; + + // precalc the title string + QString titleString = item->name(); + if ( item->version().length() > 0 ) + titleString += " v." + item->version(); + + // precalc the string for displaying stars (normal+grayed) + int starPixels = 11 + 11 * (item->rating() / 10); + QString starsString = "
"; + int grayPixels = 22 + 22 * (item->rating() / 20); + starsString = "
" + starsString + "
"; + + // precalc the string for displaying author (parsing email) + QString authorString = item->author(); + QString emailString = authorString.section( '(', 1, 1 ); + if ( emailString.contains( '@' ) && emailString.contains( ')' ) ) + { + emailString = emailString.remove( ')' ).stripWhiteSpace(); + authorString = authorString.section( '(', 0, 0 ).stripWhiteSpace(); + authorString = "" + authorString + ""; + } + + // write the HTML code for the current item + write( QString( + "" + "" + "" + "
" + // image + + imageString + + // button + "
" + "" + "
" + "
" + // contents header: item name/score + "" + "" + "" + "
" + titleString + "" + starsString + "
" + // contents body: item description + "
" + + item->summary( preferredLanguage ) + + "
" + // contents footer: author's name/date + "
" + "" + authorString + ", " + + KGlobal::locale()->formatDate( item->releaseDate(), true ) + + "
" + "
" ) + ); + } + + write( "" ); + end(); + } + + // this is the stylesheet we use + void setTheAaronnesqueStyle() + { + QString hoverColor = "#000000"; //QApplication::palette().active().highlightedText().name(); + QString hoverBackground = "#f8f8f8"; //QApplication::palette().active().highlight().name(); + QString starIconPath = locate( "data", "kpdf/pics/ghns_star.png" ); + QString starBgIconPath = locate( "data", "kpdf/pics/ghns_star_gray.png" ); + + // default elements style + QString style; + style += "body { background-color: white; color: black; padding: 0; margin: 0; }"; + style += "table, td, th { padding: 0; margin: 0; text-align: left; }"; + style += "input { color: #000080; font-size:120%; }"; + + // the main item container (custom element) + style += ".itemBox { background-color: white; color: black; width: 100%; border-bottom: 1px solid gray; margin: 0px 0px; }"; + style += ".itemBox:hover { background-color: " + hoverBackground + "; color: " + hoverColor + "; }"; + + // style of the item elements (4 cells with multiple containers) + style += ".leftColumn { width: 100px; height:100%; text-align: center; }"; + style += ".leftImage {}"; + style += ".leftButton {}"; + style += ".contentsColumn { vertical-align: top; }"; + style += ".contentsHeader { width: 100%; font-size: 120%; font-weight: bold; border-bottom: 1px solid #c8c8c8; }"; + style += ".contentsBody {}"; + style += ".contentsFooter {}"; + style += ".star { width: 0px; height: 24px; background-image: url(" + starIconPath + "); background-repeat: repeat-x; }"; + style += ".starbg { width: 110px; height: 24px; background-image: url(" + starBgIconPath + "); background-repeat: repeat-x; }"; + setUserStyleSheet( style ); + } + + // handle clicks on page links/buttons + void urlSelected( const QString & link, int, int, const QString &, KParts::URLArgs ) + { + KURL url( link ); + QString urlProtocol = url.protocol(); + QString urlPath = url.path(); + + if ( urlProtocol == "mailto" ) + { + // clicked over a mail address + kapp->invokeMailer( url ); + } + else if ( urlProtocol == "item" ) + { + // clicked over an item + bool ok; + unsigned long itemPointer = urlPath.toULong( &ok ); + if ( !ok ) + { + kdWarning() << "ItemsView: error converting item pointer." << endl; + return; + } + // I love to cast pointers + AvailableItem * item = (AvailableItem *)itemPointer; + if ( !m_items.contains( item ) ) + { + kdWarning() << "ItemsView: error retrieving item pointer." << endl; + return; + } + // perform operations on the AvailableItem (maybe already installed) + bool itemInstalled = item->installed(); + kdDebug() << itemInstalled << endl; + } + } + + // delete all the classes we own + void clear() + { + // delete all items and empty local list + AvailableItem::List::iterator it = m_items.begin(), iEnd = m_items.end(); + for ( ; it != iEnd; ++it ) + delete *it; + m_items.clear(); + } + + private: + AvailableItem::List m_items; + int m_sorting; +}; + + +/** CORE/GUI: The main dialog that performs GHNS low level operations **/ +/* internal storage structures to be used by NewStuffDialog */ +struct ProviderJobInfo +{ + const Provider * provider; + QString receivedData; +}; +struct NewStuffDialogPrivate +{ + // network classes for providers/items/files + ProviderLoader * providerLoader; + Provider::List * providersList; + QMap< KIO::Job *, ProviderJobInfo > providerJobs; + QMap< KIO::Job *, QString > transferJobs; + + // gui related vars + QWidget * parentWidget; + QLineEdit * searchLine; + QComboBox * typeCombo; + QComboBox * sortCombo; + ItemsView * itemsView; + QLabel * messageLabel; + + // other classes + QTimer * messageTimer; + QTimer * networkTimer; +}; + +NewStuffDialog::NewStuffDialog( QWidget * parentWidget ) + : QDialog( parentWidget, "kpdfNewStuff" ), d( new NewStuffDialogPrivate ) +{ + // initialize the private classes + d->providerLoader = 0; + d->providersList = 0; + d->parentWidget = parentWidget; + + d->messageTimer = new QTimer( this ); + connect( d->messageTimer, SIGNAL( timeout() ), + this, SLOT( slotResetMessageColors() ) ); + + d->networkTimer = new QTimer( this ); + connect( d->networkTimer, SIGNAL( timeout() ), + this, SLOT( slotNetworkTimeout() ) ); + + // popuplate dialog with stuff + QBoxLayout * horLay = new QHBoxLayout( this, 11 ); + + // create left picture widget (if picture found) + QPixmap p( locate( "data", "kpdf/pics/ghns.png" ) ); + if ( !p.isNull() ) + horLay->addWidget( new ExtendImageWidget( p, this ) ); + + // create right 'main' widget + QVBox * rightLayouter = new QVBox( this ); + rightLayouter->setSpacing( 6 ); + horLay->addWidget( rightLayouter ); + + // create upper label + QLabel * mainLabel = new QLabel( i18n("All you ever wanted, in a click!"), rightLayouter); + QFont mainFont = mainLabel->font(); + mainFont.setBold( true ); + mainLabel->setFont( mainFont ); + + // create the control panel + QFrame * panelFrame = new QFrame( rightLayouter ); + panelFrame->setFrameStyle( QFrame::StyledPanel | QFrame::Raised ); + QGridLayout * panelLayout = new QGridLayout( panelFrame, 2, 4, 11, 6 ); + // add widgets to the control panel + QLabel * label1 = new QLabel( i18n("Show:"), panelFrame ); + panelLayout->addWidget( label1, 0, 0 ); + d->typeCombo = new QComboBox( false, panelFrame ); + panelLayout->addWidget( d->typeCombo, 0, 1 ); + d->typeCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum ); + d->typeCombo->setMinimumWidth( 150 ); + d->typeCombo->setEnabled( false ); + connect( d->typeCombo, SIGNAL( activated(int) ), + this, SLOT( slotLoadProvider(int) ) ); + + QLabel * label2 = new QLabel( i18n("Order by:"), panelFrame ); + panelLayout->addWidget( label2, 0, 2 ); + d->sortCombo = new QComboBox( false, panelFrame ); + panelLayout->addWidget( d->sortCombo, 0, 3 ); + d->sortCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum ); + d->sortCombo->setMinimumWidth( 100 ); + d->sortCombo->setEnabled( false ); + connect( d->sortCombo, SIGNAL( activated(int) ), + this, SLOT( slotSortingSelected(int) ) ); + + QLabel * label3 = new QLabel( i18n("Search:"), panelFrame ); + panelLayout->addWidget( label3, 1, 0 ); + d->searchLine = new QLineEdit( panelFrame ); + panelLayout->addMultiCellWidget( d->searchLine, 1, 1, 1, 3 ); + d->searchLine->setEnabled( false ); + + // create the ItemsView used to display available items + d->itemsView = new ItemsView( rightLayouter ); + + // create bottom buttons + QHBox * bottomLine = new QHBox( rightLayouter ); + // create info label + d->messageLabel = new QLabel( bottomLine ); + d->messageLabel->setFrameStyle( QFrame::StyledPanel | QFrame::Raised ); + d->messageLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ); + // close button + QPushButton * closeButton = new QPushButton( i18n("Close"), bottomLine ); + //closeButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) + connect( closeButton, SIGNAL( clicked() ), this, SLOT( accept() ) ); + + // start with a nice size + resize( 700, 400 ); + slotResetMessageColors(); + + // start loading providers list + QTimer::singleShot( 100, this, SLOT( slotLoadProviders() ) ); +} + +NewStuffDialog::~NewStuffDialog() +{ + delete d->providerLoader; + delete d; +} + +void NewStuffDialog::displayMessage( const QString & msg, MessageType type, int timeOutMs ) +{ + // stop the pending timer if present + if ( d->messageTimer ) + d->messageTimer->stop(); + + // set background color based on message type + switch ( type ) + { + case Info: + d->messageLabel->setPaletteForegroundColor( palette().active().highlightedText() ); + d->messageLabel->setPaletteBackgroundColor( palette().active().highlight() ); + break; + case Error: + d->messageLabel->setPaletteForegroundColor( Qt::white ); + d->messageLabel->setPaletteBackgroundColor( Qt::red ); + break; + default: + slotResetMessageColors(); + break; + } + + // set text to messageLabel + d->messageLabel->setText( msg ); + + // single shot the resetColors timer (and create it if null) + d->messageTimer->start( timeOutMs, true ); +} + + +void NewStuffDialog::slotResetMessageColors() // SLOT +{ + d->messageLabel->setPaletteForegroundColor( palette().active().text() ); + d->messageLabel->setPaletteBackgroundColor( palette().active().background() ); +} + +void NewStuffDialog::slotNetworkTimeout() // SLOT +{ + displayMessage( i18n("Timeout. Check internet connection!"), Error ); +} + +void NewStuffDialog::slotSortingSelected( int sortType ) // SLOT +{ + d->itemsView->setSorting( sortType ); +} + + +//BEGIN Providers Loading +void NewStuffDialog::slotLoadProviders() +{ + // create the helpful loader class (may be done internally in future) + if ( !d->providerLoader ) + { + d->providerLoader = new ProviderLoader( d->parentWidget ); + connect( d->providerLoader, SIGNAL( providersLoaded( Provider::List* ) ), + this, SLOT( slotProvidersLoaded( Provider::List* ) ) ); + } + + // start loading + d->providersList = 0; + d->providerLoader->load( RES_TYPE, PROVIDERS_URL ); + + // start the 'network watchdog timer' + d->networkTimer->start( 20*1000, true /*single shot*/ ); + + // inform the user + displayMessage( i18n("Loading providers list...") ); +} + +void NewStuffDialog::slotProvidersLoaded( Provider::List * list ) +{ + // stop network watchdog + d->networkTimer->stop(); + + // safety check + d->providersList = list; + if ( !list || !list->count() ) + { + displayMessage( i18n("Problems loading providers. Please retry later."), Error ); + return; + } + + // update and PopUp the providers ComboBox + d->typeCombo->clear(); + int providersNumber = list->count(); + for ( int i = 0; i < providersNumber; i++ ) + { + const Provider * provider = list->at( i ); + // provider icon: using local KIconLoader, not loading from remote url + QPixmap icon = DesktopIcon( provider->icon().url(), 16 ); + QString name = provider->name(); + // insert provider in combo + d->typeCombo->insertItem( icon, name ); + } + + // inform the user + displayMessage( i18n("Loaded %1 providers").arg( providersNumber ), Info ); + + // automatically load the first provider + d->typeCombo->setEnabled( true ); + d->typeCombo->setCurrentItem( 0 ); + QTimer::singleShot( 500, this, SLOT( slotLoadProvider() ) ); +} +//END Providers Loading + +//BEGIN AvailableItem(s) Loading +void NewStuffDialog::slotLoadProvider( int pNumber ) +{ + // safety check + if ( !d->providersList || !d->typeCombo->isEnabled() || + pNumber < 0 || pNumber >= (int)d->providersList->count() ) + { + displayMessage( i18n("Error with this provider"), Error ); + return; + } + + // create a job that will feed provider data + const Provider * provider = d->providersList->at( pNumber ); + KIO::TransferJob * job = KIO::get( provider->downloadUrl(), false /*refetch*/, false /*progress*/ ); + connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ), + this, SLOT( slotProviderInfoData( KIO::Job *, const QByteArray & ) ) ); + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( slotProviderInfoResult( KIO::Job * ) ) ); + + // create a job description and data holder + ProviderJobInfo info; + info.provider = provider; + d->providerJobs[ job ] = info; + + // inform the user + displayMessage( i18n("Loading %1...").arg( provider->name() ) ); + + // start the 'network watchdog timer' + d->networkTimer->start( 30*1000, true /*single shot*/ ); + + // block any possible recourring calls while we're running + d->typeCombo->setEnabled( false ); +} + +void NewStuffDialog::slotProviderInfoData( KIO::Job * job, const QByteArray & data ) +{ + // safety check + if ( data.isEmpty() || !d->providerJobs.contains( job ) ) + return; + + // append the data buffer to the 'receivedData' string + QCString str( data, data.size() + 1 ); + d->providerJobs[ job ].receivedData.append( QString::fromUtf8( str ) ); +} + +void NewStuffDialog::slotProviderInfoResult( KIO::Job * job ) +{ + // stop network watchdog + d->networkTimer->stop(); + + // enable gui controls + d->typeCombo->setEnabled( true ); + d->sortCombo->setEnabled( true ); + + // safety check + if ( job->error() || !d->providerJobs.contains( job ) || + d->providerJobs[ job ].receivedData.isEmpty() || + d->providerJobs[ job ].receivedData.contains("404 Not Found") ) + { + displayMessage( i18n("Error loading provider description!"), Error ); + return; + } + + // build XML DOM from the received data + QDomDocument doc; + bool docOk = doc.setContent( d->providerJobs[ job ].receivedData ); + QString providerName = d->providerJobs[ job ].provider->name(); + d->providerJobs.remove( job ); + if ( !docOk ) + { + displayMessage( i18n("Problems parsing provider description."), Info ); + return; + } + + // create AvailableItem(s) based on the knewstuff.dtd + AvailableItem::List itemList; + QDomNode stuffNode = doc.documentElement().firstChild(); + while ( !stuffNode.isNull() ) + { + QDomElement elem = stuffNode.toElement(); + stuffNode = stuffNode.nextSibling(); + // WARNING: disabled the stuff type checking (use only in kate afaik) + if ( elem.tagName() == "stuff" /*&& elem.attribute( "type", RES_TYPE ) == RES_TYPE*/ ) + itemList.append( new AvailableItem( elem, providerName ) ); + } + + // update the control widget and inform user about the current operation + d->itemsView->setItems( itemList ); + if ( itemList.count() ) + displayMessage( i18n("You have %1 resources available.").arg( itemList.count() ) ); + else + displayMessage( i18n("No resources available on this provider." ) ); +} +//END AvailableItem(s) loading + +/* UNCOMPRESS (specify uncompression method) +KTar tar( fileName, "application/x-gzip" ); +tar.open( IO_ReadOnly ); +const KArchiveDirectory *dir = tar.directory(); +dir->copyTo( "somedir" ); +tar.close(); +QFile::remove( fileName ); +*/ + +/* EXECUTE (specify the 'cmd' command) +QStringList list = QStringList::split( " ", cmd ), + list2; +for ( QStringList::iterator it = list.begin(); it != list.end(); it++ ) + list2 << (*it).replace("%f", fileName); +KProcess proc; +proc << list2; +proc.start( KProcess::Block ); +*/ + +/* callbacks +emit something(); +*/ + +/* +QString target = item ? item->fullName() : "/"; +QString path = "/tmp"; //locateLocal( resourceString.utf8(), target) +QString file = path + "/" + target; + +if ( KStandardDirs::exists( file ) ) + if ( KMessageBox::questionYesNo( d->parentWidget, + i18n("The file '%1' already exists. Do you want to override it?") + .arg( file ), + QString::null, i18n("Overwrite") ) == KMessageBox::No ) + return QString(); +*/ + +//END of the NewStuffDialog + +#include "newstuff.moc" diff --git a/ui/newstuff.h b/ui/newstuff.h new file mode 100644 index 000000000..1823e3e0a --- /dev/null +++ b/ui/newstuff.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2005 by Enrico Ros * + * * + * 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. * + ***************************************************************************/ + +#ifndef _KPDF_NEWSTUFF_H_ +#define _KPDF_NEWSTUFF_H_ + +#include +#include +using namespace KNS; +namespace KIO { class JOB; } + +class AvailableItem; + +class NewStuffDialog : public QDialog +{ + Q_OBJECT + public: + NewStuffDialog( QWidget * parent ); + ~NewStuffDialog(); + + // show a message in the bottom bar + enum MessageType { Normal, Info, Error }; + void displayMessage( const QString & msg, MessageType type = Normal, + int timeOutMs = 3000 ); + + private: + // private storage class + class NewStuffDialogPrivate * d; + + private slots: + void slotResetMessageColors(); + void slotNetworkTimeout(); + void slotSortingSelected( int sortType ); + // providers loading related + void slotLoadProviders(); + void slotProvidersLoaded( Provider::List * list ); + // items loading related + void slotLoadProvider( int provider = 0 ); + void slotProviderInfoData( KIO::Job *, const QByteArray & ); + void slotProviderInfoResult( KIO::Job * ); + // files downloading related + //void slotDownloadItem( AvailableItem * ); + //void slotItemDownloaded( KIO::Job * ); +}; + +#endif