+ed
[kdepim.git] / knotes / knotesapp.cpp
blobc85213a637070f070a075011e1fe60bce40ca859
1 /*******************************************************************
2 KNotes -- Notes for the KDE project
4 Copyright (c) 1997-2009, The KNotes Developers
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *******************************************************************/
21 #include "knoteconfigdlg.h"
22 #include "knote.h"
23 #include "knotes/resourcemanager.h"
24 #include "knotesadaptor.h"
25 #include "knotesalarm.h"
26 #include "knotesapp.h"
27 #include "knotesglobalconfig.h"
28 #include "knoteslegacy.h"
29 #include "knotesnetrecv.h"
31 #include <kaction.h>
32 #include <kactioncollection.h>
33 #include <kconfig.h>
34 #include <kdebug.h>
35 #include <kfind.h>
36 #include <kfinddialog.h>
37 #include <khelpmenu.h>
38 #include <kicon.h>
39 #include <kiconeffect.h>
40 #include <klocale.h>
41 #include <kmenu.h>
42 #include <kshortcutsdialog.h>
43 #include <ksocketfactory.h>
44 #include <kstandardaction.h>
45 #include <kstandarddirs.h>
46 #include <ksystemtrayicon.h>
47 #include <kwindowsystem.h>
48 #include <kxmlguibuilder.h>
49 #include <kxmlguifactory.h>
50 #include <KStatusNotifierItem>
52 #include <kcal/calendarlocal.h>
53 #include <kcal/journal.h>
54 #include <kiconloader.h>
56 #include <QPixmap>
57 #include <QClipboard>
58 #include <QTcpServer>
60 #include <dnssd/publicservice.h>
63 class KNotesKeyDialog
64 : public KDialog
66 public:
67 KNotesKeyDialog( KActionCollection *globals, QWidget *parent )
68 : KDialog( parent )
70 setCaption( i18n( "Configure Shortcuts" ) );
71 setButtons( Default | Ok | Cancel );
73 m_keyChooser = new KShortcutsEditor( globals, this );
74 setMainWidget( m_keyChooser );
75 connect( this, SIGNAL(defaultClicked()),
76 m_keyChooser, SLOT(allDefault()) );
79 void insert( KActionCollection *actions )
81 m_keyChooser->addCollection( actions, i18n( "Note Actions" ) );
84 void configure()
86 if ( exec() == Accepted ) {
87 m_keyChooser->save();
91 private:
92 KShortcutsEditor *m_keyChooser;
96 static bool qActionLessThan( const QAction *a1, const QAction *a2 )
98 return ( a1->text() < a2->text() );
102 KNotesApp::KNotesApp()
103 : QWidget(), m_alarm( 0 ), m_listener( 0 ), m_publisher( 0 ), m_find( 0 ), m_findPos( 0 )
105 new KNotesAdaptor( this );
106 QDBusConnection::sessionBus().registerObject( "/KNotes" , this );
107 kapp->setQuitOnLastWindowClosed( false );
109 // create the dock widget...
110 m_tray = new KStatusNotifierItem(0);
112 m_tray->setToolTipTitle( i18n( "KNotes: Sticky notes for KDE" ) );
113 m_tray->setIconByName( "knotes" );
114 m_tray->setToolTipIconByName( "knotes" );
115 m_tray->setStatus( KStatusNotifierItem::Active );
116 m_tray->setCategory( KStatusNotifierItem::ApplicationStatus );
117 m_tray->setStandardActionsEnabled(false);
118 connect( m_tray, SIGNAL(activateRequested(bool,QPoint)), this, SLOT(slotActivateRequested(bool,QPoint)) );
119 connect( m_tray, SIGNAL(secondaryActivateRequested(QPoint)), this, SLOT(slotSecondaryActivateRequested(QPoint)) );
121 // set the initial style
122 #ifdef __GNUC__
123 #warning FIXME
124 #endif
125 // KNote::setStyle( KNotesGlobalConfig::style() );
127 // create the GUI...
128 KAction *action = new KAction( KIcon( "document-new" ),
129 i18n( "New Note" ), this );
130 actionCollection()->addAction( "new_note", action );
131 action->setGlobalShortcut( KShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_N ));
132 connect( action, SIGNAL(triggered()), SLOT(newNote()) );
134 action = new KAction( KIcon( "edit-paste" ),
135 i18n( "New Note From Clipboard" ), this );
136 actionCollection()->addAction( "new_note_clipboard", action );
137 action->setGlobalShortcut( KShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_C ));
138 connect( action, SIGNAL(triggered()), SLOT(newNoteFromClipboard()) );
140 action = new KAction( KIcon( "knotes" ), i18n( "Show All Notes" ), this );
141 actionCollection()->addAction( "show_all_notes", action );
142 action->setGlobalShortcut( KShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_S ));
143 connect( action, SIGNAL(triggered()), SLOT(showAllNotes()) );
145 action = new KAction( KIcon( "window-close" ),
146 i18n( "Hide All Notes" ), this );
147 actionCollection()->addAction( "hide_all_notes", action );
148 action->setGlobalShortcut( KShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_H ));
149 connect( action, SIGNAL(triggered()), SLOT(hideAllNotes()) );
151 new KHelpMenu( this, KGlobal::mainComponent().aboutData(), false,
152 actionCollection() );
154 m_findAction = KStandardAction::find( this, SLOT(slotOpenFindDialog()),
155 actionCollection() );
156 KStandardAction::preferences( this, SLOT(slotPreferences()),
157 actionCollection() );
158 KStandardAction::keyBindings( this, SLOT(slotConfigureAccels()),
159 actionCollection() );
160 //FIXME: no shortcut removing!?
161 KStandardAction::quit( this, SLOT(slotQuit()),
162 actionCollection() )->setShortcut( 0 );
164 setXMLFile( componentData().componentName() + "appui.rc" );
166 m_guiBuilder = new KXMLGUIBuilder( this );
167 m_guiFactory = new KXMLGUIFactory( m_guiBuilder, this );
168 m_guiFactory->addClient( this );
170 m_contextMenu = static_cast<KMenu *>( m_guiFactory->container(
171 "knotes_context",
172 this ) );
173 m_noteMenu = static_cast<KMenu *>( m_guiFactory->container(
174 "notes_menu", this ) );
175 m_tray->setContextMenu( m_contextMenu );
176 // get the most recent XML UI file
177 QString xmlFileName = componentData().componentName() + "ui.rc";
178 QString filter = componentData().componentName() + '/' + xmlFileName;
179 const QStringList fileList =
180 componentData().dirs()->findAllResources( "data", filter ) +
181 componentData().dirs()->findAllResources( "data", xmlFileName );
183 QString doc;
184 KXMLGUIClient::findMostRecentXMLFile( fileList, doc );
185 m_noteGUI.setContent( doc );
187 KConfigGroup config( KGlobal::config(), "Global Keybindings" );
189 // clean up old config files
190 KNotesLegacy::cleanUp();
192 // create the resource manager
193 m_manager = new KNotesResourceManager();
194 connect( m_manager, SIGNAL(sigRegisteredNote(KCal::Journal*)),
195 this, SLOT(createNote(KCal::Journal*)) );
196 connect( m_manager, SIGNAL(sigDeregisteredNote(KCal::Journal*)),
197 this, SLOT(killNote(KCal::Journal*)) );
199 // read the notes
200 m_manager->load();
202 // read the old config files, convert and add them
203 KCal::CalendarLocal calendar( QString::fromLatin1( "UTC" ) );
204 if ( KNotesLegacy::convert( &calendar ) ) {
205 KCal::Journal::List notes = calendar.journals();
206 KCal::Journal::List::ConstIterator it;
207 for ( it = notes.constBegin(); it != notes.constEnd(); ++it ) {
208 m_manager->addNewNote( *it );
211 m_manager->save();
214 // set up the alarm reminder - do it after loading the notes because this
215 // is used as a check if updateNoteActions has to be called for a new note
216 m_alarm = new KNotesAlarm( m_manager, this );
218 updateNetworkListener();
220 if ( m_notes.size() == 0 && !kapp->isSessionRestored() ) {
221 newNote();
224 updateNoteActions();
227 KNotesApp::~KNotesApp()
229 saveNotes();
231 blockSignals( true );
232 qDeleteAll( m_notes );
233 m_notes.clear();
234 qDeleteAll( m_noteActions );
235 m_noteActions.clear();
236 blockSignals( false );
238 delete m_listener;
239 m_listener=0;
240 delete m_publisher;
241 m_publisher=0;
242 delete m_findPos;
243 m_findPos = 0;
244 //delete m_listener;
245 delete m_manager;
246 delete m_guiBuilder;
247 delete m_tray;
250 bool KNotesApp::commitData( QSessionManager & )
252 foreach ( KNote *note, m_notes ) {
253 note->commitData();
255 saveConfigs();
256 return true;
259 // -------------------- public D-Bus interface -------------------- //
261 QString KNotesApp::newNote( const QString &name, const QString &text )
263 // create the new note
264 KCal::Journal *journal = new KCal::Journal();
266 // new notes have the current date/time as title if none was given
267 if ( !name.isEmpty() ) {
268 journal->setSummary( name );
269 } else {
270 journal->setSummary( KGlobal::locale()->formatDateTime(
271 QDateTime::currentDateTime() ) );
274 // the body of the note
275 journal->setDescription( text );
277 if ( m_manager->addNewNote( journal ) ) {
278 showNote( journal->uid() );
281 return journal->uid();
284 QString KNotesApp::newNoteFromClipboard( const QString &name )
286 const QString &text = KApplication::clipboard()->text();
287 return newNote( name, text );
290 void KNotesApp::hideAllNotes() const
292 foreach ( KNote *note, m_notes ) {
293 note->slotClose();
297 void KNotesApp::showAllNotes() const
299 foreach ( KNote *note, m_notes ) {
300 // workaround to BUG 149116
301 note->hide();
303 note->show();
307 void KNotesApp::showNote( const QString &id ) const
309 KNote *note = m_notes.value( id );
310 if ( note ) {
311 showNote( note );
312 } else {
313 kWarning( 5500 ) << "showNote: no note with id:" << id;
317 void KNotesApp::hideNote( const QString &id ) const
319 KNote *note = m_notes.value( id );
320 if ( note ) {
321 note->hide();
322 } else {
323 kWarning( 5500 ) << "hideNote: no note with id:" << id;
327 void KNotesApp::killNote( const QString &id, bool force )
329 KNote *note = m_notes.value( id );
330 if ( note ) {
331 note->slotKill( force );
332 } else {
333 kWarning( 5500 ) << "killNote: no note with id:" << id;
337 // "bool force = false" doesn't work with dcop
338 void KNotesApp::killNote( const QString &id )
340 killNote( id, false );
343 QVariantMap KNotesApp::notes() const
345 QVariantMap notes;
347 foreach ( KNote *note, m_notes ) {
348 notes.insert( note->noteId(), note->name() );
351 return notes;
354 QString KNotesApp::name( const QString &id ) const
356 KNote *note = m_notes.value( id );
357 if ( note ) {
358 return note->name();
359 } else {
360 return QString();
364 QString KNotesApp::text( const QString &id ) const
366 KNote *note = m_notes.value( id );
367 if ( note ) {
368 return note->text();
369 } else {
370 return QString();
374 void KNotesApp::setName( const QString &id, const QString &newName )
376 KNote *note = m_notes.value( id );
377 if ( note ) {
378 note->setName( newName );
379 } else {
380 kWarning( 5500 ) << "setName: no note with id:" << id;
384 void KNotesApp::setText( const QString &id, const QString &newText )
386 KNote *note = m_notes.value( id );
387 if ( note ) {
388 note->setText( newText );
389 } else {
390 kWarning( 5500 ) << "setText: no note with id:" << id;
394 // -------------------- protected slots -------------------- //
396 void KNotesApp::slotActivateRequested( bool, const QPoint&)
398 if ( m_notes.size() == 1 ) {
399 showNote( *m_notes.begin() );
400 } else if ( m_notes.size() != 1 ) {
401 m_noteMenu->popup( QCursor::pos ());
405 void KNotesApp::slotSecondaryActivateRequested( const QPoint & )
407 newNote();
410 void KNotesApp::slotShowNote()
412 // tell the WM to give this note focus
413 showNote( sender()->objectName() );
416 void KNotesApp::slotWalkThroughNotes()
418 // show next note
419 QMap<QString, KNote *>::const_iterator it = m_notes.constBegin();
420 for ( ; it != m_notes.constEnd(); ++it ) {
421 if ( ( *it )->hasFocus() ) {
422 if ( ++it != m_notes.constEnd() ) {
423 showNote( *it );
424 } else {
425 showNote( *m_notes.constBegin() );
427 break;
432 void KNotesApp::slotOpenFindDialog()
434 KFindDialog findDia( this );
435 findDia.setObjectName( "find_dialog" );
436 findDia.setHasSelection( false );
437 findDia.setHasCursor( false );
438 findDia.setSupportsBackwardsFind( false );
440 if ( (findDia.exec() != QDialog::Accepted) ) {
441 delete m_findPos;
442 m_findPos = 0;
443 delete m_find;
444 m_find = 0;
445 return;
448 delete m_findPos;
449 m_findPos = new QMap<QString, KNote *>::iterator();
450 *m_findPos = m_notes.begin();
452 // this could be in an own method if searching without a dialog
453 // should be possible
454 delete m_find;
455 m_find = new KFind( findDia.pattern(), findDia.options(), this );
457 slotFindNext();
460 void KNotesApp::slotFindNext()
462 if ( *m_findPos != m_notes.end() ) {
463 KNote *note = * ( (*m_findPos)++ );
464 note->find( m_find );
465 } else {
466 m_find->displayFinalDialog();
467 m_find->deleteLater(); //we can't delete m_find now because it is the signal emitter
468 m_find = 0;
469 delete m_findPos;
470 m_findPos = 0;
474 void KNotesApp::slotPreferences()
476 // create a new preferences dialog...
477 KNoteConfigDlg *dialog = new KNoteConfigDlg( i18n( "Settings" ), this);
478 connect( dialog, SIGNAL(configWrote()),
479 this, SLOT(updateNetworkListener()) );
480 connect( dialog, SIGNAL(configWrote()),
481 this, SLOT(updateStyle()) );
482 dialog->show();
485 void KNotesApp::slotConfigureAccels()
487 KNotesKeyDialog keys( actionCollection(), this );
489 QMap<QString, KNote *>::const_iterator it = m_notes.constBegin();
491 if ( !m_notes.isEmpty() ) {
492 keys.insert( ( *it )->actionCollection() );
495 keys.configure();
497 // update GUI doc for new notes
498 m_noteGUI.setContent(
499 KXMLGUIFactory::readConfigFile( componentData().componentName() + "ui.rc",
500 componentData() )
503 if ( m_notes.isEmpty() ) {
504 return;
507 foreach ( QAction *action, ( *it )->actionCollection()->actions() ) {
508 it = m_notes.constBegin();
509 for ( ++it; it != m_notes.constEnd(); ++it ) {
511 // Not sure if this is what this message has in mind but since both
512 // action->objectName() and KAction::action() are QStrings, this
513 // might be fine.
514 // Correct me if I am wrong... ~ gamaral
515 #ifdef __GNUC__
516 #warning Port KAction::action() to QString
517 #endif
519 QAction *toChange =
520 ( *it )->actionCollection()->action( action->objectName() );
522 if ( toChange ) {
523 toChange->setShortcuts( action->shortcuts() );
529 void KNotesApp::slotNoteKilled( KCal::Journal *journal )
531 m_noteUidModify.clear();
532 m_manager->deleteNote( journal );
533 saveNotes();
536 void KNotesApp::slotQuit()
538 foreach ( KNote *note, m_notes ) {
539 if ( note->isModified() ) {
540 note->saveData(false);
543 saveConfigs();
544 kapp->quit();
547 // -------------------- private methods -------------------- //
549 void KNotesApp::showNote( KNote *note ) const
551 note->show();
552 #ifdef Q_WS_X11
553 KWindowSystem::setCurrentDesktop( KWindowSystem::windowInfo( note->winId(),
554 NET::WMDesktop ).desktop() );
555 KWindowSystem::forceActiveWindow( note->winId() );
556 #endif
557 note->setFocus();
560 void KNotesApp::createNote( KCal::Journal *journal )
562 if( journal->uid() == m_noteUidModify)
564 KNote *note = m_notes.value( m_noteUidModify );
565 if ( note )
566 note->changeJournal(journal);
567 return;
570 m_noteUidModify = journal->uid();
571 KNote *newNote = new KNote( m_noteGUI, journal, 0 );
572 m_notes.insert( newNote->noteId(), newNote );
574 connect( newNote, SIGNAL(sigRequestNewNote()),
575 SLOT(newNote()) );
576 connect( newNote, SIGNAL(sigShowNextNote()),
577 SLOT(slotWalkThroughNotes()) ) ;
578 connect( newNote, SIGNAL(sigKillNote(KCal::Journal*)),
579 SLOT(slotNoteKilled(KCal::Journal*)) );
580 connect( newNote, SIGNAL(sigNameChanged(QString)),
581 SLOT(updateNoteActions()) );
582 connect( newNote, SIGNAL(sigDataChanged(QString)),
583 SLOT(saveNotes(QString)) );
584 connect( newNote, SIGNAL(sigColorChanged()),
585 SLOT(updateNoteActions()) );
586 connect( newNote, SIGNAL(sigFindFinished()),
587 SLOT(slotFindNext()) );
589 // don't call this during startup for each and every loaded note
590 if ( m_alarm ) {
591 updateNoteActions();
593 //TODO
594 #if 0
595 if (m_tray->isVisible()) {
596 // we already booted, so this is a new note
597 // sucks, semantically speaking
598 newNote->slotRename();
600 #endif
603 void KNotesApp::killNote( KCal::Journal *journal )
605 if(m_noteUidModify == journal->uid())
607 return;
609 // this kills the KNote object
610 KNote *note = m_notes.take( journal->uid() );
611 if ( note )
613 delete note;
614 updateNoteActions();
618 void KNotesApp::acceptConnection()
620 // Accept the connection and make KNotesNetworkReceiver do the job
621 QTcpSocket *s = m_listener->nextPendingConnection();
623 if ( s ) {
624 KNotesNetworkReceiver *recv = new KNotesNetworkReceiver( s );
625 connect( recv,
626 SIGNAL(sigNoteReceived(QString,QString)),
627 SLOT(newNote(QString,QString)) );
631 void KNotesApp::saveNotes( const QString & uid )
633 m_noteUidModify = uid;
634 saveNotes();
637 void KNotesApp::saveNotes()
639 KNotesGlobalConfig::self()->writeConfig();
640 m_manager->save();
643 void KNotesApp::saveConfigs()
645 foreach ( KNote *note, m_notes ) {
646 note->saveConfig();
650 void KNotesApp::updateNoteActions()
652 unplugActionList( "notes" );
653 m_noteActions.clear();
655 foreach ( KNote *note, m_notes ) {
656 // what does this actually mean? ~gamaral
657 #ifdef __GNUC__
658 #warning utf8: use QString
659 #endif
660 KAction *action = new KAction( note->name().replace( "&", "&&" ), this );
661 action->setObjectName( note->noteId() );
662 connect( action, SIGNAL(triggered(bool)), SLOT(slotShowNote()) );
663 KIconEffect effect;
664 QPixmap icon =
665 effect.apply( qApp->windowIcon().pixmap( IconSize( KIconLoader::Small ),
666 IconSize( KIconLoader::Small ) ),
667 KIconEffect::Colorize,
669 note->palette().color( note->backgroundRole() ),
670 false );
672 action->setIcon( icon );
673 m_noteActions.append( action );
676 if ( m_noteActions.isEmpty() ) {
677 actionCollection()->action( "hide_all_notes" )->setEnabled( false );
678 actionCollection()->action( "show_all_notes" )->setEnabled( false );
679 m_findAction->setEnabled( false );
680 KAction *action = new KAction( i18n( "No Notes" ), this );
681 m_noteActions.append( action );
683 else
685 qSort( m_noteActions.begin(), m_noteActions.end(), qActionLessThan );
686 actionCollection()->action( "hide_all_notes" )->setEnabled( true );
687 actionCollection()->action( "show_all_notes" )->setEnabled( true );
688 m_findAction->setEnabled( true );
690 plugActionList( "notes", m_noteActions );
693 void KNotesApp::updateNetworkListener()
695 delete m_listener;
696 m_listener=0;
697 delete m_publisher;
698 m_publisher=0;
700 if ( KNotesGlobalConfig::receiveNotes() ) {
701 // create the socket and start listening for connections
702 m_listener=KSocketFactory::listen( "knotes" , QHostAddress::Any,
703 KNotesGlobalConfig::port() );
704 connect( m_listener, SIGNAL(newConnection()),
705 SLOT(acceptConnection()) );
706 m_publisher=new DNSSD::PublicService(KNotesGlobalConfig::senderID(), "_knotes._tcp", KNotesGlobalConfig::port());
707 m_publisher->publishAsync();
711 void KNotesApp::updateStyle()
713 #ifdef __GNUC__
714 #warning FIXME!
715 #endif
716 // KNote::setStyle( KNotesGlobalConfig::style() );
718 foreach ( KNote *note, m_notes ) {
719 QApplication::postEvent( note, new QEvent( QEvent::LayoutRequest ) );
723 #include "knotesapp.moc"