[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-core-devel
Subject:    Re: System tray "close" dialog needs work
From:       Sébastien_Laoût <slaout () linux62 ! org>
Date:       2005-01-05 21:15:01
Message-ID: 1104959701.2829.22.camel () localhost ! localdomain
[Download RAW message or body]

By seeing the screenshot (theire is no black border surrounding the
screenshot), aKregator include an old version of the systray dialog.

I am the one who programmed it.
I posted (here) a version that make some checks and don't show the
screenshot if it detects it's not visible...

But nobody replied.

Since not all problems are fixed (and I don't see how to solve them all)
I assumed it's not mature to be included in KDE libs.

I attach the last version of the dialog.
(don't be careful of the name "2": it's just for easy insertion in my
program.)

Le mer 05/01/2005 à 20:58, Aaron J. Seigo a écrit :
> you should be able to check isVisible() on the icon to see if the user has 
> hidden it, though this probably won't help in the case of hidden panels (they 
> are "visible", but off-screen). so in addition to checking isVisible(), one 
> would also likely have to see if the current coordinates are within the 
> screen rect... 

This is done in that version (part 2. and 3.).
So, this is OK.

The last problem I'l faced to:
I failed to raise the kicker (or other systray manager) if it's hidden:

* I've tryed to use kapp->widgetAt(g) (where g is a corner of the icon
(a QPoint)), but it doesn't work since the widget is not in the current
app (althrouth KSystemTray is a QLabel).
So: no way to test if the kicker is raised/visible or not.

* I also tryed to raise in <in any circumstance>, but it doesn't want:
it is alwayse raised after the dialog is shown, no matter if I call
sleep(1), kapp->proccessEvents() multiple times...

* And the sleep(1) is not acceptable by users!!

This configuration should be rare (ie. windows are allowed to be above
kicker, or systray manager is on the desktop, eg. a SuperKaramba
script...).
But that's not solvable.

Until we use X.Org XComposite extension...

Best regards,

Sébastien Laoût.


["ksystemtray2.cpp" (ksystemtray2.cpp)]

// To draw the systray screenshot image:
#include <qdesktopwidget.h>
#include <qmime.h>
#include <qpainter.h>
#include <qpoint.h>
#include <qpixmap.h>
// To know the program name:
#include <kglobal.h>
#include <kinstance.h>
#include <kaboutdata.h>
// Others:
#include <kmessagebox.h>
#include <kmanagerselection.h>

#include "ksystemtray2.h"

KSystemTray2::KSystemTray2(QWidget *parent, const char *name)
 : KSystemTray(parent, name)
{
}

KSystemTray2::~KSystemTray2()
{
}

void KSystemTray2::displayCloseMessage(QString fileMenu)
{
	/* IDEAS OF IMPROVEMENTS:
	*  - Use queuedMessageBox() but it need a dontAskAgainName parameter
	*    and image in QMimeSourceFactory shouldn't be removed.
	*  - Sometimes the systray icon is covered (a passive popup...)
	*    Use XComposite extension, if available, to get the kicker pixmap.
	*  - Perhapse desaturate the area around the proper SysTray icon,
	*    helping bring it into sharper focus. Or draw the cicle with XOR
	*    brush.
	*  - Perhapse add the icon in the text (eg. "... in the
	*    system tray ([icon])."). Add some clutter to the dialog.
	*/
#if KDE_IS_VERSION( 3, 1, 90 )
	// Don't do all the computations if they are unneeded:
	if ( ! KMessageBox::shouldBeShownContinue("hideOnCloseInfo") )
		return;
#endif
	// "Default parameter". Here, to avoid a i18n() call and dependancy in the .h
	if (fileMenu.isEmpty())
		fileMenu = i18n("File");

	// Some values we need:
	QPoint g = mapToGlobal(pos());
	int desktopWidth  = kapp->desktop()->width();
	int desktopHeight = kapp->desktop()->height();
	int tw = width();
	int th = height();

	// We are triying to make a live screenshot of the systray icon to circle it
	//  and show it to the user. If no systray is used or if the icon is not visible,
	//  we should not show that screenshot but only a text!

	// 1. Determine if the user use a system tray area or not:
	QCString screenstr;
	screenstr.setNum(qt_xscreen());
	QCString trayatom = "_NET_SYSTEM_TRAY_S" + screenstr;
	bool useSystray = (KSelectionWatcher(trayatom).owner() != None);

	// 2. And then if the icon is visible too (eg. this->show() has been called):
	useSystray = useSystray && isVisible();

	// 3. Kicker (or another systray manager) can be visible but masked out of
	//    the screen (ie. on right or on left of it). We check if the icon isn't
	//    out of screen.
	if (useSystray) {
		QRect deskRect(0, 0, desktopWidth, desktopHeight);
		if ( !deskRect.contains(g.x(), g.y()) ||
		     !deskRect.contains(g.x() + tw, g.y() + th) )
			useSystray = false;
	}

	// 4. We raise the window containing the systray icon (typically the kicker) to
	//    have the most chances it is visible during the capture:
/*	if (useSystray) {
		// We are testing if one of the corners is hidden, and if yes, we would enter
		// a time consuming process (raise kicker and wait some time):
//		if (kapp->widgetAt(g) != this ||
//		    kapp->widgetAt(g + QPoint(tw-1, 0)) != this ||
//		    kapp->widgetAt(g + QPoint(0, th-1)) != this ||
//		    kapp->widgetAt(g + QPoint(tw-1, th-1)) != this) {
			int systrayManagerWinId = topLevelWidget()->winId();
			KWin::forceActiveWindow(systrayManagerWinId);
			kapp->processEvents(); // Because without it the systrayManager is raised only after the \
messageBox is displayed //			KWin::activateWindow(systrayManagerWinId);
//			kapp->processEvents(); // Because without it the systrayManager is raised only after the \
messageBox is displayed //			KWin::raiseWindow(systrayManagerWinId);
//			kapp->processEvents(); // Because without it the systrayManager is raised only after the \
messageBox is displayed  sleep(1);
			// TODO: Re-verify that at least one corner is now visible
//		}
	}*/

//	KMessageBox::information(this, QString::number(g.x()) + ":" + QString::number(g.y()) + ":" +
//	                         QString::number((int)(kapp->widgetAt(g+QPoint(1,1)))));

	QString message = i18n(
		"<p>Closing the main window will keep %1 running in the system tray. "
		"Use <b>Quit</b> from the <b>%2</b> menu to quit the application.</p>"
			).arg(KGlobal::instance()->aboutData()->programName(), "Basket");
	// We are sure the systray icon is visible: ouf!
	if (useSystray) {
		// Compute size and position of the pixmap to be grabbed:
		int w = desktopWidth / 4;
		int h = desktopHeight / 9;
		int x = g.x() + tw/2 - w/2; // Center the rectange in the systray icon
		int y = g.y() + th/2 - h/2;
		if (x < 0)                 x = 0; // Move the rectangle to stay in the desktop limits
		if (y < 0)                 y = 0;
		if (x + w > desktopWidth)  x = desktopWidth - w;
		if (y + h > desktopHeight) y = desktopHeight - h;

		// Grab the desktop and draw a circle arround the icon:
		QPixmap shot = QPixmap::grabWindow(qt_xrootwin(), x, y, w, h);
		QPainter painter(&shot);
		const int CIRCLE_MARGINS = 6;
		const int CIRCLE_WIDTH   = 3;
		const int SHADOW_OFFSET  = 1;
		const int IMAGE_BORDER   = 1;
		int ax = g.x() - x - CIRCLE_MARGINS - 1;
		int ay = g.y() - y - CIRCLE_MARGINS - 1;
		painter.setPen( QPen(KApplication::palette().active().dark(), CIRCLE_WIDTH) );
		painter.drawArc(ax + SHADOW_OFFSET, ay + SHADOW_OFFSET,
		                tw + 2*CIRCLE_MARGINS, th + 2*CIRCLE_MARGINS, 0, 16*360);
		painter.setPen( QPen(Qt::red/*KApplication::palette().active().highlight()*/, CIRCLE_WIDTH) \
);  painter.drawArc(ax, ay, tw + 2*CIRCLE_MARGINS, th + 2*CIRCLE_MARGINS, 0, 16*360);
		painter.end();

		// Then, we add a border arround the image to make it more visible:
		QPixmap finalShot(w + 2*IMAGE_BORDER, h + 2*IMAGE_BORDER);
		finalShot.fill(KApplication::palette().active().foreground());
		painter.begin(&finalShot);
		painter.drawPixmap(IMAGE_BORDER, IMAGE_BORDER, shot);
		painter.end();

		// Associate source to image and show the dialog:
		QMimeSourceFactory::defaultFactory()->setPixmap("systray_shot", finalShot);
		KMessageBox::information(this,
			message + "<p><center><img source=\"systray_shot\"></center></p>",
			i18n("Docking in System Tray"), "hideOnCloseInfo");
		QMimeSourceFactory::defaultFactory()->setData("systray_shot", 0L);
	} else {
		KMessageBox::information(this,
			message,
			i18n("Docking in System Tray"), "hideOnCloseInfo");
	}
}


["ksystemtray2.h" (ksystemtray2.h)]

#include <ksystemtray.h>
#include <qstring.h>

/** Convenient class to develop the displayCloseMessage() dialog
  * hopefuly integrated in KDE 3.4
  * @author Sébastien Laoût
  */
class KSystemTray2 : public KSystemTray
{
  Q_OBJECT
  public:
	KSystemTray2(QWidget *parent = 0, const char *name = 0);
	~KSystemTray2();
	/**
	  * Call this method when the user clicked the close button of the window
	  * (the [x]) to inform him that the application sit in the system tray
	  * and willn't be closed (as he is used to).
	  *
	  * You usualy call it from reimplemented KMainWindow::queryClose()
	  *
	  * @ since 3.4
	  */
	void displayCloseMessage(QString fileMenu = "");
};


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic