/* Copyright (c) 2010, Stijn Leenknegt All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the KHL nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Stijn Leenknegt ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Stijn Leenknegt BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/#include <QPainter>#include <QFontMetrics>#include <QSizeF>#include <KLocale>#include <QWidget>#include <QString>#include <QDir>#include <QDomDocument>#include <kconfigdialog.h>#include <kconfiggroup.h>#include <kmessagebox.h>#include <plasma/theme.h>#include <Plasma/Separator>#include <KRichTextWidget>#include "entrywidget.h"#include "khluurrooster.h"KHLUurrooster::KHLUurrooster(QObject *parent, const QVariantList &args) : Plasma::Applet(parent, args){ // this will get us the standard applet background, for free! setHasConfigurationInterface(true); setBackgroundHints(DefaultBackground); resize(300, 200); //setup the rooster object rooster = new Uurrooster(); connect(rooster, SIGNAL(errorOccurred(QString)), this, SLOT(roosterError(QString))); connect(rooster, SIGNAL(entriesChanged()), this, SLOT(sendNextIcalLink())); //set the timer who get the latest uurrooster information online timerUpdate = new QTimer(this); connect(timerUpdate, SIGNAL(timeout()), this, SLOT(updateRoosters())); //set the refresh timer timerRefresh = new QTimer(this); timerRefresh->start(60 * 1000); //om de minuut de entries hertekenen connect(timerRefresh, SIGNAL(timeout()), this, SLOT(displayEntries())); //set the error timer timerError = new QTimer(this); connect(timerError, SIGNAL(timeout()), this, SLOT(updateRoosters())); //make layout for the widget layout = new QGraphicsGridLayout(this); setLayout(layout); //set date date = QDate::currentDate(); //set UI components headerLabel = new Plasma::Label(); vorigeButton = new Plasma::PushButton(); volgendeButton = new Plasma::PushButton(); m_scrollWidget = new Plasma::ScrollWidget(); m_scroller = new QGraphicsWidget(); m_scrollerLayout = new QGraphicsLinearLayout(Qt::Vertical, m_scroller); //configre the UI components headerLabel->setAlignment(Qt::AlignCenter); m_scrollWidget->setMinimumSize(300, 200); m_scrollWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_scrollWidget->setWidget(m_scroller); //connect the buttons connect(vorigeButton, SIGNAL(clicked()), this, SLOT(prevDate())); connect(volgendeButton, SIGNAL(clicked()), this, SLOT(nextDate())); //add to layout layout->addItem(headerLabel, 0, 0, 1, 2); layout->addItem(m_scrollWidget, 1, 0, 1, 2); layout->addItem(vorigeButton, 2, 0); layout->addItem(volgendeButton, 2, 1);}KHLUurrooster::~KHLUurrooster(){}void KHLUurrooster::init(){ readConfig(); //make ~/.khl_personal.xml file = new QFile(QDir::homePath() + "/.khl_personal.xml"); if(! file->exists()) { QDomDocument doc("personal"); QDomElement root = doc.createElement("entries"); doc.appendChild(root); file->open(QIODevice::WriteOnly); file->write(doc.toByteArray()); file->close(); } if(ical_1.isEmpty() && ical_2.isEmpty() && ical_3.isEmpty()) { //no configuration setted yet this->setConfigurationRequired(true, "Je moet op zijn minst 1 iCal link opgeven"); } else { configChanged(); }}void KHLUurrooster::showHelp(){ KDialog *dialog = new KDialog(); dialog->setCaption("Help pagina"); dialog->setButtons(KDialog::Ok); KRichTextWidget *widget = new KRichTextWidget(dialog); widget->setHtml("<h1>KHL Uurrooster KDE widget</h1><ul><li><a href=\"#over\">Over de widget</a></li><li><a href=\"#configuratie\">Configuratie</a></li><li><a href=\"#personal\">Zelf afspraken toevoegen</a></li></ul><a name=\"over\" /><h2>Over de widget</h2><p>De KHL Uurrooster KDE widget is een KDE widget. Een KDE widget is een programma dat draait op je bureaublad. De bedoeling van deze widget is om het uurrooster van de student te tonen. Omdat er op iedere uurrooster pagia van de KHL een iCal link is voorzien, werkt deze widget met die iCal kalender.</p><p>De widget laat de verschillende uurroosters zien in verschillende kleuren en kan ook zelfde vakken verbergen. Stel dat je les volgt in verschillende reeksen, dan kan het zijn dat die reeksen gemeenschappelijke vakken heeft, die kan je laten verbergen.</p><p>Je kan ook zelf afspraken toevoegen. Dit kan handig zijn om je uurrooster een persoonlijke touch te geven. Zo kan je afspraken met lectoren erbij plaatsen.</p><p>Deze widget is gemaakt door Stijn Leenknegt, student aan de KHL departement G&T, voor het vak Small ICT Projects. De widget kan je ook vinden op de volgende website: <a href=\"http://aiids.wordpress.com/khl-uurrooster-applet/\">http://aiids.wordpress.com/khl-uurrooster-applet/</<a>.</p><a name=\"configuratie\" /><h2>Configuratie</h2><p>Bij de configuratie heb je drie tabs: iCal instellingen, Opties en Afspraak toevoegen. De eerste tab is belangrijk. Wanneer je op de portaal site bent en je gaat naar je uurrooster zie je op de pagina de tekst <em>ICal-adres voor deze uurrooster</em> staan. De link moet je kopieren en plakken in een veldje. Je begint bij <em>iCal link 1</em> en gaat zo verder tot <em>iCal link 3</em>. Je kan dus drie uurroosters toevoegen indien je les volgt in meerdere reeksen.</p><p>Bij het tablblad <em>Opties</em> kan je de kleuren instellen en ook kiezen of je dubbele vakken wilt verbergen.</p><a name=\"personal\" /><h2>Zelf afspraken toevoegen</h2>Je kan ook zelf afspraken toevoegen. Bij de configuratie klik je op het derde tabblad en vul je de gegevens in. Wanneer je op toevoegen klikt, wordt alles toevoegd aan een XML bestand <em>(~/.khl_personal.xml)</em> en getoond in je uurrooster widget.</p>"); dialog->setMainWidget(widget); dialog->setGeometry(0, 0, 600, 450); dialog->show();}void KHLUurrooster::createConfigurationInterface(KConfigDialog *parent){ QWidget *widget = new QWidget(0); ui.setupUi(widget); //als er op ok of apply gedrukt is, moet de config opgeslaan worden connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted())); connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted())); connect(parent, SIGNAL(user1Clicked()), this, SLOT(showHelp())); //stel de value van de textbox in ui.txtIcal_1->setText(ical_1); ui.txtIcal_2->setText(ical_2); ui.txtIcal_3->setText(ical_3); //stel de opties in ui.checkDuplicates->setChecked(hide_duplicates); if(! ical_1_color.isEmpty()) { ui.comboKleur1->setCurrentIndex(ui.comboKleur1->findText(ical_1_color)); } if(! ical_2_color.isEmpty()) { ui.comboKleur2->setCurrentIndex(ui.comboKleur1->findText(ical_2_color)); } if(! ical_3_color.isEmpty()) { ui.comboKleur3->setCurrentIndex(ui.comboKleur1->findText(ical_3_color)); } if(! personal_color.isEmpty()) { ui.comboKleurA->setCurrentIndex(ui.comboKleur1->findText(personal_color)); } //personal entries toevoegen ui.dtStart->setDateTime(QDateTime::currentDateTime()); ui.dtEind->setDateTime(QDateTime::currentDateTime().addSecs(3600)); connect(ui.btnToevoegen, SIGNAL(clicked(bool)), this, SLOT(savePersonalEntry())); //alle persoonlijke entries toevoegen aan de delete lijst QList<uurrooster_entry> *p_entries = rooster->getPersonalEntries(); for(int i = 0 ; i < p_entries->size() ; i++) { ui.comboDelete->addItem(p_entries->at(i).beschrijving); } connect(ui.btnDelete, SIGNAL(clicked(bool)), this, SLOT(deletePersonalEntry())); parent->setButtons(KDialog::Ok | KDialog::Apply | KDialog::Cancel | KDialog::User1); parent->setButtonIcon(KDialog::User1, KIcon("help-contents")); parent->setButtonText(KDialog::User1, "Help"); parent->setGeometry(QRect(0, 0, 475, 350)); parent->addPage(widget, "KHL Uurrooster", "view-calendar-list");}void KHLUurrooster::readConfig(){ KConfigGroup cg = config(); ical_1 = cg.readEntry("ical_1"); ical_2 = cg.readEntry("ical_2"); ical_3 = cg.readEntry("ical_3"); ical_1_color = cg.readEntry("ical_1_color"); ical_2_color = cg.readEntry("ical_2_color"); ical_3_color = cg.readEntry("ical_3_color"); personal_color = cg.readEntry("personal_color"); hide_duplicates = (cg.readEntry("hide_duplicates") == "true") ? true : false;}void KHLUurrooster::configChanged(){ //stop the timers if(timerUpdate->isActive()) timerUpdate->stop(); if(timerRefresh->isActive()) timerRefresh->stop(); if(timerError->isActive()) timerError->stop(); //get the config readConfig(); //check if filled in if(ical_1.isEmpty() && ical_2.isEmpty() && ical_3.isEmpty()) { //no configuration setted yet this->setConfigurationRequired(true, "Je moet op zijn minst 1 iCal link opgeven"); } else { this->setConfigurationRequired(false, "Je moet op zijn minst 1 iCal link opgeven"); //update het uurrooster, hier wordt het ical terug opgehaald. ical_busy = 1; rooster->removeEntries(); rooster->update(ical_1, ical_1_color, hide_duplicates); //start the timer with a given timeout, default it's once a hour. It checks if the uurrooster is updated online or not. timerUpdate->start(60 * 60 * 1000); }}void KHLUurrooster::configAccepted(){ ical_1 = ui.txtIcal_1->text(); ical_2 = ui.txtIcal_2->text(); ical_3 = ui.txtIcal_3->text(); ical_1_color = ui.comboKleur1->currentText(); ical_2_color = ui.comboKleur2->currentText(); ical_3_color = ui.comboKleur3->currentText(); personal_color = ui.comboKleurA->currentText(); hide_duplicates = ui.checkDuplicates->isChecked(); KConfigGroup cg = config(); cg.writeEntry("ical_1", ical_1); cg.writeEntry("ical_2", ical_2); cg.writeEntry("ical_3", ical_3); cg.writeEntry("ical_1_color", ical_1_color); cg.writeEntry("ical_2_color", ical_2_color); cg.writeEntry("ical_3_color", ical_3_color); cg.writeEntry("personal_color", personal_color); cg.writeEntry("hide_duplicates", (hide_duplicates) ? "true" : "false"); emit configNeedsSaving();}void KHLUurrooster::paintInterface(QPainter *p, const QStyleOptionGraphicsItem *option, const QRect &contentsRect){ Q_UNUSED(option); p->setRenderHint(QPainter::SmoothPixmapTransform); p->setRenderHint(QPainter::Antialiasing); layout->setGeometry(contentsRect);}void KHLUurrooster::displayEntries(){ if(ical_1.isEmpty() && ical_2.isEmpty() && ical_3.isEmpty()) return; //ff de refreshtimer stoppen if(timerRefresh->isActive()) { timerRefresh->stop(); } //de uurrooster entries, mooi gesorteerd. QList<uurrooster_entry> *entries= rooster->getEntries(date); //delete body frame entries QGraphicsLayoutItem *item; while(m_scrollerLayout->count() > 0) { item = m_scrollerLayout->itemAt(m_scrollerLayout->count() - 1); m_scrollerLayout->removeItem(item); delete item; } //set the label text headerLabel->setText("<b>" + date.toString(Qt::DefaultLocaleLongDate) + "</b>"); //set the button labels vorigeButton->setText(date.addDays(-1).toString()); volgendeButton->setText(date.addDays(1).toString()); //add entries to frame if(entries->count() > 0) { EntryWidget *widget; Plasma::Separator *sep; QTime currTime = QTime::currentTime(); QDate currDate = QDate::currentDate(); //loop the entries for(int i = 0 ; i < entries->size() ; i++) { //markeer de items die nu bezig zijn in het bold if(entries->at(i).starttijd.date() == currDate && entries->at(i).starttijd.time() <= currTime && currTime <= entries->at(i).eindtijd.time()) { widget = new EntryWidget(entries->at(i), true); } else { widget = new EntryWidget(entries->at(i), false); } m_scrollerLayout->addItem(widget); //scheiding toevoegen tussen de verschillende entries if(i != entries->size()-1) { sep = new Plasma::Separator(); sep->setOrientation(Qt::Horizontal); m_scrollerLayout->addItem(sep); } } } else { Plasma::Label *nothingLabel; nothingLabel = new Plasma::Label(); nothingLabel->setText("Geen les vandaag! JOEPIE!"); nothingLabel->setAlignment(Qt::AlignCenter); m_scroller->resize(300, 200); m_scrollerLayout->addItem(nothingLabel); } //resume the refresh timer timerRefresh->start(60 * 1000);}void KHLUurrooster::displayError(const QString& error){ //stop de timers if(timerUpdate->isActive()) timerUpdate->stop(); if(timerRefresh->isActive()) timerRefresh->stop(); if(timerError->isActive()) timerError->stop(); //delete body frame entries QGraphicsLayoutItem *item; while(m_scrollerLayout->count() > 0) { item = m_scrollerLayout->itemAt(m_scrollerLayout->count() - 1); m_scrollerLayout->removeItem(item); delete item; } //display the error on applet Plasma::Label *errorLabel = new Plasma::Label(); errorLabel->setText("<b>Fout:</b><br />" + error); m_scroller->resize(300, 200); m_scrollerLayout->addItem(errorLabel); //start error timer met een interval van 10 seconden timerError->start(10 * 1000);}void KHLUurrooster::updateRoosters(){ if(timerError->isActive()) { timerError->stop(); } date = QDate::currentDate(); ical_busy = 1; rooster->removeEntries(); rooster->update(ical_1, ical_1_color, hide_duplicates);}void KHLUurrooster::roosterError(const QString& error){ displayError(error);}void KHLUurrooster::sendNextIcalLink(){ ical_busy += 1; if(ical_busy == 2) { rooster->update(ical_2, ical_2_color, hide_duplicates); } else if(ical_busy == 3) { rooster->update(ical_3, ical_3_color, hide_duplicates); } else { rooster->updatePersonal(personal_color); displayEntries(); }}void KHLUurrooster::nextDate(){ if(! timerError->isActive()) { date = date.addDays(1); displayEntries(); }}void KHLUurrooster::prevDate(){ if(! timerError->isActive()) { date = date.addDays(-1); displayEntries(); }}void KHLUurrooster::savePersonalEntry(){ QDomDocument *doc = new QDomDocument("personal"); if(file != NULL) delete file; file = new QFile(QDir::homePath() + "/.khl_personal.xml"); file->open(QIODevice::ReadWrite); doc->setContent(file); //find root QDomElement root = doc->documentElement(); //maak een element QDomElement entry = doc->createElement("entry"); QDomElement beschrijving = doc->createElement("beschrijving"); QDomElement locatie = doc->createElement("locatie"); QDomElement starttijd = doc->createElement("starttijd"); QDomElement eindtijd = doc->createElement("eindtijd"); //maak de content elementen QDomText txtBeschrijving = doc->createTextNode(ui.txtOmschrijving->text()); QDomText txtLocatie = doc->createTextNode(ui.txtLocatie->text()); QDomText txtStarttijd = doc->createTextNode(ui.dtStart->text()); QDomText txtEindtijd = doc->createTextNode(ui.dtEind->text()); //link elements met inhoud beschrijving.appendChild(txtBeschrijving); locatie.appendChild(txtLocatie); starttijd.appendChild(txtStarttijd); eindtijd.appendChild(txtEindtijd); //link elementen met de entry elementen entry.appendChild(beschrijving); entry.appendChild(locatie); entry.appendChild(starttijd); entry.appendChild(eindtijd); //append it to the xml root.appendChild(entry); //write it to the file file->resize(0); file->write(doc->toByteArray()); file->close(); //show message ui.comboDelete->addItem(ui.txtOmschrijving->text()); ui.txtLocatie->setText(""); ui.txtOmschrijving->setText(""); ui.dtStart->setDateTime(QDateTime::currentDateTime()); ui.dtEind->setDateTime(QDateTime::currentDateTime().addSecs(3600)); KMessageBox::information(NULL, "Afspraak toegevoegd!");}void KHLUurrooster::deletePersonalEntry(){ QString beschrijving = ui.comboDelete->currentText(); QDomDocument *doc = new QDomDocument("personal"); QFile *file = new QFile(QDir::homePath() + "/.khl_personal.xml"); file->open(QIODevice::ReadWrite); doc->setContent(file); //find root QDomElement root = doc->documentElement(); //iterate the personal elements QDomNodeList nodeList = root.elementsByTagName("entry"); QDomNode el; for(int i = 0 ; i < nodeList.size() ; i++) { el = nodeList.at(i); if(el.childNodes().at(0).childNodes().at(0).nodeValue() == beschrijving) { root.removeChild(el); } } file->resize(0); file->write(doc->toByteArray()); file->close(); ui.comboDelete->removeItem(ui.comboDelete->currentIndex()); KMessageBox::information(NULL, "Afspraak verwijderd!"); updateRoosters();}#include "khluurrooster.moc"