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"
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"
32 #include <kactioncollection.h>
36 #include <kfinddialog.h>
37 #include <khelpmenu.h>
39 #include <kiconeffect.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>
60 #include <dnssd/publicservice.h>
67 KNotesKeyDialog( KActionCollection
*globals
, QWidget
*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" ) );
86 if ( exec() == Accepted
) {
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
125 // KNote::setStyle( KNotesGlobalConfig::style() );
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(
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
);
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
*)) );
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
);
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() ) {
227 KNotesApp::~KNotesApp()
231 blockSignals( true );
232 qDeleteAll( m_notes
);
234 qDeleteAll( m_noteActions
);
235 m_noteActions
.clear();
236 blockSignals( false );
250 bool KNotesApp::commitData( QSessionManager
& )
252 foreach ( KNote
*note
, m_notes
) {
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
);
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
) {
297 void KNotesApp::showAllNotes() const
299 foreach ( KNote
*note
, m_notes
) {
300 // workaround to BUG 149116
307 void KNotesApp::showNote( const QString
&id
) const
309 KNote
*note
= m_notes
.value( id
);
313 kWarning( 5500 ) << "showNote: no note with id:" << id
;
317 void KNotesApp::hideNote( const QString
&id
) const
319 KNote
*note
= m_notes
.value( id
);
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
);
331 note
->slotKill( force
);
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
347 foreach ( KNote
*note
, m_notes
) {
348 notes
.insert( note
->noteId(), note
->name() );
354 QString
KNotesApp::name( const QString
&id
) const
356 KNote
*note
= m_notes
.value( id
);
364 QString
KNotesApp::text( const QString
&id
) const
366 KNote
*note
= m_notes
.value( id
);
374 void KNotesApp::setName( const QString
&id
, const QString
&newName
)
376 KNote
*note
= m_notes
.value( id
);
378 note
->setName( newName
);
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
);
388 note
->setText( newText
);
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
& )
410 void KNotesApp::slotShowNote()
412 // tell the WM to give this note focus
413 showNote( sender()->objectName() );
416 void KNotesApp::slotWalkThroughNotes()
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() ) {
425 showNote( *m_notes
.constBegin() );
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
) ) {
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
455 m_find
= new KFind( findDia
.pattern(), findDia
.options(), this );
460 void KNotesApp::slotFindNext()
462 if ( *m_findPos
!= m_notes
.end() ) {
463 KNote
*note
= * ( (*m_findPos
)++ );
464 note
->find( m_find
);
466 m_find
->displayFinalDialog();
467 m_find
->deleteLater(); //we can't delete m_find now because it is the signal emitter
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()) );
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() );
497 // update GUI doc for new notes
498 m_noteGUI
.setContent(
499 KXMLGUIFactory::readConfigFile( componentData().componentName() + "ui.rc",
503 if ( m_notes
.isEmpty() ) {
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
514 // Correct me if I am wrong... ~ gamaral
516 #warning Port KAction::action() to QString
520 ( *it
)->actionCollection()->action( action
->objectName() );
523 toChange
->setShortcuts( action
->shortcuts() );
529 void KNotesApp::slotNoteKilled( KCal::Journal
*journal
)
531 m_noteUidModify
.clear();
532 m_manager
->deleteNote( journal
);
536 void KNotesApp::slotQuit()
538 foreach ( KNote
*note
, m_notes
) {
539 if ( note
->isModified() ) {
540 note
->saveData(false);
547 // -------------------- private methods -------------------- //
549 void KNotesApp::showNote( KNote
*note
) const
553 KWindowSystem::setCurrentDesktop( KWindowSystem::windowInfo( note
->winId(),
554 NET::WMDesktop
).desktop() );
555 KWindowSystem::forceActiveWindow( note
->winId() );
560 void KNotesApp::createNote( KCal::Journal
*journal
)
562 if( journal
->uid() == m_noteUidModify
)
564 KNote
*note
= m_notes
.value( m_noteUidModify
);
566 note
->changeJournal(journal
);
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()),
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
595 if (m_tray
->isVisible()) {
596 // we already booted, so this is a new note
597 // sucks, semantically speaking
598 newNote
->slotRename();
603 void KNotesApp::killNote( KCal::Journal
*journal
)
605 if(m_noteUidModify
== journal
->uid())
609 // this kills the KNote object
610 KNote
*note
= m_notes
.take( journal
->uid() );
618 void KNotesApp::acceptConnection()
620 // Accept the connection and make KNotesNetworkReceiver do the job
621 QTcpSocket
*s
= m_listener
->nextPendingConnection();
624 KNotesNetworkReceiver
*recv
= new KNotesNetworkReceiver( s
);
626 SIGNAL(sigNoteReceived(QString
,QString
)),
627 SLOT(newNote(QString
,QString
)) );
631 void KNotesApp::saveNotes( const QString
& uid
)
633 m_noteUidModify
= uid
;
637 void KNotesApp::saveNotes()
639 KNotesGlobalConfig::self()->writeConfig();
643 void KNotesApp::saveConfigs()
645 foreach ( KNote
*note
, m_notes
) {
650 void KNotesApp::updateNoteActions()
652 unplugActionList( "notes" );
653 m_noteActions
.clear();
655 foreach ( KNote
*note
, m_notes
) {
656 // what does this actually mean? ~gamaral
658 #warning utf8: use QString
660 KAction
*action
= new KAction( note
->name().replace( "&", "&&" ), this );
661 action
->setObjectName( note
->noteId() );
662 connect( action
, SIGNAL(triggered(bool)), SLOT(slotShowNote()) );
665 effect
.apply( qApp
->windowIcon().pixmap( IconSize( KIconLoader::Small
),
666 IconSize( KIconLoader::Small
) ),
667 KIconEffect::Colorize
,
669 note
->palette().color( note
->backgroundRole() ),
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
);
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()
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()
716 // KNote::setStyle( KNotesGlobalConfig::style() );
718 foreach ( KNote
*note
, m_notes
) {
719 QApplication::postEvent( note
, new QEvent( QEvent::LayoutRequest
) );
723 #include "knotesapp.moc"