1 /* This file is part of the KDE project
2 Copyright (C) 2005-2006 Tom Albers <tomalbers@kde.nl>
3 Copyright (C) 2006 Bram Schoenmakers <bramschoenmakers@kde.nl>
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include <qpushbutton.h>
23 #include <qdatetime.h>
24 #include <qlineedit.h>
27 #include <qstringlist.h>
28 #include <qfileinfo.h>
37 #include <kapplication.h>
41 #include <dcopclient.h>
42 #include <kmessagebox.h>
43 #include <kiconloader.h>
45 #include <kimageeffect.h>
51 #include "rsiwidget.h"
54 #include "rsirelaxpopup.h"
55 #include "rsitooltip.h"
56 #include "rsiglobals.h"
58 RSIWidget::RSIWidget( QWidget
*parent
, const char *name
)
59 : QWidget( parent
, name
, WType_Popup
),
63 // Update legacy settings before reading any config.
64 RSIGlobals::updateLegacySettings();
66 // Keep these 3 lines _above_ the messagebox, so the text actually is right.
67 m_tray
= new RSIDock(this,"Tray Item");
68 m_tray
->setPixmap( KSystemTray::loadIcon("rsibreak0") );
71 if (KMessageBox::shouldBeShownContinue("dont_show_welcome_again_for_001"))
73 takeScreenshotOfTrayIcon();
74 KMessageBox::information(parent
,
75 i18n("<p>Welcome to RSIBreak<p><p>"
76 "In your tray you can now see RSIBreak: ")
77 + "<p><center><img source=\"systray_shot\"></center></p><p>"
78 + i18n("When you right-click on that you will see "
79 "a menu, from which you can go to the "
80 "configuration for example.<p>When you want to "
81 "know when the next break is, hover "
82 "over the icon.<p>Use RSIBreak wisely."),
84 "dont_show_welcome_again_for_001");
86 // we don't need to show the blurp about the changes in 0.5 for new users.
87 KMessageBox::saveDontShowAgainContinue("dont_show_welcome_again_for_050");
90 KMessageBox::information(parent
,
91 i18n("Changes in RSIBreak 0.5.0\n\n"
92 "In this version we have changed the way "
93 "the timers work. Instead of indicating "
94 "the number of short breaks you want to "
95 "take before taking a long break, you "
96 "can now setup how often, in minutes, "
97 "you want a lomg break.\nPlease review "
98 "your current settings.\n\n"),
100 "dont_show_welcome_again_for_050");
102 m_backgroundimage
.resize(QApplication::desktop()->width(),
103 QApplication::desktop()->height());
104 setBackgroundMode( QWidget::NoBackground
);
105 setGeometry( QApplication::desktop()->screenGeometry( this ) );
108 m_tooltip
= new RSIToolTip( m_tray
, "Tooltip" );
109 connect( m_tray
, SIGNAL( showToolTip() ), m_tooltip
, SLOT( show() ) );
110 connect( m_tray
, SIGNAL( hideToolTip() ), m_tooltip
, SLOT( hide() ) );
112 m_relaxpopup
= new RSIRelaxPopup(m_tray
);
113 connect( m_relaxpopup
, SIGNAL( lock() ), SLOT( slotLock() ) );
115 connect( m_tray
, SIGNAL( quitSelected() ), kapp
, SLOT( quit() ) );
116 connect( m_tray
, SIGNAL( configChanged( bool ) ), SLOT( readConfig() ) );
117 connect( m_tray
, SIGNAL( configChanged( bool ) ), RSIGlobals::instance(), SLOT( slotReadConfig() ) );
118 connect( m_tray
, SIGNAL( configChanged( bool ) ), m_relaxpopup
, SLOT( slotReadConfig() ) );
119 connect( m_tray
, SIGNAL( suspend( bool ) ), m_tooltip
, SLOT( setSuspended( bool ) ) );
120 connect( m_tray
, SIGNAL( suspend( bool ) ), m_relaxpopup
, SLOT( hide() ) );
122 m_dcopIface
= new DCOPIface(this, "actions");
123 connect( m_dcopIface
, SIGNAL( signalSuspend( bool) ),
124 m_tooltip
, SLOT( setSuspended( bool ) ) );
125 connect( m_dcopIface
, SIGNAL( signalSuspend( bool) ),
126 m_relaxpopup
, SLOT( setVisible( bool ) ) );
127 connect( m_dcopIface
, SIGNAL( signalWhereAmI() ),
128 this, SLOT( slotShowWhereIAm() ) );
130 srand ( time(NULL
) );
132 QBoxLayout
*topLayout
= new QVBoxLayout( this, 5);
134 m_countDown
= new RSILabel(this);
135 m_countDown
->setAlignment( AlignCenter
);
136 m_countDown
->setBackgroundMode( Qt::NoBackground
);
137 m_countDown
->setBackgroundOrigin(QWidget::ParentOrigin
);
139 topLayout
->addWidget(m_countDown
);
141 topLayout
->addStretch(5);
143 QBoxLayout
*buttonRow
= new QHBoxLayout( topLayout
);
144 m_miniButton
= new QPushButton( i18n("Skip"), this );
145 buttonRow
->addWidget( m_miniButton
);
146 m_lockButton
= new QPushButton( i18n("Lock desktop"), this );
147 buttonRow
->addWidget( m_lockButton
);
148 buttonRow
->addStretch(10);
150 m_accel
= new KAccel(this);
152 // This connects only get used when there the user locks and
153 // unlocks before the break is over. The lock releases the mouse
154 // and keyboard, and that way the event methods are not called.
155 connect(m_lockButton
, SIGNAL( clicked() ), SLOT( slotLock() ) );
157 m_timer_slide
= new QTimer(this);
158 connect(m_timer_slide
, SIGNAL(timeout()), SLOT(slotNewSlide()));
160 m_grab
= new QTimer(this);
161 connect(m_grab
, SIGNAL(timeout()), SLOT(slotGrab()));
167 // if there are no images found, the break will appear in black.
168 // if the text color is black (default) then change that.
169 if (m_files
.count() == 0 &&
170 m_countDown
->paletteForegroundColor() == Qt::black
)
171 m_countDown
->setPaletteForegroundColor( Qt::white
);
174 RSIWidget::~RSIWidget()
176 delete RSIGlobals::instance();
179 void RSIWidget::slotShowWhereIAm()
181 takeScreenshotOfTrayIcon();
182 KMessageBox::information(this,
183 i18n("<p>RSIBreak is already running<p><p>It is located here:")
184 + "<p><center><img source=\"systray_shot\"></center></p><p>",
185 i18n("Already Running"));
188 void RSIWidget::takeScreenshotOfTrayIcon()
190 // Process the events else the icon will not be there and the screenie will fail!
191 kapp
->processEvents();
193 // ********************************************************************************
194 // This block is copied from Konversation - KonversationMainWindow::queryClose()
195 // The part about the border is copied from KSystemTray::displayCloseMessage()
197 // Compute size and position of the pixmap to be grabbed:
198 QPoint g
= m_tray
->mapToGlobal( m_tray
-> pos() );
199 int desktopWidth
= kapp
->desktop()->width();
200 int desktopHeight
= kapp
->desktop()->height();
201 int tw
= m_tray
->width();
202 int th
= m_tray
->height();
203 int w
= desktopWidth
/ 4;
204 int h
= desktopHeight
/ 9;
205 int x
= g
.x() + tw
/2 - w
/2; // Center the rectange in the systray icon
206 int y
= g
.y() + th
/2 - h
/2;
207 if ( x
< 0 ) x
= 0; // Move the rectangle to stay in the desktop limits
209 if ( x
+ w
> desktopWidth
) x
= desktopWidth
- w
;
210 if ( y
+ h
> desktopHeight
) y
= desktopHeight
- h
;
212 // Grab the desktop and draw a circle around the icon:
213 QPixmap shot
= QPixmap::grabWindow( qt_xrootwin(), x
, y
, w
, h
);
214 QPainter
painter( &shot
);
215 const int MARGINS
= 6;
217 int ax
= g
.x() - x
- MARGINS
-1;
218 int ay
= g
.y() - y
- MARGINS
-1;
219 painter
.setPen( QPen( Qt::red
, WIDTH
) );
220 painter
.drawArc( ax
, ay
, tw
+ 2*MARGINS
, th
+ 2*MARGINS
, 0, 16*360 );
223 // Then, we add a border around the image to make it more visible:
224 QPixmap
finalShot(w
+ 2, h
+ 2);
225 finalShot
.fill(KApplication::palette().active().foreground());
226 painter
.begin(&finalShot
);
227 painter
.drawPixmap(1, 1, shot
);
230 // Associate source to image and show the dialog:
231 QMimeSourceFactory::defaultFactory()->setPixmap( "systray_shot", finalShot
);
234 // ********************************************************************************
237 void RSIWidget::minimize( bool newImage
)
239 // stop the m_grab, if the break is ESCaped during the
240 // first second, slotGrab is called after the widget
241 // is minimized again.
244 m_timer_slide
->stop();
252 void RSIWidget::maximize()
254 setBackgroundMode( QWidget::NoBackground
);
255 show(); // Keep it above the KWin calls.
256 KWin::forceActiveWindow(winId());
257 KWin::setOnAllDesktops(winId(),true);
258 KWin::setState(winId(), NET::KeepAbove
);
259 KWin::setState(winId(), NET::FullScreen
);
261 // prevent that users accidently press this button while
262 // they were writing text when the break appears
263 m_miniButton
->clearFocus();
264 m_lockButton
->clearFocus();
266 // Small delay for grabbing keyboard and mouse, because
267 // it will not grab when widget not visible
268 m_grab
->start(1000, true);
270 // If there are no images found, we gray the screen and wait....
272 if (m_files
.count() == 0 || !m_useImages
)
275 QTimer::singleShot( 10, this, SLOT( slotGrayEffect() ) );
279 if (m_slideInterval
>0)
280 m_timer_slide
->start( m_slideInterval
*1000 );
282 bitBlt( this, 0, 0, &m_backgroundimage
);
286 void RSIWidget::loadImage()
288 if (m_files
.count() == 0)
291 // reset if all images are shown
292 if (m_files_done
.count() == m_files
.count())
293 m_files_done
.clear();
295 // get a not yet used number
296 int j
= (int) ((m_files
.count()) * (rand() / (RAND_MAX
+ 1.0)));
297 while (m_files_done
.findIndex( QString::number(j
) ) != -1)
298 j
= (int) ((m_files
.count()) * (rand() / (RAND_MAX
+ 1.0)));
300 // Use that number to load the right image
301 m_files_done
.append(QString::number(j
));
303 kdDebug() << "Loading: " << m_files
[j
] <<
304 "( " << j
<< " / " << m_files
.count() << " ) " << endl
;
306 // Base the size on the size of the screen, for xinerama.
307 QRect size
= QApplication::desktop()->screenGeometry(
308 QApplication::desktop()->primaryScreen() );
310 QImage m
= QImage( m_files
[ j
]).smoothScale( size
.width(),
317 if (!m_backgroundimage
.convertFromImage(m
))
318 kdWarning() << "Failed to set new background image" << endl
;
323 void RSIWidget::findImagesInFolder(const QString
& folder
)
325 if ( folder
.isNull() )
330 if ( !dir
.exists() || !dir
.isReadable() )
332 kdWarning() << "Folder does not exist or is not readable: "
337 // TODO: make an automated filter, maybe with QImageIO.
338 QString
ext("*.png *.jpg *.jpeg *.tif *.tiff *.gif *.bmp *.xpm *.ppm *.pnm *.xcf *.pcx");
339 dir
.setNameFilter(ext
+ ' ' + ext
.upper());
340 dir
.setFilter(QDir::Dirs
| QDir::Files
| QDir::NoSymLinks
);
341 dir
.setMatchAllDirs ( true );
343 const QFileInfoList
*list
= dir
.entryInfoList();
347 QFileInfoListIterator
it( *list
);
350 while ( (fi
= it
.current()) != 0 )
353 m_files
.append(fi
->filePath());
354 else if (fi
->isDir() && m_searchRecursive
&&
355 fi
->fileName() != "." && fi
->fileName() != "..")
356 findImagesInFolder(fi
->absFilePath());
361 // This slot is copied from KDE's logout screen.
362 // from various authors found in:
363 // branches/KDE/3.5/kdebase/ksmserver/shutdowndlg.cpp
364 void RSIWidget::slotGrayEffect()
366 if ( m_currentY
>= height() ) {
368 if ( backgroundMode() == QWidget::NoBackground
) {
369 setBackgroundMode( QWidget::NoBackground
);
370 setBackgroundPixmap( m_backgroundimage
);
376 pixmap
= QPixmap::grabWindow( qt_xrootwin(), 0, m_currentY
, width(), 10 );
377 QImage image
= pixmap
.convertToImage();
378 KImageEffect::blend( Qt::black
, image
, 0.4 );
379 KImageEffect::toGray( image
, true );
380 pixmap
.convertFromImage( image
);
381 bitBlt( this, 0, m_currentY
, &pixmap
);
382 bitBlt( &m_backgroundimage
, 0, m_currentY
, &pixmap
);
384 QTimer::singleShot( 1, this, SLOT( slotGrayEffect() ) );
387 // -------------------------- SLOTS ------------------------//
389 void RSIWidget::slotNewSlide()
391 if (m_files
.count() == 1 || !m_useImages
)
395 bitBlt( this, 0, 0, &m_backgroundimage
);
398 void RSIWidget::slotLock()
400 m_timer_slide
->stop();
404 QCString
appname( "kdesktop" );
405 int rsibreak_screen
= qt_xscreen();
406 if ( rsibreak_screen
)
407 appname
.sprintf("kdesktop-screen-%d", rsibreak_screen
);
408 kapp
->dcopClient()->send(appname
, "KScreensaverIface", "lock()", "");
411 void RSIWidget::slotGrab()
413 grabMouse( QCursor( ArrowCursor
) );
417 void RSIWidget::setCounters( int timeleft
)
421 int minutes
= (int)floor(timeleft
/60);
422 int seconds
= timeleft
-(minutes
*60);
425 if (minutes
> 0 && seconds
> 0)
427 cdString
= i18n("minutes:seconds","%1:%2")
428 .arg( QString::number( minutes
))
429 .arg( QString::number( seconds
).rightJustify(2,'0'));
431 else if ( minutes
== 0 && seconds
> 0 )
433 cdString
= QString::number( seconds
);
435 else if ( minutes
>0 && seconds
== 0 )
437 cdString
= i18n("minutes:seconds","%1:00")
441 m_countDown
->setText( cdString
);
443 else if ( m_timer
->isSuspended() )
445 m_countDown
->setText( i18n("Suspended") );
449 m_countDown
->setText (QString::null
);
454 setBackgroundPixmap( m_backgroundimage
);
459 void RSIWidget::updateIdleAvg( double idleAvg
)
461 if ( idleAvg
== 0.0 )
463 else if ( idleAvg
>0 && idleAvg
<30 )
465 else if ( idleAvg
>=30 && idleAvg
<60 )
467 else if ( idleAvg
>=60 && idleAvg
<90 )
473 void RSIWidget::setIcon(int level
)
475 static QString currentIcon
;
476 static KIconLoader il
;
477 QString newIcon
= "rsibreak" +
478 ( m_timer
->isSuspended() ? QString("x") : QString::number(level
) );
480 if (newIcon
!= currentIcon
)
482 QPixmap dockPixmap
= KSystemTray::loadIcon( newIcon
);
483 QPixmap toolPixmap
= il
.loadIcon( newIcon
, KIcon::Desktop
);
484 currentIcon
= newIcon
;
485 m_tray
->setPixmap( dockPixmap
);
486 m_tooltip
->setPixmap( toolPixmap
);
490 // ------------------- Popup for skipping break ------------- //
492 void RSIWidget::tinyBreakSkipped()
494 if (!m_showTimerReset
)
497 m_tooltip
->setText( i18n("Timer for the short break has now been reset"));
501 void RSIWidget::bigBreakSkipped()
503 if (!m_showTimerReset
)
506 m_tooltip
->setText( i18n("The timers have now been reset"));
510 void RSIWidget::breakSkipped()
512 static KIconLoader il
;
514 disconnect( m_timer
, SIGNAL( updateToolTip( int, int ) ),
515 m_tooltip
, SLOT( setCounters( int, int ) ) );
517 m_tooltip
->setPixmap( il
.loadIcon( "rsibreak0", KIcon::Desktop
) );
518 m_tooltip
->setTimeout(0); // autoDelete is false, but after the ->show() it still
519 // gets hidden after the timeout. Setting to 0 helps.
523 void RSIWidget::skipBreakEnded()
525 if (!m_showTimerReset
)
528 connect( m_timer
, SIGNAL( updateToolTip( int, int ) ),
529 m_tooltip
, SLOT( setCounters( int, int ) ) );
533 // ----------------------------- EVENTS -----------------------//
535 void RSIWidget::closeEvent( QCloseEvent
* )
540 void RSIWidget::mousePressEvent( QMouseEvent
* e
)
542 if (e
->button() != LeftButton
)
545 if ( m_miniButton
->geometry().contains(e
->pos()))
546 m_miniButton
->setDown( true );
548 else if (m_lockButton
->geometry().contains(e
->pos()))
549 m_lockButton
->setDown( true );
552 void RSIWidget::mouseReleaseEvent( QMouseEvent
* e
)
554 if ( m_miniButton
->geometry().contains(e
->pos()))
556 m_miniButton
->setDown( false );
557 m_timer
->skipBreak();
560 else if (m_lockButton
->geometry().contains(e
->pos()))
562 m_lockButton
->setDown( false );
567 void RSIWidget::keyPressEvent( QKeyEvent
* e
)
569 if (e
->key() == m_accel
->shortcut("minimize"))
570 m_timer
->skipBreak();
572 //--------------------------- CONFIG ----------------------------//
574 void RSIWidget::startTimer( bool idle
)
577 static bool first
= true;
581 kdDebug() << "Current Timer: " << m_timer
->className()
582 << " wanted: " << (idle
? "RSITimer" : "RSITimerNoIdle") << endl
;
584 if (!idle
&& m_timer
->isA("RSITimerNoIdle"))
587 if (idle
&& m_timer
->isA("RSITimer"))
590 kdDebug() << "Switching timers" << endl
;
592 m_timer
->deleteLater();
593 m_accel
->remove("minimize");
597 m_timer
= new RSITimer(this,"Timer");
599 m_timer
= new RSITimerNoIdle(this,"Timer");
601 connect( m_timer
, SIGNAL( breakNow() ), SLOT( maximize() ) );
602 connect( m_timer
, SIGNAL( updateWidget( int ) ),
603 SLOT( setCounters( int ) ) );
604 connect( m_timer
, SIGNAL( updateToolTip( int, int ) ),
605 m_tooltip
, SLOT( setCounters( int, int ) ) );
606 connect( m_timer
, SIGNAL( updateIdleAvg( double ) ), SLOT( updateIdleAvg( double ) ) );
607 connect( m_timer
, SIGNAL( minimize( bool ) ), SLOT( minimize( bool ) ) );
608 connect( m_timer
, SIGNAL( relax( int, bool ) ), m_relaxpopup
, SLOT( relax( int, bool ) ) );
609 connect( m_timer
, SIGNAL( relax( int, bool ) ), m_tooltip
, SLOT( hide() ) );
610 connect( m_timer
, SIGNAL( relax( int, bool ) ), m_tray
, SLOT( relaxEntered( int, bool ) ) );
611 connect( m_timer
, SIGNAL( tinyBreakSkipped() ), SLOT( tinyBreakSkipped() ) );
612 connect( m_timer
, SIGNAL( bigBreakSkipped() ), SLOT( bigBreakSkipped() ) );
613 connect( m_timer
, SIGNAL( skipBreakEnded() ), SLOT( skipBreakEnded() ) );
615 connect( m_tray
, SIGNAL( configChanged( bool ) ), m_timer
, SLOT( slotReadConfig( bool ) ) );
616 connect( m_tray
, SIGNAL( dialogEntered() ), m_timer
, SLOT( slotStopNoImage( ) ) );
617 connect( m_tray
, SIGNAL( dialogLeft() ), m_timer
, SLOT( slotStartNoImage() ) );
618 connect( m_tray
, SIGNAL( breakRequest() ), m_timer
, SLOT( slotRequestBreak() ) );
619 connect( m_tray
, SIGNAL( debugRequest() ), m_timer
, SLOT( slotRequestDebug() ) );
620 connect( m_tray
, SIGNAL( suspend( bool ) ), m_timer
, SLOT( slotSuspended( bool ) ) );
622 connect( m_dcopIface
, SIGNAL( signalDoTinyBreak() ),
623 m_timer
, SLOT( slotRequestTinyBreak() ) );
624 connect( m_dcopIface
, SIGNAL( signalDoBigBreak() ),
625 m_timer
, SLOT( slotRequestBigBreak() ) );
626 connect( m_dcopIface
, SIGNAL( signalSuspend( bool) ),
627 m_timer
, SLOT( slotSuspended( bool ) ) );
629 connect( m_relaxpopup
, SIGNAL( skip() ), m_timer
, SLOT( skipBreak() ) );
631 // This connects only get used when there the user locks and
632 // unlocks before the break is over. The lock releases the mouse
633 // and keyboard, and that way the event methods are not called.
634 connect(m_miniButton
, SIGNAL( clicked() ), m_timer
, SLOT( skipBreak() ) );
636 m_accel
->insert("minimize", i18n("Skip"),
637 i18n("Abort a break"),Qt::Key_Escape
,
638 m_timer
, SLOT( skipBreak() ));
643 void RSIWidget::readConfig()
645 KConfig
* config
= kapp
->config();
646 QColor
*Black
= new QColor(Qt::black
);
647 QFont
*t
= new QFont( QApplication::font().family(), 40, 75, true );
649 config
->setGroup("General Settings");
650 m_countDown
->setPaletteForegroundColor(
651 config
->readColorEntry("CounterColor", Black
) );
652 m_miniButton
->setHidden(
653 config
->readBoolEntry("HideMinimizeButton", false));
654 m_relaxpopup
->setSkipButtonHidden(
655 config
->readBoolEntry("HideMinimizeButton", false));
656 m_countDown
->setHidden(
657 config
->readBoolEntry("HideCounter", false));
659 m_countDown
->setFont(
660 config
->readFontEntry("CounterFont", t
) );
662 bool uImages
= config
->readBoolEntry("ShowImages", false);
665 config
->readBoolEntry("SearchRecursiveCheck", false);
666 QString path
= config
->readEntry("ImageFolder");
668 bool timertype
= config
->readBoolEntry("UseNoIdleTimer", false);
669 startTimer(!timertype
);
671 // Hook in the shortcut after the timer initialisation.
672 m_accel
->setEnabled("minimize",
673 !config
->readBoolEntry("DisableAccel", false));
674 QString shortcut
= config
->readEntry("MinimizeKey", "Escape");
675 m_accel
->setShortcut("minimize",KShortcut(shortcut
));
677 if (m_basePath
!= path
|| m_searchRecursive
!= recursive
||
678 m_useImages
!= uImages
)
681 m_files_done
.clear();
683 m_searchRecursive
= recursive
;
684 m_useImages
= uImages
;
687 findImagesInFolder( path
);
688 // LoadImage (smoothscale) takes some time, so when booting this seriously
689 // drains startup speed. So, deleay this a few seconds...
690 QTimer::singleShot(2000, this, SLOT(slotNewSlide()));
694 m_slideInterval
= config
->readNumEntry("SlideInterval", 2);
695 m_showTimerReset
= config
->readBoolEntry("ShowTimerReset", false);
705 RSILabel::RSILabel( QWidget
*parent
, const char *name
)
706 : QLabel( parent
, name
)
710 RSILabel::~RSILabel()
714 void RSILabel::setText( const QString
&str
)
716 QLabel::setText( str
);
720 void RSILabel::updateMask()
734 #include "rsiwidget.moc"