Merge branch 'master' of git+ssh://mcfletch@dev.laptop.org/git/sugar
This commit is contained in:
commit
df4919de2f
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
|
||||
|
@ -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 = \
|
||||
|
@ -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
|
||||
|
190
browser/GeckoBrowserPersist.cpp
Normal file
190
browser/GeckoBrowserPersist.cpp
Normal file
@ -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
|
19
browser/GeckoBrowserPersist.h
Normal file
19
browser/GeckoBrowserPersist.h
Normal file
@ -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__
|
241
browser/GeckoDocumentObject.cpp
Normal file
241
browser/GeckoDocumentObject.cpp
Normal file
@ -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
|
31
browser/GeckoDocumentObject.h
Normal file
31
browser/GeckoDocumentObject.h
Normal file
@ -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__
|
218
browser/GeckoDragDropHooks.cpp
Normal file
218
browser/GeckoDragDropHooks.cpp
Normal file
@ -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(×tamp, 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
|
25
browser/GeckoDragDropHooks.h
Normal file
25
browser/GeckoDragDropHooks.h
Normal file
@ -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__
|
@ -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 \
|
||||
|
@ -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)) {
|
||||
@ -210,25 +227,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)
|
||||
{
|
||||
@ -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);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(browser_class);
|
||||
|
||||
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,32 +570,10 @@ 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)
|
||||
{
|
||||
#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
|
||||
|
@ -1,6 +1,7 @@
|
||||
sugardir = $(pkgdatadir)
|
||||
sugardir = $(pkgdatadir)/data
|
||||
sugar_DATA = \
|
||||
gtkrc \
|
||||
sugar.gtkrc \
|
||||
sugar-xo.gtkrc \
|
||||
gecko-prefs.js \
|
||||
mime.types
|
||||
|
||||
|
@ -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
|
@ -1,2 +1,3 @@
|
||||
application/x-squeak-project pr
|
||||
application/x-abiword abw
|
||||
application/vnd.olpc-x-sugar xo
|
||||
|
7
data/sugar-xo.gtkrc
Normal file
7
data/sugar-xo.gtkrc
Normal file
@ -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
|
5
data/sugar.gtkrc
Normal file
5
data/sugar.gtkrc
Normal file
@ -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
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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...')
|
||||
|
@ -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
|
||||
"""
|
@ -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"]
|
||||
|
@ -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():
|
||||
|
@ -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
|
||||
@ -122,6 +141,9 @@ class PresenceService(dbus.service.Object):
|
||||
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()
|
||||
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()
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
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,6 +676,7 @@ 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
|
||||
@ -550,6 +685,7 @@ class ServerPlugin(gobject.GObject):
|
||||
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)
|
||||
|
@ -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:
|
||||
|
@ -4,7 +4,6 @@ bin_SCRIPTS = sugar-shell
|
||||
|
||||
sugardir = $(pkgdatadir)/shell
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
shellservice.py
|
||||
|
||||
confdir = $(pkgdatadir)/shell
|
||||
|
@ -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 = \
|
||||
_extensions_la_CFLAGS = \
|
||||
$(WARN_CFLAGS) \
|
||||
$(PYTHON_INCLUDES) \
|
||||
$(PYGTK_CFLAGS) \
|
||||
$(SHELL_CFLAGS) \
|
||||
$(top_srcdir)/shell/extensions
|
||||
$(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)\
|
||||
|
@ -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 *
|
||||
|
@ -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");
|
@ -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);
|
||||
}
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,15 +117,25 @@ 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
|
||||
|
||||
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
|
||||
|
||||
# FIXME: would be better to check for format.onDisk
|
||||
try:
|
||||
@ -124,8 +144,10 @@ class ClipboardIcon(CanvasIcon):
|
||||
path_exists = False
|
||||
|
||||
if path_exists:
|
||||
uri = 'file://' + path
|
||||
if self._activity:
|
||||
activityfactory.create_with_uri(self._activity, uri)
|
||||
else:
|
||||
self._install_xo(path)
|
||||
else:
|
||||
logging.debug("Clipboard item file path %s didn't exist" % path)
|
||||
|
||||
@ -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.'
|
||||
|
@ -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)
|
||||
|
@ -1,4 +1,8 @@
|
||||
import shutil
|
||||
import os
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
import hippo
|
||||
import gtk
|
||||
|
||||
@ -72,9 +76,25 @@ 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()
|
||||
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,
|
||||
@ -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)
|
||||
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.finish(True, False, time)
|
||||
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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
_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()
|
||||
self._friends_box = FriendsBox(shell, MenuShell(self))
|
||||
self._mesh_box = MeshBox(shell, MenuShell(self))
|
||||
self._transition_box = TransitionBox()
|
||||
|
||||
canvas = hippo.Canvas()
|
||||
box = FriendsBox(shell, MenuShell(canvas))
|
||||
canvas.set_root(box)
|
||||
self._nb.append_page(canvas)
|
||||
canvas.show()
|
||||
self.set_root(self._home_box)
|
||||
|
||||
canvas = hippo.Canvas()
|
||||
self._mesh_box = MeshBox(shell, MenuShell(canvas))
|
||||
canvas.set_root(self._mesh_box)
|
||||
self._nb.append_page(canvas)
|
||||
canvas.show()
|
||||
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()
|
||||
|
||||
|
@ -7,4 +7,5 @@ sugar_PYTHON = \
|
||||
HomeBox.py \
|
||||
HomeWindow.py \
|
||||
MeshBox.py \
|
||||
MyIcon.py
|
||||
MyIcon.py \
|
||||
transitionbox.py
|
||||
|
@ -278,6 +278,9 @@ class MeshBox(SpreadBox):
|
||||
|
||||
def _add_alone_buddy(self, buddy_model):
|
||||
icon = BuddyIcon(self._shell, self._menu_shell, buddy_model)
|
||||
if buddy_model.is_owner():
|
||||
self.set_center_item(icon)
|
||||
else:
|
||||
self.add_item(icon)
|
||||
|
||||
self._buddies[buddy_model.get_key()] = icon
|
||||
|
66
shell/view/home/transitionbox.py
Normal file
66
shell/view/home/transitionbox.py
Normal file
@ -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
|
||||
|
@ -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)
|
||||
|
||||
def _start_matchbox():
|
||||
cmd = ['matchbox-window-manager']
|
||||
|
||||
cmd.extend(['-use_titlebar', 'no'])
|
||||
cmd.extend(['-theme', 'olpc'])
|
||||
|
||||
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:
|
||||
gtkrc_filename = 'sugar-xo.gtkrc'
|
||||
|
||||
os.environ['GTK2_RC_FILES'] = env.get_data_path(gtkrc_filename)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
program = 'sugar-shell'
|
||||
else:
|
||||
_start_matchbox()
|
||||
program = sys.argv[1]
|
||||
|
||||
if gtk.gdk.screen_width() < 1200 or gtk.gdk.screen_height() < 900:
|
||||
width = -1
|
||||
height = -1
|
||||
else:
|
||||
width = 1200
|
||||
height = 900
|
||||
|
||||
_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')
|
||||
|
||||
os.execlp('dbus-launch', 'dbus-launch', '--exit-with-session', program)
|
||||
|
@ -6,5 +6,6 @@ sugar_PYTHON = \
|
||||
date.py \
|
||||
env.py \
|
||||
logger.py \
|
||||
ltihooks.py \
|
||||
profile.py \
|
||||
util.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)
|
||||
|
||||
|
@ -3,4 +3,9 @@
|
||||
XUL Runner and gtkmozembed and is produced by the PyGTK
|
||||
.defs system.
|
||||
"""
|
||||
|
||||
try:
|
||||
from sugar.browser._sugarbrowser import *
|
||||
except ImportError:
|
||||
from sugar import ltihooks
|
||||
from sugar.browser._sugarbrowser import *
|
||||
|
23
sugar/env.py
23
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)
|
||||
|
@ -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()
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
5
sugar/graphics2/Makefile.am
Normal file
5
sugar/graphics2/Makefile.am
Normal file
@ -0,0 +1,5 @@
|
||||
sugardir = $(pythondir)/sugar/graphics2
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
window.py \
|
||||
toolbox.py
|
0
sugar/graphics2/__init__.py
Normal file
0
sugar/graphics2/__init__.py
Normal file
32
sugar/graphics2/toolbox.py
Normal file
32
sugar/graphics2/toolbox.py
Normal file
@ -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))
|
49
sugar/graphics2/window.py
Normal file
49
sugar/graphics2/window.py
Normal file
@ -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
|
72
sugar/ltihooks.py
Normal file
72
sugar/ltihooks.py
Normal file
@ -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()
|
@ -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
|
||||
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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)
|
||||
|
||||
|
@ -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()
|
98
tests/test-ui.py
Executable file
98
tests/test-ui.py
Executable file
@ -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()
|
@ -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()
|
Loading…
Reference in New Issue
Block a user