Merge branch 'master' of git+ssh://mcfletch@dev.laptop.org/git/sugar

This commit is contained in:
Mike C. Fletcher 2007-04-21 14:05:08 -04:00
commit df4919de2f
73 changed files with 2200 additions and 1149 deletions

1
.gitignore vendored
View File

@ -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

View File

@ -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 = \

View File

@ -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

View 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

View 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__

View 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

View 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__

View 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(&timestamp, NULL);
mFilePath.Append(g_get_tmp_dir());
mFilePath.Append("/");
mFilePath.AppendInt(timestamp.tv_sec);
mFilePath.AppendInt(timestamp.tv_usec);
image_name = mDocumentObject->GetImageName();
file_ext = strrchr(image_name, '.');
mime_type = mDocumentObject->GetImageMimeType();
nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1",
&rv));
NS_ENSURE_SUCCESS(rv, rv);
rv = mimeService->GetPrimaryExtension(nsCString(mime_type),
EmptyCString(),
mime_ext);
NS_ENSURE_SUCCESS(rv, rv);
if(!file_ext) {
mFilePath.Append(image_name);
mFilePath.Append(".");
mFilePath.Append(mime_ext);
} else if(strcmp(file_ext, mime_ext.get())) {
image_name[strlen(file_ext)] = 0;
mFilePath.Append(image_name);
mFilePath.Append(".");
mFilePath.Append(mime_ext);
} else {
mFilePath.Append(image_name);
}
g_free(mime_type);
g_free(image_name);
if(!mDocumentObject->SaveImage(mFilePath.get())) {
return NS_ERROR_FAILURE;
}
nsCString localURI(FILE_LOCALHOST);
localURI.Append(mFilePath);
nsString localURI16;
NS_CStringToUTF16(localURI, NS_CSTRING_ENCODING_UTF8, localURI16);
nsCOMPtr<nsISupportsString> localURIData(do_CreateInstance(
"@mozilla.org/supports-string;1", &rv));
if(NS_FAILED(rv)) return rv;
rv = localURIData->SetData(localURI16);
if(NS_FAILED(rv)) return rv;
CallQueryInterface(localURIData, aData);
*aDataLen = sizeof(nsISupportsString*);
// FIXME: Why do we need this? Is there a leak in mozilla?
this->Release();
return rv;
}
//*****************************************************************************
// GeckoDragDropHooks
//*****************************************************************************
NS_IMPL_ISUPPORTS1(GeckoDragDropHooks, nsIClipboardDragDropHooks)
GeckoDragDropHooks::GeckoDragDropHooks(SugarBrowser *browser)
: mBrowser(browser)
{
}
GeckoDragDropHooks::~GeckoDragDropHooks()
{
}
NS_IMETHODIMP
GeckoDragDropHooks::AllowStartDrag(nsIDOMEvent *event, PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
GeckoDragDropHooks::AllowDrop(nsIDOMEvent *event, nsIDragSession *session,
PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
GeckoDragDropHooks::OnCopyOrDrag(nsIDOMEvent *aEvent, nsITransferable *trans,
PRBool *_retval)
{
nsresult rv;
*_retval = true;
nsCOMPtr<nsIDOMMouseEvent> mouseEvent;
mouseEvent = do_QueryInterface(aEvent, &rv);
if(NS_FAILED(rv)) return rv;
nsCOMPtr<nsIDOMEventTarget> eventTarget;
rv = mouseEvent->GetTarget(getter_AddRefs(eventTarget));
if(NS_FAILED(rv)) return rv;
nsCOMPtr<nsIDOMNode> targetNode;
targetNode = do_QueryInterface(eventTarget, &rv);
if(NS_FAILED(rv)) return rv;
GeckoDocumentObject *documentObject = new GeckoDocumentObject(mBrowser,
targetNode);
if(documentObject->IsImage()) {
rv = trans->RemoveDataFlavor(TEXT_X_MOZ_URL);
if(NS_FAILED(rv)) return rv;
rv = trans->AddDataFlavor(TEXT_URI_LIST);
if(NS_FAILED(rv)) return rv;
UriListDataProvider *rawPtr = new UriListDataProvider(documentObject);
nsCOMPtr<nsISupports> dataProvider(do_QueryInterface(rawPtr, &rv));
if(NS_FAILED(rv)) return rv;
rv = trans->SetTransferData(TEXT_URI_LIST, dataProvider, 0);
if(NS_FAILED(rv)) return rv;
}
return rv;
}
NS_IMETHODIMP
GeckoDragDropHooks::OnPasteOrDrop(nsIDOMEvent *event, nsITransferable *trans,
PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
#endif

View 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__

View File

@ -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 \

View File

@ -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

View File

@ -1,6 +1,7 @@
sugardir = $(pkgdatadir)
sugardir = $(pkgdatadir)/data
sugar_DATA = \
gtkrc \
sugar.gtkrc \
sugar-xo.gtkrc \
gecko-prefs.js \
mime.types

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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...')

View File

@ -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
"""

View File

@ -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"]

View File

@ -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():

View File

@ -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()

View File

@ -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])

View File

@ -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)

View File

@ -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:

View File

@ -4,7 +4,6 @@ bin_SCRIPTS = sugar-shell
sugardir = $(pkgdatadir)/shell
sugar_PYTHON = \
__init__.py \
shellservice.py
confdir = $(pkgdatadir)/shell

View File

@ -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)\

View File

@ -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 *

View File

@ -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");

View File

@ -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);
}

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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.'

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -7,4 +7,5 @@ sugar_PYTHON = \
HomeBox.py \
HomeWindow.py \
MeshBox.py \
MyIcon.py
MyIcon.py \
transitionbox.py

View File

@ -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

View 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

View File

@ -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)

View File

@ -6,5 +6,6 @@ sugar_PYTHON = \
date.py \
env.py \
logger.py \
ltihooks.py \
profile.py \
util.py

View File

@ -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)

View File

@ -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 *

View File

@ -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)

View File

@ -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()

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,5 @@
sugardir = $(pythondir)/sugar/graphics2
sugar_PYTHON = \
__init__.py \
window.py \
toolbox.py

View File

View 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
View 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
View 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()

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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
View 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()

View File

@ -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()