Merge lp:~fboucault/unity-2d/nautilus_window_instead_of_wallpaper into lp:unity-2d

Proposed by Florian Boucault
Status: Work in progress
Proposed branch: lp:~fboucault/unity-2d/nautilus_window_instead_of_wallpaper
Merge into: lp:unity-2d
Diff against target: 375 lines (+219/-26)
10 files modified
libunity-2d-private/Unity2d/plugin.cpp (+3/-0)
libunity-2d-private/src/CMakeLists.txt (+1/-0)
libunity-2d-private/src/rootwindowinfo.cpp (+121/-0)
libunity-2d-private/src/rootwindowinfo.h (+57/-0)
libunity-2d-private/src/unity2ddeclarativeview.cpp (+11/-0)
libunity-2d-private/src/unity2ddeclarativeview.h (+4/-0)
places/app/dashdeclarativeview.cpp (+1/-1)
spread/Workspace.qml (+21/-10)
spread/app/spreadview.cpp (+0/-12)
spread/app/spreadview.h (+0/-3)
To merge this branch: bzr merge lp:~fboucault/unity-2d/nautilus_window_instead_of_wallpaper
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
Florian Boucault (community) Needs Fixing
Review via email: mp+86170@code.launchpad.net

Description of the change

[workspace switcher] Draw Nautilus desktop window's screenshot instead of loading the GNOME wallpaper.

Greatly improves startup speed therefore contributing to fixing https://bugs.launchpad.net/unity-2d/+bug/745764

The following changes were necessary to have the desktop window screenshot taken only when necessary:
- Defined new 'visible' property on Unity2dDeclarativeView.
- Cleaned up descendant classes handling of hideEvent and showEvent.

To post a comment you must log in.
Revision history for this message
Florian Boucault (fboucault) wrote :

Do not merge yet as the screenshoting of Nautilus is done as many times as we have desktops (taking in average 35ms on my laptop).

Revision history for this message
Florian Boucault (fboucault) :
review: Needs Fixing
Revision history for this message
Florian Boucault (fboucault) wrote :

https://bugs.launchpad.net/unity-2d/+bug/900019 should also be fixed with this MR

Revision history for this message
Florian Boucault (fboucault) wrote :

> Do not merge yet as the screenshoting of Nautilus is done as many times as we
> have desktops (taking in average 35ms on my laptop).

A cache based on timestamp should solve that.

832. By Andrea Cimitan

Some fixes to dash layout, especially on the filters pane. Fixes: https://bugs.launchpad.net/bugs/906235. Appoved by .

833. By Albert Astals Cid

[launcher] Move the RMB menu from clicked to pressed

Fixes LP bug #813036
. Fixes: https://bugs.launchpad.net/bugs/813036. Appoved by Lohith D Shivamurthy.

834. By Tiago Salem Herrmann

This merge request intends to fix lp:838708, making BFB behavior and visual match the design.. Fixes: https://bugs.launchpad.net/bugs/838708. Appoved by Lohith D Shivamurthy.

835. By Gerry Boland

[tests] Update visual asset of BFB - UNBLOCK. Fixes: . Appoved by .

836. By Albert Astals Cid

Use QScopedPointers to properly delete the lists returned from qtbamf. Fixes: . Appoved by .

837. By Albert Astals Cid

Reset window to NULL if it's not the one we are looking for

Otherwise in case we don't find the one with the xid we want the function returns the pointer of the last window of the list
. Fixes: . Appoved by .

838. By Albert Astals Cid

Do not invoke m_application->windows() multiple times

Two reasons for that:
 * the result of m_application->windows() needs to be deleted and if we call it in the for loop we can't
 * The size of the list itself can change (and more critically shrink) between calls ending up in a call to an invalid index (Bug 854634)
. Fixes: https://bugs.launchpad.net/bugs/854634. Appoved by Gerry Boland, Lohith D Shivamurthy.

839. By Tiago Salem Herrmann

The request to open the command lens (alt+f2) happens before the list of available lenses is properly populated when unity-2d-places is started by a dbus call.
This patch adds a simple "queue" that checks if the requested queue is available everytime a new lens is added to the "lenses" variable. Fixes: https://bugs.launchpad.net/bugs/883392. Appoved by Gerry Boland, Lohith D Shivamurthy.

840. By Michał Sawicz

[debian] package debug symbols. Fixes: . Appoved by Gerry Boland.

841. By Tiago Salem Herrmann

[dash] Remove underline from "Filter results" when it is highlighted. Fixes: https://bugs.launchpad.net/bugs/893061. Appoved by Gerry Boland.

842. By Gerry Boland

[test] [xdo] Ensure WM controls work by checking metacity's settings. Fixes: . Appoved by Lohith D Shivamurthy.

843. By Gerry Boland

[test] Fix Xdo to properly send function keys and re-enable Alt+F1 focus test. Fixes: . Appoved by Lohith D Shivamurthy.

844. By Didier Roche-Tolomelli

Dummy commit to rebuild against latest libunity and unity packaging
(libunity ABI bump) UNBLOCK. Fixes: . Appoved by Aurélien Gâteau.

845. By Aurélien Gâteau

* debian/control:
  - bump build-dep versions.

846. By Aurélien Gâteau

releasing version 5.2.0-0ubuntu1

847. By Didier Roche-Tolomelli

post-changelog edit

848. By Didier Roche-Tolomelli

remove TODO

849. By Albert Astals Cid

[test] Check that F1 toggles the launcher and brings the focus to the previous active window
. Fixes: . Appoved by Gerry Boland.

850. By Albert Astals Cid

[test] Check that showing the desktop shows also the launcher
. Fixes: . Appoved by .

851. By Gerry Boland

Rewrite launcher autohide_show_tests to use the verify* methods. Also add TmpWindow class manage creating and closing windows needed for tests.

Replacing the assert* with verify* methods mean we can remove the arbitrary sleep times, which means the test suite will run more smoothly (and faster!). The TmpWindow class provides book-keeping so that all created windows can be closed by the test teardown method, so that one failed test won't cause other tests to fail.. Fixes: . Appoved by Albert Astals Cid.

852. By Albert Astals Cid

Do not use "" to assign to QStrings, use QString() instead that is much faster or do not do nothing if they were just declared. Fixes: . Appoved by .

853. By Albert Astals Cid

Add some const &

It is true that those classes are refcounted so the win is not huge, but there's no reason not to save some locks in some atomic integers used for the ref/unrefing. Fixes: . Appoved by Gerry Boland.

854. By Albert Astals Cid

Add two more files to .bzrignore. Fixes: . Appoved by Gerry Boland.

855. By Albert Astals Cid

Wait for the Quit event to be delivered before really quitting

I think this is a problem either in Qt or in gnome-session, but we can "easily" workaround it ourselves, and could not find how to fix it "properly" so this seems like an acceptable workaround to me.. Fixes: https://bugs.launchpad.net/bugs/812104. Appoved by Gerry Boland.

856. By Albert Astals Cid

[test] Small improvements to autohide tests regarding timing on mouse moves
. Fixes: . Appoved by Gerry Boland.

857. By Albert Astals Cid

Remove Places related code as it is not used anymore. Fixes: . Appoved by Gerry Boland.

858. By Albert Astals Cid

[test] Check that the launcher is around for 1 second after removing an application tile
. Fixes: . Appoved by Michał Sawicz.

859. By Florian Boucault

Defined new 'visible' property on Unity2dDeclarativeView.
Cleaned up descendant classes handling of hideEvent and showEvent.

860. By Florian Boucault

[workspace switcher] Draw Nautilus desktop window's screenshot instead of loading the GNOME wallpaper.

861. By Florian Boucault

Added FIXME.

862. By Florian Boucault

Merged lp:unity-2d

863. By Florian Boucault

Fixes bug #900019

Revision history for this message
Gerry Boland (gerboland) wrote :

This improves the Spread visuals a lot.

It looks less good on multimonitor (the whole desktop of all screens is squeezed into each desktop), but as spread requires lots of MM work, I will accept this.

review: Approve
Revision history for this message
Florian Boucault (fboucault) wrote :

Thanks Gerry. There is still work to do on this MR though, work highlighted above. In summary:
* FIXME: Nautilus' window pixmap is reloaded as many times as there is workspaces
* writing automated tests:
  - make sure nautilus' pixmap is loaded only once
  - make sure nautilus XID retrieval works

Revision history for this message
Florian Boucault (fboucault) wrote :

> This improves the Spread visuals a lot.
>
> It looks less good on multimonitor (the whole desktop of all screens is
> squeezed into each desktop), but as spread requires lots of MM work, I will
> accept this.

Can you post a screenshot of what you described here. I sense that this is not acceptable as it would be a regression of what we currently have.

Revision history for this message
Gerry Boland (gerboland) wrote :

MM Screengrab: https://imgur.com/I4OVY

Yes it is a regression of what we have right now.

Since you plan to continue working on this branch, can you please mark it as Work in Progress until it's ready for review again?

Unmerged revisions

863. By Florian Boucault

Fixes bug #900019

862. By Florian Boucault

Merged lp:unity-2d

861. By Florian Boucault

Added FIXME.

860. By Florian Boucault

[workspace switcher] Draw Nautilus desktop window's screenshot instead of loading the GNOME wallpaper.

859. By Florian Boucault

Defined new 'visible' property on Unity2dDeclarativeView.
Cleaned up descendant classes handling of hideEvent and showEvent.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libunity-2d-private/Unity2d/plugin.cpp'
2--- libunity-2d-private/Unity2d/plugin.cpp 2012-01-24 16:46:52 +0000
3+++ libunity-2d-private/Unity2d/plugin.cpp 2012-01-26 22:03:24 +0000
4@@ -36,6 +36,7 @@
5 #include "windowinfo.h"
6 #include "windowslist.h"
7 #include "screeninfo.h"
8+#include "rootwindowinfo.h"
9 #include "plugin.h"
10 #include "cacheeffect.h"
11 #include "iconutilities.h"
12@@ -102,6 +103,7 @@
13 qmlRegisterType<WindowsList>(uri, 0, 1, "WindowsList");
14 qmlRegisterType<ScreenInfo>(); // Register the type as non creatable
15 qmlRegisterType<WorkspacesInfo>(); // Register the type as non creatable
16+ qmlRegisterType<RootWindowInfo>(); // Register the type as non creatable
17
18 qmlRegisterType<CacheEffect>(uri, 0, 1, "CacheEffect");
19 qmlRegisterType<QGraphicsBlurEffect>("Effects", 1, 0, "Blur");
20@@ -175,6 +177,7 @@
21 not creatable directly in QML */
22 engine->rootContext()->setContextProperty("screen", ScreenInfo::instance());
23 engine->rootContext()->setContextProperty("iconUtilities", new IconUtilities(engine));
24+ engine->rootContext()->setContextProperty("rootWindow", RootWindowInfo::instance());
25
26 /* Expose QConf objects as a context property not to initialize it multiple times */
27 engine->rootContext()->setContextProperty("dash2dConfiguration", &dash2dConfiguration());
28
29=== modified file 'libunity-2d-private/src/CMakeLists.txt'
30--- libunity-2d-private/src/CMakeLists.txt 2012-01-24 11:50:38 +0000
31+++ libunity-2d-private/src/CMakeLists.txt 2012-01-26 22:03:24 +0000
32@@ -34,6 +34,7 @@
33 windowinfo.cpp
34 windowslist.cpp
35 screeninfo.cpp
36+ rootwindowinfo.cpp
37 cacheeffect.cpp
38 workspacesinfo.cpp
39 signalwaiter.cpp
40
41=== added file 'libunity-2d-private/src/rootwindowinfo.cpp'
42--- libunity-2d-private/src/rootwindowinfo.cpp 1970-01-01 00:00:00 +0000
43+++ libunity-2d-private/src/rootwindowinfo.cpp 2012-01-26 22:03:24 +0000
44@@ -0,0 +1,121 @@
45+/*
46+ * Copyright (C) 2011 Canonical, Ltd.
47+ *
48+ * Authors:
49+ * Florian Boucault <florian.boucault@canonical.com>
50+ *
51+ * This program is free software; you can redistribute it and/or modify
52+ * it under the terms of the GNU General Public License as published by
53+ * the Free Software Foundation; version 3.
54+ *
55+ * This program is distributed in the hope that it will be useful,
56+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
57+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
58+ * GNU General Public License for more details.
59+ *
60+ * You should have received a copy of the GNU General Public License
61+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
62+ */
63+
64+#include "rootwindowinfo.h"
65+
66+#include <QX11Info>
67+#include <QAbstractEventDispatcher>
68+
69+#include <X11/Xlib.h>
70+#include <X11/Xatom.h>
71+
72+
73+QAbstractEventDispatcher::EventFilter RootWindowInfo::s_oldEventFilter = NULL;
74+Atom RootWindowInfo::NAUTILUS_DESKTOP_WINDOW_ID = 0;
75+
76+RootWindowInfo::RootWindowInfo(QObject *parent) :
77+ QObject(parent),
78+ m_nautilusDesktopWindowId(0)
79+{
80+ RootWindowInfo::internX11Atoms();
81+
82+ /* Setup a low-level event filter to receive X11 events directly,
83+ then ask X11 to notify us of property changes on the root window.
84+ */
85+ QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance();
86+ RootWindowInfo::s_oldEventFilter = dispatcher->setEventFilter(RootWindowInfo::globalEventFilter);
87+ XSelectInput(QX11Info::display(), QX11Info::appRootWindow(), PropertyChangeMask);
88+
89+ /* Initialize cached values of properties */
90+ updateNautilusDesktopWindowId();
91+}
92+
93+RootWindowInfo* RootWindowInfo::instance()
94+{
95+ static RootWindowInfo* singleton = new RootWindowInfo();
96+ return singleton;
97+}
98+
99+void RootWindowInfo::internX11Atoms()
100+{
101+ NAUTILUS_DESKTOP_WINDOW_ID = XInternAtom(QX11Info::display(),
102+ "NAUTILUS_DESKTOP_WINDOW_ID",
103+ False);
104+}
105+
106+bool RootWindowInfo::globalEventFilter(void* message)
107+{
108+ /* Always call the old event handler before we do our thing, and
109+ make sure to always return what it did return, as we don't really
110+ "handle" any event, we just monitor them */
111+ bool result;
112+ result = (RootWindowInfo::s_oldEventFilter != NULL) ? RootWindowInfo::s_oldEventFilter(message) : false;
113+
114+ XEvent* event = static_cast<XEvent*>(message);
115+ if (event->type == PropertyNotify) {
116+ if (event->xproperty.atom == NAUTILUS_DESKTOP_WINDOW_ID) {
117+ RootWindowInfo::instance()->updateNautilusDesktopWindowId();
118+ }
119+ }
120+
121+ return result;
122+}
123+
124+bool RootWindowInfo::getX11Property(Atom property, long long_length, Atom req_type, unsigned long &value)
125+{
126+ Atom actual_type_return;
127+ int actual_format_return;
128+ unsigned long nitems_return;
129+ unsigned long bytes_after_return;
130+ unsigned char *prop_return;
131+ int result;
132+
133+ result = XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(),
134+ property,
135+ 0, long_length, False, req_type,
136+ &actual_type_return, &actual_format_return, &nitems_return,
137+ &bytes_after_return, &prop_return);
138+ if (result == Success && actual_type_return != None) {
139+ value = ((unsigned long*) prop_return)[0];
140+ XFree(prop_return);
141+ return true;
142+ }
143+
144+ return false;
145+}
146+
147+void RootWindowInfo::updateNautilusDesktopWindowId()
148+{
149+ unsigned long value = 0;
150+
151+ getX11Property(NAUTILUS_DESKTOP_WINDOW_ID, 1, XA_WINDOW, value);
152+
153+ if (m_nautilusDesktopWindowId != value) {
154+ m_nautilusDesktopWindowId = value;
155+ Q_EMIT nautilusDesktopWindowIdChanged();
156+ }
157+}
158+
159+unsigned long RootWindowInfo::nautilusDesktopWindowId() const
160+{
161+ return m_nautilusDesktopWindowId;
162+}
163+
164+
165+#include "rootwindowinfo.moc"
166
167=== added file 'libunity-2d-private/src/rootwindowinfo.h'
168--- libunity-2d-private/src/rootwindowinfo.h 1970-01-01 00:00:00 +0000
169+++ libunity-2d-private/src/rootwindowinfo.h 2012-01-26 22:03:24 +0000
170@@ -0,0 +1,57 @@
171+/*
172+ * Copyright (C) 2011 Canonical, Ltd.
173+ *
174+ * Authors:
175+ * Florian Boucault <florian.boucault@canonical.com>
176+ *
177+ * This program is free software; you can redistribute it and/or modify
178+ * it under the terms of the GNU General Public License as published by
179+ * the Free Software Foundation; version 3.
180+ *
181+ * This program is distributed in the hope that it will be useful,
182+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
183+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
184+ * GNU General Public License for more details.
185+ *
186+ * You should have received a copy of the GNU General Public License
187+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
188+ */
189+
190+#ifndef ROOTWINDOWINFO_H
191+#define ROOTWINDOWINFO_H
192+
193+#include <QObject>
194+#include <QAbstractEventDispatcher>
195+
196+typedef unsigned long Atom;
197+
198+class RootWindowInfo : public QObject
199+{
200+ Q_OBJECT
201+
202+ Q_PROPERTY(unsigned long nautilusDesktopWindowId READ nautilusDesktopWindowId
203+ NOTIFY nautilusDesktopWindowIdChanged)
204+public:
205+ static RootWindowInfo* instance();
206+
207+ /* getters */
208+ unsigned long nautilusDesktopWindowId() const;
209+
210+Q_SIGNALS:
211+ void nautilusDesktopWindowIdChanged();
212+
213+private:
214+ explicit RootWindowInfo(QObject *parent = 0);
215+
216+ static void internX11Atoms();
217+ static bool globalEventFilter(void* message);
218+ static bool getX11Property(Atom property, long long_length, Atom req_type, unsigned long &value);
219+
220+ void updateNautilusDesktopWindowId();
221+
222+ static QAbstractEventDispatcher::EventFilter s_oldEventFilter;
223+ static Atom NAUTILUS_DESKTOP_WINDOW_ID;
224+ unsigned long m_nautilusDesktopWindowId;
225+};
226+
227+#endif // ROOTWINDOWINFO_H
228
229=== modified file 'libunity-2d-private/src/unity2ddeclarativeview.cpp'
230--- libunity-2d-private/src/unity2ddeclarativeview.cpp 2011-12-08 19:41:38 +0000
231+++ libunity-2d-private/src/unity2ddeclarativeview.cpp 2012-01-26 22:03:24 +0000
232@@ -158,6 +158,17 @@
233 Q_EMIT globalPositionChanged(globalPosition());
234 }
235
236+void Unity2DDeclarativeView::showEvent(QShowEvent* event)
237+{
238+ QDeclarativeView::showEvent(event);
239+ Q_EMIT visibleChanged(true);
240+}
241+
242+void Unity2DDeclarativeView::hideEvent(QHideEvent* event)
243+{
244+ QDeclarativeView::hideEvent(event);
245+ Q_EMIT visibleChanged(false);
246+}
247
248 /* Obtaining & Discarding Keyboard Focus for Window on Demand
249 *
250
251=== modified file 'libunity-2d-private/src/unity2ddeclarativeview.h'
252--- libunity-2d-private/src/unity2ddeclarativeview.h 2011-12-08 19:41:38 +0000
253+++ libunity-2d-private/src/unity2ddeclarativeview.h 2012-01-26 22:03:24 +0000
254@@ -26,6 +26,7 @@
255 Q_PROPERTY(bool useOpenGL READ useOpenGL WRITE setUseOpenGL NOTIFY useOpenGLChanged)
256 Q_PROPERTY(bool transparentBackground READ transparentBackground WRITE setTransparentBackground NOTIFY transparentBackgroundChanged)
257 Q_PROPERTY(QPoint globalPosition READ globalPosition NOTIFY globalPositionChanged)
258+ Q_PROPERTY(bool visible READ isVisible NOTIFY visibleChanged)
259
260 public:
261 Unity2DDeclarativeView(QWidget *parent = 0);
262@@ -44,10 +45,13 @@
263 void useOpenGLChanged(bool);
264 void transparentBackgroundChanged(bool);
265 void globalPositionChanged(QPoint);
266+ void visibleChanged(bool);
267
268 protected:
269 void setupViewport();
270 virtual void moveEvent(QMoveEvent* event);
271+ virtual void showEvent(QShowEvent* event);
272+ virtual void hideEvent(QHideEvent* event);
273
274 protected Q_SLOTS:
275 void forceActivateWindow();
276
277=== modified file 'places/app/dashdeclarativeview.cpp'
278--- places/app/dashdeclarativeview.cpp 2012-01-26 13:12:12 +0000
279+++ places/app/dashdeclarativeview.cpp 2012-01-26 22:03:24 +0000
280@@ -134,7 +134,7 @@
281 void
282 DashDeclarativeView::showEvent(QShowEvent *event)
283 {
284- QDeclarativeView::showEvent(event);
285+ Unity2DDeclarativeView::showEvent(event);
286 /* Note that this has to be called everytime the window is shown, as the WM
287 will remove the flags when the window is hidden */
288 setWMFlags();
289
290=== modified file 'spread/Workspace.qml'
291--- spread/Workspace.qml 2011-10-21 12:46:08 +0000
292+++ spread/Workspace.qml 2012-01-26 22:03:24 +0000
293@@ -16,7 +16,7 @@
294 * along with this program. If not, see <http://www.gnu.org/licenses/>.
295 */
296
297-import QtQuick 1.0
298+import QtQuick 1.1
299 import "utils.js" as Utils
300 import Unity2d 1.0
301
302@@ -47,15 +47,26 @@
303 height: switcher.height
304 state: parent.state
305
306- GnomeBackground {
307- anchors.fill: parent
308- overlay_color: "black"
309- overlay_alpha: 0
310-
311- clip: true
312- cached: false
313- offsetX: -screen.panelsFreeGeometry.x
314- offsetY: -screen.panelsFreeGeometry.y
315+ clip: true
316+
317+ Image {
318+ x: -declarativeView.globalPosition.x
319+ y: -declarativeView.globalPosition.y
320+ width: screen.geometry.width
321+ height: screen.geometry.height
322+
323+ smooth: true
324+ /* The window is scaled to a rectangle as large as possible inside
325+ sourceSize while preserving its aspect ratio.
326+ It saves video memory when using the OpenGL backend.
327+ It makes scaling cheaper in the spread when using the raster backend.
328+ */
329+ sourceSize.width: 512
330+ sourceSize.height: 512
331+
332+ source: declarativeView.visible ? "image://window/" + rootWindow.nautilusDesktopWindowId : ""
333+ /* FIXME: Nautilus' window pixmap is reloaded as many times as there is workspaces */
334+ cache: false
335 }
336
337 Windows {
338
339=== modified file 'spread/app/spreadview.cpp'
340--- spread/app/spreadview.cpp 2011-12-08 18:39:18 +0000
341+++ spread/app/spreadview.cpp 2012-01-26 22:03:24 +0000
342@@ -97,15 +97,3 @@
343
344 return false;
345 }
346-
347-void SpreadView::showEvent(QShowEvent *event)
348-{
349- Q_UNUSED(event);
350- Q_EMIT visibleChanged(true);
351-}
352-
353-void SpreadView::hideEvent(QHideEvent *event)
354-{
355- Q_UNUSED(event);
356- Q_EMIT visibleChanged(false);
357-}
358
359=== modified file 'spread/app/spreadview.h'
360--- spread/app/spreadview.h 2011-12-08 18:39:18 +0000
361+++ spread/app/spreadview.h 2012-01-26 22:03:24 +0000
362@@ -39,13 +39,10 @@
363 protected:
364 virtual void focusInEvent( QFocusEvent * event );
365 virtual void focusOutEvent( QFocusEvent * event );
366- virtual void showEvent(QShowEvent *event);
367- virtual void hideEvent(QHideEvent *event);
368 bool eventFilter(QObject *obj, QEvent *event);
369
370 Q_SIGNALS:
371 void outsideClick();
372- void visibleChanged(bool visible);
373 };
374
375 #endif // SPREADVIEW_H

Subscribers

People subscribed via source and target branches