Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--bin/Makefile.am4
-rw-r--r--bin/sugar.in2
-rw-r--r--browser/GeckoBrowserPersist.cpp190
-rw-r--r--browser/GeckoBrowserPersist.h19
-rw-r--r--browser/GeckoDocumentObject.cpp241
-rw-r--r--browser/GeckoDocumentObject.h31
-rw-r--r--browser/GeckoDragDropHooks.cpp218
-rw-r--r--browser/GeckoDragDropHooks.h25
-rw-r--r--browser/Makefile.am9
-rw-r--r--browser/sugar-browser.cpp238
-rw-r--r--data/Makefile.am5
-rw-r--r--data/gtkrc6
-rw-r--r--data/mime.types1
-rw-r--r--data/sugar-xo.gtkrc7
-rw-r--r--data/sugar.gtkrc5
-rw-r--r--services/clipboard/clipboardobject.py11
-rw-r--r--services/clipboard/clipboardservice.py3
-rwxr-xr-xservices/clipboard/sugar-clipboard2
-rw-r--r--services/clipboard/typeregistry.py62
-rwxr-xr-xservices/console/sugar-console2
-rwxr-xr-xservices/datastore/sugar-data-store2
-rw-r--r--services/presence/__init__.py36
-rw-r--r--services/presence/activity.py162
-rw-r--r--services/presence/buddy.py343
-rw-r--r--services/presence/presenceservice.py52
-rw-r--r--services/presence/psutils.py6
-rw-r--r--services/presence/server_plugin.py163
-rwxr-xr-xservices/presence/sugar-presence-service2
-rw-r--r--shell/Makefile.am1
-rw-r--r--shell/extensions/Makefile.am31
-rw-r--r--shell/extensions/__init__.py6
-rw-r--r--shell/extensions/_extensions.defs (renamed from shell/extensions/extensions.defs)0
-rw-r--r--shell/extensions/_extensions.override (renamed from shell/extensions/extensions.override)0
-rw-r--r--shell/extensions/_extensionsmodule.c (renamed from shell/extensions/extensionsmodule.c)10
-rw-r--r--shell/extensions/extensions.c260
-rw-r--r--shell/model/BuddyModel.py12
-rw-r--r--shell/model/MeshModel.py19
-rw-r--r--shell/model/homemodel.py2
-rw-r--r--shell/view/BuddyMenu.py2
-rw-r--r--shell/view/clipboardicon.py56
-rw-r--r--shell/view/clipboardmenu.py14
-rw-r--r--shell/view/frame/clipboardbox.py51
-rw-r--r--shell/view/frame/frame.py4
-rw-r--r--shell/view/home/FriendsBox.py11
-rw-r--r--shell/view/home/HomeWindow.py69
-rw-r--r--shell/view/home/Makefile.am3
-rw-r--r--shell/view/home/MeshBox.py5
-rw-r--r--shell/view/home/transitionbox.py66
-rwxr-xr-xsugar-emulator58
-rw-r--r--sugar/Makefile.am13
-rw-r--r--sugar/activity/activityfactoryservice.py2
-rw-r--r--sugar/browser/__init__.py7
-rw-r--r--sugar/env.py23
-rw-r--r--sugar/graphics/animator.py8
-rw-r--r--sugar/graphics/canvasicon.py5
-rw-r--r--sugar/graphics/snowflakebox.py13
-rw-r--r--sugar/graphics/spreadbox.py25
-rw-r--r--sugar/graphics2/Makefile.am5
-rw-r--r--sugar/graphics2/__init__.py0
-rw-r--r--sugar/graphics2/toolbox.py32
-rw-r--r--sugar/graphics2/window.py49
-rw-r--r--sugar/ltihooks.py72
-rw-r--r--sugar/presence/presenceservice.py13
-rwxr-xr-xtests/test-button.py66
-rwxr-xr-xtests/test-entry.py71
-rwxr-xr-xtests/test-frame.py36
-rwxr-xr-xtests/test-label.py51
-rwxr-xr-xtests/test-option-menu.py64
-rwxr-xr-xtests/test-snowflake-box.py4
-rwxr-xr-xtests/test-theme.py121
-rwxr-xr-xtests/test-ui.py98
-rw-r--r--tests/test-window-manager.py45
73 files changed, 2201 insertions, 1150 deletions
diff --git a/.gitignore b/.gitignore
index fb168b1..6e3a9e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,4 @@ services/clipboard/org.laptop.Clipboard.service
services/console/org.laptop.sugar.Console.service
services/presence/org.laptop.Sugar.Presence.service
bin/sugar
+shell/extensions/_extensions.c
diff --git a/bin/Makefile.am b/bin/Makefile.am
index db5e219..ffbc7bb 100644
--- a/bin/Makefile.am
+++ b/bin/Makefile.am
@@ -1,3 +1,6 @@
+sugardir = $(pkgdatadir)/bin
+sugar_SCRIPTS = sugar-activity-factory
+
bin_SCRIPTS = \
sugar \
sugar-activity \
@@ -6,6 +9,7 @@ bin_SCRIPTS = \
EXTRA_DIST = \
$(bin_SCRIPTS) \
+ $(sugar_SCRIPTS) \
sugar.in
DISTCLEANFILES = \
diff --git a/bin/sugar.in b/bin/sugar.in
index bfdf8f9..de2595d 100644
--- a/bin/sugar.in
+++ b/bin/sugar.in
@@ -1,2 +1,2 @@
-export GTK2_RC_FILES=@prefix@/share/sugar/gtkrc
+export GTK2_RC_FILES=@prefix@/share/sugar/data/gtkrc
dbus-launch --exit-with-session sugar-shell
diff --git a/browser/GeckoBrowserPersist.cpp b/browser/GeckoBrowserPersist.cpp
new file mode 100644
index 0000000..2c019c5
--- /dev/null
+++ b/browser/GeckoBrowserPersist.cpp
@@ -0,0 +1,190 @@
+#include <config.h>
+
+#ifdef HAVE_GECKO_1_9
+
+#include <stdio.h>
+
+#include <gtkmozembed.h>
+#include <gtkmozembed_internal.h>
+#include <nsIRequest.h>
+#include <nsNetUtil.h>
+#include <nsISeekableStream.h>
+#include <nsIHttpChannel.h>
+#include <nsIUploadChannel.h>
+#include <nsIWebBrowser.h>
+#include <nsISHistory.h>
+#include <nsIHistoryEntry.h>
+#include <nsISHEntry.h>
+#include <nsIInputStream.h>
+#include <nsIWebNavigation.h>
+
+#include "GeckoBrowserPersist.h"
+
+GeckoBrowserPersist::GeckoBrowserPersist(SugarBrowser *browser)
+ : mBrowser(browser)
+{
+}
+
+GeckoBrowserPersist::~GeckoBrowserPersist()
+{
+}
+
+static nsresult
+NewURI(const char *uri, nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIServiceManager> mgr;
+ NS_GetServiceManager (getter_AddRefs (mgr));
+ NS_ENSURE_TRUE(mgr, FALSE);
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = mgr->GetServiceByContractID ("@mozilla.org/network/io-service;1",
+ NS_GET_IID (nsIIOService),
+ getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ nsCString cSpec(uri);
+ return ioService->NewURI (cSpec, nsnull, nsnull, result);
+}
+
+static nsresult
+GetPostData(SugarBrowser *browser, nsIInputStream **postData)
+{
+#ifdef HAVE_NS_WEB_BROWSER
+ nsCOMPtr<nsIWebBrowser> webBrowser;
+ gtk_moz_embed_get_nsIWebBrowser(GTK_MOZ_EMBED(browser),
+ getter_AddRefs(webBrowser));
+ NS_ENSURE_TRUE(webBrowser, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(webBrowser));
+ NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
+
+ PRInt32 sindex;
+
+ nsCOMPtr<nsISHistory> sessionHistory;
+ webNav->GetSessionHistory(getter_AddRefs(sessionHistory));
+ NS_ENSURE_TRUE(sessionHistory, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIHistoryEntry> entry;
+ sessionHistory->GetIndex(&sindex);
+ sessionHistory->GetEntryAtIndex(sindex, PR_FALSE, getter_AddRefs(entry));
+
+ nsCOMPtr<nsISHEntry> shEntry(do_QueryInterface(entry));
+ if (shEntry) {
+ shEntry->GetPostData(postData);
+ }
+
+ return NS_OK;
+#endif
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+bool
+GeckoBrowserPersist::SaveURI(const char *uri, const char *filename)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> sourceURI;
+ rv = NewURI(uri, getter_AddRefs(sourceURI));
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ nsCOMPtr<nsILocalFile> destFile = do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(destFile, FALSE);
+
+ destFile->InitWithNativePath(nsCString(filename));
+
+ nsCOMPtr<nsIInputStream> postData;
+ GetPostData(mBrowser, getter_AddRefs(postData));
+
+ nsCOMPtr<nsIChannel> inputChannel;
+ rv = NS_NewChannel(getter_AddRefs(inputChannel), sourceURI,
+ nsnull, nsnull, nsnull, nsIRequest::LOAD_NORMAL);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
+ if (httpChannel) {
+ nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(postData));
+ if (stream)
+ {
+ // Rewind the postdata stream
+ stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
+ // Attach the postdata to the http channel
+ uploadChannel->SetUploadStream(postData, EmptyCString(), -1);
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = inputChannel->Open(getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ nsCOMPtr<nsIFileOutputStream> fileOutputStream =
+ do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ rv = fileOutputStream->Init(destFile, -1, -1, 0);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ // Read data from the input and write to the output
+ char buffer[8192];
+ PRUint32 bytesRead;
+ PRUint32 bytesRemaining;
+ PRBool cancel = PR_FALSE;
+ PRBool readError;
+
+ rv = stream->Available(&bytesRemaining);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ while (!cancel && bytesRemaining)
+ {
+ readError = PR_TRUE;
+ rv = stream->Read(buffer, PR_MIN(sizeof(buffer), bytesRemaining), &bytesRead);
+ if (NS_SUCCEEDED(rv))
+ {
+ readError = PR_FALSE;
+ // Write out the data until something goes wrong, or, it is
+ // all written. We loop because for some errors (e.g., disk
+ // full), we get NS_OK with some bytes written, then an error.
+ // So, we want to write again in that case to get the actual
+ // error code.
+ const char *bufPtr = buffer; // Where to write from.
+ while (NS_SUCCEEDED(rv) && bytesRead)
+ {
+ PRUint32 bytesWritten = 0;
+ rv = fileOutputStream->Write(bufPtr, bytesRead, &bytesWritten);
+ if (NS_SUCCEEDED(rv))
+ {
+ bytesRead -= bytesWritten;
+ bufPtr += bytesWritten;
+ bytesRemaining -= bytesWritten;
+ // Force an error if (for some reason) we get NS_OK but
+ // no bytes written.
+ if (!bytesWritten)
+ {
+ rv = NS_ERROR_FAILURE;
+ cancel = PR_TRUE;
+ }
+ }
+ else
+ {
+ // Disaster - can't write out the bytes - disk full / permission?
+ cancel = PR_TRUE;
+ }
+ }
+ }
+ else
+ {
+ // Disaster - can't read the bytes - broken link / file error?
+ cancel = PR_TRUE;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ stream->Close();
+
+ return TRUE;
+}
+
+#endif
diff --git a/browser/GeckoBrowserPersist.h b/browser/GeckoBrowserPersist.h
new file mode 100644
index 0000000..e976789
--- /dev/null
+++ b/browser/GeckoBrowserPersist.h
@@ -0,0 +1,19 @@
+#ifndef __GECKO_BROWSER_PERSIST_H__
+#define __GECKO_BROWSER_PERSIST_H__
+
+#include "sugar-browser.h"
+
+class GeckoBrowserPersist
+{
+public:
+ GeckoBrowserPersist(SugarBrowser *browser);
+ ~GeckoBrowserPersist();
+
+ bool SaveURI(const char *uri, const char *filename);
+private:
+ SugarBrowser *mBrowser;
+protected:
+ /* additional members */
+};
+
+#endif // __GECKO_BROWSER_PERSIST_H__
diff --git a/browser/GeckoDocumentObject.cpp b/browser/GeckoDocumentObject.cpp
new file mode 100644
index 0000000..26eb13d
--- /dev/null
+++ b/browser/GeckoDocumentObject.cpp
@@ -0,0 +1,241 @@
+#include <config.h>
+
+#ifdef HAVE_GECKO_1_9
+
+#include <unistd.h>
+
+#include <glib.h>
+#include <imgICache.h>
+#include <nsComponentManagerUtils.h>
+#include <nsCOMPtr.h>
+#include <nsIDOMHTMLElement.h>
+#include <nsIInterfaceRequestorUtils.h>
+#include <nsIIOService.h>
+#include <nsILocalFile.h>
+#include <nsIMIMEHeaderParam.h>
+#include <nsIProperties.h>
+#include <nsISupportsPrimitives.h>
+#include <nsIURI.h>
+#include <nsIURL.h>
+#include <nsServiceManagerUtils.h>
+#include <nsStringAPI.h>
+
+#include "GeckoDocumentObject.h"
+#include "GeckoBrowserPersist.h"
+
+GeckoDocumentObject::GeckoDocumentObject(SugarBrowser *browser, nsIDOMNode *node)
+ : mBrowser(browser),
+ mNode(node),
+ mImage(NULL)
+{
+}
+
+GeckoDocumentObject::~GeckoDocumentObject()
+{
+}
+
+bool GeckoDocumentObject::IsImage()
+{
+ if(mImage) {
+ return true;
+ }
+
+ nsresult rv;
+
+ PRUint16 type;
+ rv = mNode->GetNodeType(&type);
+ if(NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(mNode);
+ if ((nsIDOMNode::ELEMENT_NODE == type) && element) {
+ nsString uTag;
+ rv = element->GetLocalName(uTag);
+ if(NS_FAILED(rv)) return rv;
+
+ nsCString tag;
+ NS_UTF16ToCString (uTag, NS_CSTRING_ENCODING_UTF8, tag);
+
+ if (g_ascii_strcasecmp (tag.get(), "img") == 0) {
+ nsCOMPtr <nsIDOMHTMLImageElement> imagePtr;
+ imagePtr = do_QueryInterface(mNode, &rv);
+ if(NS_FAILED(rv)) return rv;
+
+ mImage = imagePtr;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static nsresult
+NewURI(const char *uri, nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIServiceManager> mgr;
+ NS_GetServiceManager (getter_AddRefs (mgr));
+ NS_ENSURE_TRUE(mgr, FALSE);
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = mgr->GetServiceByContractID ("@mozilla.org/network/io-service;1",
+ NS_GET_IID (nsIIOService),
+ getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ nsCString cSpec(uri);
+ return ioService->NewURI (cSpec, nsnull, nsnull, result);
+}
+
+static nsresult
+FilenameFromContentDisposition(nsCString contentDisposition, nsCString &fileName)
+{
+ nsresult rv;
+ nsCString fallbackCharset;
+
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService("@mozilla.org/network/mime-hdrparam;1");
+ NS_ENSURE_TRUE(mimehdrpar, NS_ERROR_FAILURE);
+
+ nsString aFileName;
+ rv = mimehdrpar->GetParameter (contentDisposition, "filename",
+ fallbackCharset, PR_TRUE, nsnull,
+ aFileName);
+
+ if (NS_FAILED(rv) || !fileName.Length()) {
+ rv = mimehdrpar->GetParameter (contentDisposition, "name",
+ fallbackCharset, PR_TRUE, nsnull,
+ aFileName);
+ }
+
+ if (NS_SUCCEEDED(rv) && fileName.Length()) {
+ NS_UTF16ToCString (aFileName, NS_CSTRING_ENCODING_UTF8, fileName);
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+GetImageProperties(char *imgURIStr, nsIProperties **properties)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> imageURI;
+ rv = NewURI(imgURIStr, getter_AddRefs(imageURI));
+ if(NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIServiceManager> mgr;
+ NS_GetServiceManager(getter_AddRefs(mgr));
+ NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
+
+ nsCOMPtr<imgICache> imgCache;
+ rv = mgr->GetServiceByContractID("@mozilla.org/image/cache;1",
+ NS_GET_IID(imgICache),
+ getter_AddRefs(imgCache));
+ if(NS_FAILED(rv)) return rv;
+
+ imgCache->FindEntryProperties(imageURI, properties);
+ NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+char *
+GeckoDocumentObject::GetImageName()
+{
+ if(!IsImage()) {
+ return NULL;
+ }
+
+ if(mImageName.Length()) {
+ return g_strdup(mImageName.get());
+ }
+
+ nsresult rv;
+ char *imgURIStr = GetImageURI();
+
+ nsCOMPtr<nsIProperties> imgProperties;
+ rv = GetImageProperties(imgURIStr, getter_AddRefs(imgProperties));
+ NS_ENSURE_SUCCESS(rv, NULL);
+ if (imgProperties) {
+ nsCOMPtr<nsISupportsCString> dispositionCString;
+ imgProperties->Get("content-disposition",
+ NS_GET_IID(nsISupportsCString),
+ getter_AddRefs(dispositionCString));
+ if (dispositionCString) {
+ nsCString contentDisposition;
+ dispositionCString->GetData(contentDisposition);
+ FilenameFromContentDisposition(contentDisposition, mImageName);
+ }
+ }
+
+ if (!mImageName.Length()) {
+ nsCOMPtr<nsIURI> imageURI;
+ rv = NewURI(imgURIStr, getter_AddRefs(imageURI));
+ NS_ENSURE_SUCCESS(rv, NULL);
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(imageURI));
+ if (url) {
+ url->GetFileName(mImageName);
+ }
+ }
+
+ return mImageName.Length() ? g_strdup(mImageName.get()) : NULL;
+}
+
+char *
+GeckoDocumentObject::GetImageMimeType()
+{
+ if(!IsImage()) {
+ return NULL;
+ }
+
+ if(mImageMimeType.Length()) {
+ return g_strdup(mImageMimeType.get());
+ }
+
+ nsresult rv;
+ char *imgURIStr = GetImageURI();
+
+ nsCOMPtr<nsIProperties> imgProperties;
+ rv = GetImageProperties(imgURIStr, getter_AddRefs(imgProperties));
+ NS_ENSURE_SUCCESS(rv, NULL);
+ if (imgProperties) {
+ nsCOMPtr<nsISupportsCString> typeCString;
+ imgProperties->Get("type",
+ NS_GET_IID(nsISupportsCString),
+ getter_AddRefs(typeCString));
+ if (typeCString) {
+ typeCString->GetData(mImageMimeType);
+ }
+ }
+
+ return mImageMimeType.Length() ? g_strdup(mImageMimeType.get()) : NULL;
+}
+
+char *
+GeckoDocumentObject::GetImageURI()
+{
+ if(!IsImage()) {
+ return NULL;
+ }
+
+ if(!mImageURI.Length()) {
+ nsresult rv;
+ nsString img;
+ rv = mImage->GetSrc(img);
+ if (NS_FAILED(rv)) return NULL;
+
+ NS_UTF16ToCString (img, NS_CSTRING_ENCODING_UTF8, mImageURI);
+ }
+ return g_strdup(mImageURI.get());
+}
+
+bool
+GeckoDocumentObject::SaveImage(const char *filename)
+{
+ GeckoBrowserPersist browserPersist(mBrowser);
+ return browserPersist.SaveURI(mImageURI.get(), filename);
+}
+
+#endif
diff --git a/browser/GeckoDocumentObject.h b/browser/GeckoDocumentObject.h
new file mode 100644
index 0000000..5f4069f
--- /dev/null
+++ b/browser/GeckoDocumentObject.h
@@ -0,0 +1,31 @@
+#ifndef __GECKO_DOCUMENT_OBJECT_H__
+#define __GECKO_DOCUMENT_OBJECT_H__
+
+#include <nsIDOMNode.h>
+#include <nsIDOMHTMLImageElement.h>
+
+#include "sugar-browser.h"
+
+class GeckoDocumentObject
+{
+public:
+ GeckoDocumentObject(SugarBrowser *browser, nsIDOMNode *node);
+ ~GeckoDocumentObject();
+
+ bool IsImage();
+ char *GetImageURI();
+ char *GetImageName();
+ char *GetImageMimeType();
+ bool SaveImage(const char *filename);
+private:
+ SugarBrowser *mBrowser;
+ nsCOMPtr<nsIDOMNode> mNode;
+ nsCOMPtr<nsIDOMHTMLImageElement> mImage;
+ nsCString mImageURI;
+ nsCString mImageName;
+ nsCString mImageMimeType;
+protected:
+ /* additional members */
+};
+
+#endif // __GECKO_DOCUMENT_OBJECT_H__
diff --git a/browser/GeckoDragDropHooks.cpp b/browser/GeckoDragDropHooks.cpp
new file mode 100644
index 0000000..6439f42
--- /dev/null
+++ b/browser/GeckoDragDropHooks.cpp
@@ -0,0 +1,218 @@
+#include <config.h>
+
+#ifdef HAVE_GECKO_1_9
+
+#include <sys/time.h>
+#include <time.h>
+
+#include <glib.h>
+#include <nsStringAPI.h>
+#include <nsCOMPtr.h>
+#include <nsITransferable.h>
+#include <nsISupportsPrimitives.h>
+#include <nsIDOMEventTarget.h>
+#include <nsComponentManagerUtils.h>
+#include <nsServiceManagerUtils.h>
+#include <nsIInterfaceRequestorUtils.h>
+#include <nsIDOMMouseEvent.h>
+#include <nsIMIMEService.h>
+
+#include "GeckoDragDropHooks.h"
+#include "GeckoDocumentObject.h"
+
+#define TEXT_URI_LIST "text/uri-list"
+#define TEXT_X_MOZ_URL "text/x-moz-url"
+#define FILE_LOCALHOST "file://"
+
+//*****************************************************************************
+// UriListDataProvider
+//*****************************************************************************
+
+class UriListDataProvider : public nsIFlavorDataProvider
+{
+public:
+ UriListDataProvider(GeckoDocumentObject *mDocumentObject);
+ virtual ~UriListDataProvider();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFLAVORDATAPROVIDER
+private:
+ GeckoDocumentObject *mDocumentObject;
+ nsCString mFilePath;
+};
+
+//*****************************************************************************
+
+NS_IMPL_ISUPPORTS1(UriListDataProvider, nsIFlavorDataProvider)
+
+UriListDataProvider::UriListDataProvider(GeckoDocumentObject *documentObject)
+ : mDocumentObject(documentObject)
+{
+}
+
+UriListDataProvider::~UriListDataProvider()
+{
+ if(mFilePath.Length()) {
+ remove(mFilePath.get());
+ }
+
+ delete mDocumentObject;
+}
+
+NS_IMETHODIMP
+UriListDataProvider::GetFlavorData(nsITransferable *aTransferable,
+ const char *aFlavor, nsISupports **aData,
+ PRUint32 *aDataLen)
+{
+ NS_ENSURE_ARG_POINTER(aData && aDataLen);
+
+ nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
+ char *image_name;
+ char *mime_type;
+ char *file_ext;
+ nsCString mime_ext;
+ timeval timestamp;
+
+ *aData = nsnull;
+ *aDataLen = 0;
+
+ if(g_ascii_strcasecmp(aFlavor, TEXT_URI_LIST) != 0) {
+ return rv;
+ }
+
+ gettimeofday(&timestamp, NULL);
+
+ mFilePath.Append(g_get_tmp_dir());
+ mFilePath.Append("/");
+ mFilePath.AppendInt(timestamp.tv_sec);
+ mFilePath.AppendInt(timestamp.tv_usec);
+
+ image_name = mDocumentObject->GetImageName();
+ file_ext = strrchr(image_name, '.');
+ mime_type = mDocumentObject->GetImageMimeType();
+
+ nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1",
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mimeService->GetPrimaryExtension(nsCString(mime_type),
+ EmptyCString(),
+ mime_ext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if(!file_ext) {
+ mFilePath.Append(image_name);
+ mFilePath.Append(".");
+ mFilePath.Append(mime_ext);
+ } else if(strcmp(file_ext, mime_ext.get())) {
+ image_name[strlen(file_ext)] = 0;
+ mFilePath.Append(image_name);
+ mFilePath.Append(".");
+ mFilePath.Append(mime_ext);
+ } else {
+ mFilePath.Append(image_name);
+ }
+
+ g_free(mime_type);
+ g_free(image_name);
+
+ if(!mDocumentObject->SaveImage(mFilePath.get())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString localURI(FILE_LOCALHOST);
+ localURI.Append(mFilePath);
+
+ nsString localURI16;
+ NS_CStringToUTF16(localURI, NS_CSTRING_ENCODING_UTF8, localURI16);
+
+ nsCOMPtr<nsISupportsString> localURIData(do_CreateInstance(
+ "@mozilla.org/supports-string;1", &rv));
+ if(NS_FAILED(rv)) return rv;
+
+ rv = localURIData->SetData(localURI16);
+ if(NS_FAILED(rv)) return rv;
+
+ CallQueryInterface(localURIData, aData);
+ *aDataLen = sizeof(nsISupportsString*);
+
+ // FIXME: Why do we need this? Is there a leak in mozilla?
+ this->Release();
+
+ return rv;
+}
+
+//*****************************************************************************
+// GeckoDragDropHooks
+//*****************************************************************************
+
+NS_IMPL_ISUPPORTS1(GeckoDragDropHooks, nsIClipboardDragDropHooks)
+
+GeckoDragDropHooks::GeckoDragDropHooks(SugarBrowser *browser)
+ : mBrowser(browser)
+{
+}
+
+GeckoDragDropHooks::~GeckoDragDropHooks()
+{
+}
+
+NS_IMETHODIMP
+GeckoDragDropHooks::AllowStartDrag(nsIDOMEvent *event, PRBool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GeckoDragDropHooks::AllowDrop(nsIDOMEvent *event, nsIDragSession *session,
+ PRBool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GeckoDragDropHooks::OnCopyOrDrag(nsIDOMEvent *aEvent, nsITransferable *trans,
+ PRBool *_retval)
+{
+ nsresult rv;
+
+ *_retval = true;
+
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent;
+ mouseEvent = do_QueryInterface(aEvent, &rv);
+ if(NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIDOMEventTarget> eventTarget;
+ rv = mouseEvent->GetTarget(getter_AddRefs(eventTarget));
+ if(NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIDOMNode> targetNode;
+ targetNode = do_QueryInterface(eventTarget, &rv);
+ if(NS_FAILED(rv)) return rv;
+
+ GeckoDocumentObject *documentObject = new GeckoDocumentObject(mBrowser,
+ targetNode);
+ if(documentObject->IsImage()) {
+ rv = trans->RemoveDataFlavor(TEXT_X_MOZ_URL);
+ if(NS_FAILED(rv)) return rv;
+
+ rv = trans->AddDataFlavor(TEXT_URI_LIST);
+ if(NS_FAILED(rv)) return rv;
+
+ UriListDataProvider *rawPtr = new UriListDataProvider(documentObject);
+ nsCOMPtr<nsISupports> dataProvider(do_QueryInterface(rawPtr, &rv));
+ if(NS_FAILED(rv)) return rv;
+
+ rv = trans->SetTransferData(TEXT_URI_LIST, dataProvider, 0);
+ if(NS_FAILED(rv)) return rv;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GeckoDragDropHooks::OnPasteOrDrop(nsIDOMEvent *event, nsITransferable *trans,
+ PRBool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#endif
diff --git a/browser/GeckoDragDropHooks.h b/browser/GeckoDragDropHooks.h
new file mode 100644
index 0000000..c551c0e
--- /dev/null
+++ b/browser/GeckoDragDropHooks.h
@@ -0,0 +1,25 @@
+#ifndef __GECKO_DRAG_DROP_HOOKS_H__
+#define __GECKO_DRAG_DROP_HOOKS_H__
+
+#include <nsIClipboardDragDropHooks.h>
+
+#include "sugar-browser.h"
+
+class GeckoDragDropHooks : public nsIClipboardDragDropHooks
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARDDRAGDROPHOOKS
+
+ GeckoDragDropHooks(SugarBrowser *browser);
+
+private:
+ ~GeckoDragDropHooks();
+
+ SugarBrowser *mBrowser;
+
+protected:
+ /* additional members */
+};
+
+#endif // __GECKO_DRAG_DROP_HOOKS_H__
diff --git a/browser/Makefile.am b/browser/Makefile.am
index ae013e0..b418583 100644
--- a/browser/Makefile.am
+++ b/browser/Makefile.am
@@ -3,6 +3,7 @@ libsugarbrowser_la_CPPFLAGS = \
$(LIB_CFLAGS) \
$(GECKO_CFLAGS) \
$(NSPR_CFLAGS) \
+ -I$(MOZILLA_INCLUDE_DIR)/commandhandler \
-I$(MOZILLA_INCLUDE_DIR)/dom \
-I$(MOZILLA_INCLUDE_DIR)/docshell \
-I$(MOZILLA_INCLUDE_DIR)/exthandler \
@@ -15,6 +16,8 @@ libsugarbrowser_la_CPPFLAGS = \
-I$(MOZILLA_INCLUDE_DIR)/uriloader \
-I$(MOZILLA_INCLUDE_DIR)/webbrwsr \
-I$(MOZILLA_INCLUDE_DIR)/webbrowserpersist \
+ -I$(MOZILLA_INCLUDE_DIR)/widget \
+ -I$(MOZILLA_INCLUDE_DIR)/xpcom \
-DPLUGIN_DIR=\"$(libdir)/mozilla/plugins\" \
-DSHARE_DIR=\"$(pkgdatadir)\"
@@ -26,8 +29,14 @@ libsugarbrowser_la_LIBADD = \
libsugarbrowser_la_SOURCES = \
$(BUILT_SOURCES) \
+ GeckoBrowserPersist.h \
+ GeckoBrowserPersist.cpp \
GeckoContentHandler.h \
GeckoContentHandler.cpp \
+ GeckoDocumentObject.h \
+ GeckoDocumentObject.cpp \
+ GeckoDragDropHooks.h \
+ GeckoDragDropHooks.cpp \
GeckoDownload.h \
GeckoDownload.cpp \
sugar-address-entry.c \
diff --git a/browser/sugar-browser.cpp b/browser/sugar-browser.cpp
index cd455b6..df5769b 100644
--- a/browser/sugar-browser.cpp
+++ b/browser/sugar-browser.cpp
@@ -24,6 +24,12 @@
#include "GeckoContentHandler.h"
#include "GeckoDownload.h"
+#ifdef HAVE_GECKO_1_9
+#include "GeckoDragDropHooks.h"
+#include "GeckoDocumentObject.h"
+#include "GeckoBrowserPersist.h"
+#endif
+
#include <gdk/gdkx.h>
#include <gtkmozembed_internal.h>
#include <nsCOMPtr.h>
@@ -56,6 +62,10 @@
#include <nsIHistoryEntry.h>
#include <nsISHEntry.h>
#include <nsIInputStream.h>
+#include <nsICommandManager.h>
+#include <nsIClipboardDragDropHooks.h>
+
+#define SUGAR_PATH "SUGAR_PATH"
enum {
PROP_0,
@@ -75,6 +85,8 @@ enum {
static guint signals[N_SIGNALS];
+static GObjectClass *parent_class = NULL;
+
static const nsModuleComponentInfo sSugarComponents[] = {
{
"Gecko Content Handler",
@@ -151,9 +163,11 @@ sugar_browser_startup(const char *profile_path, const char *profile_name)
NS_ENSURE_TRUE(prefService, FALSE);
/* Read our predefined default prefs */
+ nsCString pathToPrefs(g_getenv(SUGAR_PATH));
+ pathToPrefs.Append("/data/gecko-prefs.js");
+
nsCOMPtr<nsILocalFile> file;
- NS_NewNativeLocalFile(nsCString(SHARE_DIR"/gecko-prefs.js"),
- PR_TRUE, getter_AddRefs(file));
+ NS_NewNativeLocalFile(pathToPrefs, PR_TRUE, getter_AddRefs(file));
NS_ENSURE_TRUE(file, FALSE);
rv = prefService->ReadUserPrefs (file);
@@ -166,7 +180,10 @@ sugar_browser_startup(const char *profile_path, const char *profile_name)
prefService->GetBranch ("", getter_AddRefs(pref));
NS_ENSURE_TRUE(pref, FALSE);
- pref->SetCharPref ("helpers.private_mime_types_file", SHARE_DIR"/mime.types");
+ nsCString pathToMimeTypes(g_getenv(SUGAR_PATH));
+ pathToMimeTypes.Append("/data/mime.types");
+
+ pref->SetCharPref ("helpers.private_mime_types_file", pathToMimeTypes.get());
rv = prefService->ReadUserPrefs (nsnull);
if (NS_FAILED(rv)) {
@@ -211,25 +228,6 @@ sugar_browser_shutdown(void)
G_DEFINE_TYPE(SugarBrowser, sugar_browser, GTK_TYPE_MOZ_EMBED)
static nsresult
-NewURI(const char *uri, nsIURI **result)
-{
- nsresult rv;
-
- nsCOMPtr<nsIServiceManager> mgr;
- NS_GetServiceManager (getter_AddRefs (mgr));
- NS_ENSURE_TRUE(mgr, FALSE);
-
- nsCOMPtr<nsIIOService> ioService;
- rv = mgr->GetServiceByContractID ("@mozilla.org/network/io-service;1",
- NS_GET_IID (nsIIOService),
- getter_AddRefs(ioService));
- NS_ENSURE_SUCCESS(rv, FALSE);
-
- nsCString cSpec(uri);
- return ioService->NewURI (cSpec, nsnull, nsnull, result);
-}
-
-static nsresult
FilenameFromContentDisposition(nsCString contentDisposition, nsCString &fileName)
{
nsresult rv;
@@ -258,38 +256,6 @@ FilenameFromContentDisposition(nsCString contentDisposition, nsCString &fileName
return NS_OK;
}
-static nsresult
-ImageNameFromCache(nsIURI *imgURI, nsCString &imgName)
-{
- nsresult rv;
-
- nsCOMPtr<nsIServiceManager> mgr;
- NS_GetServiceManager (getter_AddRefs (mgr));
- NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
-
- nsCOMPtr<imgICache> imgCache;
- rv = mgr->GetServiceByContractID("@mozilla.org/image/cache;1",
- NS_GET_IID (imgICache),
- getter_AddRefs(imgCache));
- NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
- nsCOMPtr<nsIProperties> imgProperties;
- imgCache->FindEntryProperties(imgURI, getter_AddRefs(imgProperties));
- if (imgProperties) {
- nsCOMPtr<nsISupportsCString> dispositionCString;
- imgProperties->Get("content-disposition",
- NS_GET_IID(nsISupportsCString),
- getter_AddRefs(dispositionCString));
- if (dispositionCString) {
- nsCString contentDisposition;
- dispositionCString->GetData(contentDisposition);
- FilenameFromContentDisposition(contentDisposition, imgName);
- }
- }
-
- return NS_OK;
-}
-
static SugarBrowserMetadata *
sugar_browser_get_document_metadata(SugarBrowser *browser)
{
@@ -382,13 +348,47 @@ sugar_browser_get_property(GObject *object,
}
}
+static void
+sugar_browser_realize(GtkWidget *widget)
+{
+ GTK_WIDGET_CLASS(parent_class)->realize(widget);
+
+#ifdef HAVE_NS_WEB_BROWSER
+ GtkMozEmbed *embed = GTK_MOZ_EMBED(widget);
+ nsCOMPtr<nsIWebBrowser> webBrowser;
+ gtk_moz_embed_get_nsIWebBrowser(embed, getter_AddRefs(webBrowser));
+ NS_ENSURE_TRUE(webBrowser, );
+
+ nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(webBrowser);
+ if (commandManager) {
+ nsresult rv;
+ nsIClipboardDragDropHooks *rawPtr = new GeckoDragDropHooks(
+ SUGAR_BROWSER(widget));
+ nsCOMPtr<nsIClipboardDragDropHooks> geckoDragDropHooks(
+ do_QueryInterface(rawPtr, &rv));
+ NS_ENSURE_SUCCESS(rv, );
+
+ nsCOMPtr<nsIDOMWindow> DOMWindow = do_GetInterface(webBrowser);
+ nsCOMPtr<nsICommandParams> cmdParamsObj = do_CreateInstance(
+ NS_COMMAND_PARAMS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, );
+ cmdParamsObj->SetISupportsValue("addhook", geckoDragDropHooks);
+ commandManager->DoCommand("cmd_clipboardDragDropHook", cmdParamsObj,
+ DOMWindow);
+ }
+#endif
+}
static void
sugar_browser_class_init(SugarBrowserClass *browser_class)
{
- GObjectClass *gobject_class = G_OBJECT_CLASS(browser_class);
+ GObjectClass *gobject_class = G_OBJECT_CLASS(browser_class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(browser_class);
- gobject_class->get_property = sugar_browser_get_property;
+ parent_class = (GObjectClass *) g_type_class_peek_parent(browser_class);
+
+ gobject_class->get_property = sugar_browser_get_property;
+ widget_class->realize = sugar_browser_realize;
signals[MOUSE_CLICK] = g_signal_new ("mouse_click",
SUGAR_TYPE_BROWSER,
@@ -570,33 +570,11 @@ location_cb(GtkMozEmbed *embed)
update_navigation_properties(browser);
}
-static char *
-get_image_name(const char *uri)
-{
- nsresult rv;
-
- nsCString imgName;
-
- nsCOMPtr<nsIURI> imgURI;
- rv = NewURI(uri, getter_AddRefs(imgURI));
- NS_ENSURE_SUCCESS(rv, NULL);
-
- ImageNameFromCache(imgURI, imgName);
-
- if (!imgName.Length()) {
- nsCOMPtr<nsIURL> url(do_QueryInterface(imgURI));
- if (url) {
- url->GetFileName(imgName);
- }
- }
-
- return imgName.Length() ? g_strdup(imgName.get()) : NULL;
-}
-
static gboolean
dom_mouse_click_cb(GtkMozEmbed *embed, nsIDOMMouseEvent *mouseEvent)
{
- SugarBrowser *browser = SUGAR_BROWSER(embed);
+#ifdef HAVE_GECKO_1_9
+ SugarBrowser *browser = SUGAR_BROWSER(embed);
SugarBrowserEvent *event;
gint return_value = FALSE;
@@ -610,36 +588,10 @@ dom_mouse_click_cb(GtkMozEmbed *embed, nsIDOMMouseEvent *mouseEvent)
event = sugar_browser_event_new();
- nsresult rv;
-
- PRUint16 type;
- rv = targetNode->GetNodeType(&type);
- if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
-
- nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(targetNode);
- if ((nsIDOMNode::ELEMENT_NODE == type) && element) {
- nsString uTag;
- rv = element->GetLocalName(uTag);
- if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
-
- nsCString tag;
- NS_UTF16ToCString (uTag, NS_CSTRING_ENCODING_UTF8, tag);
-
- if (g_ascii_strcasecmp (tag.get(), "img") == 0) {
- nsString img;
-
- nsCOMPtr <nsIDOMHTMLImageElement> image;
- image = do_QueryInterface(targetNode, &rv);
- if (NS_FAILED(rv) || !image) return NS_ERROR_FAILURE;
-
- rv = image->GetSrc(img);
- if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
-
- nsCString cImg;
- NS_UTF16ToCString (img, NS_CSTRING_ENCODING_UTF8, cImg);
- event->image_uri = g_strdup(cImg.get());
- event->image_name = get_image_name(event->image_uri);
- }
+ GeckoDocumentObject documentObject(browser, targetNode);
+ if(documentObject.IsImage()) {
+ event->image_uri = documentObject.GetImageURI();
+ event->image_name = documentObject.GetImageName();
}
PRUint16 btn = 0;
@@ -651,6 +603,9 @@ dom_mouse_click_cb(GtkMozEmbed *embed, nsIDOMMouseEvent *mouseEvent)
sugar_browser_event_free(event);
return return_value;
+#else
+ return FALSE;
+#endif
}
static void
@@ -712,71 +667,14 @@ sugar_browser_grab_focus(SugarBrowser *browser)
}
}
-
-static nsresult
-GetPostData(SugarBrowser *browser, nsIInputStream **postData)
-{
-#ifdef HAVE_NS_WEB_BROWSER
- nsCOMPtr<nsIWebBrowser> webBrowser;
- gtk_moz_embed_get_nsIWebBrowser(GTK_MOZ_EMBED(browser),
- getter_AddRefs(webBrowser));
- NS_ENSURE_TRUE(webBrowser, NS_ERROR_FAILURE);
-
- nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(webBrowser));
- NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
-
- PRInt32 sindex;
-
- nsCOMPtr<nsISHistory> sessionHistory;
- webNav->GetSessionHistory(getter_AddRefs(sessionHistory));
- NS_ENSURE_TRUE(sessionHistory, NS_ERROR_FAILURE);
-
- nsCOMPtr<nsIHistoryEntry> entry;
- sessionHistory->GetIndex(&sindex);
- sessionHistory->GetEntryAtIndex(sindex, PR_FALSE, getter_AddRefs(entry));
-
- nsCOMPtr<nsISHEntry> shEntry(do_QueryInterface(entry));
- if (shEntry) {
- shEntry->GetPostData(postData);
- }
-
- return NS_OK;
-#endif
- return NS_ERROR_NOT_IMPLEMENTED;
-}
-
gboolean
sugar_browser_save_uri(SugarBrowser *browser,
const char *uri,
const char *filename)
{
-#ifdef HAVE_NS_WEB_BROWSER
- nsresult rv;
-
- nsCOMPtr<nsIURI> sourceURI;
- rv = NewURI(uri, getter_AddRefs(sourceURI));
- NS_ENSURE_SUCCESS(rv, FALSE);
-
- nsCOMPtr<nsILocalFile> destFile = do_CreateInstance("@mozilla.org/file/local;1");
- NS_ENSURE_TRUE(destFile, FALSE);
-
- destFile->InitWithNativePath(nsCString(filename));
-
- nsCOMPtr<nsIWebBrowser> webBrowser;
- gtk_moz_embed_get_nsIWebBrowser(GTK_MOZ_EMBED(browser),
- getter_AddRefs(webBrowser));
- NS_ENSURE_TRUE(webBrowser, FALSE);
-
- nsCOMPtr<nsIWebBrowserPersist> webPersist = do_QueryInterface (webBrowser);
- NS_ENSURE_TRUE(webPersist, FALSE);
-
- nsCOMPtr<nsIInputStream> postData;
- GetPostData(browser, getter_AddRefs(postData));
-
- rv = webPersist->SaveURI(sourceURI, nsnull, nsnull, postData, nsnull, destFile);
- NS_ENSURE_SUCCESS(rv, FALSE);
-
- return TRUE;
+#ifdef HAVE_GECKO_1_9
+ GeckoBrowserPersist browserPersist(browser);
+ return browserPersist.SaveURI(uri, filename);
#else
return FALSE;
#endif
diff --git a/data/Makefile.am b/data/Makefile.am
index 632c2c9..8e99b34 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,6 +1,7 @@
-sugardir = $(pkgdatadir)
+sugardir = $(pkgdatadir)/data
sugar_DATA = \
- gtkrc \
+ sugar.gtkrc \
+ sugar-xo.gtkrc \
gecko-prefs.js \
mime.types
diff --git a/data/gtkrc b/data/gtkrc
deleted file mode 100644
index cc0d2ca..0000000
--- a/data/gtkrc
+++ /dev/null
@@ -1,6 +0,0 @@
-gtk-theme-name = "olpc"
-gtk-icon-theme-name = "olpc"
-gtk-font-name = "Bitstream Vera Sans 7"
-gtk-icon-sizes = "gtk-menu=32,32:gtk-button=32,32"
-gtk-cursor-theme-name = "olpc"
-gtk-cursor-theme-size = 48
diff --git a/data/mime.types b/data/mime.types
index 0f76f39..a6ffcb2 100644
--- a/data/mime.types
+++ b/data/mime.types
@@ -1,2 +1,3 @@
application/x-squeak-project pr
application/x-abiword abw
+application/vnd.olpc-x-sugar xo
diff --git a/data/sugar-xo.gtkrc b/data/sugar-xo.gtkrc
new file mode 100644
index 0000000..6c6a1a6
--- /dev/null
+++ b/data/sugar-xo.gtkrc
@@ -0,0 +1,7 @@
+gtk-theme-name = "sugar-xo"
+gtk-icon-theme-name = "sugar"
+gtk-font-name = "Sans Serif 7"
+gtk-icon-sizes = "gtk-menu=32,32:gtk-button=32,32"
+gtk-cursor-theme-name = "sugar"
+gtk-cursor-theme-size = 48
+gtk-toolbar-style = GTK_TOOLBAR_ICONS
diff --git a/data/sugar.gtkrc b/data/sugar.gtkrc
new file mode 100644
index 0000000..d742026
--- /dev/null
+++ b/data/sugar.gtkrc
@@ -0,0 +1,5 @@
+gtk-theme-name = "sugar"
+gtk-icon-theme-name = "sugar"
+gtk-font-name = "Sans Serif 11"
+gtk-cursor-theme-name = "sugar"
+gtk-toolbar-style = GTK_TOOLBAR_ICONS
diff --git a/services/clipboard/clipboardobject.py b/services/clipboard/clipboardobject.py
index 919acd0..ab00b14 100644
--- a/services/clipboard/clipboardobject.py
+++ b/services/clipboard/clipboardobject.py
@@ -1,3 +1,6 @@
+import os
+import logging
+
import typeregistry
class ClipboardObject:
@@ -8,6 +11,10 @@ class ClipboardObject:
self._percent = 0
self._formats = {}
+ def destroy(self):
+ for type, format in self._formats.iteritems():
+ format.destroy()
+
def get_id(self):
return self._id
@@ -49,6 +56,10 @@ class Format:
self._data = data
self._on_disk = on_disk
+ def destroy(self):
+ if self._on_disk:
+ os.remove(self._data.replace('file://', ''))
+
def get_type(self):
return self._type
diff --git a/services/clipboard/clipboardservice.py b/services/clipboard/clipboardservice.py
index 0ed423b..16e37b1 100644
--- a/services/clipboard/clipboardservice.py
+++ b/services/clipboard/clipboardservice.py
@@ -109,7 +109,8 @@ class ClipboardDBusServiceHelper(dbus.service.Object):
@dbus.service.method(_CLIPBOARD_DBUS_INTERFACE,
in_signature="o", out_signature="")
def delete_object(self, object_path):
- del self._objects[str(object_path)]
+ cb_object = self._objects.pop(str(object_path))
+ cb_object.destroy()
self.object_deleted(object_path)
logging.debug('Deleted object with object_id ' + object_path)
diff --git a/services/clipboard/sugar-clipboard b/services/clipboard/sugar-clipboard
index 9ee32e7..f9dcfb3 100755
--- a/services/clipboard/sugar-clipboard
+++ b/services/clipboard/sugar-clipboard
@@ -32,7 +32,7 @@ import dbus.glib
from sugar import env
-sys.path.insert(0, env.get_service_path('clipboard'))
+sys.path.append(env.get_service_path('clipboard'))
from clipboardservice import ClipboardService
diff --git a/services/clipboard/typeregistry.py b/services/clipboard/typeregistry.py
index b794cee..0cab0e9 100644
--- a/services/clipboard/typeregistry.py
+++ b/services/clipboard/typeregistry.py
@@ -1,5 +1,7 @@
import logging
from gettext import gettext as _
+import urlparse
+import posixpath
class FileType:
def __init__(self, formats):
@@ -197,6 +199,64 @@ class OOTextFileType(FileType):
return mime_type in cls._types
matches_mime_type = classmethod(matches_mime_type)
+class UriListFileType(FileType):
+
+ _types = set(['text/uri-list'])
+
+ def _is_image(self):
+ uris = self._formats['text/uri-list'].get_data().split('\n')
+ if len(uris) == 1:
+ uri = urlparse.urlparse(uris[0])
+ ext = posixpath.splitext(uri.path)[1]
+ logging.debug(ext)
+ # FIXME: Bad hack, the type registry should treat text/uri-list as a special case.
+ if ext in ['.jpg', '.jpeg', '.gif', '.png', '.svg']:
+ return True
+
+ return False
+
+ def get_name(self):
+ if self._is_image():
+ return _('Image')
+ else:
+ return _('File')
+
+ def get_icon(self):
+ if self._is_image():
+ return 'theme:object-image'
+ else:
+ return 'theme:stock-missing'
+
+ def get_preview(self):
+ return ''
+
+ def get_activity(self):
+ return ''
+
+ def matches_mime_type(cls, mime_type):
+ return mime_type in cls._types
+ matches_mime_type = classmethod(matches_mime_type)
+
+class XoFileType(FileType):
+
+ _types = set(['application/vnd.olpc-x-sugar'])
+
+ def get_name(self):
+ return _('Activity package')
+
+ def get_icon(self):
+ return 'theme:stock-missing'
+
+ def get_preview(self):
+ return ''
+
+ def get_activity(self):
+ return ''
+
+ def matches_mime_type(cls, mime_type):
+ return mime_type in cls._types
+ matches_mime_type = classmethod(matches_mime_type)
+
class UnknownFileType(FileType):
def get_name(self):
return _('Object')
@@ -221,11 +281,13 @@ class TypeRegistry:
self._types.append(MsWordFileType)
self._types.append(RtfFileType)
self._types.append(OOTextFileType)
+ self._types.append(UriListFileType)
self._types.append(UriFileType)
self._types.append(ImageFileType)
self._types.append(AbiwordFileType)
self._types.append(TextFileType)
self._types.append(SqueakProjectFileType)
+ self._types.append(XoFileType)
def get_type(self, formats):
for file_type in self._types:
diff --git a/services/console/sugar-console b/services/console/sugar-console
index d4d7af0..af709a6 100755
--- a/services/console/sugar-console
+++ b/services/console/sugar-console
@@ -7,6 +7,6 @@ import os
import sys
from sugar import env
-sys.path.insert(0, env.get_service_path('console'))
+sys.path.append(env.get_service_path('console'))
import console
diff --git a/services/datastore/sugar-data-store b/services/datastore/sugar-data-store
index 003e273..eb325f2 100755
--- a/services/datastore/sugar-data-store
+++ b/services/datastore/sugar-data-store
@@ -24,7 +24,7 @@ import logging
from sugar import logger
from sugar import env
-sys.path.insert(0, env.get_service_path('datastore'))
+sys.path.append(env.get_service_path('datastore'))
logger.start('data-store')
logging.info('Starting the data store...')
diff --git a/services/presence/__init__.py b/services/presence/__init__.py
index e69de29..bd64375 100644
--- a/services/presence/__init__.py
+++ b/services/presence/__init__.py
@@ -0,0 +1,36 @@
+"""Service to track buddies and activities on the network
+
+Model objects:
+
+ activity.Activity -- tracks a (shared/shareable) activity
+ with many properties and observable events
+
+ buddy.Buddy -- tracks a reference to a particular actor
+ on the network
+
+ buddy.GenericOwner -- actor who owns a particular
+ activity on the network
+
+ buddy.ShellOwner -- actor who owns the local machine
+ connects to the owner module (on the server)
+
+Controller objects:
+
+ presenceservice.PresenceService -- controller which connects
+ a networking plugin to a DBUS service. Generates events
+ for networking events, forwards updates/requests to the
+ server plugin.
+
+ server_plugin.ServerPlugin -- implementation of networking
+ plugin using telepathy Python (Jabber) to provide the
+ underlying communications layer. Generates GObject
+ events that the PresenceService observes to forward onto
+ the DBUS clients.
+
+Utility machinery:
+
+ buddyiconcache.BuddyIconCache -- caches buddy icons on disk
+ based on the "jid" XXX Jabber ID? of the buddy.
+
+ psutils -- trivial function to decode int-list to characters
+"""
diff --git a/services/presence/activity.py b/services/presence/activity.py
index 7471b46..e9c3c05 100644
--- a/services/presence/activity.py
+++ b/services/presence/activity.py
@@ -30,6 +30,9 @@ class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGOb
class Activity(DBusGObject):
+ """Represents a potentially shareable activity on the network.
+ """
+
__gtype_name__ = "Activity"
__gsignals__ = {
@@ -50,6 +53,15 @@ class Activity(DBusGObject):
}
def __init__(self, bus_name, object_id, tp, **kwargs):
+ """Initializes the activity and sets its properties to default values.
+
+ bus_name -- DBUS name for lookup on local host
+ object_id -- The unique worldwide ID for this activity
+ tp -- The server plugin object (stands for "telepathy plugin")
+ kwargs -- Keyword arguments for the GObject properties
+
+ """
+
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int):
@@ -88,6 +100,13 @@ class Activity(DBusGObject):
tp.update_activity_properties(self._id)
def do_get_property(self, pspec):
+ """Gets the value of a property associated with this activity.
+
+ pspec -- Property specifier
+
+ returns The value of the given property.
+ """
+
if pspec.name == "id":
return self._id
elif pspec.name == "name":
@@ -104,6 +123,15 @@ class Activity(DBusGObject):
return self._local
def do_set_property(self, pspec, value):
+ """Sets the value of a property associated with this activity.
+
+ pspec -- Property specifier
+ value -- Desired value
+
+ Note that the "type" property can be set only once; attempting to set it
+ to something different later will raise a RuntimeError.
+
+ """
if pspec.name == "id":
self._id = value
elif pspec.name == "name":
@@ -122,6 +150,15 @@ class Activity(DBusGObject):
self._update_validity()
def _update_validity(self):
+ """Sends a "validity-changed" signal if this activity's validity has changed.
+
+ Determines whether this activity's status has changed from valid to
+ invalid, or invalid to valid, and emits a "validity-changed" signal
+ if either is true. "Valid" means that the object's type, ID, name,
+ colour and type properties have all been set to something valid
+ (i.e., not "None").
+
+ """
try:
old_valid = self._valid
if self._color and self._name and self._id and self._type:
@@ -138,42 +175,80 @@ class Activity(DBusGObject):
@dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o")
def BuddyJoined(self, buddy_path):
+ """Generates DBUS signal when a buddy joins this activity.
+
+ buddy_path -- DBUS path to buddy object
+ """
pass
@dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o")
def BuddyLeft(self, buddy_path):
+ """Generates DBUS signal when a buddy leaves this activity.
+
+ buddy_path -- DBUS path to buddy object
+ """
pass
@dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o")
def NewChannel(self, channel_path):
+ """Generates DBUS signal when a new channel is created for this activity.
+
+ channel_path -- DBUS path to new channel
+
+ XXX - what is this supposed to do? Who is supposed to call it?
+ What is the channel path? Right now this is never called.
+
+ """
pass
# dbus methods
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetId(self):
+ """DBUS method to get this activity's ID
+
+ returns Activity ID
+ """
return self.props.id
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetColor(self):
+ """DBUS method to get this activity's colour
+
+ returns Activity colour
+ """
return self.props.color
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetType(self):
+ """DBUS method to get this activity's type
+
+ returns Activity type
+ """
return self.props.type
@dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="",
async_callbacks=('async_cb', 'async_err_cb'))
def Join(self, async_cb, async_err_cb):
+ """DBUS method to for the local user to attempt to join the activity
+
+ async_cb -- Callback method to be called if join attempt is successful
+ async_err_cb -- Callback method to be called if join attempt is unsuccessful
+
+ """
self.join(async_cb, async_err_cb)
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="ao")
def GetJoinedBuddies(self):
+ """DBUS method to return a list of valid buddies who are joined in this activity
+
+ returns A list of buddy object paths
+ """
ret = []
for buddy in self._buddies:
if buddy.props.valid:
@@ -183,18 +258,37 @@ class Activity(DBusGObject):
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="soao")
def GetChannels(self):
+ """DBUS method to get the list of channels associated with this activity
+
+ returns XXX - Not sure what this returns as get_channels doesn't actually return
+ a list of channels!
+ """
return self.get_channels()
@dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s")
def GetName(self):
+ """DBUS method to get this activity's name
+
+ returns Activity name
+ """
return self.props.name
# methods
def object_path(self):
+ """Retrieves our dbus.ObjectPath object
+
+ returns DBUS ObjectPath object
+ """
return dbus.ObjectPath(self._object_path)
def get_joined_buddies(self):
+ """Local method to return a list of valid buddies who are joined in this activity
+
+ This method is called by the PresenceService on the local machine.
+
+ returns A list of buddy objects
+ """
ret = []
for buddy in self._buddies:
if buddy.props.valid:
@@ -202,18 +296,40 @@ class Activity(DBusGObject):
return ret
def buddy_joined(self, buddy):
+ """Adds a buddy to this activity and sends a BuddyJoined signal
+
+ buddy -- Buddy object representing the buddy being added
+
+ Adds a buddy to this activity if the buddy is not already in the buddy list.
+ If this activity is "valid", a BuddyJoined signal is also sent.
+ This method is called by the PresenceService on the local machine.
+
+ """
if buddy not in self._buddies:
self._buddies.append(buddy)
if self.props.valid:
self.BuddyJoined(buddy.object_path())
def buddy_left(self, buddy):
+ """Removes a buddy from this activity and sends a BuddyLeft signal.
+
+ buddy -- Buddy object representing the buddy being removed
+
+ Removes a buddy from this activity if the buddy is in the buddy list.
+ If this activity is "valid", a BuddyLeft signal is also sent.
+ This method is called by the PresenceService on the local machine.
+
+ """
if buddy in self._buddies:
self._buddies.remove(buddy)
if self.props.valid:
self.BuddyLeft(buddy.object_path())
def _handle_share_join(self, tp, text_channel):
+ """Called when a join to a network activity was successful.
+
+ Called by the _shared_cb and _joined_cb methods.
+ """
if not text_channel:
logging.debug("Error sharing: text channel was None, shouldn't happen")
raise RuntimeError("Plugin returned invalid text channel")
@@ -225,6 +341,8 @@ class Activity(DBusGObject):
return True
def _shared_cb(self, tp, activity_id, text_channel, exc, userdata):
+ """XXX - not documented yet
+ """
if activity_id != self.props.id:
# Not for us
return
@@ -243,6 +361,11 @@ class Activity(DBusGObject):
logging.debug("Share of activity %s succeeded." % self._id)
def _share(self, (async_cb, async_err_cb), owner):
+ """XXX - not documented yet
+
+ XXX - This method is called externally by the PresenceService despite the fact
+ that this is supposed to be an internal method!
+ """
logging.debug("Starting share of activity %s" % self._id)
if self._joined:
async_err_cb(RuntimeError("Already shared activity %s" % self.props.id))
@@ -252,6 +375,8 @@ class Activity(DBusGObject):
logging.debug("done with share attempt %s" % self._id)
def _joined_cb(self, tp, activity_id, text_channel, exc, userdata):
+ """XXX - not documented yet
+ """
if activity_id != self.props.id:
# Not for us
return
@@ -266,6 +391,16 @@ class Activity(DBusGObject):
async_cb()
def join(self, async_cb, async_err_cb):
+ """Local method for the local user to attempt to join the activity.
+
+ async_cb -- Callback method to be called if join attempt is successful
+ async_err_cb -- Callback method to be called if join attempt is unsuccessful
+
+ The two callbacks are passed to the server_plugin ("tp") object, which in turn
+ passes them back as parameters in a callback to the _joined_cb method; this
+ callback is set up within this method.
+
+ """
if self._joined:
async_err_cb(RuntimeError("Already joined activity %s" % self.props.id))
return
@@ -273,19 +408,35 @@ class Activity(DBusGObject):
self._tp.join_activity(self.props.id, (sigid, async_cb, async_err_cb))
def get_channels(self):
+ """Local method to get the list of channels associated with this activity
+
+ returns XXX - expected a list of channels, instead returning a tuple? ???
+ """
conn = self._tp.get_connection()
# FIXME add tubes and others channels
return str(conn.service_name), conn.object_path, [self._text_channel.object_path]
def leave(self):
+ """Local method called when the user wants to leave the activity.
+
+ (XXX - doesn't appear to be called anywhere!)
+
+ """
if self._joined:
self._text_channel[CHANNEL_INTERFACE].Close()
def _text_channel_closed_cb(self):
+ """Callback method called when the text channel is closed.
+
+ This callback is set up in the _handle_share_join method.
+ """
self._joined = False
self._text_channel = None
def send_properties(self):
+ """Tells the Telepathy server what the properties of this activity are.
+
+ """
props = {}
props['name'] = self._name
props['color'] = self._color
@@ -293,6 +444,17 @@ class Activity(DBusGObject):
self._tp.set_activity_properties(self.props.id, props)
def set_properties(self, properties):
+ """Sets name, colour and/or type properties for this activity all at once.
+
+ properties - Dictionary object containing properties keyed by property names
+
+ Note that if any of the name, colour and/or type property values is changed from
+ what it originally was, the update_validity method will be called, resulting in
+ a "validity-changed" signal being generated. (Also note that unlike with the
+ do_set_property method, it *is* possible to change an already-set activity type
+ to something else; this may be a bug.) Called by the PresenceService on the
+ local machine.
+ """
changed = False
if "name" in properties.keys():
name = properties["name"]
diff --git a/services/presence/buddy.py b/services/presence/buddy.py
index 35191e6..ab91cea 100644
--- a/services/presence/buddy.py
+++ b/services/presence/buddy.py
@@ -1,3 +1,4 @@
+"""An "actor" on the network, whether remote or local"""
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
@@ -29,6 +30,7 @@ _BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"
class NotFoundError(dbus.DBusException):
+ """Raised when a given actor is not found on the network"""
def __init__(self):
dbus.DBusException.__init__(self)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
@@ -37,9 +39,35 @@ class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pas
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
+_PROP_NICK = "nick"
+_PROP_KEY = "key"
+_PROP_ICON = "icon"
+_PROP_CURACT = "current-activity"
+_PROP_COLOR = "color"
+_PROP_OWNER = "owner"
+_PROP_VALID = "valid"
+
class Buddy(DBusGObject):
- """Represents another person on the network and keeps track of the
- activities and resources they make available for sharing."""
+ """Person on the network (tracks properties and shared activites)
+
+ The Buddy is a collection of metadata describing a particular
+ actor/person on the network. The Buddy object tracks a set of
+ activities which the actor has shared with the presence service.
+
+ Buddies have a "valid" property which is used to flag Buddies
+ which are no longer reachable. That is, a Buddy may represent
+ a no-longer reachable target on the network.
+
+ The Buddy emits GObject events that the PresenceService uses
+ to track changes in its status.
+
+ Attributes:
+
+ _activities -- dictionary mapping activity ID to
+ activity.Activity objects
+ handles -- dictionary mapping telepresence client to
+ "handle" (XXX what's that)
+ """
__gsignals__ = {
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
@@ -51,17 +79,26 @@ class Buddy(DBusGObject):
}
__gproperties__ = {
- 'key' : (str, None, None, None,
+ _PROP_KEY : (str, None, None, None,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
- 'icon' : (object, None, None, gobject.PARAM_READWRITE),
- 'nick' : (str, None, None, None, gobject.PARAM_READWRITE),
- 'color' : (str, None, None, None, gobject.PARAM_READWRITE),
- 'current-activity' : (str, None, None, None, gobject.PARAM_READWRITE),
- 'valid' : (bool, None, None, False, gobject.PARAM_READABLE),
- 'owner' : (bool, None, None, False, gobject.PARAM_READABLE)
+ _PROP_ICON : (object, None, None, gobject.PARAM_READWRITE),
+ _PROP_NICK : (str, None, None, None, gobject.PARAM_READWRITE),
+ _PROP_COLOR : (str, None, None, None, gobject.PARAM_READWRITE),
+ _PROP_CURACT : (str, None, None, None, gobject.PARAM_READWRITE),
+ _PROP_VALID : (bool, None, None, False, gobject.PARAM_READABLE),
+ _PROP_OWNER : (bool, None, None, False, gobject.PARAM_READABLE)
}
def __init__(self, bus_name, object_id, **kwargs):
+ """Initialize the Buddy object
+
+ bus_name -- DBUS object bus name (identifier)
+ object_id -- the activity's unique identifier
+ kwargs -- used to initialize the object's properties
+
+ constructs a DBUS "object path" from the _BUDDY_PATH
+ and object_id
+ """
if not bus_name:
raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int):
@@ -83,44 +120,62 @@ class Buddy(DBusGObject):
self._nick = None
self._color = None
- if not kwargs.get("key"):
+ if not kwargs.get(_PROP_KEY):
raise ValueError("key required")
+ _ALLOWED_INIT_PROPS = [_PROP_NICK, _PROP_KEY, _PROP_ICON, _PROP_CURACT, _PROP_COLOR]
+ for (key, value) in kwargs.items():
+ if key not in _ALLOWED_INIT_PROPS:
+ logging.debug("Invalid init property '%s'; ignoring..." % key)
+ del kwargs[key]
+
gobject.GObject.__init__(self, **kwargs)
def do_get_property(self, pspec):
- if pspec.name == "key":
+ """Retrieve current value for the given property specifier
+
+ pspec -- property specifier with a "name" attribute
+ """
+ if pspec.name == _PROP_KEY:
return self._key
- elif pspec.name == "icon":
+ elif pspec.name == _PROP_ICON:
return self._icon
- elif pspec.name == "nick":
+ elif pspec.name == _PROP_NICK:
return self._nick
- elif pspec.name == "color":
+ elif pspec.name == _PROP_COLOR:
return self._color
- elif pspec.name == "current-activity":
+ elif pspec.name == _PROP_CURACT:
if not self._current_activity:
return None
if not self._activities.has_key(self._current_activity):
return None
return self._current_activity
- elif pspec.name == "valid":
+ elif pspec.name == _PROP_VALID:
return self._valid
- elif pspec.name == "owner":
+ elif pspec.name == _PROP_OWNER:
return self._owner
def do_set_property(self, pspec, value):
- if pspec.name == "icon":
+ """Set given property
+
+ pspec -- property specifier with a "name" attribute
+ value -- value to set
+
+ emits 'icon-changed' signal on icon setting
+ calls _update_validity on all calls
+ """
+ if pspec.name == _PROP_ICON:
if str(value) != self._icon:
self._icon = str(value)
self.IconChanged(self._icon)
self.emit('icon-changed', self._icon)
- elif pspec.name == "nick":
+ elif pspec.name == _PROP_NICK:
self._nick = value
- elif pspec.name == "color":
+ elif pspec.name == _PROP_COLOR:
self._color = value
- elif pspec.name == "current-activity":
+ elif pspec.name == _PROP_CURACT:
self._current_activity = value
- elif pspec.name == "key":
+ elif pspec.name == _PROP_KEY:
self._key = value
self._update_validity()
@@ -129,27 +184,42 @@ class Buddy(DBusGObject):
@dbus.service.signal(_BUDDY_INTERFACE,
signature="ay")
def IconChanged(self, icon_data):
- pass
+ """Generates DBUS signal with icon_data"""
@dbus.service.signal(_BUDDY_INTERFACE,
signature="o")
def JoinedActivity(self, activity_path):
- pass
+ """Generates DBUS signal when buddy joins activity
+
+ activity_path -- DBUS path to the activity object
+ """
@dbus.service.signal(_BUDDY_INTERFACE,
signature="o")
def LeftActivity(self, activity_path):
- pass
+ """Generates DBUS signal when buddy leaves activity
+
+ activity_path -- DBUS path to the activity object
+ """
@dbus.service.signal(_BUDDY_INTERFACE,
signature="a{sv}")
def PropertyChanged(self, updated):
- pass
+ """Generates DBUS signal when buddy's property changes
+
+ updated -- updated property-set (dictionary) with the
+ Buddy's property (changed) values. Note: not the
+ full set of properties, just the changes.
+ """
# dbus methods
@dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ay")
def GetIcon(self):
+ """Retrieve Buddy's icon data
+
+ returns empty string or dbus.ByteArray
+ """
if not self.props.icon:
return ""
return dbus.ByteArray(self.props.icon)
@@ -157,6 +227,11 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ao")
def GetJoinedActivities(self):
+ """Retrieve set of Buddy's joined activities (paths)
+
+ returns list of dbus service paths for the Buddy's joined
+ activities
+ """
acts = []
for act in self.get_joined_activities():
acts.append(act.object_path())
@@ -165,22 +240,41 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="a{sv}")
def GetProperties(self):
+ """Retrieve set of Buddy's properties
+
+ returns dictionary of
+ nick : str(nickname)
+ owner : bool( whether this Buddy is an owner??? )
+ XXX what is the owner flag for?
+ key : str(public-key)
+ color: Buddy's icon colour
+ XXX what type?
+ current-activity: Buddy's current activity_id, or
+ "" if no current activity
+ """
props = {}
- props['nick'] = self.props.nick
- props['owner'] = self.props.owner
- props['key'] = self.props.key
- props['color'] = self.props.color
+ props[_PROP_NICK] = self.props.nick
+ props[_PROP_OWNER] = self.props.owner
+ props[_PROP_KEY] = self.props.key
+ props[_PROP_COLOR] = self.props.color
if self.props.current_activity:
- props['current-activity'] = self.props.current_activity
+ props[_PROP_CURACT] = self.props.current_activity
else:
- props['current-activity'] = ""
+ props[_PROP_CURACT] = ""
return props
# methods
def object_path(self):
+ """Retrieve our dbus.ObjectPath object"""
return dbus.ObjectPath(self._object_path)
def add_activity(self, activity):
+ """Add an activity to the Buddy's set of activities
+
+ activity -- activity.Activity instance
+
+ calls JoinedActivity
+ """
actid = activity.props.id
if self._activities.has_key(actid):
return
@@ -189,6 +283,12 @@ class Buddy(DBusGObject):
self.JoinedActivity(activity.object_path())
def remove_activity(self, activity):
+ """Remove the activity from the Buddy's set of activities
+
+ activity -- activity.Activity instance
+
+ calls LeftActivity
+ """
actid = activity.props.id
if not self._activities.has_key(actid):
return
@@ -197,6 +297,7 @@ class Buddy(DBusGObject):
self.LeftActivity(activity.object_path())
def get_joined_activities(self):
+ """Retrieves list of still-valid activity objects"""
acts = []
for act in self._activities.values():
if act.props.valid:
@@ -204,36 +305,54 @@ class Buddy(DBusGObject):
return acts
def set_properties(self, properties):
+ """Set the given set of properties on the object
+
+ properties -- set of property values to set
+
+ if no change, no events generated
+ if change, generates property-changed and
+ calls _update_validity
+ """
changed = False
- if "nick" in properties.keys():
- nick = properties["nick"]
+ changed_props = {}
+ if _PROP_NICK in properties.keys():
+ nick = properties[_PROP_NICK]
if nick != self._nick:
self._nick = nick
+ changed_props[_PROP_NICK] = nick
changed = True
- if "color" in properties.keys():
- color = properties["color"]
+ if _PROP_COLOR in properties.keys():
+ color = properties[_PROP_COLOR]
if color != self._color:
self._color = color
+ changed_props[_PROP_COLOR] = color
changed = True
- if "current-activity" in properties.keys():
- curact = properties["current-activity"]
+ if _PROP_CURACT in properties.keys():
+ curact = properties[_PROP_CURACT]
if curact != self._current_activity:
self._current_activity = curact
+ changed_props[_PROP_CURACT] = curact
changed = True
- if not changed:
+ if not changed or not len(changed_props.keys()):
return
# Try emitting PropertyChanged before updating validity
# to avoid leaking a PropertyChanged signal before the buddy is
# actually valid the first time after creation
if self._valid:
- self.PropertyChanged(properties)
- self.emit('property-changed', properties)
+ self.PropertyChanged(changed_props)
+ self.emit('property-changed', changed_props)
self._update_validity()
def _update_validity(self):
+ """Check whether we are now valid
+
+ validity is True if color, nick and key are non-null
+
+ emits validity-changed if we have changed validity
+ """
try:
old_valid = self._valid
if self._color and self._nick and self._key:
@@ -247,6 +366,13 @@ class Buddy(DBusGObject):
self._valid = False
class GenericOwner(Buddy):
+ """Common functionality for Local User-like objects
+
+ The TestOwner wants to produce something *like* a
+ ShellOwner, but with randomised changes and the like.
+ This class provides the common features for a real
+ local owner and a testing one.
+ """
__gtype_name__ = "GenericOwner"
__gproperties__ = {
@@ -255,7 +381,17 @@ class GenericOwner(Buddy):
'key-hash' : (str, None, None, None, gobject.PARAM_READABLE | gobject.PARAM_CONSTRUCT)
}
- def __init__(self, bus_name, object_id, **kwargs):
+ def __init__(self, ps, bus_name, object_id, **kwargs):
+ """Initialize the GenericOwner instance
+
+ ps -- presenceservice.PresenceService object
+ bus_name -- DBUS object bus name (identifier)
+ object_id -- the activity's unique identifier
+ kwargs -- used to initialize the object's properties
+
+ calls Buddy.__init__
+ """
+ self._ps = ps
self._server = 'olpc.collabora.co.uk'
self._key_hash = None
self._registered = False
@@ -273,28 +409,47 @@ class GenericOwner(Buddy):
self._owner = True
def get_registered(self):
+ """Retrieve whether owner has registered with presence server"""
return self._registered
def get_server(self):
+ """Retrieve presence server (XXX url??)"""
return self._server
def get_key_hash(self):
+ """Retrieve the user's private-key hash"""
return self._key_hash
def set_registered(self, registered):
+ """Customisation point: handle the registration of the owner"""
raise RuntimeError("Subclasses must implement")
class ShellOwner(GenericOwner):
- """Class representing the owner of the machine. This is the client
- portion of the Owner, paired with the server portion in Owner.py."""
-
+ """Representation of the local-machine owner using Sugar's Shell
+
+ The ShellOwner uses the Sugar Shell's dbus services to
+ register for updates about the user's profile description.
+ """
__gtype_name__ = "ShellOwner"
_SHELL_SERVICE = "org.laptop.Shell"
_SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
_SHELL_PATH = "/org/laptop/Shell"
- def __init__(self, bus_name, object_id, test=False):
+ def __init__(self, ps, bus_name, object_id, test=False):
+ """Initialize the ShellOwner instance
+
+ ps -- presenceservice.PresenceService object
+ bus_name -- DBUS object bus name (identifier)
+ object_id -- the activity's unique identifier
+ test -- ignored
+
+ Retrieves initial property values from the profile
+ module. Loads the buddy icon from file as well.
+ XXX note: no error handling on that
+
+ calls GenericOwner.__init__
+ """
server = profile.get_server()
key_hash = profile.get_private_key_hash()
registered = profile.get_server_registered()
@@ -307,7 +462,7 @@ class ShellOwner(GenericOwner):
icon = f.read()
f.close()
- GenericOwner.__init__(self, bus_name, object_id, key=key, nick=nick,
+ GenericOwner.__init__(self, ps, bus_name, object_id, key=key, nick=nick,
color=color, icon=icon, server=server, key_hash=key_hash,
registered=registered)
@@ -324,10 +479,17 @@ class ShellOwner(GenericOwner):
pass
def set_registered(self, value):
+ """Handle notification that we have been registered"""
if value:
profile.set_server_registered()
def _name_owner_changed_handler(self, name, old, new):
+ """Handle DBUS notification of a new / renamed service
+
+ Watches for the _SHELL_SERVICE, i.e. the Sugar Shell,
+ and registers with it if we have not yet registered
+ with it (using _connect_to_shell).
+ """
if name != self._SHELL_SERVICE:
return
if (old and len(old)) and (not new and not len(new)):
@@ -338,6 +500,11 @@ class ShellOwner(GenericOwner):
self._connect_to_shell()
def _connect_to_shell(self):
+ """Connect to the Sugar Shell service to watch for events
+
+ Connects the various XChanged events on the Sugar Shell
+ service to our _x_changed_cb methods.
+ """
obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH)
self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE)
self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb)
@@ -347,21 +514,30 @@ class ShellOwner(GenericOwner):
self._cur_activity_changed_cb)
def _icon_changed_cb(self, icon):
+ """Handle icon change, set property to generate event"""
self.props.icon = icon
def _color_changed_cb(self, color):
- props = {'color': color}
+ """Handle color change, set property to generate event"""
+ props = {_PROP_COLOR: color}
self.set_properties(props)
def _nick_changed_cb(self, nick):
- props = {'nick': nick}
+ """Handle nickname change, set property to generate event"""
+ props = {_PROP_NICK: nick}
self.set_properties(props)
def _cur_activity_changed_cb(self, activity_id):
+ """Handle current-activity change, set property to generate event
+
+ Filters out local activities (those not in self.activites)
+ because the network users can't join those activities, so
+ the activity_id shared will be None in those cases...
+ """
if not self._activities.has_key(activity_id):
# This activity is local-only
activity_id = None
- props = {'current-activity': activity_id}
+ props = {_PROP_CURACT: activity_id}
self.set_properties(props)
@@ -371,9 +547,12 @@ class TestOwner(GenericOwner):
__gtype_name__ = "TestOwner"
- def __init__(self, bus_name, object_id, test_num):
+ def __init__(self, ps, bus_name, object_id, test_num):
self._cp = ConfigParser()
self._section = "Info"
+ self._test_activities = []
+ self._test_cur_act = ""
+ self._change_timeout = 0
self._cfg_file = os.path.join(env.get_profile_path(), 'test-buddy-%d' % test_num)
@@ -392,11 +571,46 @@ class TestOwner(GenericOwner):
color = xocolor.XoColor().to_string()
icon = _get_random_image()
- GenericOwner.__init__(self, bus_name, object_id, key=pubkey, nick=nick,
+ logging.debug("pubkey is %s" % pubkey)
+ GenericOwner.__init__(self, ps, bus_name, object_id, key=pubkey, nick=nick,
color=color, icon=icon, registered=registered, key_hash=privkey_hash)
+ self._ps.connect('connection-status', self._ps_connection_status_cb)
+
+ def _share_reply_cb(self, actid, object_path):
+ activity = self._ps.internal_get_activity(actid)
+ if not activity or not object_path:
+ logging.debug("Couldn't find activity %s even though it was shared." % actid)
+ return
+ logging.debug("Shared activity %s (%s)." % (actid, activity.props.name))
+ self._test_activities.append(activity)
+
+ def _share_error_cb(self, actid, err):
+ logging.debug("Error sharing activity %s: %s" % (actid, str(err)))
+
+ def _ps_connection_status_cb(self, ps, connected):
+ if not connected:
+ return
+
+ if not len(self._test_activities):
+ # Share some activities
+ actid = util.unique_id("Activity 1")
+ callbacks = (lambda *args: self._share_reply_cb(actid, *args),
+ lambda *args: self._share_error_cb(actid, *args))
+ atype = "org.laptop.WebActivity"
+ properties = {"foo": "bar"}
+ self._ps._share_activity(actid, atype, "Wembley Stadium", properties, callbacks)
+
+ actid2 = util.unique_id("Activity 2")
+ callbacks = (lambda *args: self._share_reply_cb(actid2, *args),
+ lambda *args: self._share_error_cb(actid2, *args))
+ atype = "org.laptop.WebActivity"
+ properties = {"baz": "bar"}
+ self._ps._share_activity(actid2, atype, "Maine Road", properties, callbacks)
+
# Change a random property ever 10 seconds
- gobject.timeout_add(10000, self._update_something)
+ if self._change_timeout == 0:
+ self._change_timeout = gobject.timeout_add(10000, self._update_something)
def set_registered(self, value):
if value:
@@ -437,23 +651,26 @@ class TestOwner(GenericOwner):
self.props.icon = _get_random_image()
elif it == 1:
from sugar.graphics import xocolor
- props = {'color': xocolor.XoColor().to_string()}
+ props = {_PROP_COLOR: xocolor.XoColor().to_string()}
self.set_properties(props)
elif it == 2:
- props = {'nick': _get_random_name()}
+ props = {_PROP_NICK: _get_random_name()}
self.set_properties(props)
elif it == 3:
- bork = random.randint(25, 65)
- it = ""
- for i in range(0, bork):
- it += chr(random.randint(40, 127))
- from sugar import util
- props = {'current-activity': util.unique_id(it)}
+ actid = ""
+ idx = random.randint(0, len(self._test_activities))
+ # if idx == len(self._test_activites), it means no current
+ # activity
+ if idx < len(self._test_activities):
+ activity = self._test_activities[idx]
+ actid = activity.props.id
+ props = {_PROP_CURACT: actid}
self.set_properties(props)
return True
def _hash_private_key(self):
+ """Unused method to has a private key, see profile"""
self.privkey_hash = None
key_path = os.path.join(env.get_profile_path(), 'owner.key')
@@ -504,6 +721,7 @@ def _extract_public_key(keyfile):
return key
def _extract_private_key(keyfile):
+ """Get a private key from a private key file"""
# Extract the private key
try:
f = open(keyfile, "r")
@@ -527,6 +745,7 @@ def _extract_private_key(keyfile):
return key
def _get_new_keypair(num):
+ """Retrieve a public/private key pair for testing"""
# Generate keypair
privkeyfile = os.path.join("/tmp", "test%d.key" % num)
pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num)
@@ -559,10 +778,12 @@ def _get_new_keypair(num):
return (pubkey, privkey)
def _get_random_name():
+ """Produce random names for testing"""
names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
return names[random.randint(0, len(names) - 1)]
def _get_random_image():
+ """Produce a random image for display"""
import cairo, math, random, gtk
def rand():
diff --git a/services/presence/presenceservice.py b/services/presence/presenceservice.py
index fd4cbb6..1312fec 100644
--- a/services/presence/presenceservice.py
+++ b/services/presence/presenceservice.py
@@ -40,24 +40,36 @@ class NotFoundError(dbus.DBusException):
dbus.DBusException.__init__(self)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
+class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pass
+class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
+
+class PresenceService(DBusGObject):
+ __gtype_name__ = "PresenceService"
+
+ __gsignals__ = {
+ 'connection-status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_BOOLEAN]))
+ }
-class PresenceService(dbus.service.Object):
def __init__(self, test=0):
self._next_object_id = 0
+ self._connected = False
self._buddies = {} # key -> Buddy
self._handles_buddies = {} # tp client -> (handle -> Buddy)
self._activities = {} # activity id -> Activity
+ gobject.GObject.__init__(self)
+
bus = dbus.SessionBus()
self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=bus)
# Create the Owner object
objid = self._get_next_object_id()
if test > 0:
- self._owner = TestOwner(self._bus_name, objid, test)
+ self._owner = TestOwner(self, self._bus_name, objid, test)
else:
- self._owner = ShellOwner(self._bus_name, objid)
+ self._owner = ShellOwner(self, self._bus_name, objid)
self._buddies[self._owner.props.key] = self._owner
self._registry = ManagerRegistry()
@@ -92,8 +104,15 @@ class PresenceService(dbus.service.Object):
async_err_cb(exc)
def _server_status_cb(self, plugin, status, reason):
+ # FIXME: figure out connection status when we have a salut plugin too
+ old_status = self._connected
if status == CONNECTION_STATUS_CONNECTED:
- pass
+ self._connected = True
+ else:
+ self._connected = False
+
+ if self._connected != old_status:
+ self.emit('connection-status', self._connected)
def _contact_online(self, tp, handle, props):
new_buddy = False
@@ -120,8 +139,11 @@ class PresenceService(dbus.service.Object):
else:
self.BuddyDisappeared(buddy.object_path())
logging.debug("Buddy left: %s (%s)" % (buddy.props.nick, buddy.props.color))
-
+
def _contact_offline(self, tp, handle):
+ if not self._handles_buddies[tp].has_key(handle):
+ return
+
buddy = self._handles_buddies[tp].pop(handle)
key = buddy.props.key
@@ -144,11 +166,11 @@ class PresenceService(dbus.service.Object):
logging.debug("Buddy %s icon updated" % buddy.props.nick)
buddy.props.icon = avatar
- def _buddy_properties_changed(self, tp, handle, prop):
+ def _buddy_properties_changed(self, tp, handle, properties):
buddy = self._handles_buddies[tp].get(handle)
if buddy:
- buddy.set_properties(prop)
- logging.debug("Buddy %s properties updated" % buddy.props.nick)
+ buddy.set_properties(properties)
+ logging.debug("Buddy %s properties updated: %s" % (buddy.props.nick, properties.keys()))
def _new_activity(self, activity_id, tp):
try:
@@ -257,11 +279,10 @@ class PresenceService(dbus.service.Object):
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="s", out_signature="o")
def GetActivityById(self, actid):
- if self._activities.has_key(actid):
- act = self._activities[actid]
- if act.props.valid:
- return act.object_path()
- raise NotFoundError("The activity was not found.")
+ act = self.internal_get_activity(actid)
+ if not act or not act.props.valid:
+ raise NotFoundError("The activity was not found.")
+ return act.object_path()
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="ao")
def GetBuddies(self):
@@ -330,6 +351,11 @@ class PresenceService(dbus.service.Object):
if activity:
activity.set_properties(props)
+ def internal_get_activity(self, actid):
+ if not self._activities.has_key(actid):
+ return None
+ return self._activities[actid]
+
def main(test=False):
loop = gobject.MainLoop()
diff --git a/services/presence/psutils.py b/services/presence/psutils.py
index 995ea3a..76583d6 100644
--- a/services/presence/psutils.py
+++ b/services/presence/psutils.py
@@ -16,7 +16,11 @@
def bytes_to_string(bytes):
- # Handle both DBus byte arrays and strings
+ """The function converts a D-BUS byte array provided by dbus to string format.
+
+ bytes -- a D-Bus array of bytes. Handle both DBus byte arrays and strings
+
+ """
try:
# DBus Byte array
ret = ''.join([chr(item) for item in bytes])
diff --git a/services/presence/server_plugin.py b/services/presence/server_plugin.py
index 5180692..5330567 100644
--- a/services/presence/server_plugin.py
+++ b/services/presence/server_plugin.py
@@ -1,3 +1,4 @@
+"""Telepathy-python presence server interface/implementation plugin"""
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd.
#
@@ -41,7 +42,7 @@ CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
_PROTOCOL = "jabber"
class InvalidBuddyError(Exception):
- pass
+ """(Unused) exception to indicate an invalid buddy specifier"""
def _buddy_icon_save_cb(buf, data):
data[0] += buf
@@ -76,6 +77,13 @@ def _get_buddy_icon_at_size(icon, maxw, maxh, maxsize):
class ServerPlugin(gobject.GObject):
+ """Telepathy-python-based presence server interface
+
+ The ServerPlugin instance translates network events from
+ Telepathy Python into GObject events. It provides direct
+ python calls to perform the required network operations
+ to implement the PresenceService.
+ """
__gsignals__ = {
'contact-online': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
@@ -104,6 +112,14 @@ class ServerPlugin(gobject.GObject):
}
def __init__(self, registry, owner):
+ """Initialize the ServerPlugin instance
+
+ registry -- telepathy.client.ManagerRegistry from the
+ PresenceService, used to find the "gabble" connection
+ manager in this case...
+ owner -- presence.buddy.GenericOwner instance (normally a
+ presence.buddy.ShellOwner instance)
+ """
gobject.GObject.__init__(self)
self._icon_cache = BuddyIconCache()
@@ -125,6 +141,20 @@ class ServerPlugin(gobject.GObject):
self._reconnect_id = 0
def _owner_property_changed_cb(self, owner, properties):
+ """Local user's configuration properties have changed
+
+ owner -- the Buddy object for the local user
+ properties -- set of updated properties
+
+ calls:
+
+ _set_self_current_activity current-activity
+ _set_self_alias nick
+ _set_self_olpc_properties color
+
+ depending on which properties are present in the
+ set of properties.
+ """
logging.debug("Owner properties changed: %s" % properties)
if properties.has_key("current-activity"):
@@ -137,10 +167,21 @@ class ServerPlugin(gobject.GObject):
self._set_self_olpc_properties()
def _owner_icon_changed_cb(self, owner, icon):
+ """Owner has changed their icon, forward to network"""
logging.debug("Owner icon changed to size %d" % len(str(icon)))
self._set_self_avatar(icon)
def _get_account_info(self):
+ """Retrieve metadata dictionary describing this account
+
+ returns dictionary with:
+
+ server : server url from owner
+ account : printable-ssh-key-hash@server
+ password : ssh-key-hash
+ register : whether to register (i.e. whether not yet
+ registered)
+ """
account_info = {}
account_info['server'] = self._owner.get_server()
@@ -155,6 +196,16 @@ class ServerPlugin(gobject.GObject):
return account_info
def _find_existing_connection(self):
+ """Try to find an existing Telepathy connection to this server
+
+ filters the set of connections from
+ telepathy.client.Connection.get_connections
+ to find a connection using our protocol with the
+ "self handle" of that connection being a handle
+ which matches our account (see _get_account_info)
+
+ returns connection or None
+ """
our_name = self._account['account']
# Search existing connections, if any, that we might be able to use
@@ -173,9 +224,20 @@ class ServerPlugin(gobject.GObject):
return None
def get_connection(self):
+ """Retrieve our telepathy.client.Connection object"""
return self._conn
def _init_connection(self):
+ """Set up our connection
+
+ if there is no existing connection
+ (_find_existing_connection returns None)
+ produce a new connection with our protocol for our
+ account.
+
+ if there is an existing connection, reuse it by
+ registering for various of events on it.
+ """
conn = self._find_existing_connection()
if not conn:
acct = self._account.copy()
@@ -201,6 +263,10 @@ class ServerPlugin(gobject.GObject):
return conn
def _request_list_channel(self, name):
+ """Request a contact-list channel from Telepathy
+
+ name -- publish/subscribe, for the type of channel
+ """
handle = self._conn[CONN_INTERFACE].RequestHandles(
CONNECTION_HANDLE_TYPE_LIST, [name])[0]
chan_path = self._conn[CONN_INTERFACE].RequestChannel(
@@ -212,6 +278,9 @@ class ServerPlugin(gobject.GObject):
return channel
def _connected_cb(self):
+ """Callback on successful connection to a server
+ """
+
if self._account['register']:
# we successfully register this account
self._owner.props.registered = True
@@ -257,7 +326,7 @@ class ServerPlugin(gobject.GObject):
self._activity_properties_changed_cb)
# Set initial buddy properties, avatar, and activities
- self._set_self_olpc_properties(True)
+ self._set_self_olpc_properties()
self._set_self_alias()
self._set_self_activities()
self._set_self_current_activity()
@@ -324,34 +393,58 @@ class ServerPlugin(gobject.GObject):
def _internal_join_activity(self, activity_id, signal, userdata):
handle = self._activities.get(activity_id)
if not handle:
- self._conn[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_ROOM, [activity_id],
+ # FIXME: figure out why the server can't figure this out itself
+ room_jid = activity_id + "@conference." + self._account["server"]
+ self._conn[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_ROOM, [room_jid],
reply_handler=lambda *args: self._join_activity_get_channel_cb(activity_id, signal, userdata, *args),
error_handler=lambda *args: self._join_error_cb(activity_id, signal, userdata, *args))
else:
self._join_activity_get_channel_cb(activity_id, userdata, [handle])
def share_activity(self, activity_id, userdata):
+ """Share activity with the network
+
+ activity_id -- unique ID for the activity
+ userdata -- opaque token to be passed in the resulting event
+ (id, callback, errback) normally
+
+ Asks the Telepathy server to create a "conference" channel
+ for the activity or return a handle to an already created
+ conference channel for the activity.
+ """
self._internal_join_activity(activity_id, "activity-shared", userdata)
def join_activity(self, activity_id, userdata):
+ """Join an activity on the network (or locally)
+
+ activity_id -- unique ID for the activity
+ userdata -- opaque token to be passed in the resulting event
+ (id, callback, errback) normally
+
+ Asks the Telepathy server to create a "conference" channel
+ for the activity or return a handle to an already created
+ conference channel for the activity.
+ """
self._internal_join_activity(activity_id, "activity-joined", userdata)
def _ignore_success_cb(self):
- pass
+ """Ignore an event (null-operation)"""
def _log_error_cb(self, msg, err):
+ """Log a message (error) at debug level with prefix msg"""
logging.debug("Error %s: %s" % (msg, err))
- def _set_self_olpc_properties(self, set_key=False):
+ def _set_self_olpc_properties(self):
+ """Set color and key on our Telepathy server identity"""
props = {}
props['color'] = self._owner.props.color
- if set_key:
- props['key'] = dbus.ByteArray(self._owner.props.key)
+ props['key'] = dbus.ByteArray(self._owner.props.key)
self._conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props,
reply_handler=self._ignore_success_cb,
error_handler=lambda *args: self._log_error_cb("setting properties", *args))
def _set_self_alias(self):
+ """Forwarded to SetActivities on AliasInfo channel"""
alias = self._owner.props.nick
self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
self._conn[CONN_INTERFACE_ALIASING].SetAliases({self_handle : alias},
@@ -359,11 +452,19 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting alias", *args))
def _set_self_activities(self):
+ """Forward set of joined activities to network
+
+ uses SetActivities on BuddyInfo channel
+ """
self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities(self._joined_activities,
reply_handler=self._ignore_success_cb,
error_handler=lambda *args: self._log_error_cb("setting activities", *args))
def _set_self_current_activity(self):
+ """Forward our current activity (or "") to network
+
+ uses SetCurrentActivity on BuddyInfo channel
+ """
cur_activity = self._owner.props.current_activity
cur_activity_handle = 0
if not cur_activity:
@@ -381,12 +482,20 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting current activity", *args))
def _get_handle_for_activity(self, activity_id):
+ """Retrieve current handle for given activity or None"""
for (act, handle) in self._joined_activities:
if activity_id == act:
return handle
return None
def _status_changed_cb(self, state, reason):
+ """Handle notification of connection-status change
+
+ state -- CONNECTION_STATUS_*
+ reason -- integer code describing the reason...
+
+ returns False XXX what does that mean?
+ """
if state == CONNECTION_STATUS_CONNECTING:
logging.debug("State: connecting...")
elif state == CONNECTION_STATUS_CONNECTED:
@@ -403,6 +512,16 @@ class ServerPlugin(gobject.GObject):
return False
def start(self):
+ """Start up the Telepathy networking connections
+
+ if we are already connected, query for the initial contact
+ information.
+
+ if we are already connecting, do nothing
+
+ otherwise initiate a connection and transfer control to
+ _connect_reply_cb or _connect_error_cb
+ """
logging.debug("Starting up...")
# If the connection is already connected query initial contacts
conn_status = self._conn[CONN_INTERFACE].GetStatus()
@@ -418,25 +537,30 @@ class ServerPlugin(gobject.GObject):
error_handler=self._connect_error_cb)
def _connect_reply_cb(self):
+ """Handle connection success"""
if self._reconnect_id > 0:
gobject.source_remove(self._reconnect_id)
def _reconnect(self):
+ """Reset number-of-attempted connections and re-attempt"""
self._reconnect_id = 0
self.start()
return False
def _connect_error_cb(self, exception):
+ """Handle connection failure"""
logging.debug("Connect error: %s" % exception)
if not self._reconnect_id:
self._reconnect_id = gobject.timeout_add(10000, self._reconnect)
def cleanup(self):
+ """If we still have a connection, disconnect it"""
if not self._conn:
return
self._conn[CONN_INTERFACE].Disconnect()
def _contact_offline(self, handle):
+ """Handle contact going offline (send message, update set)"""
if not self._online_contacts.has_key(handle):
return
if self._online_contacts[handle]:
@@ -444,13 +568,16 @@ class ServerPlugin(gobject.GObject):
del self._online_contacts[handle]
def _contact_online_activities_cb(self, handle, activities):
+ """Handle contact's activity list update"""
self._buddy_activities_changed_cb(handle, activities)
def _contact_online_activities_error_cb(self, handle, err):
+ """Handle contact's activity list being unavailable"""
logging.debug("Handle %s - Error getting activities: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online_aliases_cb(self, handle, props, aliases):
+ """Handle contact's alias being received (do further queries)"""
if not aliases or not len(aliases):
logging.debug("Handle %s - No aliases" % handle)
self._contact_offline(handle)
@@ -466,10 +593,12 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._contact_online_activities_error_cb(handle, *args))
def _contact_online_aliases_error_cb(self, handle, err):
+ """Handle failure to retrieve given user's alias/information"""
logging.debug("Handle %s - Error getting nickname: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online_properties_cb(self, handle, props):
+ """Handle failure to retrieve given user's alias/information"""
if not props.has_key('key'):
logging.debug("Handle %s - invalid key." % handle)
self._contact_offline(handle)
@@ -487,16 +616,19 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._contact_online_aliases_error_cb(handle, *args))
def _contact_online_properties_error_cb(self, handle, err):
+ """Handle error retrieving property-set for a user (handle)"""
logging.debug("Handle %s - Error getting properties: %s" % (handle, err))
self._contact_offline(handle)
def _contact_online(self, handle):
+ """Handle a contact coming online"""
self._online_contacts[handle] = None
self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle,
reply_handler=lambda *args: self._contact_online_properties_cb(handle, *args),
error_handler=lambda *args: self._contact_online_properties_error_cb(handle, *args))
def _presence_update_cb(self, presence):
+ """Send update for online/offline status of presence"""
for handle in presence:
timestamp, statuses = presence[handle]
online = handle in self._online_contacts
@@ -511,18 +643,19 @@ class ServerPlugin(gobject.GObject):
self._contact_offline(handle)
def _avatar_updated_cb(self, handle, new_avatar_token):
+ """Handle update of given user (handle)'s avatar"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner property changes since those
# are handled locally
return
if not self._online_contacts.has_key(handle):
- logging.debug("Handle %s not valid yet...")
+ logging.debug("Handle %s unknown." % handle)
return
jid = self._online_contacts[handle]
if not jid:
- logging.debug("Handle %s not valid yet...")
+ logging.debug("Handle %s not valid yet..." % handle)
return
icon = self._icon_cache.get_icon(jid, new_avatar_token)
@@ -535,6 +668,7 @@ class ServerPlugin(gobject.GObject):
self.emit("avatar-updated", handle, icon)
def _alias_changed_cb(self, aliases):
+ """Handle update of aliases for all users"""
for handle, alias in aliases:
prop = {'nick': alias}
#print "Buddy %s alias changed to %s" % (handle, alias)
@@ -542,14 +676,16 @@ class ServerPlugin(gobject.GObject):
self._buddy_properties_changed_cb(handle, prop)
def _buddy_properties_changed_cb(self, handle, properties):
+ """Handle update of given user (handle)'s properties"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner property changes since those
# are handled locally
return
if self._online_contacts.has_key(handle) and self._online_contacts[handle]:
- self.emit("buddy-properties-changed", handle, properties)
+ self.emit("buddy-properties-changed", handle, properties)
def _buddy_activities_changed_cb(self, handle, activities):
+ """Handle update of given user (handle)'s activities"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner activity changes since those
# are handled locally
@@ -563,6 +699,8 @@ class ServerPlugin(gobject.GObject):
self.emit("buddy-activities-changed", handle, activities_id)
def _buddy_current_activity_changed_cb(self, handle, activity, channel):
+ """Handle update of given user (handle)'s current activity"""
+
if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner current activity changes since those
# are handled locally
@@ -577,6 +715,8 @@ class ServerPlugin(gobject.GObject):
self._buddy_properties_changed_cb(handle, prop)
def _new_channel_cb(self, object_path, channel_type, handle_type, handle, suppress_handler):
+ """Handle creation of a new channel
+ """
if handle_type == CONNECTION_HANDLE_TYPE_ROOM and channel_type == CHANNEL_TYPE_TEXT:
channel = Channel(self._conn._dbus_object._named_service, object_path)
@@ -595,6 +735,7 @@ class ServerPlugin(gobject.GObject):
self.emit("private-invitation", object_path)
def update_activity_properties(self, act_id):
+ """Request update from network on the activity properties of act_id"""
handle = self._activities.get(act_id)
if not handle:
raise RuntimeError("Unknown activity %s: couldn't find handle.")
@@ -604,6 +745,7 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("getting activity properties", *args))
def set_activity_properties(self, act_id, props):
+ """Send update to network on the activity properties of act_id (props)"""
handle = self._activities.get(act_id)
if not handle:
raise RuntimeError("Unknown activity %s: couldn't find handle.")
@@ -613,6 +755,7 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting activity properties", *args))
def _activity_properties_changed_cb(self, room, properties):
+ """Handle update of properties for a "room" (activity handle)"""
for act_id, act_handle in self._activities.items():
if room == act_handle:
self.emit("activity-properties-changed", act_id, properties)
diff --git a/services/presence/sugar-presence-service b/services/presence/sugar-presence-service
index bba32b5..7eec696 100755
--- a/services/presence/sugar-presence-service
+++ b/services/presence/sugar-presence-service
@@ -24,7 +24,7 @@ import os
from sugar import logger
from sugar import env
-sys.path.insert(0, env.get_service_path('presence'))
+sys.path.append(env.get_service_path('presence'))
test=0
if len(sys.argv) > 1:
diff --git a/shell/Makefile.am b/shell/Makefile.am
index bb874fb..99f5525 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -4,7 +4,6 @@ bin_SCRIPTS = sugar-shell
sugardir = $(pkgdatadir)/shell
sugar_PYTHON = \
- __init__.py \
shellservice.py
confdir = $(pkgdatadir)/shell
diff --git a/shell/extensions/Makefile.am b/shell/extensions/Makefile.am
index 2bfeccd..c531fbd 100644
--- a/shell/extensions/Makefile.am
+++ b/shell/extensions/Makefile.am
@@ -1,27 +1,26 @@
-sugardir = $(pkgdatadir)/shell
+sugardir = $(pkgdatadir)/shell/extensions
sugar_PYTHON = \
__init__.py
-pkgpyexecdir = $(pkgdatadir)/shell
+pkgpyexecdir = $(pkgdatadir)/shell/extensions
-pkgpyexec_LTLIBRARIES = extensions.la
+pkgpyexec_LTLIBRARIES = _extensions.la
-extensions_la_LDFLAGS = -module -avoid-version
+_extensions_la_LDFLAGS = -module -avoid-version
-extensions_la_CFLAGS = \
- $(WARN_CFLAGS) \
- $(PYTHON_INCLUDES) \
- $(PYGTK_CFLAGS) \
- $(SHELL_CFLAGS) \
- $(top_srcdir)/shell/extensions
+_extensions_la_CFLAGS = \
+ $(WARN_CFLAGS) \
+ $(PYTHON_INCLUDES) \
+ $(PYGTK_CFLAGS) \
+ $(SHELL_CFLAGS)
-extensions_la_LIBADD = \
+_extensions_la_LIBADD = \
$(SHELL_LIBS) \
$(PYCAIRO_LIBS) \
-lgstinterfaces-0.10 \
-lgstaudio-0.10
-extensions_la_SOURCES = \
+_extensions_la_SOURCES = \
$(BUILT_SOURCES) \
eggaccelerators.h \
eggaccelerators.c \
@@ -29,10 +28,10 @@ extensions_la_SOURCES = \
sugar-audio-manager.h \
sugar-key-grabber.h \
sugar-key-grabber.c \
- extensionsmodule.c
+ _extensionsmodule.c
BUILT_SOURCES = \
- extensions.c \
+ _extensions.c \
sugar-shell-marshal.c \
sugar-shell-marshal.h
@@ -58,9 +57,9 @@ CLEANFILES = $(stamp_files) $(BUILT_SOURCES)
DISTCLEANFILES = $(stamp_files) $(BUILT_SOURCES)
MAINTAINERCLEANFILES = $(stamp_files) $(BUILT_SOURCES)
-EXTRA_DIST = sugar-marshal.list extensions.override extensions.defs
+EXTRA_DIST = sugar-marshal.list _extensions.override _extensions.defs
-extensions.c: extensions.defs extensions.override
+extensions.c: _extensions.defs _extensions.override
.defs.c:
(cd $(srcdir)\
diff --git a/shell/extensions/__init__.py b/shell/extensions/__init__.py
index ee59f2c..8290d41 100644
--- a/shell/extensions/__init__.py
+++ b/shell/extensions/__init__.py
@@ -14,4 +14,8 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-from extensions import extensions
+try:
+ from extensions._extensions import *
+except ImportError:
+ from sugar import ltihooks
+ from extensions._extensions import *
diff --git a/shell/extensions/extensions.defs b/shell/extensions/_extensions.defs
index 4fec6cc..4fec6cc 100644
--- a/shell/extensions/extensions.defs
+++ b/shell/extensions/_extensions.defs
diff --git a/shell/extensions/extensions.override b/shell/extensions/_extensions.override
index 2156ead..2156ead 100644
--- a/shell/extensions/extensions.override
+++ b/shell/extensions/_extensions.override
diff --git a/shell/extensions/extensionsmodule.c b/shell/extensions/_extensionsmodule.c
index 834d5b8..76d7e53 100644
--- a/shell/extensions/extensionsmodule.c
+++ b/shell/extensions/_extensionsmodule.c
@@ -5,21 +5,21 @@
/* include this first, before NO_IMPORT_PYGOBJECT is defined */
#include <pygobject.h>
-void pyextensions_register_classes (PyObject *d);
+void py_extensions_register_classes (PyObject *d);
-extern PyMethodDef pyextensions_functions[];
+extern PyMethodDef py_extensions_functions[];
DL_EXPORT(void)
-initextensions(void)
+init_extensions(void)
{
PyObject *m, *d;
init_pygobject ();
- m = Py_InitModule ("extensions", pyextensions_functions);
+ m = Py_InitModule ("_extensions", py_extensions_functions);
d = PyModule_GetDict (m);
- pyextensions_register_classes (d);
+ py_extensions_register_classes (d);
if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module _sugar");
diff --git a/shell/extensions/extensions.c b/shell/extensions/extensions.c
deleted file mode 100644
index 079e5bd..0000000
--- a/shell/extensions/extensions.c
+++ /dev/null
@@ -1,260 +0,0 @@
-/* -- THIS FILE IS GENERATED - DO NOT EDIT *//* -*- Mode: C; c-basic-offset: 4 -*- */
-
-#include <Python.h>
-
-
-
-#line 4 "extensions.override"
-#include <Python.h>
-
-#include "pygobject.h"
-#include "sugar-key-grabber.h"
-#include "sugar-address-entry.h"
-#include "sugar-audio-manager.h"
-
-#line 16 "extensions.c"
-
-
-/* ---------- types from other modules ---------- */
-static PyTypeObject *_PyGObject_Type;
-#define PyGObject_Type (*_PyGObject_Type)
-static PyTypeObject *_PyGtkEntry_Type;
-#define PyGtkEntry_Type (*_PyGtkEntry_Type)
-
-
-/* ---------- forward type declarations ---------- */
-PyTypeObject G_GNUC_INTERNAL PySugarKeyGrabber_Type;
-PyTypeObject G_GNUC_INTERNAL PySugarAudioManager_Type;
-
-#line 30 "extensions.c"
-
-
-
-/* ----------- SugarKeyGrabber ----------- */
-
-static PyObject *
-_wrap_sugar_key_grabber_grab(PyGObject *self, PyObject *args, PyObject *kwargs)
-{
- static char *kwlist[] = { "key", NULL };
- char *key;
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs,"s:SugarKeyGrabber.grab", kwlist, &key))
- return NULL;
-
- sugar_key_grabber_grab(SUGAR_KEY_GRABBER(self->obj), key);
-
- Py_INCREF(Py_None);
- return Py_None;
-}
-
-static PyObject *
-_wrap_sugar_key_grabber_get_key(PyGObject *self, PyObject *args, PyObject *kwargs)
-{
- static char *kwlist[] = { "keycode", "state", NULL };
- PyObject *py_keycode = NULL, *py_state = NULL;
- gchar *ret;
- guint keycode = 0, state = 0;
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs,"OO:SugarKeyGrabber.get_key", kwlist, &py_keycode, &py_state))
- return NULL;
- if (py_keycode) {
- if (PyLong_Check(py_keycode))
- keycode = PyLong_AsUnsignedLong(py_keycode);
- else if (PyInt_Check(py_keycode))
- keycode = PyInt_AsLong(py_keycode);
- else
- PyErr_SetString(PyExc_TypeError, "Parameter 'keycode' must be an int or a long");
- if (PyErr_Occurred())
- return NULL;
- }
- if (py_state) {
- if (PyLong_Check(py_state))
- state = PyLong_AsUnsignedLong(py_state);
- else if (PyInt_Check(py_state))
- state = PyInt_AsLong(py_state);
- else
- PyErr_SetString(PyExc_TypeError, "Parameter 'state' must be an int or a long");
- if (PyErr_Occurred())
- return NULL;
- }
-
- ret = sugar_key_grabber_get_key(SUGAR_KEY_GRABBER(self->obj), keycode, state);
-
- if (ret) {
- PyObject *py_ret = PyString_FromString(ret);
- g_free(ret);
- return py_ret;
- }
- Py_INCREF(Py_None);
- return Py_None;
-}
-
-static const PyMethodDef _PySugarKeyGrabber_methods[] = {
- { "grab", (PyCFunction)_wrap_sugar_key_grabber_grab, METH_VARARGS|METH_KEYWORDS,
- NULL },
- { "get_key", (PyCFunction)_wrap_sugar_key_grabber_get_key, METH_VARARGS|METH_KEYWORDS,
- NULL },
- { NULL, NULL, 0, NULL }
-};
-
-PyTypeObject G_GNUC_INTERNAL PySugarKeyGrabber_Type = {
- PyObject_HEAD_INIT(NULL)
- 0, /* ob_size */
- "extensions.KeyGrabber", /* tp_name */
- sizeof(PyGObject), /* tp_basicsize */
- 0, /* tp_itemsize */
- /* methods */
- (destructor)0, /* tp_dealloc */
- (printfunc)0, /* tp_print */
- (getattrfunc)0, /* tp_getattr */
- (setattrfunc)0, /* tp_setattr */
- (cmpfunc)0, /* tp_compare */
- (reprfunc)0, /* tp_repr */
- (PyNumberMethods*)0, /* tp_as_number */
- (PySequenceMethods*)0, /* tp_as_sequence */
- (PyMappingMethods*)0, /* tp_as_mapping */
- (hashfunc)0, /* tp_hash */
- (ternaryfunc)0, /* tp_call */
- (reprfunc)0, /* tp_str */
- (getattrofunc)0, /* tp_getattro */
- (setattrofunc)0, /* tp_setattro */
- (PyBufferProcs*)0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
- NULL, /* Documentation string */
- (traverseproc)0, /* tp_traverse */
- (inquiry)0, /* tp_clear */
- (richcmpfunc)0, /* tp_richcompare */
- offsetof(PyGObject, weakreflist), /* tp_weaklistoffset */
- (getiterfunc)0, /* tp_iter */
- (iternextfunc)0, /* tp_iternext */
- (struct PyMethodDef*)_PySugarKeyGrabber_methods, /* tp_methods */
- (struct PyMemberDef*)0, /* tp_members */
- (struct PyGetSetDef*)0, /* tp_getset */
- NULL, /* tp_base */
- NULL, /* tp_dict */
- (descrgetfunc)0, /* tp_descr_get */
- (descrsetfunc)0, /* tp_descr_set */
- offsetof(PyGObject, inst_dict), /* tp_dictoffset */
- (initproc)0, /* tp_init */
- (allocfunc)0, /* tp_alloc */
- (newfunc)0, /* tp_new */
- (freefunc)0, /* tp_free */
- (inquiry)0 /* tp_is_gc */
-};
-
-
-
-/* ----------- SugarAudioManager ----------- */
-
-static PyObject *
-_wrap_sugar_audio_manager_set_volume(PyGObject *self, PyObject *args, PyObject *kwargs)
-{
- static char *kwlist[] = { "level", NULL };
- int level;
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs,"i:SugarAudioManager.set_volume", kwlist, &level))
- return NULL;
-
- sugar_audio_manager_set_volume(SUGAR_AUDIO_MANAGER(self->obj), level);
-
- Py_INCREF(Py_None);
- return Py_None;
-}
-
-static const PyMethodDef _PySugarAudioManager_methods[] = {
- { "set_volume", (PyCFunction)_wrap_sugar_audio_manager_set_volume, METH_VARARGS|METH_KEYWORDS,
- NULL },
- { NULL, NULL, 0, NULL }
-};
-
-PyTypeObject G_GNUC_INTERNAL PySugarAudioManager_Type = {
- PyObject_HEAD_INIT(NULL)
- 0, /* ob_size */
- "extensions.AudioManager", /* tp_name */
- sizeof(PyGObject), /* tp_basicsize */
- 0, /* tp_itemsize */
- /* methods */
- (destructor)0, /* tp_dealloc */
- (printfunc)0, /* tp_print */
- (getattrfunc)0, /* tp_getattr */
- (setattrfunc)0, /* tp_setattr */
- (cmpfunc)0, /* tp_compare */
- (reprfunc)0, /* tp_repr */
- (PyNumberMethods*)0, /* tp_as_number */
- (PySequenceMethods*)0, /* tp_as_sequence */
- (PyMappingMethods*)0, /* tp_as_mapping */
- (hashfunc)0, /* tp_hash */
- (ternaryfunc)0, /* tp_call */
- (reprfunc)0, /* tp_str */
- (getattrofunc)0, /* tp_getattro */
- (setattrofunc)0, /* tp_setattro */
- (PyBufferProcs*)0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
- NULL, /* Documentation string */
- (traverseproc)0, /* tp_traverse */
- (inquiry)0, /* tp_clear */
- (richcmpfunc)0, /* tp_richcompare */
- offsetof(PyGObject, weakreflist), /* tp_weaklistoffset */
- (getiterfunc)0, /* tp_iter */
- (iternextfunc)0, /* tp_iternext */
- (struct PyMethodDef*)_PySugarAudioManager_methods, /* tp_methods */
- (struct PyMemberDef*)0, /* tp_members */
- (struct PyGetSetDef*)0, /* tp_getset */
- NULL, /* tp_base */
- NULL, /* tp_dict */
- (descrgetfunc)0, /* tp_descr_get */
- (descrsetfunc)0, /* tp_descr_set */
- offsetof(PyGObject, inst_dict), /* tp_dictoffset */
- (initproc)0, /* tp_init */
- (allocfunc)0, /* tp_alloc */
- (newfunc)0, /* tp_new */
- (freefunc)0, /* tp_free */
- (inquiry)0 /* tp_is_gc */
-};
-
-
-
-/* ----------- functions ----------- */
-
-const PyMethodDef pyextensions_functions[] = {
- { NULL, NULL, 0, NULL }
-};
-
-/* initialise stuff extension classes */
-void
-pyextensions_register_classes(PyObject *d)
-{
- PyObject *module;
-
- if ((module = PyImport_ImportModule("gobject")) != NULL) {
- _PyGObject_Type = (PyTypeObject *)PyObject_GetAttrString(module, "GObject");
- if (_PyGObject_Type == NULL) {
- PyErr_SetString(PyExc_ImportError,
- "cannot import name GObject from gobject");
- return ;
- }
- } else {
- PyErr_SetString(PyExc_ImportError,
- "could not import gobject");
- return ;
- }
- if ((module = PyImport_ImportModule("gtk")) != NULL) {
- _PyGtkEntry_Type = (PyTypeObject *)PyObject_GetAttrString(module, "Entry");
- if (_PyGtkEntry_Type == NULL) {
- PyErr_SetString(PyExc_ImportError,
- "cannot import name Entry from gtk");
- return ;
- }
- } else {
- PyErr_SetString(PyExc_ImportError,
- "could not import gtk");
- return ;
- }
-
-
-#line 256 "extensions.c"
- pygobject_register_class(d, "SugarKeyGrabber", SUGAR_TYPE_KEY_GRABBER, &PySugarKeyGrabber_Type, Py_BuildValue("(O)", &PyGObject_Type));
- pyg_set_object_has_new_constructor(SUGAR_TYPE_KEY_GRABBER);
- pygobject_register_class(d, "SugarAudioManager", SUGAR_TYPE_AUDIO_MANAGER, &PySugarAudioManager_Type, Py_BuildValue("(O)", &PyGObject_Type));
- pyg_set_object_has_new_constructor(SUGAR_TYPE_AUDIO_MANAGER);
-}
diff --git a/shell/model/BuddyModel.py b/shell/model/BuddyModel.py
index a06a728..a551e1e 100644
--- a/shell/model/BuddyModel.py
+++ b/shell/model/BuddyModel.py
@@ -84,6 +84,11 @@ class BuddyModel(gobject.GObject):
def get_buddy(self):
return self._buddy
+ def is_owner(self):
+ if not self._buddy:
+ return False
+ return self._buddy.props.owner
+
def is_present(self):
if self._buddy:
return True
@@ -125,6 +130,8 @@ class BuddyModel(gobject.GObject):
if 'color' in keys:
self._set_color_from_string(self._buddy.props.color)
self.emit('color-changed', self.get_color())
+ if 'current-activity' in keys:
+ self.emit('current-activity-changed', buddy.props.current_activity)
def _buddy_disappeared_cb(self, buddy):
if buddy != self._buddy:
@@ -139,8 +146,3 @@ class BuddyModel(gobject.GObject):
def _buddy_icon_changed_cb(self, buddy):
self.emit('icon-changed')
-
- def _buddy_current_activity_changed_cb(self, buddy, activity=None):
- if not self._buddy:
- return
- self.emit('current-activity-changed', activity)
diff --git a/shell/model/MeshModel.py b/shell/model/MeshModel.py
index dd25d9f..b037bb7 100644
--- a/shell/model/MeshModel.py
+++ b/shell/model/MeshModel.py
@@ -160,19 +160,14 @@ class MeshModel(gobject.GObject):
def get_buddies(self):
return self._buddies.values()
- def _buddy_activity_changed_cb(self, buddy, cur_activity):
- if not self._buddies.has_key(buddy.props.key):
+ def _buddy_activity_changed_cb(self, model, cur_activity):
+ if not self._buddies.has_key(model.get_key()):
return
- buddy_model = self._buddies[buddy.props.key]
- if cur_activity == None:
- self.emit('buddy-moved', buddy_model, None)
- else:
- self._notify_buddy_change(buddy_model, cur_activity)
-
- def _notify_buddy_change(self, buddy_model, cur_activity):
- if self._activities.has_key(cur_activity.get_id()):
+ if cur_activity and self._activities.has_key(cur_activity.get_id()):
activity_model = self._activities[cur_activity.get_id()]
- self.emit('buddy-moved', buddy_model, activity_model)
+ self.emit('buddy-moved', model, activity_model)
+ else:
+ self.emit('buddy-moved', model, None)
def _buddy_appeared_cb(self, pservice, buddy):
if self._buddies.has_key(buddy.props.key):
@@ -186,7 +181,7 @@ class MeshModel(gobject.GObject):
cur_activity = buddy.props.current_activity
if cur_activity:
- self._notify_buddy_change(model, cur_activity)
+ self._buddy_activity_changed_cb(model, cur_activity)
def _buddy_disappeared_cb(self, pservice, buddy):
if not self._buddies.has_key(buddy.props.key):
diff --git a/shell/model/homemodel.py b/shell/model/homemodel.py
index 0b1eeb5..99b0512 100644
--- a/shell/model/homemodel.py
+++ b/shell/model/homemodel.py
@@ -166,7 +166,7 @@ class HomeModel(gobject.GObject):
else:
# activity got lost, took longer to launch than we allow,
# or it was launched by something other than the shell
- act_type = act_service.get_service_name()
+ act_type = service.get_service_name()
bundle = self._bundle_registry.get_bundle(act_type)
if not bundle:
raise RuntimeError("No bundle for activity type '%s'." % act_type)
diff --git a/shell/view/BuddyMenu.py b/shell/view/BuddyMenu.py
index 1a76168..16e43cf 100644
--- a/shell/view/BuddyMenu.py
+++ b/shell/view/BuddyMenu.py
@@ -88,7 +88,7 @@ class BuddyMenu(Menu):
activity = shell_model.get_home().get_current_activity()
if activity != None:
- activity_ps = pservice.get_activity(activity.get_id())
+ activity_ps = pservice.get_activity(activity.get_activity_id())
# FIXME check that the buddy is not in the activity already
diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py
index ef0de29..143d016 100644
--- a/shell/view/clipboardicon.py
+++ b/shell/view/clipboardicon.py
@@ -17,6 +17,7 @@
import logging
import os
+import urlparse
import gobject
@@ -74,8 +75,13 @@ class ClipboardIcon(CanvasIcon):
self.props.background_color = color.TOOLBAR_BACKGROUND.get_int()
def get_popup(self):
+ cb_service = clipboardservice.get_instance()
+ obj = cb_service.get_object(self._object_id)
+ formats = obj['FORMATS']
+
self._menu = ClipboardMenu(self._name, self._percent, self._preview,
- self._activity)
+ self._activity,
+ formats[0] == 'application/vnd.olpc-x-sugar')
self._menu.connect('action', self._popup_action_cb)
return self._menu
@@ -83,15 +89,19 @@ class ClipboardIcon(CanvasIcon):
return self._popup_context
def set_state(self, name, percent, icon_name, preview, activity):
+ cb_service = clipboardservice.get_instance()
+ obj = cb_service.get_object(self._object_id)
+ installable = (obj['FORMATS'][0] == 'application/vnd.olpc-x-sugar')
+
self._name = name
self._percent = percent
self._preview = preview
self._activity = activity
self.set_property("icon_name", icon_name)
if self._menu:
- self._menu.set_state(name, percent, preview, activity)
+ self._menu.set_state(name, percent, preview, activity, installable)
- if activity and percent < 100:
+ if (activity or installable) and percent < 100:
self.props.xo_color = XoColor("#000000,#424242")
else:
self.props.xo_color = XoColor("#000000,#FFFFFF")
@@ -107,27 +117,39 @@ class ClipboardIcon(CanvasIcon):
self._open_file()
def _open_file(self):
- if self._percent < 100 or not self._activity:
+ if self._percent < 100:
return
# Get the file path
cb_service = clipboardservice.get_instance()
obj = cb_service.get_object(self._object_id)
formats = obj['FORMATS']
- if len(formats) > 0:
- path = cb_service.get_object_data(self._object_id, formats[0])
+ if len(formats) == 0:
+ return
- # FIXME: would be better to check for format.onDisk
- try:
- path_exists = os.path.exists(path)
- except TypeError:
- path_exists = False
+ if not self._activity and \
+ not formats[0] == 'application/vnd.olpc-x-sugar':
+ return
+
+ uri = cb_service.get_object_data(self._object_id, formats[0])
+ if not uri.startswith('file://'):
+ return
+
+ path = urlparse.urlparse(uri).path
- if path_exists:
- uri = 'file://' + path
+ # FIXME: would be better to check for format.onDisk
+ try:
+ path_exists = os.path.exists(path)
+ except TypeError:
+ path_exists = False
+
+ if path_exists:
+ if self._activity:
activityfactory.create_with_uri(self._activity, uri)
else:
- logging.debug("Clipboard item file path %s didn't exist" % path)
+ self._install_xo(path)
+ else:
+ logging.debug("Clipboard item file path %s didn't exist" % path)
def _popup_action_cb(self, popup, menu_item):
action = menu_item.props.action_id
@@ -153,3 +175,9 @@ class ClipboardIcon(CanvasIcon):
self.props.background_color = color.DESKTOP_BACKGROUND.get_int()
else:
self.props.background_color = color.TOOLBAR_BACKGROUND.get_int()
+
+ def _install_xo(self, path):
+ logging.debug('mec')
+ if os.spawnlp(os.P_WAIT, 'sugar-install-bundle', 'sugar-install-bundle',
+ path):
+ raise RuntimeError, 'An error occurred while extracting the .xo contents.'
diff --git a/shell/view/clipboardmenu.py b/shell/view/clipboardmenu.py
index 3e8239d..9db6922 100644
--- a/shell/view/clipboardmenu.py
+++ b/shell/view/clipboardmenu.py
@@ -33,7 +33,7 @@ class ClipboardMenu(Menu):
ACTION_OPEN = 1
ACTION_STOP_DOWNLOAD = 2
- def __init__(self, name, percent, preview, activity):
+ def __init__(self, name, percent, preview, activity, installable):
Menu.__init__(self, name)
self.props.border = 0
@@ -54,10 +54,10 @@ class ClipboardMenu(Menu):
self._preview_text.props.font_desc = font.DEFAULT.get_pango_desc()
self.append(self._preview_text)
- self._update_icons(percent, activity)
+ self._update_icons(percent, activity, installable)
- def _update_icons(self, percent, activity):
- if percent == 100 and activity:
+ def _update_icons(self, percent, activity, installable):
+ if percent == 100 and (activity or installable):
if not self._remove_item:
self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE,
_('Remove'),
@@ -73,7 +73,7 @@ class ClipboardMenu(Menu):
if self._stop_item:
self.remove_item(self._stop_item)
self._stop_item = None
- elif percent == 100 and not activity:
+ elif percent == 100 and (not activity and not installable):
if not self._remove_item:
self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE,
_('Remove'),
@@ -102,8 +102,8 @@ class ClipboardMenu(Menu):
self.remove_item(self._open_item)
self._open_item = None
- def set_state(self, name, percent, preview, activity):
+ def set_state(self, name, percent, preview, activity, installable):
self.set_title(name)
if self._progress_bar:
self._progress_bar.set_property('percent', percent)
- self._update_icons(percent, activity)
+ self._update_icons(percent, activity, installable)
diff --git a/shell/view/frame/clipboardbox.py b/shell/view/frame/clipboardbox.py
index 2dcad10..4a01610 100644
--- a/shell/view/frame/clipboardbox.py
+++ b/shell/view/frame/clipboardbox.py
@@ -1,4 +1,8 @@
+import shutil
+import os
import logging
+import urlparse
+
import hippo
import gtk
@@ -72,13 +76,29 @@ class ClipboardBox(hippo.CanvasBox):
if not selection.data:
return
- logging.debug('ClipboardBox: adding type ' + selection.type + '.')
-
+ logging.debug('ClipboardBox: adding type ' + selection.type + ' ' + selection.data)
+
cb_service = clipboardservice.get_instance()
- cb_service.add_object_format(object_id,
- selection.type,
- selection.data,
- on_disk = False)
+ if selection.type == 'text/uri-list':
+ uris = selection.data.split('\n')
+ if len(uris) > 1:
+ raise NotImplementedError('Multiple uris in text/uri-list still not supported.')
+ uri = urlparse.urlparse(uris[0])
+ path, file_name = os.path.split(uri.path)
+
+ # Copy the file, as it will be deleted when the dnd operation finishes.
+ new_file_path = os.path.join(path, 'cb' + file_name)
+ shutil.copyfile(uri.path, new_file_path)
+
+ cb_service.add_object_format(object_id,
+ selection.type,
+ uri.scheme + "://" + new_file_path,
+ on_disk=True)
+ else:
+ cb_service.add_object_format(object_id,
+ selection.type,
+ selection.data,
+ on_disk=False)
def _object_added_cb(self, cb_service, object_id, name):
icon = ClipboardIcon(self._popup_context, object_id, name)
@@ -165,15 +185,16 @@ class ClipboardBox(hippo.CanvasBox):
def drag_data_received_cb(self, widget, context, x, y, selection, targetType, time):
logging.debug('ClipboardBox: got data for target ' + selection.target)
- if selection:
- object_id = self._context_map.get_object_id(context)
- self._add_selection(object_id, selection)
- else:
- logging.warn('ClipboardBox: empty selection for target ' + selection.target)
-
- # If it's the last target to be processed, finish the dnd transaction
- if not self._context_map.has_context(context):
- context.finish(True, False, time)
+ try:
+ if selection:
+ object_id = self._context_map.get_object_id(context)
+ self._add_selection(object_id, selection)
+ else:
+ logging.warn('ClipboardBox: empty selection for target ' + selection.target)
+ finally:
+ # If it's the last target to be processed, finish the dnd transaction
+ if not self._context_map.has_context(context):
+ context.drop_finish(True, gtk.get_current_event_time())
def drag_data_get_cb(self, widget, context, selection, targetType, eventTime):
logging.debug("drag_data_get_cb: requested target " + selection.target)
diff --git a/shell/view/frame/frame.py b/shell/view/frame/frame.py
index bd598aa..472cb5f 100644
--- a/shell/view/frame/frame.py
+++ b/shell/view/frame/frame.py
@@ -169,7 +169,7 @@ class Frame(object):
if self._animator:
self._animator.stop()
- self._animator = animator.Animator(0.5, 30, animator.EASE_OUT_EXPO)
+ self._animator = animator.Animator(0.5)
self._animator.add(_Animation(self, 0.0))
self._animator.start()
@@ -188,7 +188,7 @@ class Frame(object):
if self._animator:
self._animator.stop()
- self._animator = animator.Animator(0.5, 30, animator.EASE_OUT_EXPO)
+ self._animator = animator.Animator(0.5)
self._animator.add(_Animation(self, 1.0))
self._animator.start()
diff --git a/shell/view/home/FriendsBox.py b/shell/view/home/FriendsBox.py
index 6f1cdb7..71b9026 100644
--- a/shell/view/home/FriendsBox.py
+++ b/shell/view/home/FriendsBox.py
@@ -24,7 +24,7 @@ from sugar.graphics import units
from view.home.MyIcon import MyIcon
from view.home.FriendView import FriendView
-class FriendsBox(SpreadBox, hippo.CanvasItem):
+class FriendsBox(SpreadBox):
__gtype_name__ = 'SugarFriendsBox'
def __init__(self, shell, menu_shell):
SpreadBox.__init__(self, background_color=0xe2e2e2ff)
@@ -34,7 +34,7 @@ class FriendsBox(SpreadBox, hippo.CanvasItem):
self._friends = {}
self._my_icon = MyIcon(units.LARGE_ICON_SCALE)
- self.append(self._my_icon, hippo.PACK_FIXED)
+ self.set_center_item(self._my_icon)
friends = self._shell.get_model().get_friends()
@@ -56,10 +56,3 @@ class FriendsBox(SpreadBox, hippo.CanvasItem):
def _friend_removed_cb(self, data_model, key):
self.remove_item(self._friends[key])
del self._friends[key]
-
- def do_allocate(self, width, height, origin_changed):
- SpreadBox.do_allocate(self, width, height, origin_changed)
-
- [icon_width, icon_height] = self._my_icon.get_allocation()
- self.set_position(self._my_icon, (width - icon_width) / 2,
- (height - icon_height) / 2)
diff --git a/shell/view/home/HomeWindow.py b/shell/view/home/HomeWindow.py
index a37aeae..3f33919 100644
--- a/shell/view/home/HomeWindow.py
+++ b/shell/view/home/HomeWindow.py
@@ -19,20 +19,26 @@ import hippo
import cairo
from sugar.graphics.menushell import MenuShell
+from sugar.graphics.window import Window
+from sugar.graphics import units
import sugar
+
from view.home.MeshBox import MeshBox
from view.home.HomeBox import HomeBox
from view.home.FriendsBox import FriendsBox
+from view.home.transitionbox import TransitionBox
-_HOME_PAGE = 0
-_FRIENDS_PAGE = 1
-_MESH_PAGE = 2
+_HOME_PAGE = 0
+_FRIENDS_PAGE = 1
+_MESH_PAGE = 2
+_TRANSITION_PAGE = 3
-class HomeWindow(gtk.Window):
+class HomeWindow(Window):
def __init__(self, shell):
- gtk.Window.__init__(self)
+ Window.__init__(self)
self._shell = shell
self._active = False
+ self._level = sugar.ZOOM_HOME
self.set_default_size(gtk.gdk.screen_width(),
gtk.gdk.screen_height())
@@ -43,30 +49,15 @@ class HomeWindow(gtk.Window):
self.connect('focus-in-event', self._focus_in_cb)
self.connect('focus-out-event', self._focus_out_cb)
- self._nb = gtk.Notebook()
- self._nb.set_show_border(False)
- self._nb.set_show_tabs(False)
-
- self.add(self._nb)
- self._nb.show()
-
- canvas = hippo.Canvas()
self._home_box = HomeBox(shell)
- canvas.set_root(self._home_box)
- self._nb.append_page(canvas)
- canvas.show()
-
- canvas = hippo.Canvas()
- box = FriendsBox(shell, MenuShell(canvas))
- canvas.set_root(box)
- self._nb.append_page(canvas)
- canvas.show()
-
- canvas = hippo.Canvas()
- self._mesh_box = MeshBox(shell, MenuShell(canvas))
- canvas.set_root(self._mesh_box)
- self._nb.append_page(canvas)
- canvas.show()
+ self._friends_box = FriendsBox(shell, MenuShell(self))
+ self._mesh_box = MeshBox(shell, MenuShell(self))
+ self._transition_box = TransitionBox()
+
+ self.set_root(self._home_box)
+
+ self._transition_box.connect('completed',
+ self._transition_completed_cb)
def _key_release_cb(self, widget, event):
keyname = gtk.gdk.keyval_name(event.keyval)
@@ -74,7 +65,7 @@ class HomeWindow(gtk.Window):
self._home_box.release()
def _update_mesh_state(self):
- if self._active and self._nb.get_current_page() == _MESH_PAGE:
+ if self._active and self._level == sugar.ZOOM_MESH:
self._mesh_box.resume()
else:
self._mesh_box.suspend()
@@ -88,12 +79,26 @@ class HomeWindow(gtk.Window):
self._update_mesh_state()
def set_zoom_level(self, level):
+ self._level = level
+
+ self.set_root(self._transition_box)
+
if level == sugar.ZOOM_HOME:
- self._nb.set_current_page(_HOME_PAGE)
+ scale = units.XLARGE_ICON_SCALE
elif level == sugar.ZOOM_FRIENDS:
- self._nb.set_current_page(_FRIENDS_PAGE)
+ scale = units.LARGE_ICON_SCALE
elif level == sugar.ZOOM_MESH:
- self._nb.set_current_page(_MESH_PAGE)
+ scale = units.STANDARD_ICON_SCALE
+
+ self._transition_box.set_scale(scale)
+
+ def _transition_completed_cb(self, transition_box):
+ if self._level == sugar.ZOOM_HOME:
+ self.set_root(self._home_box)
+ elif self._level == sugar.ZOOM_FRIENDS:
+ self.set_root(self._friends_box)
+ elif self._level == sugar.ZOOM_MESH:
+ self.set_root(self._mesh_box)
self._update_mesh_state()
diff --git a/shell/view/home/Makefile.am b/shell/view/home/Makefile.am
index 11e221c..466187b 100644
--- a/shell/view/home/Makefile.am
+++ b/shell/view/home/Makefile.am
@@ -7,4 +7,5 @@ sugar_PYTHON = \
HomeBox.py \
HomeWindow.py \
MeshBox.py \
- MyIcon.py
+ MyIcon.py \
+ transitionbox.py
diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py
index 218a44d..fa85b03 100644
--- a/shell/view/home/MeshBox.py
+++ b/shell/view/home/MeshBox.py
@@ -278,7 +278,10 @@ class MeshBox(SpreadBox):
def _add_alone_buddy(self, buddy_model):
icon = BuddyIcon(self._shell, self._menu_shell, buddy_model)
- self.add_item(icon)
+ if buddy_model.is_owner():
+ self.set_center_item(icon)
+ else:
+ self.add_item(icon)
self._buddies[buddy_model.get_key()] = icon
diff --git a/shell/view/home/transitionbox.py b/shell/view/home/transitionbox.py
new file mode 100644
index 0000000..3d83347
--- /dev/null
+++ b/shell/view/home/transitionbox.py
@@ -0,0 +1,66 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import hippo
+import gobject
+
+from sugar.graphics import units
+from sugar.graphics import animator
+from sugar.graphics.spreadbox import SpreadBox
+
+from view.home.MyIcon import MyIcon
+
+class _Animation(animator.Animation):
+ def __init__(self, icon, start_scale, end_scale):
+ animator.Animation.__init__(self, 0.0, 1.0)
+
+ self._icon = icon
+ self.start_scale = start_scale
+ self.end_scale = end_scale
+
+ def next_frame(self, current):
+ d = (self.end_scale - self.start_scale) * current
+ self._icon.props.scale = self.start_scale + d
+
+class TransitionBox(SpreadBox):
+ __gtype_name__ = 'SugarTransitionBox'
+
+ __gsignals__ = {
+ 'completed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ def __init__(self):
+ SpreadBox.__init__(self, background_color=0xe2e2e2ff)
+
+ self._scale = units.XLARGE_ICON_SCALE
+
+ self._my_icon = MyIcon(self._scale)
+ self.set_center_item(self._my_icon)
+
+ self._animator = animator.Animator(0.3)
+ self._animator.connect('completed', self._animation_completed_cb)
+
+ def _animation_completed_cb(self, anim):
+ self.emit('completed')
+
+ def set_scale(self, scale):
+ self._animator.remove_all()
+ self._animator.add(_Animation(self._my_icon, self._scale, scale))
+ self._animator.start()
+
+ self._scale = scale
+
diff --git a/sugar-emulator b/sugar-emulator
index d4005d5..29b0260 100755
--- a/sugar-emulator
+++ b/sugar-emulator
@@ -25,8 +25,6 @@ pygtk.require('2.0')
import gtk
import gobject
-from sugar import env
-
def _get_display_number():
"""Find a free display number trying to connect to 6000+ ports"""
retries = 20
@@ -50,22 +48,23 @@ def _get_display_number():
logging.error('Cannot find a free display.')
sys.exit(0)
-def _start_xephyr(width, height, dpi):
+def _start_xephyr():
display = _get_display_number()
cmd = [ 'Xephyr' ]
cmd.append(':%d' % display)
cmd.append('-ac')
- if width > 0 and height > 0:
- cmd.append('-screen')
- cmd.append('%dx%d' % (width, height))
- else:
+ if gtk.gdk.screen_width() < 1200 or gtk.gdk.screen_height() < 900:
cmd.append('-fullscreen')
+ else:
+ cmd.append('-screen')
+ cmd.append('%dx%d' % (1200, 900))
+ dpi = gtk.settings_get_default().get_property('gtk-xft-dpi')
if dpi > 0:
cmd.append('-dpi')
- cmd.append(str(dpi))
+ cmd.append('%d' % int(dpi/1024))
result = gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
pid = result[0]
@@ -73,23 +72,40 @@ def _start_xephyr(width, height, dpi):
os.environ['DISPLAY'] = ":%d" % (display)
os.environ['SUGAR_EMULATOR_PID'] = str(pid)
-os.environ['SUGAR_EMULATOR'] = 'yes'
+def _start_matchbox():
+ cmd = ['matchbox-window-manager']
-if len(sys.argv) == 1:
- program = 'sugar-shell'
-else:
- program = sys.argv[1]
+ cmd.extend(['-use_titlebar', 'no'])
+ cmd.extend(['-theme', 'olpc'])
-if gtk.gdk.screen_width() < 1200 or gtk.gdk.screen_height() < 900:
- width = -1
- height = -1
+ gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
+
+def _setup_env():
+ os.environ['SUGAR_EMULATOR'] = 'yes'
+
+ source_dir = os.path.dirname(os.path.abspath(__file__))
+ if os.path.isfile(os.path.join(source_dir, 'autogen.sh')):
+ os.environ['SUGAR_PATH'] = source_dir
+ if os.environ.has_key('PYTHONPATH'):
+ path = os.environ['PYTHONPATH']
+ os.environ['PYTHONPATH'] = source_dir + ':' + path
+
+_setup_env()
+_start_xephyr()
+
+from sugar import env
+
+if env.is_emulator():
+ gtkrc_filename = 'sugar.gtkrc'
else:
- width = 1200
- height = 900
+ gtkrc_filename = 'sugar-xo.gtkrc'
-_gtk_xft_dpi = float(gtk.settings_get_default().get_property('gtk-xft-dpi'))
-_start_xephyr(width, height, _gtk_xft_dpi / 1024)
+os.environ['GTK2_RC_FILES'] = env.get_data_path(gtkrc_filename)
-os.environ['GTK2_RC_FILES'] = env.get_data_path('gtkrc')
+if len(sys.argv) == 1:
+ program = 'sugar-shell'
+else:
+ _start_matchbox()
+ program = sys.argv[1]
os.execlp('dbus-launch', 'dbus-launch', '--exit-with-session', program)
diff --git a/sugar/Makefile.am b/sugar/Makefile.am
index 7ea659a..9e8db38 100644
--- a/sugar/Makefile.am
+++ b/sugar/Makefile.am
@@ -1,10 +1,11 @@
SUBDIRS = activity browser clipboard graphics p2p presence datastore
sugardir = $(pythondir)/sugar
-sugar_PYTHON = \
- __init__.py \
- date.py \
- env.py \
- logger.py \
- profile.py \
+sugar_PYTHON = \
+ __init__.py \
+ date.py \
+ env.py \
+ logger.py \
+ ltihooks.py \
+ profile.py \
util.py
diff --git a/sugar/activity/activityfactoryservice.py b/sugar/activity/activityfactoryservice.py
index 5dcedb3..ee98f51 100644
--- a/sugar/activity/activityfactoryservice.py
+++ b/sugar/activity/activityfactoryservice.py
@@ -140,7 +140,7 @@ def run_with_args(args):
run(options.bundle_path)
def run(bundle_path):
- sys.path.insert(0, bundle_path)
+ sys.path.append(bundle_path)
bundle = Bundle(bundle_path)
diff --git a/sugar/browser/__init__.py b/sugar/browser/__init__.py
index a79048b..7de5339 100644
--- a/sugar/browser/__init__.py
+++ b/sugar/browser/__init__.py
@@ -3,4 +3,9 @@
XUL Runner and gtkmozembed and is produced by the PyGTK
.defs system.
"""
-from sugar.browser._sugarbrowser import *
+
+try:
+ from sugar.browser._sugarbrowser import *
+except ImportError:
+ from sugar import ltihooks
+ from sugar.browser._sugarbrowser import *
diff --git a/sugar/env.py b/sugar/env.py
index f25f460..3555e93 100644
--- a/sugar/env.py
+++ b/sugar/env.py
@@ -29,6 +29,17 @@ def _get_prefix_path(base, path=None):
else:
return os.path.join(prefix, base)
+def _get_sugar_path(base, path=None):
+ if os.environ.has_key('SUGAR_PATH'):
+ sugar_path = os.environ['SUGAR_PATH']
+ else:
+ sugar_path = _get_prefix_path('share/sugar')
+
+ if path:
+ return os.path.join(sugar_path, base, path)
+ else:
+ return os.path.join(sugar_path, base)
+
def is_emulator():
if os.environ.has_key('SUGAR_EMULATOR'):
if os.environ['SUGAR_EMULATOR'] == 'yes':
@@ -56,17 +67,17 @@ def get_profile_path(path=None):
def get_user_activities_path():
return os.path.expanduser('~/Activities')
-def get_bin_path(path=None):
- return _get_prefix_path('bin', path)
-
def get_locale_path(path=None):
return _get_prefix_path('share/locale', path)
+def get_bin_path(path=None):
+ return _get_sugar_path('bin', path)
+
def get_service_path(name):
- return _get_prefix_path('share/sugar/services', name)
+ return _get_sugar_path('services', name)
def get_shell_path(path=None):
- return _get_prefix_path('share/sugar/shell', path)
+ return _get_sugar_path('shell', path)
def get_data_path(path=None):
- return _get_prefix_path('share/sugar', path)
+ return _get_sugar_path('data', path)
diff --git a/sugar/graphics/animator.py b/sugar/graphics/animator.py
index 60ff9f3..459851b 100644
--- a/sugar/graphics/animator.py
+++ b/sugar/graphics/animator.py
@@ -28,7 +28,7 @@ class Animator(gobject.GObject):
gobject.TYPE_NONE, ([])),
}
- def __init__(self, time, fps, easing=EASE_OUT_EXPO):
+ def __init__(self, time, fps=20, easing=EASE_OUT_EXPO):
gobject.GObject.__init__(self)
self._animations = []
self._time = time
@@ -39,6 +39,10 @@ class Animator(gobject.GObject):
def add(self, animation):
self._animations.append(animation)
+ def remove_all(self):
+ self.stop()
+ self._animations = []
+
def start(self):
if self._timeout_sid:
self.stop()
@@ -51,7 +55,7 @@ class Animator(gobject.GObject):
if self._timeout_sid:
gobject.source_remove(self._timeout_sid)
self._timeout_sid = 0
- self.emit('completed')
+ self.emit('completed')
def _next_frame_cb(self):
current_time = min(self._time, time.time() - self._start_time)
diff --git a/sugar/graphics/canvasicon.py b/sugar/graphics/canvasicon.py
index d50e9f9..14f8351 100644
--- a/sugar/graphics/canvasicon.py
+++ b/sugar/graphics/canvasicon.py
@@ -369,6 +369,11 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
if not self._popup:
return
+ if not self.get_context():
+ # If we have been detached from our parent, don't show up the popup
+ # in this case.
+ return
+
popup_context = self.get_popup_context()
[x, y] = [None, None]
diff --git a/sugar/graphics/snowflakebox.py b/sugar/graphics/snowflakebox.py
index dfef45b..14f4559 100644
--- a/sugar/graphics/snowflakebox.py
+++ b/sugar/graphics/snowflakebox.py
@@ -20,9 +20,11 @@ import math
import cairo
import hippo
-_BASE_RADIUS = 65
+from sugar.graphics import units
+
+_BASE_RADIUS = units.points_to_pixels(20)
_CHILDREN_FACTOR = 1
-_FLAKE_DISTANCE = 6
+_FLAKE_DISTANCE = units.points_to_pixels(4)
class SnowflakeBox(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarSnowflakeBox'
@@ -67,6 +69,13 @@ class SnowflakeBox(hippo.CanvasBox, hippo.CanvasItem):
self.set_position(child, int(x), int(y))
+ def do_get_height_request(self, for_width):
+ hippo.CanvasBox.do_get_height_request(self, for_width)
+
+ height = for_width
+
+ return (height, height)
+
def do_get_width_request(self):
hippo.CanvasBox.do_get_width_request(self)
diff --git a/sugar/graphics/spreadbox.py b/sugar/graphics/spreadbox.py
index 24e12f3..ff50f71 100644
--- a/sugar/graphics/spreadbox.py
+++ b/sugar/graphics/spreadbox.py
@@ -22,20 +22,31 @@ import gtk
from sugar.graphics import units
+_WIDTH = gtk.gdk.screen_width()
+_HEIGHT = gtk.gdk.screen_height()
_CELL_WIDTH = units.grid_to_pixels(1)
_CELL_HEIGHT = units.grid_to_pixels(1)
-_GRID_WIDTH = gtk.gdk.screen_width() / _CELL_WIDTH
-_GRID_HEIGHT = gtk.gdk.screen_height() / _CELL_HEIGHT
+_GRID_WIDTH = _WIDTH / _CELL_WIDTH
+_GRID_HEIGHT = _HEIGHT / _CELL_HEIGHT
-class SpreadBox(hippo.CanvasBox):
+class SpreadBox(hippo.CanvasBox, hippo.CanvasItem):
+ __gtype_name__ = 'SugarSpreadBox'
def __init__(self, **kwargs):
hippo.CanvasBox.__init__(self, **kwargs)
self._grid = []
+ self._center = None
for i in range(0, _GRID_WIDTH * _GRID_HEIGHT):
self._grid.append(None)
+ def set_center_item(self, item):
+ if self._center:
+ self.remove(self._center)
+
+ self._center = item
+ self.append(item, hippo.PACK_FIXED)
+
def add_item(self, item):
start_pos = int(random.random() * len(self._grid))
@@ -60,3 +71,11 @@ class SpreadBox(hippo.CanvasBox):
if self._grid[i] == item:
self._grid[i] = None
self.remove(item)
+
+ def do_allocate(self, width, height, origin_changed):
+ hippo.CanvasBox.do_allocate(self, width, height, origin_changed)
+
+ if self._center:
+ [icon_width, icon_height] = self._center.get_allocation()
+ self.set_position(self._center, (width - icon_width) / 2,
+ (height - icon_height) / 2)
diff --git a/sugar/graphics2/Makefile.am b/sugar/graphics2/Makefile.am
new file mode 100644
index 0000000..f5bde18
--- /dev/null
+++ b/sugar/graphics2/Makefile.am
@@ -0,0 +1,5 @@
+sugardir = $(pythondir)/sugar/graphics2
+sugar_PYTHON = \
+ __init__.py \
+ window.py \
+ toolbox.py
diff --git a/sugar/graphics2/__init__.py b/sugar/graphics2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sugar/graphics2/__init__.py
diff --git a/sugar/graphics2/toolbox.py b/sugar/graphics2/toolbox.py
new file mode 100644
index 0000000..be7e5e0
--- /dev/null
+++ b/sugar/graphics2/toolbox.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+
+class Toolbox(gtk.VBox):
+ __gtype_name__ = 'SugarToolbox'
+ def __init__(self):
+ gtk.VBox.__init__(self)
+
+ self._notebook = gtk.Notebook()
+ self._notebook.set_tab_pos(gtk.POS_BOTTOM)
+ self._notebook.set_show_border(False)
+ self.pack_start(self._notebook)
+ self._notebook.show()
+
+ def add_toolbar(self, name, toolbar):
+ self._notebook.append_page(toolbar, gtk.Label(name))
diff --git a/sugar/graphics2/window.py b/sugar/graphics2/window.py
new file mode 100644
index 0000000..d03788b
--- /dev/null
+++ b/sugar/graphics2/window.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import hippo
+
+from sugar.graphics2.toolbox import Toolbox
+
+class Window(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self)
+
+ vbox = gtk.VBox()
+ self.add(vbox)
+
+ self.toolbox = Toolbox()
+ vbox.pack_start(self.toolbox, False)
+ self.toolbox.show()
+
+ self._canvas_box = gtk.VBox()
+ vbox.pack_start(self._canvas_box)
+ self._canvas_box.show()
+
+ self.canvas = hippo.Canvas()
+ self._canvas_box.pack_start(self.canvas)
+ self.canvas.show()
+
+ vbox.show()
+
+ def set_canvas(self, canvas):
+ if self.canvas:
+ self._canvas_box.remove(self.canvas)
+
+ self._canvas_box.add(canvas)
+ self.canvas = canvas
diff --git a/sugar/ltihooks.py b/sugar/ltihooks.py
new file mode 100644
index 0000000..d68f2eb
--- /dev/null
+++ b/sugar/ltihooks.py
@@ -0,0 +1,72 @@
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+
+# ltihooks.py: python import hooks that understand libtool libraries.
+# Copyright (C) 2000 James Henstridge.
+# renamed to gstltihooks.py so it does not accidentally get imported by
+# an installed copy of gtk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import os, ihooks
+
+class LibtoolHooks(ihooks.Hooks):
+ def get_suffixes(self):
+ """Like normal get_suffixes, but adds .la suffixes to list"""
+ ret = ihooks.Hooks.get_suffixes(self)
+ ret.insert(0, ('module.la', 'rb', 3))
+ ret.insert(0, ('.la', 'rb', 3))
+ return ret
+
+ def load_dynamic(self, name, filename, file=None):
+ """Like normal load_dynamic, but treat .la files specially"""
+ if len(filename) > 3 and filename[-3:] == '.la':
+ fp = open(filename, 'r')
+ dlname = ''
+ installed = 1
+ line = fp.readline()
+ while line:
+ # dlname: the name that we can dlopen
+ if len(line) > 7 and line[:7] == 'dlname=':
+ dlname = line[8:-2]
+ # installed: whether it's already installed
+ elif len(line) > 10 and line[:10] == 'installed=':
+ installed = line[10:-1] == 'yes'
+ line = fp.readline()
+ fp.close()
+ if dlname:
+ if installed:
+ filename = os.path.join(os.path.dirname(filename),
+ dlname)
+ else:
+ # if .libs already there, don't need to add it again
+ if os.path.dirname(filename).endswith('.libs'):
+ filename = os.path.join(os.path.dirname(filename),
+ dlname)
+ else:
+ filename = os.path.join(os.path.dirname(filename),
+ '.libs', dlname)
+ return ihooks.Hooks.load_dynamic(self, name, filename, file)
+
+importer = ihooks.ModuleImporter()
+importer.set_hooks(LibtoolHooks())
+
+def install():
+ print 'Installed ltihooks.'
+ importer.install()
+def uninstall():
+ importer.uninstall()
+
+install()
diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py
index 78f51e6..e61c8d7 100644
--- a/sugar/presence/presenceservice.py
+++ b/sugar/presence/presenceservice.py
@@ -337,6 +337,19 @@ class PresenceService(gobject.GObject):
reply_handler=lambda *args: self._share_activity_cb(activity, *args),
error_handler=lambda *args: self._share_activity_error_cb(activity, *args))
+ def get_preferred_connection(self):
+ """Gets the preferred telepathy connection object that an activity
+ should use when talking directly to telepathy
+
+ returns the bus name and the object path of the Telepathy connection"""
+
+ try:
+ bus_name, object_path = self._ps.GetPreferredConnection()
+ except dbus.exceptions.DBusException:
+ return None
+
+ return bus_name, object_path
+
class _MockPresenceService(gobject.GObject):
"""Test fixture allowing testing of items that use PresenceService
diff --git a/tests/test-button.py b/tests/test-button.py
deleted file mode 100755
index 9cceb92..0000000
--- a/tests/test-button.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2007, One Laptop Per Child
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import sys
-sys.path.insert(0, '/home/tomeu/sugar-jhbuild/source/sugar')
-
-import gtk
-import hippo
-
-from sugar.graphics.toolbar import Toolbar
-from sugar.graphics.iconbutton import IconButton
-from sugar.graphics.toggleiconbutton import ToggleIconButton
-from sugar.graphics.button import Button
-from sugar.graphics.entry import Entry
-
-def _button_activated_cb(button):
- print "_button_activated_cb"
-
-def _toggled_changed_cb(button, pspec):
- print "Toggle state: %d" % button.props.toggled
-
-window = gtk.Window()
-window.connect("destroy", lambda w: gtk.main_quit())
-window.show()
-
-canvas = hippo.Canvas()
-window.add(canvas)
-canvas.show()
-
-vbox = hippo.CanvasBox()
-canvas.set_root(vbox)
-
-for i in [1, 2]:
- toolbar = Toolbar()
- toolbar.props.box_width = 400
- vbox.append(toolbar)
-
- icon_button = IconButton(icon_name='theme:stock-close')
- toolbar.append(icon_button)
-
- toggle = ToggleIconButton(icon_name='theme:stock-back')
- toggle.connect('notify::toggled', _toggled_changed_cb)
- toolbar.append(toggle)
-
- button = Button(text='Click me!', icon_name='theme:stock-close')
- button.connect('activated', _button_activated_cb)
- toolbar.append(button)
-
- entry = Entry(text='mec')
- toolbar.append(entry)
-
-gtk.main()
diff --git a/tests/test-entry.py b/tests/test-entry.py
deleted file mode 100755
index 80f0e23..0000000
--- a/tests/test-entry.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2007, One Laptop Per Child
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import gtk
-import hippo
-
-from sugar.graphics.toolbar import Toolbar
-from sugar.graphics.frame import Frame
-from sugar.graphics.iconbutton import IconButton
-from sugar.graphics.entry import Entry
-
-def _entry_activated_cb(entry):
- print "_entry_activated_cb"
-
-def _entry_button_activated_cb(entry, action_id):
- print "_entry_button_activated_cb: " + str(action_id)
- entry.props.text = ''
-
-window = gtk.Window()
-window.connect("destroy", lambda w: gtk.main_quit())
-window.show()
-
-canvas = hippo.Canvas()
-window.add(canvas)
-canvas.show()
-
-vbox = hippo.CanvasBox()
-canvas.set_root(vbox)
-
-for i in [1, 2]:
- toolbar = Toolbar()
- vbox.append(toolbar)
-
- button = IconButton('theme:stock-close')
- toolbar.append(button)
-
- BUTTON_DELETE = 1
- entry = Entry()
- entry.props.text = 'mec mac'
- entry.add_button('theme:stock-close', BUTTON_DELETE)
- entry.connect('activated', _entry_activated_cb)
- entry.connect('button-activated', _entry_button_activated_cb)
- toolbar.append(entry, hippo.PACK_EXPAND)
-
- entry = Entry()
- entry.props.text = 'moc muc'
- toolbar.append(entry, hippo.PACK_EXPAND)
-
- gtk_entry = gtk.Entry()
- gtk_entry.props.has_frame = False
- #gtk_entry.connect("activate", self._entry_activate_cb)
-
- gtk_entry_widget = hippo.CanvasWidget()
- gtk_entry_widget.props.widget = gtk_entry
- toolbar.append(gtk_entry_widget, hippo.PACK_EXPAND)
-
-gtk.main()
diff --git a/tests/test-frame.py b/tests/test-frame.py
deleted file mode 100755
index 4a22fd1..0000000
--- a/tests/test-frame.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2006, Red Hat, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-import gtk
-import hippo
-
-from sugar.graphics.frame import Frame
-
-window = gtk.Window()
-window.connect("destroy", lambda w: gtk.main_quit())
-window.show()
-
-canvas = hippo.Canvas()
-
-frame = Frame()
-canvas.set_root(frame)
-
-window.add(canvas)
-canvas.show()
-
-gtk.main()
diff --git a/tests/test-label.py b/tests/test-label.py
deleted file mode 100755
index 6e7b6b2..0000000
--- a/tests/test-label.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2007, One Laptop Per Child
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import gtk
-import hippo
-
-from sugar.graphics.toolbar import Toolbar
-from sugar.graphics.label import Label
-from sugar.graphics.iconbutton import IconButton
-
-BUTTON_DELETE = 1
-
-window = gtk.Window()
-window.connect("destroy", lambda w: gtk.main_quit())
-window.show()
-
-canvas = hippo.Canvas()
-window.add(canvas)
-canvas.show()
-
-vbox = hippo.CanvasBox()
-canvas.set_root(vbox)
-
-toolbar = Toolbar()
-vbox.append(toolbar)
-
-button = IconButton('theme:stock-close')
-toolbar.append(button)
-
-label = Label('mec moc')
-toolbar.append(label)
-
-label = Label()
-label.props.text = 'mac mic'
-toolbar.append(label)
-
-gtk.main()
diff --git a/tests/test-option-menu.py b/tests/test-option-menu.py
deleted file mode 100755
index a9202da..0000000
--- a/tests/test-option-menu.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2007, One Laptop Per Child
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import sys
-sys.path.insert(0, '/home/tomeu/sugar-jhbuild/source/sugar')
-from gettext import gettext as _
-
-import gtk
-import hippo
-
-from sugar.graphics.toolbar import Toolbar
-from sugar.graphics.optionmenu import OptionMenu
-from sugar.graphics.menu import MenuItem
-from sugar.graphics.iconbutton import IconButton
-
-def _option_menu_changed_cb(option_menu):
- print '_option_menu_activated_cb: %i' % option_menu.props.value
-
-window = gtk.Window()
-window.connect("destroy", lambda w: gtk.main_quit())
-window.show()
-
-canvas = hippo.Canvas()
-window.add(canvas)
-canvas.show()
-
-vbox = hippo.CanvasBox()
-canvas.set_root(vbox)
-
-toolbar = Toolbar()
-vbox.append(toolbar)
-
-button = IconButton(icon_name='theme:stock-close')
-toolbar.append(button)
-
-OPTION_ANYTHING = 1
-OPTION_DRAW = 2
-OPTION_WRITE = 3
-OPTION_CHAT = 4
-
-option_menu = OptionMenu()
-option_menu.add_item(MenuItem(OPTION_ANYTHING, 'Anything'))
-option_menu.add_separator()
-option_menu.add_item(MenuItem(OPTION_DRAW, 'Draw', 'theme:stock-close'))
-option_menu.add_item(MenuItem(OPTION_WRITE, 'Write'))
-option_menu.add_item(MenuItem(OPTION_CHAT, 'Chat'))
-option_menu.connect('changed', _option_menu_changed_cb)
-toolbar.append(option_menu)
-
-gtk.main()
diff --git a/tests/test-snowflake-box.py b/tests/test-snowflake-box.py
index a0c35bb..4164af4 100755
--- a/tests/test-snowflake-box.py
+++ b/tests/test-snowflake-box.py
@@ -33,14 +33,14 @@ from sugar.graphics.canvasicon import CanvasIcon
def _create_snowflake(parent, children):
color = XoColor()
- icon = CanvasIcon(scale=0.5, xo_color=color,
+ icon = CanvasIcon(scale=1.0, xo_color=color,
icon_name='theme:object-link')
parent.append(icon, hippo.PACK_FIXED)
parent.set_root(icon)
for i in range(0, children):
color = XoColor()
- icon = CanvasIcon(scale=1.0, xo_color=color,
+ icon = CanvasIcon(scale=0.5, xo_color=color,
icon_name='theme:stock-buddy')
parent.append(icon, hippo.PACK_FIXED)
diff --git a/tests/test-theme.py b/tests/test-theme.py
deleted file mode 100755
index eae835f..0000000
--- a/tests/test-theme.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2006, Red Hat, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-import pygtk
-pygtk.require('2.0')
-
-import gtk
-
-# Main window
-window = gtk.Window()
-window.connect("destroy", lambda w: gtk.main_quit())
-#window.set_border_width(10)
-
-# Main VBox
-
-main_vbox = gtk.VBox(homogeneous=False, spacing=0)
-window.add(main_vbox)
-
-############################### ##############################
-############################### Menus ##############################
-############################### ##############################
-
-menu = gtk.Menu()
-file_menu = gtk.Menu() # Don't need to show menus
-edit_menu = gtk.Menu()
-
-# Create the menu items
-dummy_item_1 = gtk.MenuItem("Dummy Item 1")
-dummy_item_2 = gtk.MenuItem("Dummy Item 2")
-quit_item = gtk.MenuItem("Quit")
-dummy_item_3 = gtk.MenuItem("Dummy Item 3")
-dummy_item_4 = gtk.MenuItem("Dummy Item 4")
-dummy_item_5 = gtk.MenuItem("Dummy Item 5")
-
-# Add them to the menu
-file_menu.append(dummy_item_1)
-file_menu.append(dummy_item_2)
-file_menu.append(quit_item)
-
-edit_menu.append(dummy_item_3)
-edit_menu.append(dummy_item_4)
-edit_menu.append(dummy_item_5)
-
-# We can attach the Quit menu item to our exit function
-quit_item.connect_object ("activate", lambda w: gtk.main_quit (), "file.quit")
-
-# We do need to show menu items
-dummy_item_1.show()
-dummy_item_2.show()
-quit_item.show()
-dummy_item_3.show()
-dummy_item_4.show()
-dummy_item_5.show()
-
-# Pack the menu into the menubar
-menu_bar = gtk.MenuBar()
-main_vbox.pack_start(menu_bar, False, False, 0)
-menu_bar.show()
-
-file_item = gtk.MenuItem("File")
-file_item.show()
-menu_bar.append(file_item)
-file_item.set_submenu(file_menu)
-
-edit_item = gtk.MenuItem("Edit")
-edit_item.show()
-menu_bar.append(edit_item)
-edit_item.set_submenu(edit_menu)
-
-
-# Scrolled window
-scrolled_window = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
-#scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
-scrolled_window.set_border_width(10)
-main_vbox.pack_start(scrolled_window, True, True, 0)
-
-# Vbox inside the scrolled window
-vbox = gtk.VBox(homogeneous=False, spacing=10)
-scrolled_window.add_with_viewport(vbox)
-vbox.set_border_width (10)
-
-# Label
-label = gtk.Label("This is a label")
-vbox.pack_start(label, False, False, 0)
-
-# Entry
-entry = gtk.Entry ()
-entry.set_text("Type some text here")
-vbox.pack_start(entry, False, False, 0)
-
-# Buttons
-buttons_hbox = gtk.HBox(homogeneous=False, spacing=5)
-vbox.pack_start(buttons_hbox, False, False, 0)
-
-button_1 = gtk.Button ("Button 1")
-buttons_hbox.pack_start(button_1, False, False, 0)
-
-button_2 = gtk.Button ("Button 2")
-buttons_hbox.pack_start(button_2, False, False, 0)
-
-button_3 = gtk.Button ("Button 3")
-buttons_hbox.pack_start(button_3, False, False, 0)
-
-window.show_all()
-
-gtk.main()
diff --git a/tests/test-ui.py b/tests/test-ui.py
new file mode 100755
index 0000000..26f3d12
--- /dev/null
+++ b/tests/test-ui.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+
+from sugar.graphics2.window import Window
+
+class ActivityToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+class EditToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+class TextToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ button = gtk.ToolButton()
+ button.set_icon_name('text-format-bold')
+ self.insert(button, -1)
+ button.show()
+
+class ImageToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+class TableToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+class FormatToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+class ViewToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+window = Window()
+window.connect("destroy", lambda w: gtk.main_quit())
+
+activity_toolbar = ActivityToolbar()
+window.toolbox.add_toolbar('Activity', activity_toolbar)
+activity_toolbar.show()
+
+edit_toolbar = EditToolbar()
+window.toolbox.add_toolbar('Edit', edit_toolbar)
+edit_toolbar.show()
+
+text_toolbar = TextToolbar()
+window.toolbox.add_toolbar('Text', text_toolbar)
+text_toolbar.show()
+
+image_toolbar = ImageToolbar()
+window.toolbox.add_toolbar('Image', image_toolbar)
+image_toolbar.show()
+
+table_toolbar = TableToolbar()
+window.toolbox.add_toolbar('Table', table_toolbar)
+table_toolbar.show()
+
+format_toolbar = FormatToolbar()
+window.toolbox.add_toolbar('Format', format_toolbar)
+format_toolbar.show()
+
+view_toolbar = ViewToolbar()
+window.toolbox.add_toolbar('View', view_toolbar)
+view_toolbar.show()
+
+scrolled_window = gtk.ScrolledWindow()
+scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+window.set_canvas(scrolled_window)
+scrolled_window.show()
+
+text_view = gtk.TextView()
+scrolled_window.add(text_view)
+text_view.show()
+
+window.show()
+
+gtk.main()
diff --git a/tests/test-window-manager.py b/tests/test-window-manager.py
deleted file mode 100644
index 539979d..0000000
--- a/tests/test-window-manager.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2006, Red Hat, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-import pygtk
-pygtk.require('2.0')
-
-from sugar.session.UITestSession import UITestSession
-
-session = UITestSession()
-session.start()
-
-import gtk
-
-def _show_dialog(window):
- dialog = gtk.Dialog(title='No Unviewed Media',
- parent=window, flags=gtk.DIALOG_MODAL,
- buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
- label = gtk.Label('There is no unviewed media to download.')
- dialog.vbox.pack_start(label, True, True, 0)
- label.show()
- response = dialog.run()
- dialog.hide()
- del dialog
-
-window = gtk.Window()
-window.show()
-
-_show_dialog(window)
-
-gtk.main()