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

List:       kde-panel-devel
Subject:    Re: [Panel-devel] Plasma and the window manager (Re:
From:       Jason Stubbs <jasonbstubbs () gmail ! com>
Date:       2007-11-26 15:06:23
Message-ID: 200711270006.23172.jasonbstubbs () gmail ! com
[Download RAW message or body]

On Monday 26 November 2007 20:09:31 Alex Merry wrote:
> This is working for me (I don't have composite enabled, and currently
> have four icons).
>
> Can we get it committed, so we have a working system tray?

I have a new patch, but I'm happy for either to be committed. ;)

This patch just fixes a couple of layouting issues. Specifically, it removes 
padding from around the outside of the widget and fixes an off-by-one issue
in size conversions from/to the GraphicsScene. Other than, just some slight
refactoring and extra comments. With the patch I sent on the "panel 
contentSize" thread, things are layed out perfectly.

Until the panel orientation and changes to it can be known, I'm done. :)

-- 
Jason Stubbs

["systray.patch" (text/x-diff)]

Index: systemtray.cpp
===================================================================
--- systemtray.cpp	(revision 741249)
+++ systemtray.cpp	(working copy)
@@ -19,66 +19,131 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
  ***************************************************************************/
 
+// Qt
+#include <QApplication>
+#include <QDesktopWidget>
+
 // Own
 #include "systemtray.h"
-#include "systemtraywidget.h"
 
-// KDE
-#include <KWindowSystem>
-#include <QGraphicsView>
-
 SystemTray::SystemTray(QObject *parent, const QVariantList &arguments)
-    : Plasma::Applet(parent, arguments),
-      m_systemTrayWidget(new SystemTrayWidget(0,
-                  Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint))
-{
-    connect(m_systemTrayWidget, SIGNAL(sizeChanged()), SLOT(updateLayout()));
-    m_systemTrayWidget->show();
-    KWindowSystem::setState(m_systemTrayWidget->winId(),
-            NET::SkipTaskbar | NET::SkipPager | NET::KeepBelow);
-}
+    : Plasma::Applet(parent, arguments)
+{ }
 
 SystemTray::~SystemTray()
 {
+    // Get rid of our SystemTrayWidget if we still have one
     delete m_systemTrayWidget;
 }
 
 QSizeF SystemTray::contentSizeHint() const
 {
-    return QSizeF(m_systemTrayWidget->size());
+    if (!m_currentView) {
+        return QSizeF();
+    }
+    
+    QRect widgetRect;
+    widgetRect.setSize(m_systemTrayWidget->minimumSizeHint());
+
+    // Transform the size into the coordinates used by our QGraphicsView
+    // Using mapToScene() causes us to lose QSize(1, 1) so we add it back
+    QSizeF size = m_currentView->mapToScene(widgetRect).boundingRect().size() + \
QSize(1, 1); +    return size;
 }
 
 Qt::Orientations SystemTray::expandingDirections() const
 {
-    // simplify layouting by giving the system tray a fixed
-    // size for now
+    // Extra space isn't useful in either direction
     return 0;
 }
 
 QVariant SystemTray::itemChange(QGraphicsItem::GraphicsItemChange change, const \
QVariant &value)  {
-    if (change == ItemPositionHasChanged) {
-        //figure out where this applet really is.
-        QPoint realPos(0, 0); //start with a default that's at least visible
-        QGraphicsScene *s=scene();
-        if (s) {
-            QList<QGraphicsView *> viewlist = s->views();
-            foreach (QGraphicsView *v, viewlist) {
-                QRectF r=v->sceneRect();
-                //now find out if this applet is actually on here
-                //I consider it "on here" if our pos is within the rect
-                //but there may be other valid ways to do this
-                if (r.contains(scenePos())) {
-                    kDebug() << "using view" << v;
-                    QPoint p=v->mapFromScene(scenePos());
-                    realPos = v->mapToGlobal(p);
-                    break; //no, I don't care if other views show it
-                }
-            }
+    // We've been added to a scene
+    if (change == ItemSceneChange) {
+        // If we were previously part of a different scene, stop monitoring it
+        // for changes
+        if (m_currentScene) {
+            disconnect(m_currentScene, SIGNAL(changed(const QList<QRectF> &)),
+                       this, SLOT(handleSceneChange(const QList<QRectF> &)));
         }
-        m_systemTrayWidget->move(realPos);
+        // Make a note of what scene we're on and start
+        // monitoring it for changes
+        // We need to monitor all changes because a QGraphicsItem normally
+        // only gets notified of changes relative to its parent but we need
+        // to know about changes relative to the view to be able to correctly
+        // place our SystemTrayWidget
+        m_currentScene = value.value<QGraphicsScene *>();
+        if (m_currentScene) {
+            connect(m_currentScene, SIGNAL(changed(const QList<QRectF> &)),
+                    this, SLOT(handleSceneChange(const QList<QRectF> &)));
+        }
     }
     return Plasma::Applet::itemChange(change, value);
 }
 
+void SystemTray::handleSceneChange(const QList<QRectF> &region)
+{
+    // Create or reparent our system tray to the current view and update the
+    // widget's geometry to match this item.
+
+    // Don't do anything if none of the scene changes affect us
+    if (!intersectsRegion(region)) {
+        return;
+    }
+
+    // Find out which QGraphicsView (if any) that we are visible on
+    QGraphicsView *view = findView();
+    if (!view) {
+        return;
+    }
+
+    // If the view is different to the view we were previously visible on or we
+    // had no view up until now, (re)create or reparent our SystemTrayWidget
+    if (view != m_currentView) {
+        m_currentView = view;
+        if (m_systemTrayWidget) {
+            m_systemTrayWidget->setParent(m_currentView);
+        } else {
+            m_systemTrayWidget = new SystemTrayWidget(view);
+            connect(m_systemTrayWidget, SIGNAL(sizeShouldChange()), this, \
SLOT(updateSize())); +        }
+        m_systemTrayWidget->setVisible(true);
+    }
+
+    // Set our SystemTrayWidget's size and position equal to that of this item
+    // Using mapFromScene() causes us to gain QSize(1, 1) so we take it off
+    QRect rect = m_currentView->mapFromScene(sceneBoundingRect()).boundingRect().adjusted(0, \
0, -1, -1); +    m_systemTrayWidget->setMaximumSize(rect.size());
+    m_systemTrayWidget->setGeometry(rect);
+}
+
+bool SystemTray::intersectsRegion(const QList<QRectF> &region)
+{
+   foreach (const QRectF &rect, region) {
+        if (rect.intersects(sceneBoundingRect())) {
+            return true;
+        }
+    }
+    return false;
+}
+
+QGraphicsView * SystemTray::findView()
+{
+    // We may be visible on more than one QGraphicsView but we only take the
+    // first because we are only able to display one system tray
+    foreach (QGraphicsView *view, m_currentScene->views()) {
+        if (view->sceneRect().contains(scenePos())) {
+            return view;
+        }
+    }
+    return 0;
+}
+
+void SystemTray::updateSize()
+{
+    // Just ask our parent's layout to give us an appropriate size
+    updateGeometry();
+}
+
 #include "systemtray.moc"
Index: systemtraywidget.cpp
===================================================================
--- systemtraywidget.cpp	(revision 741249)
+++ systemtraywidget.cpp	(working copy)
@@ -28,8 +28,6 @@
 
 // Qt
 #include <QEvent>
-#include <QHBoxLayout>
-#include <QX11EmbedContainer>
 #include <QX11Info>
 
 // Xlib
@@ -45,30 +43,66 @@
 };
 }
 
-SystemTrayWidget::SystemTrayWidget(QWidget *parent, Qt::WindowFlags f)
-    : QWidget(parent, f)
+SystemTrayContainer::SystemTrayContainer(WId clientId, QWidget *parent)
+    : QX11EmbedContainer(parent)
 {
-    m_layout = new QHBoxLayout(this);
-    setLayout(m_layout);
-    QPalette newPalette = palette();
-    newPalette.setBrush(QPalette::Window, Qt::black);
-    setPalette(newPalette);
-    KWindowSystem::setState(winId(), NET::Sticky | NET::KeepAbove);
-    init();
+    connect(this, SIGNAL(clientClosed()), SLOT(deleteLater()));
+    connect(this, SIGNAL(error(QX11EmbedContainer::Error)), \
SLOT(handleError(QX11EmbedContainer::Error))); +
+    // Tray icons have a fixed size of 22x22
+    setMinimumSize(22, 22);
+
+    // HACK: Tell the client to draw it's own black background rather than
+    // taking ours as things are broken with ARGB visuals it seems.
+    XSetWindowBackgroundPixmap(QX11Info::display(), clientId, None);
+    XSetWindowBackground(QX11Info::display(), clientId, 0 /* black */);
+
+    kDebug() << "attempting to embed" << clientId;
+    embedClient(clientId);
+
+#if 0
+    // BUG: error() sometimes return Unknown even on success
+    if (error() == Unknown || error() == InvalidWindowID) {
+        kDebug() << "embedding failed for" << clientId;
+        deleteLater();
+    }
+#endif
 }
 
-bool SystemTrayWidget::x11Event(XEvent *event)
+void SystemTrayContainer::handleError(QX11EmbedContainer::Error error)
 {
-    if (event->type == ClientMessage) {
-        if (event->xclient.message_type == m_opcodeAtom &&
-            event->xclient.data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) {
-            embedWindow((WId)event->xclient.data.l[2]);
-            return true;
-        }
-    }
-    return QWidget::x11Event(event);
+    Q_UNUSED(error);
+    deleteLater();
 }
 
+SystemTrayWidget::SystemTrayWidget(QWidget *parent)
+    : QWidget(parent),
+    // HACK: We need a better way to find out our orientation and when it changes
+    m_orientation(parent->width() > parent->height() ? Qt::Horizontal : \
Qt::Vertical), +    m_nextRow(0),
+    m_nextColumn(0)
+{
+    // Add stretches around our QGridLayout so tray icons are centered when in
+    // a vertical or horizontal orientation.
+    QHBoxLayout* hLayout = new QHBoxLayout();
+    QVBoxLayout* vLayout = new QVBoxLayout();
+    m_mainLayout = new QGridLayout();
+
+    hLayout->addStretch();
+    hLayout->addLayout(vLayout);
+    hLayout->addStretch();
+
+    vLayout->addStretch();
+    vLayout->addLayout(m_mainLayout);
+    vLayout->addStretch();
+
+    // Don't add any margins around our outside
+    setLayout(hLayout);
+    layout()->setContentsMargins(0, 0, 0, 0);
+
+    init();
+}
+
 void SystemTrayWidget::init()
 {
     Display *display = QX11Info::display();
@@ -95,51 +129,85 @@
     }
 }
 
-bool SystemTrayWidget::event(QEvent *event)
+bool SystemTrayWidget::x11Event(XEvent *event)
 {
-    if (event->type() == QEvent::LayoutRequest) {
-        resize(minimumSize());
-        emit sizeChanged();
+    if (event->type == ClientMessage) {
+        if (event->xclient.message_type == m_opcodeAtom &&
+            event->xclient.data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) {
+
+            // Set up a SystemTrayContainer for the client
+            SystemTrayContainer *container = new \
SystemTrayContainer((WId)event->xclient.data.l[2], this); +            \
addWidgetToLayout(container); +
+            connect(container, SIGNAL(clientIsEmbedded()), this, \
SIGNAL(sizeShouldChange())); +            connect(container, SIGNAL(destroyed(QObject \
*)), this, SLOT(removeContainer(QObject *))); +
+            return true;
+        }
     }
-    return QWidget::event(event);
+    return QWidget::x11Event(event);
 }
 
-void SystemTrayWidget::embedWindow(WId id)
+void SystemTrayWidget::addWidgetToLayout(QWidget *widget)
 {
-    kDebug() << "trying to add window with id " << id;
-    if (! m_containers.contains(id)) {
-        QX11EmbedContainer *container = new QX11EmbedContainer(this);
-        container->embedClient(id);
-        // TODO: add error handling
-        m_layout->addWidget(container);
-        container->show();
-        m_containers[id] = container;
-        connect(container, SIGNAL(clientClosed()), this, SLOT(windowClosed()) );
-        kDebug() << "SystemTray: Window with id " << id << "added" << container;
+    // Figure out where it should go and add it to our layout
+    
+    if (m_orientation == Qt::Horizontal) {
+        // Add down then across when horizontal
+        if (m_nextRow == m_mainLayout->rowCount()
+            && m_mainLayout->minimumSize().height() + m_mainLayout->spacing()
+               + widget->minimumHeight() > maximumHeight()) {
+            m_nextColumn++;
+            m_nextRow = 0;
+        }
+        m_mainLayout->addWidget(widget, m_nextRow, m_nextColumn);
+        m_nextRow++;
+    } else {
+        // Add across then down when vertical
+        if (m_nextColumn == m_mainLayout->columnCount()
+            && m_mainLayout->minimumSize().width() + m_mainLayout->spacing()
+               + widget->minimumWidth() > maximumWidth()) {
+            m_nextRow++;
+            m_nextColumn = 0;
+        }
+        m_mainLayout->addWidget(widget, m_nextRow, m_nextColumn);
+        m_nextColumn++;
     }
 }
 
-//what exactly is this for? is it related to QX11EmbedContainer::discardClient? why \
                is it blank?
-void SystemTrayWidget::discardWindow(WId)
+void SystemTrayWidget::removeContainer(QObject *container)
 {
-}
-
-void SystemTrayWidget::windowClosed()
-{
-    kDebug() << "Window closed";
-    //by this point the window id is gone, so we have to iterate to find out who's \
                lost theirs
-    ContainersList::iterator i = m_containers.begin();
-    while (i != m_containers.end()) {
-        QX11EmbedContainer *c=i.value();
-        if (c->clientWinId()==0) {
-            i=m_containers.erase(i);
-            kDebug() << "deleting container" << c;
-            delete c;
-            //do NOT assume that there will never be more than one without an id
-            continue;
+    // Pull all widgets from our container, skipping over the one that was just
+    // deleted
+    QList<QWidget *> remainingWidgets;
+    while (QLayoutItem* item = m_mainLayout->takeAt(0)) {
+        if (item->widget() && item->widget() != container) {
+            remainingWidgets.append(item->widget());
         }
-        ++i;
+        delete item;
     }
+
+    // Reset the widths and heights in our layout to 0 so that the removed
+    // widget's space isn't kept
+    // (Why doesn't QGridLayout do this automatically?)
+    for (int row = 0; row < m_mainLayout->rowCount(); row++) {
+        m_mainLayout->setRowMinimumHeight(row, 0);
+    }
+    for (int column = 0; column < m_mainLayout->columnCount(); column++) {
+        m_mainLayout->setColumnMinimumWidth(column, 0);
+    }
+
+    // Re-add remaining widgets
+    m_nextRow = 0;
+    m_nextColumn = 0;
+    foreach (QWidget *widget, remainingWidgets) {
+        addWidgetToLayout(widget);
+    }
+
+    // Force a layout so that minimumSizeHint() returns the correct value and
+    // signal that our size should change
+    layout()->activate();
+    emit sizeShouldChange();
 }
 
 #include "systemtraywidget.moc"
Index: systemtray.h
===================================================================
--- systemtray.h	(revision 741249)
+++ systemtray.h	(working copy)
@@ -22,11 +22,17 @@
 #ifndef SYSTEMTRAY_H
 #define SYSTEMTRAY_H
 
+// Own
+#include "systemtraywidget.h"
+
+// Qt
+#include <QGraphicsScene>
+#include <QGraphicsView>
+#include <QPointer>
+
 // Plasma
 #include <plasma/applet.h>
 
-class SystemTrayWidget;
-
 class SystemTray: public Plasma::Applet
 {
 Q_OBJECT
@@ -36,18 +42,23 @@
     ~SystemTray();
 
     QSizeF contentSizeHint() const;
-
     Qt::Orientations expandingDirections() const;
 
 protected:
     QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant \
&value);  
 private slots:
-    void updateLayout()
-    { update(); }
+    void updateSize();
+    void handleSceneChange(const QList<QRectF> &region);
 
 private:
-    SystemTrayWidget *m_systemTrayWidget;
+    bool intersectsRegion(const QList<QRectF> &region);
+    QGraphicsView * findView();
+
+    // These can all be deleted externally so we guard them
+    QPointer<SystemTrayWidget> m_systemTrayWidget;
+    QPointer<QGraphicsScene> m_currentScene;
+    QPointer<QGraphicsView> m_currentView;
 };
 
 K_EXPORT_PLASMA_APPLET(systemtray, SystemTray)
Index: systemtraywidget.h
===================================================================
--- systemtraywidget.h	(revision 741249)
+++ systemtraywidget.h	(working copy)
@@ -23,43 +23,50 @@
 #define QSYSTRAY_H
 
 // Qt
+#include <QGridLayout>
 #include <QWidget>
-#include <QHash>
+#include <QX11EmbedContainer>
 
 // Xlib
 #include <X11/Xdefs.h>
 
-class QHBoxLayout;
-class QX11EmbedContainer;
+class SystemTrayContainer: public QX11EmbedContainer
+{
+Q_OBJECT
 
+public:
+    SystemTrayContainer(WId client, QWidget *parent);
+
+private slots:
+    void handleError(QX11EmbedContainer::Error error);
+};
+
 class SystemTrayWidget: public QWidget
 {
 Q_OBJECT
 
 public:
-    SystemTrayWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
+    SystemTrayWidget(QWidget *parent);
 
 protected:
     bool x11Event(XEvent *event);
-    bool event(QEvent *event);
 
 Q_SIGNALS:
-    void sizeChanged();
+    void sizeShouldChange();
 
 private slots:
+    void removeContainer(QObject *container);
+
+private:
+    void addWidgetToLayout(QWidget *widget);
     void init();
-    void embedWindow(WId id);
-    void discardWindow(WId id);
-    /**
-     * Removes the container with id 0 from our list.
-     */
-    void windowClosed();
 
-private:
-    typedef QHash<WId, QX11EmbedContainer*> ContainersList;
-    
-    ContainersList m_containers;
-    QHBoxLayout *m_layout;
+    QGridLayout *m_mainLayout;
+    Qt::Orientation m_orientation;
+    int m_nextRow;
+    int m_nextColumn;
+
+    // These need to remain allocated for the duration of our lifetime
     Atom m_selectionAtom;
     Atom m_opcodeAtom;
 };



_______________________________________________
Panel-devel mailing list
Panel-devel@kde.org
https://mail.kde.org/mailman/listinfo/panel-devel


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

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