diff --git a/incidenceeditor-ng/combinedincidenceeditor.cpp b/incidenceeditor-ng/combinedincidenceeditor.cpp index 91c8a991b2..db4153bb58 100644 --- a/incidenceeditor-ng/combinedincidenceeditor.cpp +++ b/incidenceeditor-ng/combinedincidenceeditor.cpp @@ -1,122 +1,154 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "combinedincidenceeditor.h" #include using namespace IncidenceEditorNG; /// public methods CombinedIncidenceEditor::CombinedIncidenceEditor( QWidget *parent ) : IncidenceEditor( parent ), mDirtyEditorCount( 0 ) { } CombinedIncidenceEditor::~CombinedIncidenceEditor() { qDeleteAll( mCombinedEditors ); } void CombinedIncidenceEditor::combine( IncidenceEditor *other ) { Q_ASSERT( other ); mCombinedEditors.append( other ); connect( other, SIGNAL(dirtyStatusChanged(bool)), SLOT(handleDirtyStatusChange(bool)) ); } bool CombinedIncidenceEditor::isDirty() const { return mDirtyEditorCount > 0; } bool CombinedIncidenceEditor::isValid() const { foreach ( IncidenceEditor *editor, mCombinedEditors ) { if ( !editor->isValid() ) { const QString reason = editor->lastErrorString(); editor->focusInvalidField(); if ( !reason.isEmpty() ) { emit showMessage( reason, KMessageWidget::Warning ); } return false; } } return true; } void CombinedIncidenceEditor::handleDirtyStatusChange( bool isDirty ) { const int prevDirtyCount = mDirtyEditorCount; Q_ASSERT( mDirtyEditorCount >= 0 ); if ( isDirty ) { ++mDirtyEditorCount; } else { --mDirtyEditorCount; } Q_ASSERT( mDirtyEditorCount >= 0 ); if ( prevDirtyCount == 0 ) { emit dirtyStatusChanged( true ); } if ( mDirtyEditorCount == 0 ) { emit dirtyStatusChanged( false ); } } void CombinedIncidenceEditor::load( const KCalCore::Incidence::Ptr &incidence ) { mLoadedIncidence = incidence; foreach ( IncidenceEditor *editor, mCombinedEditors ) { // load() may fire dirtyStatusChanged(), reset mDirtyEditorCount to make sure // we don't end up with an invalid dirty count. editor->blockSignals( true ); editor->load( incidence ); editor->blockSignals( false ); if ( editor->isDirty() ) { // We are going to crash due to assert. Print some useful info before crashing. kWarning() << "Faulty editor was " << editor->objectName(); kWarning() << "Incidence " << ( incidence ? incidence->uid() : "null" ); editor->printDebugInfo(); Q_ASSERT_X( false, "load", "editor shouldn't be dirty" ); } } mWasDirty = false; mDirtyEditorCount = 0; emit dirtyStatusChanged( false ); } +void CombinedIncidenceEditor::load( const Akonadi::Item &item ) +{ + foreach ( IncidenceEditor *editor, mCombinedEditors ) { + // load() may fire dirtyStatusChanged(), reset mDirtyEditorCount to make sure + // we don't end up with an invalid dirty count. + editor->blockSignals( true ); + editor->load( item ); + editor->blockSignals( false ); + + if ( editor->isDirty() ) { + // We are going to crash due to assert. Print some useful info before crashing. + kWarning() << "Faulty editor was " << editor->objectName(); + // kWarning() << "Incidence " << ( incidence ? incidence->uid() : "null" ); + + editor->printDebugInfo(); + + Q_ASSERT_X( false, "load", "editor shouldn't be dirty" ); + } + } + + mWasDirty = false; + mDirtyEditorCount = 0; + emit dirtyStatusChanged( false ); +} + void CombinedIncidenceEditor::save( const KCalCore::Incidence::Ptr &incidence ) { foreach ( IncidenceEditor *editor, mCombinedEditors ) { editor->save( incidence ); } } +void CombinedIncidenceEditor::save( Akonadi::Item &item ) +{ + foreach ( IncidenceEditor *editor, mCombinedEditors ) { + editor->save( item ); + } +} + diff --git a/incidenceeditor-ng/combinedincidenceeditor.h b/incidenceeditor-ng/combinedincidenceeditor.h index 63839d90cd..f299d9eb9b 100644 --- a/incidenceeditor-ng/combinedincidenceeditor.h +++ b/incidenceeditor-ng/combinedincidenceeditor.h @@ -1,77 +1,80 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef COMBINEDINCIDENCEEDITOR_H #define COMBINEDINCIDENCEEDITOR_H #include "incidenceeditors-ng_export.h" #include "incidenceeditor-ng.h" #include +#include namespace IncidenceEditorNG { /** * The CombinedIncidenceEditor combines optional widgets with zero or more * IncidenceEditors. The CombinedIncidenceEditor keeps track of the dirty state * of the IncidenceEditors that where combined. */ class INCIDENCEEDITORS_NG_EXPORT CombinedIncidenceEditor : public IncidenceEditor { Q_OBJECT public: explicit CombinedIncidenceEditor( QWidget *parent = 0 ); /** * Deletes this editor as well as all editors which are combined into this * one. */ ~CombinedIncidenceEditor(); void combine( IncidenceEditor *other ); /** * Returns whether or not the current values in the editor differ from the * initial values or if one of the combined editors is dirty. */ virtual bool isDirty() const; virtual bool isValid() const; /** * Loads all data from @param inicidence into the combined editors. Note, if * you reimplement the load method in a subclass, make sure to call this * implementation too. */ virtual void load( const KCalCore::Incidence::Ptr &incidence ); + virtual void load( const Akonadi::Item &item ); virtual void save( const KCalCore::Incidence::Ptr &incidence ); + virtual void save( Akonadi::Item &item ); private Q_SLOTS: void handleDirtyStatusChange( bool isDirty ); Q_SIGNALS: void showMessage( const QString &reason, KMessageWidget::MessageType ) const; private: QVector mCombinedEditors; int mDirtyEditorCount; }; } #endif // COMBINEDINCIDENCEEDITOR_H diff --git a/incidenceeditor-ng/dialogdesktop.ui b/incidenceeditor-ng/dialogdesktop.ui index fa610b22e1..00a2fb49d2 100644 --- a/incidenceeditor-ng/dialogdesktop.ui +++ b/incidenceeditor-ng/dialogdesktop.ui @@ -1,1707 +1,1707 @@ EventOrTodoDesktop 0 0 909 833 0 0 0 Qt::LeftToRight Calendar: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 You are Invited, what do you want to do? Accept the invitation Click this button to accept the invitation. Accept Decline Qt::Horizontal 40 20 0 0 75 true Set the title Sets the Title of this event or to-do. Qt::LeftToRight T&itle: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter mSummaryEdit 0 0 Set the title Sets the Title of this event or to-do. 0 0 Qt::LeftToRight &Location: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter mLocationEdit 0 0 Set the location Sets where the event or to-do will take place. Qt::Horizontal Completion: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 100 10 true Qt::Horizontal QSlider::TicksBelow 10 0 0 completed Priority: 5 unspecified 1 (highest) 2 3 4 5 (medium) 6 7 8 9 (lowest) 0 0 Set if this to-do's start and due dates have times associated with them. All Day true Qt::Horizontal QSizePolicy::Fixed 40 20 Blocks me for other events Qt::Horizontal 40 20 0 0 Qt::LeftToRight &Start: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter mStartDateEdit 0 0 Set the start date Sets the start date for this to-do false 0 0 Sets the start time for this to-do. false 0 0 Select the timezone for this event. It will also affect recurrences 0 0 false <a href="hide"><font color='blue'>&lt;&lt; Time zones</font></a> Qt::RichText Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Qt::Horizontal 40 20 0 0 Qt::LeftToRight &End: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter mEndDateEdit 0 0 Sets the due date for this to-do. false 0 0 Sets the due time for this to-do. false 0 0 Select the timezone for this event. It will also affect recurrences Qt::Horizontal 40 0 Qt::Horizontal - 2 + 0 General - + 0 0 Set the secrecy level Sets whether the access to this event or to-do is restricted. Please note that KOrganizer currently does not use this setting, so the implementation of the restrictions will depend on the groupware server. This means that events or to-dos marked as private or confidential may be visible to others. Access: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 <a href=\"hide\"><font color='blue'>Rich text &gt;&gt;</font></a> Qt::RichText Qt::Horizontal 40 20 Set the description in plain text or rich text. Sets the description for this event, to-do or journal. This will be displayed in a reminder if one is set, as well as in a tooltip when you hover over the event. true 0 0 Categories: Set the secrecy level Sets whether the access to this event or to-do is restricted. Please note that KOrganizer currently does not use this setting, so the implementation of the restrictions will depend on the groupware server. This means that events or to-dos marked as private or confidential may be visible to others. Attendees QAbstractItemView::AllEditTriggers false Meeting organizer: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Participants: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Schedule the meeting with your attendees. Opens the scheduling dialog, which will assist in picking a time slot that works for all attendees. Schedule... Qt::Horizontal 258 24 false Substitute with group members Select attendees from your address book. Opens your address book, allowing you to select new attendees from it. Select Attendees... 0 0 1 0 0 0 Set the organizer identity Sets the identity corresponding to the organizer of this to-do or event. Identities can be set in the 'Personal' section of the KOrganizer configuration, or in the Personal'->'About Me'->'Password & User Account' section of the System Settings. In addition, identities are gathered from your KMail settings and from your address book. If you choose to set it globally for KDE in the System Settings, be sure to check 'Use email settings from System Settings' in the 'Personal' section of the KOrganizer configuration. 0 0 0 TextLabel Resources QAbstractItemView::AllEditTriggers true Qt::SolidLine false false false true true Book resource false false Qt::Horizontal QSizePolicy::Preferred 40 0 Find Resources... Reminder 0 0 Add default reminder: 0 0 0 0 Add Qt::Horizontal 141 22 QAbstractItemView::NoEditTriggers New Configure Disable Remove Qt::Vertical 88 126 Recurrence 0 0 Repeats: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Never Daily Weekly Monthly Yearly 0 0 every 0 0 1 999 1 0 0 day(s) This and future occurrences. Qt::Horizontal 40 20 0 0 2 0 0 0 0 QComboBox::AdjustToContentsOnFirstShow 0 0 0 QComboBox::AdjustToContents Qt::Horizontal 40 20 0 0 0 QComboBox::AdjustToContents Qt::Horizontal 40 20 0 0 Ends: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 never on after 0 0 2 0 0 0 0 Date after which the event or to-do should stop recurring Qt::Horizontal 276 18 0 0 1 9999 0 0 occurrence(s) 0 0 Exceptions: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 false Add Qt::Horizontal 40 20 0 0 QAbstractItemView::ExtendedSelection on: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Remove Qt::Vertical 20 40 Attachments 0 0 0 0 Add an attachment Shows a dialog used to select an attachment to add to this event or to-do as link or as inline data. Add false 0 0 Remove the selected attachment Removes the attachment selected in the list above from this event or to-do. Remove Qt::Vertical 20 210 false KRichTextEdit KTextEdit
krichtextedit.h
KTimeComboBox KComboBox
ktimecombobox.h
KDateComboBox KComboBox
kdatecombobox.h
KComboBox QComboBox
kcombobox.h
KLineEdit QLineEdit
klineedit.h
KSeparator QFrame
kseparator.h
1
KTextEdit QTextEdit
ktextedit.h
KRichTextWidget KRichTextEdit
krichtextwidget.h
KTabWidget QTabWidget
ktabwidget.h
1
KPIM::KCheckComboBox KComboBox
libkdepim/widgets/kcheckcombobox.h
KPIM::KWeekdayCheckCombo KPIM::KCheckComboBox
libkdepim/widgets/kweekdaycheckcombo.h
IncidenceEditorNG::KTimeZoneComboBox KComboBox
ktimezonecombobox.h
CalendarSupport::MessageWidget QWidget
calendarsupport/messagewidget.h
- KPIM::TagWidget + Akonadi::TagWidget QWidget -
libkdepim/widgets/tagwidgets.h
+
akonadi/tagwidget.h
1
mRecurrenceEndCombo activated(int) mRecurrenceEndStack setCurrentIndex(int) 144 314 144 314 mRecurrenceTypeCombo activated(int) mRepeatStack setCurrentIndex(int) 142 304 104 280 mNewResource returnPressed() mBookResourceButton click() 271 805 402 808
diff --git a/incidenceeditor-ng/editoritemmanager.cpp b/incidenceeditor-ng/editoritemmanager.cpp index 20ddc7ed6d..0a022a750b 100644 --- a/incidenceeditor-ng/editoritemmanager.cpp +++ b/incidenceeditor-ng/editoritemmanager.cpp @@ -1,406 +1,413 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "editoritemmanager.h" #include "individualmailcomponentfactory.h" #include #include #include #include #include #include #include #include #include +#include #include #include #include #include /// ItemEditorPrivate namespace IncidenceEditorNG { class ItemEditorPrivate { EditorItemManager *q_ptr; Q_DECLARE_PUBLIC( EditorItemManager ) public: Akonadi::Item mItem; Akonadi::Item mPrevItem; Akonadi::ItemFetchScope mFetchScope; Akonadi::Monitor *mItemMonitor; ItemEditorUi *mItemUi; bool mIsCounterProposal; EditorItemManager::SaveAction currentAction; Akonadi::IncidenceChanger *mChanger; public: ItemEditorPrivate( Akonadi::IncidenceChanger *changer, EditorItemManager *qq ); void itemChanged( const Akonadi::Item &, const QSet & ); void itemFetchResult( KJob *job ); void itemMoveResult( KJob *job ); void onModifyFinished( int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ); void onCreateFinished( int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ); void setupMonitor(); void moveJobFinished( KJob *job ); + void setItem( const Akonadi::Item &item ); }; ItemEditorPrivate::ItemEditorPrivate( Akonadi::IncidenceChanger *changer, EditorItemManager *qq ) - : q_ptr( qq ), mItemMonitor( 0 ), mIsCounterProposal( false ) + : q_ptr( qq ), /* mItemMonitor( 0 ), */mIsCounterProposal( false ) , currentAction(EditorItemManager::None) { mFetchScope.fetchFullPayload(); mFetchScope.setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); + mFetchScope.setFetchTags( true ); + mFetchScope.tagFetchScope().setFetchIdOnly(false); mChanger = changer ? changer : new Akonadi::IncidenceChanger( new IndividualMailComponentFactory(qq), qq ); qq->connect( mChanger, SIGNAL(modifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)), qq, SLOT(onModifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)) ); qq->connect( mChanger, SIGNAL(createFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)), qq, SLOT(onCreateFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)) ); } void ItemEditorPrivate::moveJobFinished( KJob *job ) { Q_Q( EditorItemManager ); if ( job->error() ) { kError() << "Error while moving and modifying " << job->errorString(); mItemUi->reject( ItemEditorUi::ItemMoveFailed, job->errorString() ); } else { Akonadi::Item item( mItem.id() ); currentAction = EditorItemManager::MoveAndModify; q->load( item ); } } void ItemEditorPrivate::itemFetchResult( KJob *job ) { Q_ASSERT( job ); Q_Q( EditorItemManager ); EditorItemManager::SaveAction action = currentAction; currentAction = EditorItemManager::None; if ( job->error() ) { mItemUi->reject( ItemEditorUi::ItemFetchFailed, job->errorString() ); return; } Akonadi::ItemFetchJob *fetchJob = qobject_cast( job ); if ( fetchJob->items().isEmpty() ) { mItemUi->reject( ItemEditorUi::ItemFetchFailed ); return; } Akonadi::Item item = fetchJob->items().first(); if ( mItemUi->hasSupportedPayload( item ) ) { - q->load( item ); + setItem( item ); if ( action != EditorItemManager::None ) { // Finally enable ok/apply buttons, we've finished loading emit q->itemSaveFinished( action ); } } else { mItemUi->reject( ItemEditorUi::ItemHasInvalidPayload ); } } +void ItemEditorPrivate::setItem( const Akonadi::Item &item ) +{ + Q_ASSERT( item.hasPayload() ); + mPrevItem = item; + mItem = item; + mItemUi->load( item ); + setupMonitor(); +} + void ItemEditorPrivate::itemMoveResult( KJob *job ) { Q_ASSERT( job ); Q_Q( EditorItemManager ); if ( job->error() ) { Akonadi::ItemMoveJob *moveJob = qobject_cast( job ); Q_ASSERT( moveJob ); Q_UNUSED( moveJob ); //Q_ASSERT( !moveJob->items().isEmpty() ); // TODO: What is reasonable behavior at this point? kError() << "Error while moving item ";// << moveJob->items().first().id() << " to collection " //<< moveJob->destinationCollection() << job->errorString(); emit q->itemSaveFailed( EditorItemManager::Move, job->errorString() ); } else { // Fetch the item again, we want a new mItem, which has an updated parentCollection Akonadi::Item item( mItem.id() ); // set currentAction, so the fetchResult slot emits itemSavedFinished( Move ); // We could emit it here, but we should only enable ok/apply buttons after the loading // is complete currentAction = EditorItemManager::Move; q->load( item ); } } void ItemEditorPrivate::onModifyFinished( int, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ) { Q_Q( EditorItemManager ); if ( resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess ) { if ( mItem.parentCollection() == mItemUi->selectedCollection() || mItem.storageCollectionId() == mItemUi->selectedCollection().id()) { mItem = item; emit q->itemSaveFinished( EditorItemManager::Modify ); setupMonitor(); } else { // There's a collection move too. Akonadi::ItemMoveJob *moveJob = new Akonadi::ItemMoveJob( mItem, mItemUi->selectedCollection() ); q->connect( moveJob, SIGNAL(result(KJob*)), SLOT(moveJobFinished(KJob*)) ); } } else if ( resultCode == Akonadi::IncidenceChanger::ResultCodeUserCanceled ) { emit q->itemSaveFailed( EditorItemManager::Modify, QString() ); q->load( Akonadi::Item( mItem.id() ) ); } else { kError() << "Modify failed " << errorString; emit q->itemSaveFailed( EditorItemManager::Modify, errorString ); } } void ItemEditorPrivate::onCreateFinished( int, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ) { Q_Q( EditorItemManager ); if ( resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess ) { q->load( item ); emit q->itemSaveFinished( EditorItemManager::Create ); setupMonitor(); } else { kError() << "Creation failed " << errorString; emit q->itemSaveFailed( EditorItemManager::Create, errorString ); } } void ItemEditorPrivate::setupMonitor() { // Q_Q( EditorItemManager ); delete mItemMonitor; mItemMonitor = new Akonadi::Monitor; mItemMonitor->ignoreSession( Akonadi::Session::defaultSession() ); mItemMonitor->itemFetchScope().fetchFullPayload(); if ( mItem.isValid() ) { mItemMonitor->setItemMonitored( mItem ); } // q->connect( mItemMonitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), // SLOT(itemChanged(Akonadi::Item,QSet)) ); } void ItemEditorPrivate::itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ) { Q_Q( EditorItemManager ); if ( mItemUi->containsPayloadIdentifiers( partIdentifiers ) ) { QPointer dlg = new QMessageBox; //krazy:exclude=qclasses dlg->setIcon( QMessageBox::Question ); dlg->setInformativeText( i18n( "The item has been changed by another application.\n" "What should be done?" ) ); dlg->addButton( i18n( "Take over changes" ), QMessageBox::AcceptRole ); dlg->addButton( i18n( "Ignore and Overwrite changes" ), QMessageBox::RejectRole ); if ( dlg->exec() == QMessageBox::AcceptRole ) { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( mItem ); job->setFetchScope( mFetchScope ); mItem = item; q->load( mItem ); } else { mItem.setRevision( item.revision() ); q->save(); } delete dlg; } // Overwrite or not, we need to update the revision and the remote id to be able // to store item later on. mItem.setRevision( item.revision() ); } /// ItemEditor EditorItemManager::EditorItemManager( ItemEditorUi *ui, Akonadi::IncidenceChanger *changer ) : d_ptr( new ItemEditorPrivate( changer, this ) ) { Q_D( ItemEditor ); d->mItemUi = ui; } EditorItemManager::~EditorItemManager() { delete d_ptr; } Akonadi::Item EditorItemManager::item( ItemState state ) const { Q_D( const ItemEditor ); switch ( state ) { case EditorItemManager::AfterSave: if ( d->mItem.hasPayload() ) { return d->mItem; } else { kDebug() << "Won't return mItem because isValid = " << d->mItem.isValid() << "; and haPayload is " << d->mItem.hasPayload(); } break; case EditorItemManager::BeforeSave: if ( d->mPrevItem.hasPayload() ) { return d->mPrevItem; } else { kDebug() << "Won't return mPrevItem because isValid = " << d->mPrevItem.isValid() << "; and haPayload is " << d->mPrevItem.hasPayload(); } break; default: kDebug() << "state = " << state; Q_ASSERT_X( false, "EditorItemManager::item", "Unknown enum value" ) ; } return Akonadi::Item(); } void EditorItemManager::load( const Akonadi::Item &item ) { Q_D( ItemEditor ); - if ( item.hasPayload() ) { - d->mPrevItem = item; - d->mItem = item; - d->mItemUi->load( item ); - d->setupMonitor(); - } else { - Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( item, this ); - job->setFetchScope( d->mFetchScope ); - connect( job, SIGNAL(result(KJob*)), SLOT(itemFetchResult(KJob*)) ); - } + //We fetch anyways to make sure we have everything required including tags + Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( item, this ); + job->setFetchScope( d->mFetchScope ); + connect( job, SIGNAL(result(KJob*)), SLOT(itemFetchResult(KJob*)) ); } void EditorItemManager::save() { Q_D( ItemEditor ); if ( !d->mItemUi->isValid() ) { emit itemSaveFailed( d->mItem.isValid() ? Modify : Create, QString() ); return; } if ( !d->mItemUi->isDirty() && d->mItemUi->selectedCollection() == d->mItem.parentCollection() ) { // Item did not change and was not moved emit itemSaveFinished( None ); return; } d->mChanger->setGroupwareCommunication( CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication() ); Akonadi::Item updateItem = d->mItemUi->save( d->mItem ); Q_ASSERT( updateItem.id() == d->mItem.id() ); d->mItem = updateItem; if ( d->mItem.isValid() ) { // A valid item. Means we're modifying. Q_ASSERT( d->mItem.parentCollection().isValid() ); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( d->mItem ); KCalCore::Incidence::Ptr oldPayload = CalendarSupport::incidence( d->mPrevItem ); if ( d->mItem.parentCollection() == d->mItemUi->selectedCollection() || d->mItem.storageCollectionId() == d->mItemUi->selectedCollection().id()) { d->mChanger->modifyIncidence( d->mItem, oldPayload ); } else { Q_ASSERT( d->mItemUi->selectedCollection().isValid() ); Q_ASSERT( d->mItem.parentCollection().isValid() ); // ETM and the KSelectionProxyModel has a bug wrt collections moves, so this is disabled. // To test this, enable the collection combo-box and remove the following assert. kError() << "Moving between collections is disabled for now: " << d->mItemUi->selectedCollection().id() << d->mItem.parentCollection().id(); Q_ASSERT_X( false, "save()", "Moving between collections is disabled for now" ); if ( d->mItemUi->isDirty() ) { d->mChanger->modifyIncidence( d->mItem, oldPayload ); } else { Akonadi::ItemMoveJob *itemMoveJob = new Akonadi::ItemMoveJob( d->mItem, d->mItemUi->selectedCollection() ); connect( itemMoveJob, SIGNAL(result(KJob*)), SLOT(itemMoveResult(KJob*)) ); } } } else { // An invalid item. Means we're creating. if ( d->mIsCounterProposal ) { // We don't write back to akonadi, that will be done in ITipHandler. emit itemSaveFinished( EditorItemManager::Modify ); } else { Q_ASSERT( d->mItemUi->selectedCollection().isValid() ); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( d->mItem ); d->mChanger->createIncidence( incidence, d->mItemUi->selectedCollection() ); } } } void EditorItemManager::setFetchScope( const Akonadi::ItemFetchScope &fetchScope ) { Q_D( ItemEditor ); d->mFetchScope = fetchScope; } Akonadi::ItemFetchScope &EditorItemManager::fetchScope() { Q_D( ItemEditor ); return d->mFetchScope; } void EditorItemManager::setIsCounterProposal( bool isCounterProposal ) { Q_D( ItemEditor ); d->mIsCounterProposal = isCounterProposal; } Akonadi::Collection EditorItemManager::collection() const { Q_D( const ItemEditor ); return d->mChanger->lastCollectionUsed(); } ItemEditorUi::~ItemEditorUi() { } bool ItemEditorUi::isValid() const { return true; } } // namespace #include "moc_editoritemmanager.cpp" diff --git a/incidenceeditor-ng/incidencecategories.cpp b/incidenceeditor-ng/incidencecategories.cpp index 4169d36a30..8593d628b0 100644 --- a/incidenceeditor-ng/incidencecategories.cpp +++ b/incidenceeditor-ng/incidencecategories.cpp @@ -1,139 +1,108 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "incidencecategories.h" #include "editorconfig.h" -#ifdef KDEPIM_MOBILE_UI - #include "ui_dialogmoremobile.h" -#else - #include "ui_dialogdesktop.h" -#endif +#include "ui_dialogdesktop.h" #include #include #include #include using namespace IncidenceEditorNG; -#ifdef KDEPIM_MOBILE_UI -IncidenceCategories::IncidenceCategories( Ui::EventOrTodoMore *ui ) -#else IncidenceCategories::IncidenceCategories( Ui::EventOrTodoDesktop *ui ) -#endif : mUi( ui ) { setObjectName( "IncidenceCategories" ); -#ifdef KDEPIM_MOBILE_UI -connect( mUi->mSelectCategoriesButton, SIGNAL(clicked()), - SLOT(selectCategories()) ); -#else - connect( mUi->mTagWidget, SIGNAL(selectionChanged(QStringList)), - SLOT(onSelectionChanged(QStringList)) ); -#endif + connect( mUi->mTagWidget, SIGNAL(selectionChanged(Akonadi::Tag::List)), + SLOT(onSelectionChanged(Akonadi::Tag::List)) ); } -void IncidenceCategories::onSelectionChanged(const QStringList &list) +void IncidenceCategories::onSelectionChanged(const Akonadi::Tag::List &list) { - mSelectedCategories = list; + mSelectedTags = list; + mDirty = true; checkDirtyStatus(); } void IncidenceCategories::load( const KCalCore::Incidence::Ptr &incidence ) { mLoadedIncidence = incidence; + mDirty = false; if ( mLoadedIncidence ) { checkForUnknownCategories( mLoadedIncidence->categories() ); - setCategories( mLoadedIncidence->categories() ); } else { - setCategories( QStringList() ); + mSelectedTags.clear(); } mWasDirty = false; } +void IncidenceCategories::load( const Akonadi::Item &item ) +{ + mSelectedTags = item.tags(); + mUi->mTagWidget->setSelection(item.tags()); +} + void IncidenceCategories::save( const KCalCore::Incidence::Ptr &incidence ) { Q_ASSERT( incidence ); incidence->setCategories( categories() ); } -QStringList IncidenceCategories::categories() const +void IncidenceCategories::save( Akonadi::Item &item ) { - return mSelectedCategories; + item.setTags(mSelectedTags); } -bool IncidenceCategories::isDirty() const -{ - // If no Incidence was loaded, mSelectedCategories should be empty. - bool categoriesEqual = categories().isEmpty(); - - if ( mLoadedIncidence ) { // There was an Incidence loaded - categoriesEqual = - ( mLoadedIncidence->categories().toSet() == categories().toSet() ); - } - return !categoriesEqual; -} - -void IncidenceCategories::selectCategories() +QStringList IncidenceCategories::categories() const { - QPointer dialog( new KPIM::TagSelectionDialog() ); - dialog->setSelection( categories() ); - dialog->exec(); - - if (dialog) { - setCategories( dialog->selection() ); - delete dialog; + QStringList list; + Q_FOREACH (const Akonadi::Tag &tag, mSelectedTags) { + list << tag.name(); } + return list; } -void IncidenceCategories::setCategories( const QStringList &categories ) +bool IncidenceCategories::isDirty() const { - mSelectedCategories = categories; -#ifdef KDEPIM_MOBILE_UI - mUi->mCategoriesLabel->setText( mSelectedCategories.join( QLatin1String( "," ) ) ); -#else - mUi->mTagWidget->setSelection(categories); -#endif - - checkDirtyStatus(); + return mDirty; } void IncidenceCategories::printDebugInfo() const { kDebug() << "mSelectedCategories = " << categories(); kDebug() << "mLoadedIncidence->categories() = " << mLoadedIncidence->categories(); } void IncidenceCategories::checkForUnknownCategories( const QStringList &categoriesToCheck ) { -#ifndef KDEPIM_MOBILE_UI // desktop only foreach ( const QString &category, categoriesToCheck ) { Akonadi::TagCreateJob *tagCreateJob = new Akonadi::TagCreateJob(Akonadi::Tag(category), this); tagCreateJob->setMergeIfExisting(true); + //TODO add the missing tags to the item and add them to the list of selected tags in the widget } -#else - Q_UNUSED( categoriesToCheck ); -#endif } diff --git a/incidenceeditor-ng/incidencecategories.h b/incidenceeditor-ng/incidencecategories.h index b2e408f9ea..bb25321551 100644 --- a/incidenceeditor-ng/incidencecategories.h +++ b/incidenceeditor-ng/incidencecategories.h @@ -1,84 +1,73 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCIDENCEEDITOR_INCIDENCECATEGORIES_H #define INCIDENCEEDITOR_INCIDENCECATEGORIES_H #include "incidenceeditor-ng.h" namespace Ui { class EventOrTodoDesktop; class EventOrTodoMore; } namespace IncidenceEditorNG { class INCIDENCEEDITORS_NG_EXPORT IncidenceCategories : public IncidenceEditor { Q_OBJECT public: -#ifdef KDEPIM_MOBILE_UI - explicit IncidenceCategories( Ui::EventOrTodoMore *ui ); -#else explicit IncidenceCategories( Ui::EventOrTodoDesktop *ui ); -#endif virtual void load( const KCalCore::Incidence::Ptr &incidence ); + virtual void load( const Akonadi::Item &item ); virtual void save( const KCalCore::Incidence::Ptr &incidence ); - - /** - * Sets the currently selected categories. - */ - void setCategories( const QStringList &categories ); + virtual void save( Akonadi::Item &item ); /** * Returns the list of currently selected categories. */ QStringList categories() const; virtual bool isDirty() const; /**reimp*/ void printDebugInfo() const; private slots: - void selectCategories(); - void onSelectionChanged(const QStringList &); + void onSelectionChanged(const Akonadi::Tag::List &); private: /** If the incidence comes from outside of KDE it can contain unknown categories. * KOrganizer usually checks for these, but it can happen that it checks before the * items are in the ETM, due to akonadi's async nature. * So we make the check inside the editor, and add new categories to config. This way * the editor can be used standalone too. * */ void checkForUnknownCategories( const QStringList &categoriesToCheck ); - QStringList mSelectedCategories; -#ifdef KDEPIM_MOBILE_UI - Ui::EventOrTodoMore *mUi; -#else Ui::EventOrTodoDesktop *mUi; -#endif + Akonadi::Tag::List mSelectedTags; + bool mDirty; }; } #endif diff --git a/incidenceeditor-ng/incidencedialog.cpp b/incidenceeditor-ng/incidencedialog.cpp index 440fb54ee8..13e022738a 100644 --- a/incidenceeditor-ng/incidencedialog.cpp +++ b/incidenceeditor-ng/incidencedialog.cpp @@ -1,834 +1,838 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Copyright (C) 2012 Allen Winter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "incidencedialog.h" #include "combinedincidenceeditor.h" #include "editorconfig.h" #include "incidencealarm.h" #include "incidenceattachment.h" #include "incidenceattendee.h" #include "incidencecategories.h" #include "incidencecompletionpriority.h" #include "incidencedatetime.h" #include "incidencedescription.h" #include "incidencerecurrence.h" #include "incidenceresource.h" #include "incidencesecrecy.h" #include "incidencewhatwhere.h" #include "templatemanagementdialog.h" #include "ui_dialogdesktop.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; namespace IncidenceEditorNG { enum Tabs { GeneralTab = 0, AttendeesTab, ResourcesTab, AlarmsTab, RecurrenceTab, AttachmentsTab }; class IncidenceDialogPrivate : public ItemEditorUi { IncidenceDialog *q_ptr; Q_DECLARE_PUBLIC( IncidenceDialog ) public: Ui::EventOrTodoDesktop *mUi; Akonadi::CollectionComboBox *mCalSelector; bool mCloseOnSave; EditorItemManager *mItemManager; CombinedIncidenceEditor *mEditor; IncidenceDateTime *mIeDateTime; IncidenceAttendee *mIeAttendee; IncidenceRecurrence *mIeRecurrence; IncidenceResource *mIeResource; bool mInitiallyDirty; Akonadi::Item mItem; QString typeToString( const int type ) const; public: IncidenceDialogPrivate( Akonadi::IncidenceChanger *changer, IncidenceDialog *qq ); ~IncidenceDialogPrivate(); /// General methods void handleAlarmCountChange( int newCount ); void handleRecurrenceChange( IncidenceEditorNG::RecurrenceType type ); void loadTemplate( const QString &templateName ); void manageTemplates(); void saveTemplate( const QString &templateName ); void storeTemplatesInConfig( const QStringList &newTemplates ); void updateAttachmentCount( int newCount ); void updateAttendeeCount( int newCount ); void updateResourceCount( int newCount ); void updateButtonStatus( bool isDirty ); void showMessage( const QString &text, KMessageWidget::MessageType type ); /// ItemEditorUi methods virtual bool containsPayloadIdentifiers( const QSet &partIdentifiers ) const; void handleItemSaveFinish( EditorItemManager::SaveAction ); void handleItemSaveFail( EditorItemManager::SaveAction, const QString &errorMessage ); virtual bool hasSupportedPayload( const Akonadi::Item &item ) const; virtual bool isDirty() const; virtual bool isValid() const; virtual void load( const Akonadi::Item &item ); virtual Akonadi::Item save( const Akonadi::Item &item ); virtual Akonadi::Collection selectedCollection() const; void slotButtonClicked( int button ); virtual void reject( RejectReason reason, const QString &errorMessage = QString() ); }; } IncidenceDialogPrivate::IncidenceDialogPrivate( Akonadi::IncidenceChanger *changer, IncidenceDialog *qq ) : q_ptr( qq ), mUi( new Ui::EventOrTodoDesktop ), mCalSelector( new Akonadi::CollectionComboBox ), mCloseOnSave( false ), mItemManager( new EditorItemManager( this, changer ) ), mEditor( new CombinedIncidenceEditor(qq) ), mInitiallyDirty( false ) { Q_Q( IncidenceDialog ); mUi->setupUi( q->mainWidget() ); QGridLayout *layout = new QGridLayout( mUi->mCalSelectorPlaceHolder ); layout->setSpacing( 0 ); layout->addWidget( mCalSelector ); mCalSelector->setAccessRightsFilter( Akonadi::Collection::CanCreateItem ); q->connect( mCalSelector, SIGNAL(currentChanged(Akonadi::Collection)), SLOT(handleSelectedCollectionChange(Akonadi::Collection)) ); // Now instantiate the logic of the dialog. These editors update the ui, validate // fields and load/store incidences in the ui. IncidenceWhatWhere *ieGeneral = new IncidenceWhatWhere( mUi ); mEditor->combine( ieGeneral ); IncidenceCategories *ieCategories = new IncidenceCategories( mUi ); mEditor->combine( ieCategories ); mIeDateTime = new IncidenceDateTime( mUi ); mEditor->combine( mIeDateTime ); IncidenceCompletionPriority *ieCompletionPriority = new IncidenceCompletionPriority( mUi ); mEditor->combine( ieCompletionPriority ); IncidenceDescription *ieDescription = new IncidenceDescription( mUi ); mEditor->combine( ieDescription ); IncidenceAlarm *ieAlarm = new IncidenceAlarm( mIeDateTime, mUi ); mEditor->combine( ieAlarm ); IncidenceAttachment *ieAttachments = new IncidenceAttachment( mUi ); mEditor->combine( ieAttachments ); mIeRecurrence = new IncidenceRecurrence( mIeDateTime, mUi ); mEditor->combine( mIeRecurrence ); IncidenceSecrecy *ieSecrecy = new IncidenceSecrecy( mUi ); mEditor->combine( ieSecrecy ); mIeAttendee = new IncidenceAttendee( qq, mIeDateTime, mUi ); mIeAttendee->setParent(qq); mEditor->combine( mIeAttendee ); mIeResource = new IncidenceResource( mIeAttendee, mIeDateTime, mUi ); mEditor->combine( mIeResource ); q->connect( mEditor, SIGNAL(showMessage(QString,KMessageWidget::MessageType)), SLOT(showMessage(QString,KMessageWidget::MessageType)) ); q->connect( mEditor, SIGNAL(dirtyStatusChanged(bool)), SLOT(updateButtonStatus(bool)) ); q->connect( mItemManager, SIGNAL(itemSaveFinished(IncidenceEditorNG::EditorItemManager::SaveAction)), SLOT(handleItemSaveFinish(IncidenceEditorNG::EditorItemManager::SaveAction))); q->connect( mItemManager, SIGNAL(itemSaveFailed(IncidenceEditorNG::EditorItemManager::SaveAction,QString)), SLOT(handleItemSaveFail(IncidenceEditorNG::EditorItemManager::SaveAction,QString))); q->connect( ieAlarm, SIGNAL(alarmCountChanged(int)), SLOT(handleAlarmCountChange(int)) ); q->connect( mIeRecurrence, SIGNAL(recurrenceChanged(IncidenceEditorNG::RecurrenceType)), SLOT(handleRecurrenceChange(IncidenceEditorNG::RecurrenceType)) ); q->connect( ieAttachments, SIGNAL(attachmentCountChanged(int)), SLOT(updateAttachmentCount(int)) ); q->connect( mIeAttendee, SIGNAL(attendeeCountChanged(int)), SLOT(updateAttendeeCount(int)) ); q->connect( mIeResource, SIGNAL(resourceCountChanged(int)), SLOT(updateResourceCount(int)) ); } IncidenceDialogPrivate::~IncidenceDialogPrivate() { delete mItemManager; delete mEditor; delete mUi; } void IncidenceDialogPrivate::showMessage( const QString &text, KMessageWidget::MessageType type ) { mUi->mMessageWidget->setText(text); mUi->mMessageWidget->setMessageType(type); mUi->mMessageWidget->show(); } void IncidenceDialogPrivate::handleAlarmCountChange( int newCount ) { QString tabText; if ( newCount > 0 ) { tabText = i18nc( "@title:tab Tab to configure the reminders of an event or todo", "Reminder (%1)", newCount ); } else { tabText = i18nc( "@title:tab Tab to configure the reminders of an event or todo", "Reminder" ); } mUi->mTabWidget->setTabText( AlarmsTab, tabText ); } void IncidenceDialogPrivate::handleRecurrenceChange( IncidenceEditorNG::RecurrenceType type ) { QString tabText = i18nc( "@title:tab Tab to configure the recurrence of an event or todo", "Rec&urrence" ); // Keep this numbers in sync with the items in mUi->mRecurrenceTypeCombo. I // tried adding an enum to IncidenceRecurrence but for whatever reason I could // Qt not play nice with namespaced enums in signal/slot connections. // Anyways, I don't expect these values to change. switch ( type ) { case RecurrenceTypeNone: break; case RecurrenceTypeDaily: tabText += i18nc( "@title:tab Daily recurring event, capital first letter only", " (D)" ); break; case RecurrenceTypeWeekly: tabText += i18nc( "@title:tab Weekly recurring event, capital first letter only", " (W)" ); break; case RecurrenceTypeMonthly: tabText += i18nc( "@title:tab Monthly recurring event, capital first letter only", " (M)" ); break; case RecurrenceTypeYearly: tabText += i18nc( "@title:tab Yearly recurring event, capital first letter only", " (Y)" ); break; case RecurrenceTypeException: tabText += i18nc( "@title:tab Exception to a recurring event, capital first letter only", " (E)" ); break; default: Q_ASSERT_X( false, "handleRecurrenceChange", "Fix your program" ); } mUi->mTabWidget->setTabText( RecurrenceTab, tabText ); } QString IncidenceDialogPrivate::typeToString( const int type ) const { // Do not translate. switch( type ) { case KCalCore::Incidence::TypeEvent: return "Event"; case KCalCore::Incidence::TypeTodo: return "Todo"; case KCalCore::Incidence::TypeJournal: return "Journal"; default: return "Unknown"; } } void IncidenceDialogPrivate::loadTemplate( const QString &templateName ) { Q_Q( IncidenceDialog ); KCalCore::MemoryCalendar::Ptr cal( new KCalCore::MemoryCalendar( KSystemTimeZones::local() ) ); const QString fileName = KStandardDirs::locateLocal( "data", "korganizer/templates/" + typeToString( mEditor->type() ) + '/' + templateName ); if ( fileName.isEmpty() ) { KMessageBox::error( q, i18nc( "@info", "Unable to find template '%1'.", fileName ) ); return; } KCalCore::ICalFormat format; if ( !format.load( cal, fileName ) ) { KMessageBox::error( q, i18nc( "@info", "Error loading template file '%1'.", fileName ) ); return; } KCalCore::Incidence::List incidences = cal->incidences(); if ( incidences.isEmpty() ) { KMessageBox::error( q, i18nc( "@info", "Template does not contain a valid incidence." ) ); return; } mIeDateTime->setActiveDate( QDate() ); KCalCore::Incidence::Ptr newInc = KCalCore::Incidence::Ptr( incidences.first()->clone() ); newInc->setUid( KCalCore::CalFormat::createUniqueId() ); // We add a custom property so that some fields aren't loaded, dates for example newInc->setCustomProperty( QByteArray("kdepim"), "isTemplate", "true"); mEditor->load( newInc ); + mEditor->load( newInc ); newInc->removeCustomProperty( QByteArray(), "isTemplate"); } void IncidenceDialogPrivate::manageTemplates() { Q_Q( IncidenceDialog ); QStringList &templates = IncidenceEditorNG::EditorConfig::instance()->templates( mEditor->type() ); QPointer dialog( new IncidenceEditorNG::TemplateManagementDialog( q, templates, KCalUtils::Stringify::incidenceType( mEditor->type() ) ) ); q->connect( dialog, SIGNAL(loadTemplate(QString)), SLOT(loadTemplate(QString)) ); q->connect( dialog, SIGNAL(templatesChanged(QStringList)), SLOT(storeTemplatesInConfig(QStringList)) ); q->connect( dialog, SIGNAL(saveTemplate(QString)), SLOT(saveTemplate(QString)) ); dialog->exec(); delete dialog; } void IncidenceDialogPrivate::saveTemplate( const QString &templateName ) { Q_ASSERT( ! templateName.isEmpty() ); KCalCore::MemoryCalendar::Ptr cal( new KCalCore::MemoryCalendar( KSystemTimeZones::local() ) ); switch( mEditor->type() ) { case KCalCore::Incidence::TypeEvent: { KCalCore::Event::Ptr event( new KCalCore::Event() ); mEditor->save( event ); cal->addEvent( KCalCore::Event::Ptr( event->clone() ) ); break; } case KCalCore::Incidence::TypeTodo: { KCalCore::Todo::Ptr todo( new KCalCore::Todo ); mEditor->save( todo ); cal->addTodo( KCalCore::Todo::Ptr( todo->clone() ) ); break; } case KCalCore::Incidence::TypeJournal: { KCalCore::Journal::Ptr journal( new KCalCore::Journal ); mEditor->save( journal ); cal->addJournal( KCalCore::Journal::Ptr( journal->clone() ) ); break; } default: Q_ASSERT_X( false, "saveTemplate", "Fix your program" ); } const QString fileName = KStandardDirs::locateLocal( "data", "korganizer/templates/" + typeToString( mEditor->type() ) + '/' + templateName ); KCalCore::ICalFormat format; format.save( cal, fileName ); } void IncidenceDialogPrivate::storeTemplatesInConfig( const QStringList &templateNames ) { // I find this somewhat broken. templates() returns a reference, maybe it should // be changed by adding a setTemplates method. IncidenceEditorNG::EditorConfig::instance()->templates( mEditor->type() ) = templateNames; IncidenceEditorNG::EditorConfig::instance()->config()->writeConfig(); } void IncidenceDialogPrivate::updateAttachmentCount( int newCount ) { if ( newCount > 0 ) { mUi->mTabWidget->setTabText( AttachmentsTab, i18nc( "@title:tab Tab to modify attachments of an event or todo", "Attac&hments (%1)", newCount ) ); } else { mUi->mTabWidget->setTabText( AttachmentsTab, i18nc( "@title:tab Tab to modify attachments of an event or todo", "Attac&hments" ) ); } } void IncidenceDialogPrivate::updateAttendeeCount( int newCount ) { if ( newCount > 0 ) { mUi->mTabWidget->setTabText( AttendeesTab, i18nc( "@title:tab Tab to modify attendees of an event or todo", "&Attendees (%1)", newCount ) ); } else { mUi->mTabWidget->setTabText( AttendeesTab, i18nc( "@title:tab Tab to modify attendees of an event or todo", "&Attendees" ) ); } } void IncidenceDialogPrivate::updateResourceCount( int newCount ) { if ( newCount > 0 ) { mUi->mTabWidget->setTabText( ResourcesTab, i18nc( "@title:tab Tab to modify attendees of an event or todo", "&Resources (%1)", newCount ) ); } else { mUi->mTabWidget->setTabText( ResourcesTab, i18nc( "@title:tab Tab to modify attendees of an event or todo", "&Resources" ) ); } } void IncidenceDialogPrivate::updateButtonStatus( bool isDirty ) { Q_Q( IncidenceDialog ); q->enableButton( KDialog::Apply, isDirty || mInitiallyDirty ); } bool IncidenceDialogPrivate::containsPayloadIdentifiers( const QSet &partIdentifiers ) const { return partIdentifiers.contains( QByteArray( "PLD:RFC822" ) ); } void IncidenceDialogPrivate::handleItemSaveFail( EditorItemManager::SaveAction, const QString &errorMessage ) { Q_Q( IncidenceDialog ); bool retry = false; if ( !errorMessage.isEmpty() ) { const QString message = i18nc( "@info", "Unable to store the incidence in the calendar. Try again?\n\n " "Reason: %1", errorMessage ); retry = ( KMessageBox::warningYesNo( q, message ) == KMessageBox::Yes ); } if ( retry ) { mItemManager->save(); } else { updateButtonStatus( isDirty() ); q->enableButtonOk( true ); q->enableButtonCancel( true ); } } void IncidenceDialogPrivate::handleItemSaveFinish( EditorItemManager::SaveAction saveAction ) { Q_Q( IncidenceDialog ); if ( mCloseOnSave ) { q->accept(); } else { const Akonadi::Item item = mItemManager->item(); Q_ASSERT( item.isValid() ); Q_ASSERT( item.hasPayload() ); Q_ASSERT( item.hasPayload() ); // Now the item is succesfull saved, reload it in the editor in order to // reset the dirty status of the editor. mEditor->load( item.payload() ); + mEditor->load( item ); // Set the buttons to a reasonable state as well (ok and apply should be // disabled at this point). q->enableButtonOk( true ); q->enableButtonCancel( true ); q->enableButtonApply( isDirty() ); } if ( saveAction == EditorItemManager::Create ) { emit q->incidenceCreated( mItemManager->item() ); } } bool IncidenceDialogPrivate::hasSupportedPayload( const Akonadi::Item &item ) const { return CalendarSupport::incidence( item ); } bool IncidenceDialogPrivate::isDirty() const { if ( mItem.isValid() ) { return mEditor->isDirty() || mCalSelector->currentCollection().id() != mItem.storageCollectionId(); } else { return mEditor->isDirty(); } } bool IncidenceDialogPrivate::isValid() const { if ( mEditor->isValid() ) { // Check if there's a selected collection. if ( mCalSelector->currentCollection().isValid() ) { return true; } else { kWarning() << "Select a collection first"; } } return false; } void IncidenceDialogPrivate::load( const Akonadi::Item &item ) { Q_Q( IncidenceDialog ); Q_ASSERT( hasSupportedPayload( item ) ); if ( CalendarSupport::hasJournal( item ) ) { //mUi->mTabWidget->removeTab( 5 ); mUi->mTabWidget->removeTab( AttachmentsTab ); mUi->mTabWidget->removeTab( RecurrenceTab ); mUi->mTabWidget->removeTab( AlarmsTab ); mUi->mTabWidget->removeTab( AttendeesTab ); mUi->mTabWidget->removeTab( ResourcesTab ); } mEditor->load( CalendarSupport::incidence( item ) ); + mEditor->load( item ); const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); const QStringList allEmails = IncidenceEditorNG::EditorConfig::instance()->allEmails(); KCalCore::Attendee::Ptr me = incidence->attendeeByMails( allEmails ); if ( incidence->attendeeCount() > 1 && // >1 because you won't drink alone me && ( me->status() == KCalCore::Attendee::NeedsAction || me->status() == KCalCore::Attendee::Tentative || me->status() == KCalCore::Attendee::InProcess ) ) { // Show the invitation bar: "You are invited [accept] [decline]" mUi->mInvitationBar->show(); } else { mUi->mInvitationBar->hide(); } kDebug() << "Loading item " << item.id() << "; parent " << item.parentCollection().id() << "; storage " << item.storageCollectionId(); if ( item.storageCollectionId() > -1 ) { mCalSelector->setDefaultCollection(Akonadi::Collection(item.storageCollectionId())); } if ( !mCalSelector->mimeTypeFilter().contains( "text/calendar" ) || !mCalSelector->mimeTypeFilter().contains( incidence->mimeType() ) ) { mCalSelector->setMimeTypeFilter( QStringList() << incidence->mimeType() << "text/calendar" ); } if ( mEditor->type() == KCalCore::Incidence::TypeTodo ) { q->setWindowIcon( SmallIcon( "view-calendar-tasks" ) ); } else if ( mEditor->type() == KCalCore::Incidence::TypeEvent ) { q->setWindowIcon( SmallIcon( "view-calendar-day" ) ); } else if ( mEditor->type() == KCalCore::Incidence::TypeJournal ) { q->setWindowIcon( SmallIcon( "view-pim-journal" ) ); } // Initialize tab's titles updateAttachmentCount( incidence->attachments().size() ); updateResourceCount( mIeResource->resourceCount() ); updateAttendeeCount( mIeAttendee->attendeeCount() ); handleRecurrenceChange( mIeRecurrence->currentRecurrenceType() ); handleAlarmCountChange( incidence->alarms().count() ); mItem = item; q->show(); } Akonadi::Item IncidenceDialogPrivate::save( const Akonadi::Item &item ) { Q_ASSERT( mEditor->incidence() ); KCalCore::Incidence::Ptr incidenceInEditor = mEditor->incidence(); KCalCore::Incidence::Ptr newIncidence( incidenceInEditor->clone() ); Akonadi::Item result = item; result.setMimeType( newIncidence->mimeType() ); // There's no editor that has the relatedTo property. We must set it here, by hand. // Otherwise it gets lost. // FIXME: Why don't we clone() incidenceInEditor then pass the clone to save(), // I wonder if we're not leaking other properties. newIncidence->setRelatedTo( incidenceInEditor->relatedTo() ); mEditor->save( newIncidence ); + mEditor->save( result ); // TODO: Remove this once we support moving of events/todo's mCalSelector->setEnabled( false ); // Make sure that we don't loose uid for existing incidence newIncidence->setUid( mEditor->incidence()->uid() ); // Mark the incidence as changed if ( mItem.isValid() ) { newIncidence->setRevision( newIncidence->revision() + 1 ); } result.setPayload( newIncidence ); return result; } Akonadi::Collection IncidenceDialogPrivate::selectedCollection() const { return mCalSelector->currentCollection(); } void IncidenceDialogPrivate::reject( RejectReason reason, const QString &errorMessage ) { Q_UNUSED( reason ); Q_Q( IncidenceDialog ); kError() << "Rejecting:" << errorMessage; q->deleteLater(); } /// IncidenceDialog IncidenceDialog::IncidenceDialog( Akonadi::IncidenceChanger *changer, QWidget *parent, Qt::WFlags flags ) : KDialog( parent, flags ), d_ptr( new IncidenceDialogPrivate( changer, this ) ) { Q_D( IncidenceDialog ); setAttribute( Qt::WA_DeleteOnClose ); d->mUi->mTabWidget->setCurrentIndex( 0 ); d->mUi->mSummaryEdit->setFocus(); setButtons( KDialog::Ok | KDialog::Apply | KDialog::Cancel | KDialog::Default ); setButtonToolTip( KDialog::Apply, i18nc( "@info:tooltip", "Save current changes" ) ); setButtonToolTip( KDialog::Ok, i18nc( "@action:button", "Save changes and close dialog" ) ); setButtonToolTip( KDialog::Cancel, i18nc( "@action:button", "Discard changes and close dialog" ) ); setDefaultButton( KDialog::Ok ); enableButton( Apply, false ); setButtonText( Default, i18nc( "@action:button", "&Templates..." ) ); setButtonIcon( Default, KIcon( "project-development-new-template" ) ); setButtonToolTip( Default, i18nc( "@info:tooltip", "Manage templates for this item" ) ); setButtonWhatsThis( Default, i18nc( "@info:whatsthis", "Push this button to show a dialog that helps " "you manage a set of templates. Templates " "can make creating new items easier and faster " "by putting your favorite default values into " "the editor automatically." ) ); setModal( false ); showButtonSeparator( false ); connect( d->mUi->mAcceptInvitationButton, SIGNAL(clicked()), d->mIeAttendee, SLOT(acceptForMe()) ); connect( d->mUi->mAcceptInvitationButton, SIGNAL(clicked()), d->mUi->mInvitationBar, SLOT(hide()) ); connect( d->mUi->mDeclineInvitationButton, SIGNAL(clicked()), d->mIeAttendee, SLOT(declineForMe()) ); connect( d->mUi->mDeclineInvitationButton, SIGNAL(clicked()), d->mUi->mInvitationBar, SLOT(hide()) ); KConfigGroup group( KGlobal::config(), "IncidenceDialog" ); const QSize size = group.readEntry( "Size", QSize() ); if ( size.isValid() ) { resize( size ); } else { resize( QSize( 500, 500 ).expandedTo( minimumSizeHint() ) ); } } IncidenceDialog::~IncidenceDialog() { KConfigGroup group( KGlobal::config(), "IncidenceDialog" ); group.writeEntry( "Size", size() ); delete d_ptr; } void IncidenceDialog::load( const Akonadi::Item &item, const QDate &activeDate ) { Q_D( IncidenceDialog ); d->mIeDateTime->setActiveDate( activeDate ); if ( item.isValid() ) { // We're editing d->mItemManager->load( item ); // TODO: Remove this once we support moving of events/todo's d->mCalSelector->setEnabled( false ); } else { // We're creating Q_ASSERT( d->hasSupportedPayload( item ) ); d->load( item ); show(); } } void IncidenceDialog::selectCollection( const Akonadi::Collection &collection ) { Q_D( IncidenceDialog ); if ( collection.isValid() ) { d->mCalSelector->setDefaultCollection( collection ); } else { d->mCalSelector->setCurrentIndex( 0 ); } } void IncidenceDialog::setIsCounterProposal( bool isCounterProposal ) { Q_D( IncidenceDialog ); d->mItemManager->setIsCounterProposal( isCounterProposal ); } QObject *IncidenceDialog::typeAheadReceiver() const { Q_D( const IncidenceDialog ); return d->mUi->mSummaryEdit; } void IncidenceDialog::slotButtonClicked( int button ) { Q_D( IncidenceDialog ); switch( button ) { case KDialog::Ok: { if ( d->isDirty() || d->mInitiallyDirty ) { enableButtonOk( false ); enableButtonCancel( false ); enableButtonApply( false ); d->mCloseOnSave = true; d->mInitiallyDirty = false; d->mItemManager->save(); } else { close(); } break; } case KDialog::Apply: { enableButtonOk( false ); enableButtonCancel( false ); enableButtonApply( false ); d->mCloseOnSave = false; d->mInitiallyDirty = false; d->mItemManager->save(); break; } case KDialog::Cancel: if ( d->isDirty() && KMessageBox::questionYesNo( this, i18nc( "@info", "Do you really want to cancel?" ), i18nc( "@title:window", "KOrganizer Confirmation" ) ) == KMessageBox::Yes ) { KDialog::reject(); // Discard current changes } else if ( !d->isDirty() ) { KDialog::reject(); // No pending changes, just close the dialog. } // else { // the user wasn't finished editting after all } break; case KDialog::Default: d->manageTemplates(); break; default: Q_ASSERT( false ); // Shouldn't happen break; } } void IncidenceDialog::closeEvent( QCloseEvent *event ) { Q_D( IncidenceDialog ); if ( d->isDirty() && KMessageBox::questionYesNo( this, i18nc( "@info", "Do you really want to cancel?" ), i18nc( "@title:window", "KOrganizer Confirmation" ) ) == KMessageBox::Yes ) { KDialog::reject(); // Discard current changes KDialog::closeEvent( event ); } else if ( !d->isDirty() ) { KDialog::reject(); // No pending changes, just close the dialog. KDialog::closeEvent( event ); } else { event->ignore(); } } void IncidenceDialog::setInitiallyDirty( bool initiallyDirty ) { Q_D( IncidenceDialog ); d->mInitiallyDirty = initiallyDirty; } Akonadi::Item IncidenceDialog::item() const { Q_D( const IncidenceDialog ); return d->mItemManager->item(); } void IncidenceDialog::handleSelectedCollectionChange( const Akonadi::Collection &collection ) { Q_D( IncidenceDialog ); if ( d->mItem.parentCollection().isValid() ) { enableButton( Apply, collection.id() != d->mItem.parentCollection().id() ); } } #include "moc_incidencedialog.cpp" diff --git a/incidenceeditor-ng/incidenceeditor-ng.h b/incidenceeditor-ng/incidenceeditor-ng.h index f7c850eb56..2c748554c3 100644 --- a/incidenceeditor-ng/incidenceeditor-ng.h +++ b/incidenceeditor-ng/incidenceeditor-ng.h @@ -1,132 +1,136 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCIDENCEEDITOR_NG_H #define INCIDENCEEDITOR_NG_H #include "incidenceeditors-ng_export.h" #include - +#include namespace IncidenceEditorNG { /** * KCal Incidences are complicated objects. The user interfaces to create/modify * are therefore complex too. The IncedenceEditor class is a divide and conquer * approach to this complexity. An IncidenceEditor is an editor for a specific * part(s) of an Incidence. */ class INCIDENCEEDITORS_NG_EXPORT IncidenceEditor : public QObject { Q_OBJECT public: virtual ~IncidenceEditor(); /** * Load the values of @param incidence into the editor widgets. The passed * incidence is kept for comparing with the current values of the editor. */ virtual void load( const KCalCore::Incidence::Ptr &incidence ) = 0; + /// This was introduced to replace categories with Akonadi::Tags + virtual void load( const Akonadi::Item &item ); /** * Store the current values of the editor into @param incidince. */ virtual void save( const KCalCore::Incidence::Ptr &incidence ) = 0; + /// This was introduced to replace categories with Akonadi::Tags + virtual void save( Akonadi::Item &item ); /** * Returns whether or not the current values in the editor differ from the * initial values. */ virtual bool isDirty() const = 0; /** * Returns whether or not the content of this editor is valid. The default * implementation returns always true. */ virtual bool isValid() const; /** Returns the last error, which is set in isValid() on error, and cleared on success. */ QString lastErrorString() const; /** * Sets focus on the invalid field. */ virtual void focusInvalidField(); /** * Returns the type of the Incidence that is currently loaded. */ KCalCore::IncidenceBase::IncidenceType type() const; /** Convenience method to get a pointer for a specific const Incidence Type. */ template QSharedPointer incidence() const { return mLoadedIncidence.dynamicCast(); } /** Re-implement this and print important member values and widget enabled/disabled states that could have lead to isDirty() returning true when the user didn't do any interaction with the editor. This method is called in CombinedIncidenceEditor before crashing due to assert( !editor->isDirty() ) */ virtual void printDebugInfo() const; signals: /** * Signals whether the dirty status of this editor has changed. The new dirty * status is passed as argument. */ void dirtyStatusChanged( bool isDirty ); public slots: /** * Checks if the dirty status has changed until last check and emits the * dirtyStatusChanged signal if needed. */ void checkDirtyStatus(); protected: /** Only subclasses can instantiate IncidenceEditors */ IncidenceEditor( QObject *parent = 0 ); template QSharedPointer incidence( KCalCore::Incidence::Ptr inc ) { return inc.dynamicCast(); } protected: KCalCore::Incidence::Ptr mLoadedIncidence; mutable QString mLastErrorString; bool mWasDirty; bool mLoadingIncidence; }; } // IncidenceEditorNG #endif // INCIDENCEEDITOR_H diff --git a/incidenceeditor-ng/incidenceeditor.cpp b/incidenceeditor-ng/incidenceeditor.cpp index cc4efcb5fd..906c4ec882 100644 --- a/incidenceeditor-ng/incidenceeditor.cpp +++ b/incidenceeditor-ng/incidenceeditor.cpp @@ -1,86 +1,96 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "incidenceeditor-ng.h" #include #include static const KCatalogLoader loader( "libincidenceeditors" ); using namespace IncidenceEditorNG; IncidenceEditor::IncidenceEditor( QObject *parent ) : QObject( parent ), mWasDirty( false ), mLoadingIncidence( false ) { } IncidenceEditor::~IncidenceEditor() { } void IncidenceEditor::checkDirtyStatus() { if ( !mLoadedIncidence ) { kDebug() << "checkDirtyStatus called on an invalid incidence"; return; } if ( mLoadingIncidence ) { // Still loading the incidence, ignore changes to widgets. return; } const bool dirty = isDirty(); if ( mWasDirty != dirty ) { mWasDirty = dirty; emit dirtyStatusChanged( dirty ); } } bool IncidenceEditor::isValid() const { mLastErrorString.clear(); return true; } QString IncidenceEditor::lastErrorString() const { return mLastErrorString; } void IncidenceEditor::focusInvalidField() { } KCalCore::IncidenceBase::IncidenceType IncidenceEditor::type() const { if ( mLoadedIncidence ) { return mLoadedIncidence->type(); } else { return KCalCore::IncidenceBase::TypeUnknown; } } void IncidenceEditor::printDebugInfo() const { // implement this in derived classes. } +void IncidenceEditor::load( const Akonadi::Item &item ) +{ + Q_UNUSED(item); +} + +void IncidenceEditor::save( Akonadi::Item &item ) +{ + Q_UNUSED(item); +} + #include "moc_incidenceeditor-ng.cpp" diff --git a/libkdepim/widgets/tagwidgets.cpp b/libkdepim/widgets/tagwidgets.cpp index 9c9be6d0a6..f2e4a5952a 100644 --- a/libkdepim/widgets/tagwidgets.cpp +++ b/libkdepim/widgets/tagwidgets.cpp @@ -1,162 +1,168 @@ /* Copyright (c) 2014 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #include "tagwidgets.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIM; TagWidget::TagWidget(QWidget* parent) : QWidget(parent) { mTagWidget = new Akonadi::TagWidget(this); connect(mTagWidget, SIGNAL(selectionChanged(Akonadi::Tag::List)), this, SLOT(onSelectionChanged(Akonadi::Tag::List))); QHBoxLayout *l = new QHBoxLayout; l->addWidget(mTagWidget); setLayout(l); } void TagWidget::onSelectionChanged(const Akonadi::Tag::List &tags) { Q_UNUSED(tags); mCachedTagNames.clear(); Q_FOREACH (const Akonadi::Tag &tag, mTagWidget->selection()) { mCachedTagNames << tag.name(); } emit selectionChanged(mCachedTagNames); + emit selectionChanged(tags); } void TagWidget::setSelection(const QStringList &tagNames) { mTagList.clear(); mCachedTagNames = tagNames; foreach (const QString &name, tagNames) { //TODO fetch by GID instead, we don't really want to create tags here Akonadi::TagCreateJob *tagCreateJob = new Akonadi::TagCreateJob(Akonadi::Tag(name), this); tagCreateJob->setMergeIfExisting(true); connect(tagCreateJob, SIGNAL(result(KJob*)), this, SLOT(onTagCreated(KJob*))); } } void TagWidget::onTagCreated(KJob *job) { if (job->error()) { kWarning() << "Failed to create tag " << job->errorString(); return; } Akonadi::TagCreateJob *createJob = static_cast(job); mTagList << createJob->tag(); mTagWidget->setSelection(mTagList); } QStringList TagWidget::selection() const { return mCachedTagNames; } TagSelectionDialog::TagSelectionDialog(QWidget* parent) : Akonadi::TagSelectionDialog(parent) { } void TagSelectionDialog::setSelection(const QStringList &tagNames) { mTagList.clear(); foreach (const QString &name, tagNames) { //TODO fetch by GID instead, we don't really want to create tags here Akonadi::TagCreateJob *tagCreateJob = new Akonadi::TagCreateJob(Akonadi::Tag(name), this); tagCreateJob->setMergeIfExisting(true); connect(tagCreateJob, SIGNAL(result(KJob*)), this, SLOT(onTagCreated(KJob*))); } } void TagSelectionDialog::onTagCreated(KJob *job) { if (job->error()) { kWarning() << "Failed to create tag " << job->errorString(); return; } Akonadi::TagCreateJob *createJob = static_cast(job); mTagList << createJob->tag(); Akonadi::TagSelectionDialog::setSelection(mTagList); } QStringList TagSelectionDialog::selection() const { QStringList list; Q_FOREACH (const Akonadi::Tag &tag, Akonadi::TagSelectionDialog::selection()) { list << tag.name(); } return list; } +Akonadi::Tag::List TagSelectionDialog::tagSelection() const +{ + return Akonadi::TagSelectionDialog::selection(); +} + class MatchingCheckableProxyModel : public KCheckableProxyModel { public: MatchingCheckableProxyModel(QObject* parent = 0): KCheckableProxyModel(parent) {} virtual QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits = 1, Qt::MatchFlags flags = Qt::MatchExactly) const { if (role == Qt::CheckStateRole) { return selectionModel()->selectedRows(); } return KCheckableProxyModel::match(start, role, value, hits, flags); } }; TagSelectionCombo::TagSelectionCombo(QWidget* parent) : KPIM::KCheckComboBox(parent) { Akonadi::Monitor *monitor = new Akonadi::Monitor(this); monitor->setTypeMonitored(Akonadi::Monitor::Tags); Akonadi::TagModel *model = new Akonadi::TagModel(monitor, this); QItemSelectionModel *selectionModel = new QItemSelectionModel(model, this); KCheckableProxyModel *checkableProxy = new MatchingCheckableProxyModel( this ); checkableProxy->setSourceModel( model ); checkableProxy->setSelectionModel( selectionModel ); setModel(checkableProxy); //We need to reconnect from the constructor of KCheckComboBox to the new model connect(checkableProxy, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateCheckedItems(QModelIndex,QModelIndex)) ); } TagCombo::TagCombo(QWidget* parent) : KComboBox(parent) { Akonadi::Monitor *monitor = new Akonadi::Monitor(this); monitor->setTypeMonitored(Akonadi::Monitor::Tags); Akonadi::TagModel *model = new Akonadi::TagModel(monitor, this); setModel(model); } diff --git a/libkdepim/widgets/tagwidgets.h b/libkdepim/widgets/tagwidgets.h index a5ad100a2c..9b58682e8e 100644 --- a/libkdepim/widgets/tagwidgets.h +++ b/libkdepim/widgets/tagwidgets.h @@ -1,87 +1,90 @@ /* Copyright (c) 2014 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 */ #ifndef KDEPIM_TAGWIDGETS_H #define KDEPIM_TAGWIDGETS_H #include "kdepim_export.h" #include #include #include #include #include #include "kcheckcombobox.h" class KJob; namespace Akonadi { class TagWidget; } namespace KPIM { class KDEPIM_EXPORT TagWidget: public QWidget { Q_OBJECT public: explicit TagWidget(QWidget* parent = 0); void setSelection(const QStringList &); QStringList selection() const; + signals: void selectionChanged(const QStringList &); + void selectionChanged(const Akonadi::Tag::List &); private slots: void onTagCreated(KJob*); void onSelectionChanged(const Akonadi::Tag::List &); private: Akonadi::TagWidget *mTagWidget; Akonadi::Tag::List mTagList; QStringList mCachedTagNames; }; class KDEPIM_EXPORT TagSelectionDialog : public Akonadi::TagSelectionDialog { Q_OBJECT public: explicit TagSelectionDialog(QWidget* parent = 0); void setSelection(const QStringList &); QStringList selection() const; + Akonadi::Tag::List tagSelection() const; private slots: void onTagCreated(KJob*); private: Akonadi::Tag::List mTagList; }; class KDEPIM_EXPORT TagSelectionCombo : public KPIM::KCheckComboBox { Q_OBJECT public: explicit TagSelectionCombo(QWidget* parent = 0); }; class KDEPIM_EXPORT TagCombo : public KComboBox { Q_OBJECT public: explicit TagCombo(QWidget* parent = 0); }; } #endif