Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Hunt <ghunt@ghunt-desktop.(none)>2010-05-14 14:54:09 (GMT)
committer George Hunt <ghunt@ghunt-desktop.(none)>2010-05-14 14:54:09 (GMT)
commit580526e849afb84244b7a0373d8fdb8f5ffe0103 (patch)
treee118e3adb44be31b21d2347ad5658b2db18a5532
initial upload, working: sqlite, datastore access, thumbnails, mouse clicks. Not implemented: drag/drop, add albums, create camera rolls, help, etc
-rw-r--r--MANIFEST32
-rw-r--r--activity/XoPhoto.svg111
-rw-r--r--activity/activity.info6
-rw-r--r--activity/activity.info.gh8
-rw-r--r--activity/activity.save0
-rw-r--r--activity/scommander.svg88
-rw-r--r--dbphoto.py277
-rw-r--r--display.py486
-rw-r--r--dist/XoPhoto-1.xobin0 -> 694591 bytes
-rw-r--r--photo_toolbar.py216
-rwxr-xr-xsetup.py21
-rw-r--r--sources.py181
-rw-r--r--sugargame/__init__.py1
-rw-r--r--sugargame/canvas.py56
-rw-r--r--sugargame/event.py241
-rw-r--r--xophoto.sqlitebin0 -> 958464 bytes
-rw-r--r--xophotoactivity.py289
17 files changed, 2013 insertions, 0 deletions
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..d4efdb4
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,32 @@
+xophoto.sqlite
+dbphoto.py
+xophoto
+image_datastore.py
+dbaccess.py
+TestGame.py
+TestActivity.py
+test_blob.py
+dsget.py
+sugarcommander.py
+display.py
+DemoiselleActivity.py
+xotest.py
+dbphoto.pyo
+sources.py
+sources.pyo
+xophotoactivity.py
+xophotoactivity.pyo
+display.pyo
+setup.py
+po/SugarCommander.pot
+activity/scommander.svg
+activity/activity.info
+activity/XoPhoto.svg
+activity/activity.save
+activity/activity.info.gh
+sugargame/canvas.py
+sugargame/canvas.pyo
+sugargame/__init__.pyo
+sugargame/event.pyo
+sugargame/event.py
+sugargame/__init__.py
diff --git a/activity/XoPhoto.svg b/activity/XoPhoto.svg
new file mode 100644
index 0000000..8a1ced3
--- /dev/null
+++ b/activity/XoPhoto.svg
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#ffffff">
+]>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="55"
+ height="55"
+ id="svg4502"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ version="1.0"
+ sodipodi:docbase="/Users/eben/Desktop"
+ sodipodi:docname="circle.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ style="display:inline">
+ <defs
+ id="defs4504">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 27.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="55 : 27.5 : 1"
+ inkscape:persp3d-origin="27.5 : 18.333333 : 1"
+ id="perspective2408" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.959798"
+ inkscape:cx="30.611899"
+ inkscape:cy="12.891623"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer4"
+ width="55px"
+ height="55px"
+ inkscape:window-width="701"
+ inkscape:window-height="722"
+ inkscape:window-x="285"
+ inkscape:window-y="68"
+ showgrid="false">
+ <inkscape:grid
+ id="GridFromPre046Settings"
+ type="xygrid"
+ originx="0px"
+ originy="0px"
+ spacingx="15px"
+ spacingy="15px"
+ color="#0000ff"
+ empcolor="#0000ff"
+ opacity="0.2"
+ empopacity="0.4"
+ empspacing="5" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata4507">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer4"
+ inkscape:label="icon"
+ style="display:inline">
+ <path
+ sodipodi:type="arc"
+ style="opacity:1;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:2.43559003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2165"
+ sodipodi:cx="29.546961"
+ sodipodi:cy="27.725882"
+ sodipodi:rx="15.657364"
+ sodipodi:ry="15.657364"
+ d="M 45.204325,27.725882 A 15.657364,15.657364 0 1 1 13.889597,27.725882 A 15.657364,15.657364 0 1 1 45.204325,27.725882 z"
+ transform="matrix(1.4370235,0,0,1.4370235,-14.959677,-12.342744)" />
+ <text
+ xml:space="preserve"
+ style="font-size:19.38258234px;font-style:normal;font-weight:bold;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;line-height:125%"
+ x="12.776501"
+ y="30.940331"
+ id="text2432"
+ transform="matrix(0.9107558,8.8314569e-3,-9.9819452e-3,1.0978923,0,0)"
+ inkscape:transform-center-x="6.6723495"
+ inkscape:transform-center-y="11.705454"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan2434"
+ x="12.776501"
+ y="30.940331">XP</tspan></text>
+ </g>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..1426aee
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,6 @@
+[Activity]
+name = XoPhoto
+bundle_id = org.laptop.XoPhoto
+activity_version = 1
+icon = XoPhoto
+class = xophotoactivity.XoPhotoActivity
diff --git a/activity/activity.info.gh b/activity/activity.info.gh
new file mode 100644
index 0000000..815e9c8
--- /dev/null
+++ b/activity/activity.info.gh
@@ -0,0 +1,8 @@
+[Activity]
+name = Sugar Commander
+service_name = org.laptop.sugar.SugarCommander
+icon = scommander
+exec = sugar-activity sugarcommander.SugarCommander
+show_launcher = yes
+activity_version = 3
+license = GPLv2+
diff --git a/activity/activity.save b/activity/activity.save
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/activity/activity.save
diff --git a/activity/scommander.svg b/activity/scommander.svg
new file mode 100644
index 0000000..67fc3b8
--- /dev/null
+++ b/activity/scommander.svg
@@ -0,0 +1,88 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#ffffff">
+]>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ enable-background="new 0 0 55 55"
+ height="55px"
+ id="Layer_1"
+ version="1.1"
+ viewBox="0 0 55 55"
+ width="55px"
+ x="0px"
+ xml:space="preserve"
+ y="0px"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="scommander.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata2457"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs2455"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 27.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="55 : 27.5 : 1"
+ inkscape:persp3d-origin="27.5 : 18.333333 : 1"
+ id="perspective2459" /></defs><sodipodi:namedview
+ inkscape:window-height="816"
+ inkscape:window-width="1152"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="&fill_color;"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.313708"
+ inkscape:cx="27.5"
+ inkscape:cy="27.5"
+ inkscape:window-x="-4"
+ inkscape:window-y="-4"
+ inkscape:current-layer="Layer_1" /><g
+ display="block"
+ id="activity-journal"
+ transform="translate(0.1767767,-0.7071068)"
+ style="display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 L 12.077,49 L 12.077,6 L 41.535,6 C 43.685,6 45.867,8.154 45.867,10.33 L 45.866,44.669 L 45.866,44.669 z"
+ id="path2444"
+ style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+
+ <line
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2446"
+ style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="M 7.384,14.464 C 7.384,14.464 9.468,15.159 11.554,15.159 C 13.64,15.159 15.727,14.464 15.727,14.464"
+ id="path2448"
+ style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="M 7.384,28.021 C 7.384,28.021 9.296,28.716 11.729,28.716 C 14.162,28.716 15.728,28.021 15.728,28.021"
+ id="path2450"
+ style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="M 7.384,41.232 C 7.384,41.232 9.12,41.927 11.902,41.927 C 14.683,41.927 15.727,41.232 15.727,41.232"
+ id="path2452"
+ style="fill:none;stroke:&stroke_color;;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+</g><path
+ style="fill:none;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 26.958446,28.660272 C 31.819805,23.091806 31.819805,23.091806 31.819805,23.091806 C 36.504388,28.129942 36.504388,28.129942 36.504388,28.129942"
+ id="path2469" /><path
+ style="fill:none;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 26.870058,32.726136 C 31.643028,26.715729 31.643028,26.715729 31.643028,26.715729 C 35.974058,32.195806 35.974058,32.195806 35.974058,32.195806"
+ id="path2471" /></svg>
diff --git a/dbphoto.py b/dbphoto.py
new file mode 100644
index 0000000..1fe110d
--- /dev/null
+++ b/dbphoto.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python
+# dbphoto.py
+# The sqlite database access functions for the XoPhoto application
+#
+#
+# Copyright (C) 2010 George Hunt
+#
+# 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
+#
+from gettext import gettext as _
+
+import os
+from sqlite3 import dbapi2 as sqlite
+from sqlite3 import *
+import sqlite3
+import hashlib
+
+#pick up activity globals
+from xophotoactivity import *
+
+#define globals related to sqlite
+sqlite_file_path = None
+
+import logging
+_logger = logging.getLogger('xophoto')
+_logger.setLevel(logging.DEBUG)
+console_handler = logging.StreamHandler()
+console_formatter = logging.Formatter('%(name)s %(levelname)s %(funcName)s: %(lineno)d||| %(message)s')
+console_handler.setFormatter(console_formatter)
+_logger.addHandler(console_handler)
+
+
+class DbAccess():
+ con = None
+ cursor = None
+ def __init__(self,fn):
+ self.opendb(fn)
+ self.added = 0
+
+ def opendb(self,dbfilename):
+ try:
+ _logger.debug('opening database cwd:%s filename %s'%(os.getcwd(),dbfilename,))
+ self.con = sqlite3.connect(dbfilename)
+ self.con.row_factory = sqlite3.Row
+ self.con.text_factory = str
+ #rows generated thusly will have columns that are addressable as dict of fieldnames
+ self.cur = self.con.cursor()
+ except IOError,e:
+ _logger.debug('open database failed. exception :%s '%(e,))
+ return None
+ return self.cur
+
+ def is_open(self):
+ if self.con: return True
+ return False
+
+ def closedb(self):
+ if self.con:self.con.close()
+
+ def get_mime_list(self):
+ mime_list =[]
+ self.cur.execute('select * from config where name ="mime_type"')
+ rows = self.cur.fetchall()
+ for m in rows:
+ mime_list.append(m[2])
+ return mime_list
+
+ def get_album_list(self):
+ sql = 'select max duplicate from picture group by album'
+ album_list,cur = self.dbdo(sql)
+ if len(album_list) == 0:
+ _logger.debug('failed to retrieve albums')
+ return None
+ return album_list
+
+ def create_picture_record(self,object_id, fn):
+ """create a record in picture pointing to unique pictures in the journal.
+ Use md5 checksum to test for uniqueness
+ For non unique entries, add a copy number (fieldname:duplicate) greater than 0
+ """
+ _logger.debug('create_picture_record object_id:%s file: %s'%(object_id,fn,))
+ #if object_id == '': return
+
+ #we'll calculate the md5, check it against any pictures, and store it away
+ md5_hash = Md5Tools().md5sum(fn)
+ sql = "select * from picture where md5_sum = '%s'"%(md5_hash,)
+ self.cur.execute(sql)
+ rows_md5 = self.cur.fetchall()
+ if len(rows_md5) >0:
+ pass
+ #_logger.debug('duplicate picture, ojbect_id %s path: %s'%(object_id,fn,))
+ sql = "select * from picture where jobject_id = '%s'"%(object_id,)
+ self.cur.execute(sql)
+ rows = self.cur.fetchall()
+ #_logger.debug('rowcount %s object_id %s'%(len(rows),object_id))
+ #the object_id is supposed to be unique, so add only new object_id's
+ if len(rows) == 0:
+ info = os.stat(fn)
+ sql = """insert into picture \
+ (in_ds, mount_point, orig_size, create_date,jobject_id, md5_sum, duplicate) \
+ values (%s,'%s',%s,'%s','%s','%s',%s)""" % \
+ (1, fn, info.st_size, info.st_ctime, object_id, md5_hash,len(rows_md5),)
+ _logger.debug('sql: %s'%sql)
+ self.con.execute(sql)
+
+ def clear_in_ds(self):
+ self.con.execute('update picture set in_ds = 0')
+
+ def delete_not_in_ds(self):
+ self.con.execute('delete from picture where in_ds = 0')
+
+ def check_in_ds(self,fullpath,size):
+ sql = "select * from picture where mount_point = '%s' and orig_size = %s"%(fullpath,size,)
+ self.cur.execute(sql)
+ rows = self.cur.fetchall()
+ if len(rows)>0: return True
+ return False
+
+ def table_exists(self,table):
+ try:
+ sql = 'select * from %s'%table
+ self.con.execute(sql)
+ return True
+ except:
+ return False
+
+ def commit(self):
+ if self.con:self.con.commit()
+
+ def set_connection(self,connection,cursor):
+ self.con = connection
+ self.cur = cursor
+
+ def get_connection(self):
+ """ return connection """
+ return self.con
+
+ def numberofrows(self,table):
+ sql = "SELECT count(*) from %s"%table
+ rows,cur = self.dbdo(sql)
+ if rows:
+ return rows[0][0]
+ return 0
+
+ def fieldlist(self,table):
+ list=[] #accumulator for model
+ cur = self.con.cursor()
+ cur.execute('select * from %s'%table)
+ if cur:
+ for field in cur.description:
+ list.append(field[0])
+ return list
+
+ def row_index(self,field,table):
+ field_list = self.fieldlist(table)
+ return field_list.index(field)
+
+ def tablelist(self):
+ list=[] #accumulator for
+ sql = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
+ rows,cur = self.dbdo(sql)
+ if rows:
+ for row in rows:
+ list.append(row[0])
+ return list
+
+ def dbdo(self,sql):
+ """ execute a sql statement or definition, return rows and cursor """
+ try:
+ cur = self.con.cursor()
+ cur.execute(sql)
+ return cur.fetchall(), cur
+ #self.con.commit()
+ except sqlite.Error, e:
+ print 'An sqlite error:',e.args[0]
+ print sql+'\n'
+ return None,None
+
+ def dbtry(self,sql):
+ """ execute a sql statement return true if no error"""
+ try:
+ self.cur.execute(sql)
+ return True,None
+ except sqlite.Error, e:
+ print sql+'\n'
+ return False,e
+"""
+class osfsys():
+
+ def havewriteaccess(self,writefile):
+ try:
+ fh=open(writefile,'w+')
+ fh.close()
+ return True
+ except:
+ return False
+
+ def havereadaccess(self,readfile):
+ try:
+ fh=open(readfile,'r')
+ fh.close()
+ return True
+ except:
+ return False
+"""
+class Md5Tools():
+ def md5sum_buffer(self, buffer, hash = None):
+ if hash == None:
+ hash = hashlib.md5()
+ hash.update(buffer)
+ return hash.hexdigest()
+
+ def md5sum(self, filename, hash = None):
+ h = self._md5sum(filename,hash)
+ return h.hexdigest()
+
+ def _md5sum(self, filename, hash = None):
+ if hash == None:
+ hash = hashlib.md5()
+ try:
+ fd = None
+ fd = open(filename, 'rb')
+ while True:
+ block = fd.read(128)
+ if not block: break
+ hash.update(block)
+ finally:
+ if fd != None:
+ fd.close()
+ return hash
+
+ def md5sum_tree(self,root):
+ if not os.path.isdir(root):
+ return None
+ h = hashlib.md5()
+ for dirpath, dirnames, filenames in os.walk(root):
+ for filename in filenames:
+ abs_path = os.path.join(dirpath, filename)
+ h = self._md5sum(abs_path,h)
+ #print abs_path
+ return h.hexdigest()
+
+ def set_permissions(self,root, perms='664'):
+ if not os.path.isdir(root):
+ return None
+ for dirpath, dirnames, filenames in os.walk(root):
+ for filename in filenames:
+ abs_path = os.path.join(dirpath, filename)
+ old_perms = os.stat(abs_path).st_mode
+ if os.path.isdir(abs_path):
+ new_perms = int(perms,8) | int('771',8)
+ else:
+ new_perms = old_perms | int(perms,8)
+ os.chmod(abs_path,new_perms)
+
+if __name__ == '__main__':
+ db = DbAccess('xophoto.sqlite')
+ rows,cur = db.dbdo('select * from picture')
+ for row in rows:
+ print row['jobject_id']
+ print('index of jobject_id: %s'%db.row_index('duplicate','picture'))
+ print('number of records %s'%db.numberofrows('picture'))
+ print('fields %r'%db.fieldlist('picture'))
+ print ('tables %r'%db.tablelist())
+ \ No newline at end of file
diff --git a/display.py b/display.py
new file mode 100644
index 0000000..3bac79d
--- /dev/null
+++ b/display.py
@@ -0,0 +1,486 @@
+#!/usr/bin/env python
+# display.py
+#
+# Copyright (C) 2010 George Hunt
+#
+# 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
+#
+from gettext import gettext as _
+
+import pygame
+from pygame.locals import *
+
+from sugar.datastore import datastore
+import sys, os
+import gtk
+import shutil
+import sqlite3
+from sqlite3 import dbapi2 as sqlite
+import math
+import hashlib
+import time
+from threading import Timer
+
+#application imports
+from dbphoto import *
+from sources import *
+
+
+#pick up activity globals
+from xophotoactivity import *
+
+#Display Module globals
+background_color = (255,255,255)
+selected_color = (0,0,255)
+mouse_timer = time.time()
+in_click_delay = False
+in_drag = False
+
+import logging
+_logger = logging.getLogger('xophoto.display')
+
+_logger.setLevel(logging.DEBUG)
+console_handler = logging.StreamHandler()
+console_formatter = logging.Formatter('%(name)s %(levelname)s %(funcName)s: %(lineno)d||| %(message)s')
+console_handler.setFormatter(console_formatter)
+_logger.addHandler(console_handler)
+
+class PhotoException(Exception):
+ def __init__(self,value):
+ self.value = value
+ def __str__():
+ return repr(self.value)
+
+class DisplayOne():
+ def __init__(self,rows,db,index=0):
+ self.rows = rows
+ self.db = db
+ self.row_index = index
+ self.border = 5
+ self.x = 200
+ self.y = 200
+ self.size_x = 800
+ self.size_y = 800
+ self.surf = None
+ self.background = (200,200,200)
+ self.scaled = None
+
+ def paint(self):
+ """
+ Put an image on pygame screen.
+ Inputs: 1. cursor pointing to picture records of xophoto.sqlite
+ 2. Index into cursor
+ """
+ if not self.scaled:
+ i = self.db.row_index('jobject_id','picture')
+ id = self.rows[self.row_index][i]
+ self.surface = pygame.Surface((self.size_x,self.size_y,))
+ self.surface.fill(background_color)
+ self.scaled = self.scale_image(id,self.size_x,self.size_y)
+ if not self.scaled: return
+ if self.aspect >= 1.0:
+ self.subsurface_x = self.border
+ self.subsurface_y = (self.size_y - self.y_thumb) // 2
+ else:
+ self.subsurface_y = self.border
+ self.subsurface_x = (self.size_x - self.x_thumb) // 2
+ self.thumbnail = self.surface.subsurface([self.subsurface_x,self.subsurface_y,self.x_thumb,self.y_thumb])
+ self.thumbnail.blit(self.scaled,[0,0])
+ screen.blit(self.surface,[self.x,self.y])
+
+ def scale_image(self,id, x_size, y_size):
+ """
+ First check to see if this thumbnail is already in the database, if so, return it
+ If not, generate the thumbnail, write it to the database, and return it
+ """
+ max_dim = max(x_size,y_size) - 2*self.border
+ sql = 'select * from transforms where jobject_id = "%s"'%id
+ rows, cur = self.db.dbdo(sql)
+ for row in rows:
+ w = row['scaled_x']
+ h = row['scaled_y']
+ transform_max = max(w,h)
+ _logger.debug('transform rec max: %s request max: %s'%(transform_max,max_dim,))
+ if max_dim == transform_max:
+ self.x_thumb = w
+ self.y_thumb = h
+ self.aspect = float(w)/h
+ blob =row['thumb']
+ surf = pygame.image.frombuffer(blob,(w,h),'RGB')
+ _logger.debug('retrieved thumbnail from database')
+ return surf
+ try:
+ ds_obj = datastore.get(id)
+ except Exception,e:
+ print('get filename from id error: %s'%e)
+ return None
+ if ds_obj:
+ fn = ds_obj.get_file_path()
+ try:
+ self.surf = pygame.image.load(fn)
+ except Exception,e:
+ print('scale_image failed to load %s'%fn)
+ return None
+ finally:
+ ds_obj.destroy()
+ self.surf.convert
+ w,h = self.surf.get_size()
+ self.aspect = float(w)/h
+ if self.aspect > 1.0:
+ self.x_thumb = int(x_size - 2 * self.border)
+ self.y_thumb = int((y_size - 2 * self.border) / self.aspect)
+ else:
+ self.x_thumb = int((x_size - 2 * self.border) * self.aspect)
+ self.y_thumb = int((y_size - 2 * self.border))
+ thumb_size = (self.x_thumb,self.y_thumb)
+ ret = pygame.transform.scale(self.surf,thumb_size)
+ #write the transform to the database for speedup next time
+ thumbstr = pygame.image.tostring(ret,'RGB')
+ conn = self.db.get_connection()
+ cursor = conn.cursor()
+ thumb_binary = sqlite3.Binary(thumbstr)
+ try:
+ cursor.execute("insert into transforms (jobject_id,original_x,original_y,scaled_x,scaled_y,thumb) values (?,?,?,?,?,?)",\
+ (id,w,h,self.x_thumb,self.y_thumb,thumb_binary,))
+ except sqlite3.Error,e:
+ _logger.debug('write thumbnail error %s'%e)
+ return None
+ self.db.commit()
+ return ret
+
+ def position(self,x,y):
+ self.x = x
+ self.y = y
+
+ def size(self, x_size, y_size):
+ self.size_x = x_size
+ self.size_y = y_size
+
+ def set_border(self,b):
+ self.border = b
+
+ def select(self):
+ self.surface.fill(selected_color)
+ self.thumbnail.blit(self.scaled,[0,0])
+ screen.blit(self.surface,[self.x,self.y])
+ return self
+
+ def unselect(self):
+ self.surface.fill(background_color)
+ self.thumbnail.blit(self.scaled,[0,0])
+ screen.blit(self.surface,[self.x,self.y])
+ return self
+
+
+class DisplayMany():
+ """
+ Receives array of rows (an album), and open database object refering to
+ database:'xophoto.sqlite' which is stored in the journal
+ """
+ def __init__(self,rows,dbaccess,index=0):
+ self.rows = rows
+ self.db = dbaccess
+ self.pict_dict = {}
+ self.screen_width = 1000
+ self.screen_height = 700
+ self.screen_origin_x = 200
+ self.screen_origin_y = 000
+ self.pict_per_row = 6
+ self.num_rows = 1
+ self.display_start_index = index
+ self.origin_row = 0
+ if index < 0:
+ self.display_start_index = 0
+ self.origin_row = 0
+ elif index >= len(rows):
+ self.display_start_index = len(rows) - self.pict_per_row
+ self.origin_row = index // self.pict_per_row
+ self.selected_index = index
+ self.last_selected = None
+
+ def paint(self):
+ """
+ Put multiple images on pygame screen.
+ Inputs: 1. cursor pointing to picture records of xophoto.sqlite
+ 2. Index into cursor
+ """
+ #protect from an empty database
+ if len(self.rows) == 0: return
+ #figure out what size to paint, assuming square aspect ratio
+ if self.pict_per_row > 0:
+ #x_size = math.floor(self.screen_width/self.pict_per_row)
+ x_size = self.screen_width // self.pict_per_row
+ else:
+ raise PhotoException('pict_per_row was zero or negative')
+ if x_size > self.screen_width:
+ x_size = self.screen_width
+ if x_size > self.screen_height:
+ x_size = self.screen_height
+ y_size = x_size
+ num_pict = len(self.rows)
+ if num_pict > self.num_rows * self.pict_per_row:
+ num_pict = self.num_rows * self.pict_per_row
+ self.display_start_index = self.origin_row * self.pict_per_row
+ #check for upper bound on rows
+ if num_pict + self.display_start_index > len(self.rows):
+ num_pict = len(self.rows)-self.display_start_index
+ self.last_selected.unselect()
+ self.last_selected = None
+ screen.fill((255,255,255))
+ _logger.debug('displaymany in range %s,%s'%(self.display_start_index, num_pict + self.display_start_index,))
+ for i in range(self.display_start_index, num_pict + self.display_start_index):
+ if not self.pict_dict.has_key(i):
+ self.pict_dict[i] = DisplayOne(self.rows,self.db,i)
+ row = i // self.pict_per_row
+ pos_x = self.screen_origin_x + (i % self.pict_per_row) * x_size
+ pos_y = self.screen_origin_y + (row - self.origin_row) * y_size
+ self.pict_dict[i].position(pos_x,pos_y)
+ #_logger.debug('calling paint with size(%s,%s) and position(%s,%s)'%(x_size,y_size,pos_x,pos_y,))
+ self.pict_dict[i].size(x_size,y_size)
+ self.pict_dict[i].paint()
+ self.select_pict(self.selected_index)
+
+ def screen_width(self,width):
+ self.screen_width = width
+
+ def screen_height(self,height):
+ self.screen_height = height
+
+ def num_per_row(self,num):
+ self.pict_per_row = num
+
+ def number_of_rows(self,num):
+ self.num_rows = num
+
+ def select_pict(self,num):
+ if self.last_selected:
+ self.last_selected.unselect()
+ self.last_selected = self.pict_dict[num].select()
+
+ def next(self):
+ if self.selected_index < len(self.rows)-1:
+ self.selected_index += 1
+ #self.display_start_index = self.selected_index
+ if self.selected_index >= (self.origin_row + self.num_rows) * self.pict_per_row:
+ self.origin_row += 1
+ self.paint()
+ self.select_pict(self.selected_index)
+
+ def next_row(self):
+ if self.selected_index // self.pict_per_row < len(self.rows) // self.pict_per_row:
+ self.selected_index += self.pict_per_row
+ if self.selected_index > len(self.rows)-1:
+ self.selected_index = len(self.rows)-1
+ if self.selected_index >= (self.origin_row + self.num_rows) * self.pict_per_row:
+ self.origin_row += 1
+ self.paint()
+ self.select_pict(self.selected_index)
+
+
+ def prev(self):
+ if self.selected_index > 0:
+ self.selected_index -= 1
+ if self.selected_index < (self.origin_row) * self.pict_per_row:
+ self.origin_row -= 1
+ self.paint()
+ self.select_pict(self.selected_index)
+
+ def prev_row(self):
+ if self.selected_index // self.pict_per_row > 0:
+ self.selected_index -= self.pict_per_row
+ if self.selected_index // self.pict_per_row < self.origin_row:
+ self.origin_row -= 1
+ self.paint()
+ self.select_pict(self.selected_index)
+
+class DisplayAlbums():
+ """Shows the photo albums on left side of main screen, responds to clicks, drag/drop events"""
+
+ predefined_albums = [_('Journal'),_('Trash'),_('Duplicates'),_('Last Year'),_('Last Month'),]
+ def __init__(self):
+ self.num_of_last_rolls = 5
+ self.background = (0,0,200)
+ self.album_height = 50
+
+ def one_album(self,album):
+ surf = pygame.Surface((self.album_height,100))
+ font = pygame.font.Font(None,50)
+ text = font.render(album,0,self.background)
+ rect = text.get_rect()
+ surf.blit(text,(0,0))
+ item = surf.get_rect()
+ _logger.debug('one album %s'%album)
+ return text
+
+ def paint_albums(self):
+ for index in range(len(self.predefined_albums)):
+ screen.blit(self.one_album(self.predefined_albums[index]),(0,index*self.album_height))
+
+ def do_roll_over(self,x,y, in_drag=False):
+ """indicate willingness to be selected"""
+ pass
+
+ def do_click(self,x,y):
+ """select the pointed to item"""
+ pass
+
+ def do_drag_up(self,x,y):
+ """add the dragged item to the selected album"""
+ pass
+
+ def add_new_album(self,name):
+ pass
+
+ def delete_album(self,x,y):
+ pass
+
+
+class Application():
+ #how far does a drag need to be not to be ignored?
+ drag_threshold = 10
+
+ def run(self):
+ global screen
+ global in_click_delay
+
+ self.db = DbAccess('xophoto.sqlite')
+ if not self.db.is_open():
+ _logger.debug('filed to open "xophoto.sqlite" database')
+ return
+ """
+ conn = sqlite3.connect('xophoto.sqlite')
+ #rows generated thusly will have columns that are addressable as dict of fieldnames
+ conn.row_factory = sqlite3.Row
+ cursor = conn.cursor()
+ """
+ if True:
+
+ running = True
+ do_display = True
+
+ pygame.init()
+ pygame.display.set_mode((0, 0), pygame.RESIZABLE)
+ screen = pygame.display.get_surface()
+
+ while running:
+ # Pump GTK messages.
+ while gtk.events_pending():
+ gtk.main_iteration()
+
+ # Pump PyGame messages.
+ for event in pygame.event.get():
+ if event.type in (MOUSEBUTTONDOWN,MOUSEBUTTONUP,MOUSEMOTION):
+ x,y = event.pos
+ if event.type == KEYUP:
+ print event
+ if event.key == K_ESCAPE:
+ running = False
+ pygame.quit()
+ elif event.key == K_LEFT:
+ dm.prev()
+ pygame.display.flip()
+ elif event.key == K_RIGHT:
+ dm.next()
+ pygame.display.flip()
+ elif event.key == K_UP:
+ dm.prev_row()
+ pygame.display.flip()
+ elif event.key == K_DOWN:
+ dm.next_row()
+ pygame.display.flip()
+
+ #mouse events
+ elif event.type == MOUSEBUTTONDOWN:
+ if self.mouse_timer_running(): #this is a double click
+ self.process_mouse_double_click(x, y)
+ in_click_delay = False
+ else: #just a single click
+ self.process_mouse_click(x, y)
+ elif event.type == MOUSEMOTION:
+ self.drag(x, y)
+ elif event.type == MOUSEBUTTONUP:
+ if in_drag:
+ self.drop(x, y)
+ if event.type == pygame.QUIT:
+ return
+
+ elif event.type == pygame.VIDEORESIZE:
+ pygame.display.set_mode(event.size, pygame.RESIZABLE)
+
+
+ if do_display:
+ do_display = False
+ # Clear Display
+ screen.fill((255,255,255)) #255 for white
+ #fetch the album (array of album records)
+ sql = 'select * from picture'
+ rows,cur = self.db.dbdo(sql)
+ dm = DisplayMany(rows,self.db)
+ dm.num_per_row(10)
+ dm.number_of_rows(5)
+ dm.paint()
+ albums = DisplayAlbums()
+ albums.paint_albums()
+ #albums.paint_albums()
+
+ # Flip Display
+ pygame.display.flip()
+ else:
+ print('xophoto sqlite database failed to open')
+
+ def drag(self,x,y):
+ global in_drag
+ l,m,r = pygame.mouse.get_pressed()
+ if not l: return
+ if not in_drag:
+ print('drag started at %s,%s'%(x,y,))
+ in_drag = True
+ #record the initial position
+ self.drag_start_x,self.drag_start_y = x,y
+
+ def drop(self,x,y):
+ #if the drag is less than threshold, ignore
+ if max(abs(self.drag_start_x - x), abs(self.drag_start_y - y)) < self.drag_threshold:
+ in_drag = False
+ return
+ print('drop at %s,%s'%(x,y,))
+ pass
+
+ def process_mouse_click(self,x,y):
+ print('mouse single click')
+ pass
+
+ def process_mouse_double_click(self,x,y):
+ print('double click')
+ pass
+
+ def mouse_timer_running(self):
+ global in_click_delay
+ if not in_click_delay:
+ Timer(0.5, self.end_delay, ()).start()
+ in_click_delay = True
+ return False
+ return True
+
+ def end_delay(self):
+ global in_click_delay
+ in_click_delay = False
+
+
+
+if __name__ == '__main__':
+ ap = Application()
+ ap.run()
+
diff --git a/dist/XoPhoto-1.xo b/dist/XoPhoto-1.xo
new file mode 100644
index 0000000..aec1622
--- /dev/null
+++ b/dist/XoPhoto-1.xo
Binary files differ
diff --git a/photo_toolbar.py b/photo_toolbar.py
new file mode 100644
index 0000000..964b679
--- /dev/null
+++ b/photo_toolbar.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2009, George Hunt <georgejhunt@gmail.com>
+#
+# 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 gobject
+#import gconf
+
+from sugar.graphics.toolbox import Toolbox
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.icon import Icon
+from sugar.graphics.toolcombobox import ToolComboBox
+from sugar.graphics.toolbutton import ToolButton
+from gettext import gettext as _
+
+class ActivityToolbar(gtk.Toolbar):
+ """The Activity toolbar with the Journal entry title, sharing,
+ Keep and Stop buttons
+
+ All activities should have this toolbar. It is easiest to add it to your
+ Activity by using the ActivityToolbox.
+ """
+ def __init__(self, activity):
+ gtk.Toolbar.__init__(self)
+
+ self._activity = activity
+ self._updating_share = False
+ """
+ activity.connect('shared', self.__activity_shared_cb)
+ activity.connect('joined', self.__activity_shared_cb)
+ activity.connect('notify::max_participants',
+ self.__max_participants_changed_cb)
+ """
+ #if activity.metadata:
+ if True:
+ self.title = gtk.Entry()
+ self.title.set_size_request(int(gtk.gdk.screen_width() / 6), -1)
+ if activity.metadata:
+ self.title.set_text(activity.metadata['title'])
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+ self.title.connect('changed', self.__title_changed_cb)
+ self._add_widget(self.title)
+
+ self.add_album = ToolButton('list-add')
+ self.add_album.set_tooltip(_("Add Album"))
+ self.add_album.show()
+ self.insert(self.add_album,-1)
+
+ self.delete_album = ToolButton('list-remove')
+ self.delete_album.set_tooltip(_("Remove Album"))
+ self.delete_album.show()
+ self.insert(self.delete_album,-1)
+
+
+
+ """
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.share = ToolComboBox(label_text=_('Traceback:'))
+ self.share.combo.connect('changed', self.__traceback_changed_cb)
+ self.share.combo.append_item("traceback_plain", _('Plain'))
+ self.share.combo.append_item('traceback_context', _('Context'))
+ self.share.combo.append_item('traceback_verbose', _('Verbose'))
+ self.insert(self.share, -1)
+ self.share.show()
+
+ self._update_share()
+ """
+ self.keep = ToolButton(tooltip=_('Keep'))
+ #client = gconf.client_get_default()
+ #color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ #keep_icon = Icon(icon_name='document-save', xo_color=color)
+ keep_icon = Icon(icon_name='document-save')
+ self.keep.set_icon_widget(keep_icon)
+ keep_icon.show()
+ self.keep.props.accelerator = '<Ctrl>S'
+ self.keep.connect('clicked', self.__keep_clicked_cb)
+ self.insert(self.keep, -1)
+ self.keep.hide()
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.stop = ToolButton('activity-stop', tooltip=_('Stop'))
+ self.stop.props.accelerator = '<Ctrl>Q'
+ self.stop.connect('clicked', self.__stop_clicked_cb)
+ self.insert(self.stop, -1)
+ self.stop.show()
+
+ self._update_title_sid = None
+
+ def _update_share(self):
+ self._updating_share = True
+
+ if self._activity.props.max_participants == 1:
+ self.share.hide()
+
+ if self._activity.get_shared():
+ self.share.set_sensitive(False)
+ self.share.combo.set_active(1)
+ else:
+ self.share.set_sensitive(True)
+ self.share.combo.set_active(0)
+
+ self._updating_share = False
+
+ def __traceback_changed_cb(self, combo):
+ model = self.share.combo.get_model()
+ it = self.share.combo.get_active_iter()
+ (scope, ) = model.get(it, 0)
+ if scope == 'traceback_plain':
+ self._activity.traceback = 'Plain'
+ self._activity.debug_dict['traceback'] = 'plain'
+ elif scope == 'traceback_context':
+ self._activity.traceback = 'Context'
+ self._activity.debug_dict['traceback'] = 'context'
+ elif scope == 'traceback_verbose':
+ self._activity.traceback = 'Verbose'
+ self._activity.debug_dict['traceback'] = 'verbose'
+ self._activity.set_ipython_traceback()
+
+ def __keep_clicked_cb(self, button):
+ self._activity.save_icon_clicked = True
+ self._activity.copy()
+
+ def __stop_clicked_cb(self, button):
+ self._activity.close()
+
+ def __jobject_updated_cb(self, jobject):
+ self.title.set_text(jobject['title'])
+
+ def __title_changed_cb(self, entry):
+ if not self._update_title_sid:
+ self._update_title_sid = gobject.timeout_add_seconds(
+ 1, self.__update_title_cb)
+
+ def __update_title_cb(self):
+ title = self.title.get_text()
+
+ self._activity.metadata['title'] = title
+ self._activity.metadata['title_set_by_user'] = '1'
+ self._activity.save()
+
+ shared_activity = self._activity.get_shared_activity()
+ if shared_activity:
+ shared_activity.props.name = title
+
+ self._update_title_sid = None
+ return False
+
+ def _add_widget(self, widget, expand=False):
+ tool_item = gtk.ToolItem()
+ tool_item.set_expand(expand)
+
+ tool_item.add(widget)
+ widget.show()
+
+ self.insert(tool_item, -1)
+ tool_item.show()
+
+ def __activity_shared_cb(self, activity):
+ self._update_share()
+
+ def __max_participants_changed_cb(self, activity, pspec):
+ self._update_share()
+
+class ActivityToolbox(Toolbox):
+ """Creates the Toolbox for the Activity
+
+ By default, the toolbox contains only the ActivityToolbar. After creating
+ the toolbox, you can add your activity specific toolbars, for example the
+ EditToolbar.
+
+ To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
+
+ # Create the Toolbar with the ActivityToolbar:
+ toolbox = activity.ActivityToolbox(self)
+ ... your code, inserting all other toolbars you need, like EditToolbar
+
+ # Add the toolbox to the activity frame:
+ self.set_toolbox(toolbox)
+ # And make it visible:
+ toolbox.show()
+ """
+ def __init__(self, activity):
+ Toolbox.__init__(self)
+
+ self._activity_toolbar = ActivityToolbar(activity)
+ self.add_toolbar(_('Activity'), self._activity_toolbar)
+ self._activity_toolbar.show()
+
+ def get_activity_toolbar(self):
+ return self._activity_toolbar
+
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..d3ab3a3
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,21 @@
+#!/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
+
+from sugar.activity import bundlebuilder
+
+bundlebuilder.start()
diff --git a/sources.py b/sources.py
new file mode 100644
index 0000000..e936d70
--- /dev/null
+++ b/sources.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+# sources.py
+#
+# Copyright (C) 2010 George Hunt
+#
+# 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
+#
+from gettext import gettext as _
+
+from sugar.datastore import datastore
+import sys, os
+import gtk
+import shutil
+import sqlite3
+
+from dbphoto import *
+
+#pick up activity globals
+from xophotoactivity import *
+
+
+import logging
+_logger = logging.getLogger('xophoto')
+_logger.setLevel(logging.DEBUG)
+console_handler = logging.StreamHandler()
+console_formatter = logging.Formatter('%(name)s %(levelname)s %(funcName)s: %(lineno)d||| %(message)s')
+console_handler.setFormatter(console_formatter)
+#_logger.addHandler(console_handler)
+
+class Datastore_SQLite():
+ """class for interfacing between the Journal and an SQLite database"""
+ def __init__(self,database_access_object):
+ """receives an open dbaccess object (defined in dbphoto) """
+ #self.db = dbaccess(fn)
+ self.db = database_access_object
+
+ def ok(self):
+ if self.db.is_open():return True
+ return False
+
+ def scan_images(self):
+ """
+ returns a list of journal object ids that have mime_type equal to one
+ of the entries in mimetype table of xophoto database.
+ """
+ rtn = []
+ mime_list = self.db.get_mime_list()
+ (results,count) = datastore.find({})
+ for f in results:
+ dict = f.get_metadata().get_dictionary()
+ if dict["mime_type"].find('jpg')>-1:
+ _logger.debug('found jpg: %s in mime_list %r'%(dict["mime_type"],mime_list,))
+ if dict["mime_type"] in mime_list:
+ #record the id, file size, file date, in_ds
+ self.db.create_picture_record(f.object_id, f.get_file_path())
+ rtn.append(f.object_id)
+ f.destroy()
+ self.db.commit()
+ _logger.debug('%s found in journal'%count)
+ return rtn
+
+
+ def get_filename_from_jobject_id(self, id):
+ try:
+ ds_obj = datastore.get(id)
+ except Exception,e:
+ _logger.debug('get filename from id error: %s'%e)
+ return None
+ if ds_obj:
+ fn = ds_obj.get_file_path()
+ ds_obj.destroy()
+ return(fn)
+ return None
+
+class filetree():
+ def __init__(self,fn):
+ self.db = dbaccess(fn)
+ self.dialog = None
+
+ def get_path(self):
+ _logger.debug('dialog to get user path for importing into journal')
+ if not self.dialog:
+ self.dialog = gtk.FileChooserDialog("Select Folder..",
+ None,
+ gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+ else:
+ self.dialog.show_all()
+ self.dialog.set_default_response(gtk.RESPONSE_OK)
+ #self.dialog.set_current_folder(os.path.dirname(self.last_filename))
+
+ filter = gtk.FileFilter()
+ filter.set_name("All files")
+ filter.add_pattern("*")
+ self.dialog.add_filter(filter)
+
+ filter = gtk.FileFilter()
+ filter.set_name("Pictures")
+ filter.add_pattern("*.png,*.jpg,*jpeg,*.gif")
+ self.dialog.add_filter(filter)
+
+ response = self.dialog.run()
+ if response == gtk.RESPONSE_OK:
+ _logger.debug('%s selected'%self.dialog.get_filename() )
+ fname = self.dialog.get_filename()
+ self.last_filename = fname
+ elif response == gtk.RESPONSE_CANCEL:
+ fname = None
+ _logger.debug( 'File chooseer closed, no files selected')
+ self.dialog.hide_all()
+ self.dialog.destroy()
+ self.dialog = None
+ return fname
+
+ def copy_tree_to_ds(self,path):
+ added = 0
+ if os.path.isdir(path):
+ files = os.listdir(path)
+ for f in files:
+ mtype = ''
+ chunks = f.split('.')
+ if len(chunks)>1:
+ ext = chunks[-1]
+ if ext == 'jpg' or ext == 'jpeg':
+ mtype = 'image/jpg'
+ elif ext == 'gif':
+ mtype = 'image/gif'
+ elif ext == 'png':
+ mtype = 'image/png'
+ if mtype == '': continue
+ fullname = os.path.join(path,f)
+ info = os.stat(fullname)
+ size = info.st_size
+ if self.db.check_in_ds(fullname,size): continue
+ ds = datastore.create()
+ ds.metadata['filename'] = fullname
+ ds.metadata['title'] = f
+ ds.metadata['mime_type'] = mtype
+ dest = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],'instance',f)
+ shutil.copyfile(fullname,dest)
+ ds.set_file_path(dest)
+ datastore.write(ds,transfer_ownership=True)
+ self.db.update_picture(ds.object_id,fullname)
+ ds.destroy()
+ added += 1
+ return added
+ return 0
+
+ def fill_ds(self):
+ path = self.get_path()
+ if path:
+ return self.copy_tree_to_ds(path)
+
+
+
+if __name__ == '__main__':
+ db = DbAccess('xophoto.sqlite')
+ if db.is_open():
+ ds_sql = Datastore_SQLite(db)
+ imagelist = ds_sql.scan_images()
+ exit()
+ for i in imagelist:
+ print('\n%s'%ds.get_filename_from_jobject_id(i))
+ ft = filetree('xophoto.sqlite')
+ #new = ft.fill_ds()
+ print('%s datastore records added'%new)
+ else:
+ print('xophoto sqlite database failed to open')
diff --git a/sugargame/__init__.py b/sugargame/__init__.py
new file mode 100644
index 0000000..7e49527
--- /dev/null
+++ b/sugargame/__init__.py
@@ -0,0 +1 @@
+__version__ = '1.0'
diff --git a/sugargame/canvas.py b/sugargame/canvas.py
new file mode 100644
index 0000000..cf99a13
--- /dev/null
+++ b/sugargame/canvas.py
@@ -0,0 +1,56 @@
+import os
+import gtk
+import gobject
+import pygame
+import event
+
+CANVAS = None
+
+class PygameCanvas(gtk.EventBox):
+ def __init__(self, mainwindow):
+ gtk.EventBox.__init__(self)
+
+ global CANVAS
+ assert CANVAS == None, "Only one PygameCanvas can be created, ever."
+ CANVAS = self
+
+ self._mainwindow = mainwindow
+
+ self.set_flags(gtk.CAN_FOCUS)
+
+ self._socket = gtk.Socket()
+ self.add(self._socket)
+ self.show_all()
+
+ def run_pygame(self, main_fn):
+ # Run the main loop after a short delay. The reason for the delay is that the
+ # Sugar activity is not properly created until after its constructor returns.
+ # If the Pygame main loop is called from the activity constructor, the
+ # constructor never returns and the activity freezes.
+ gobject.idle_add(self._run_pygame_cb, main_fn)
+
+ def _run_pygame_cb(self, main_fn):
+ assert pygame.display.get_surface() is None, "PygameCanvas.run_pygame can only be called once."
+
+ # Preinitialize Pygame with the X window ID.
+ assert pygame.display.get_init() == False, "Pygame must not be initialized before calling PygameCanvas.run_pygame."
+ os.environ['SDL_WINDOWID'] = str(self._socket.get_id())
+ pygame.init()
+
+ # Restore the default cursor.
+ self._socket.get_window().set_cursor(None)
+
+ # Initialize the Pygame window.
+ r = self.get_allocation()
+ pygame.display.set_mode((r.width, r.height), pygame.RESIZABLE)
+
+ # Hook certain Pygame functions with GTK equivalents.
+ translator = event.Translator(self._mainwindow, self)
+ translator.hook_pygame()
+
+ # Run the Pygame main loop.
+ main_fn()
+ return False
+
+ def get_pygame_widget(self):
+ return self._socket
diff --git a/sugargame/event.py b/sugargame/event.py
new file mode 100644
index 0000000..52ca4ab
--- /dev/null
+++ b/sugargame/event.py
@@ -0,0 +1,241 @@
+import gtk
+import gobject
+import pygame
+import pygame.event
+import logging
+
+class _MockEvent(object):
+ def __init__(self, keyval):
+ self.keyval = keyval
+
+class Translator(object):
+ key_trans = {
+ 'Alt_L': pygame.K_LALT,
+ 'Alt_R': pygame.K_RALT,
+ 'Control_L': pygame.K_LCTRL,
+ 'Control_R': pygame.K_RCTRL,
+ 'Shift_L': pygame.K_LSHIFT,
+ 'Shift_R': pygame.K_RSHIFT,
+ 'Super_L': pygame.K_LSUPER,
+ 'Super_R': pygame.K_RSUPER,
+ 'KP_Page_Up' : pygame.K_KP9,
+ 'KP_Page_Down' : pygame.K_KP3,
+ 'KP_End' : pygame.K_KP1,
+ 'KP_Home' : pygame.K_KP7,
+ 'KP_Up' : pygame.K_KP8,
+ 'KP_Down' : pygame.K_KP2,
+ 'KP_Left' : pygame.K_KP4,
+ 'KP_Right' : pygame.K_KP6,
+
+ }
+
+ mod_map = {
+ pygame.K_LALT: pygame.KMOD_LALT,
+ pygame.K_RALT: pygame.KMOD_RALT,
+ pygame.K_LCTRL: pygame.KMOD_LCTRL,
+ pygame.K_RCTRL: pygame.KMOD_RCTRL,
+ pygame.K_LSHIFT: pygame.KMOD_LSHIFT,
+ pygame.K_RSHIFT: pygame.KMOD_RSHIFT,
+ }
+
+ def __init__(self, mainwindow, inner_evb):
+ """Initialise the Translator with the windows to which to listen"""
+ self._mainwindow = mainwindow
+ self._inner_evb = inner_evb
+
+ # Enable events
+ self._mainwindow.set_events(
+ gtk.gdk.KEY_PRESS_MASK | \
+ gtk.gdk.KEY_RELEASE_MASK \
+ )
+
+ self._inner_evb.set_events(
+ gtk.gdk.POINTER_MOTION_MASK | \
+ gtk.gdk.POINTER_MOTION_HINT_MASK | \
+ gtk.gdk.BUTTON_MOTION_MASK | \
+ gtk.gdk.BUTTON_PRESS_MASK | \
+ gtk.gdk.BUTTON_RELEASE_MASK
+ )
+
+ self._mainwindow.set_flags(gtk.CAN_FOCUS)
+ self._inner_evb.set_flags(gtk.CAN_FOCUS)
+
+ # Callback functions to link the event systems
+ self._mainwindow.connect('unrealize', self._quit_cb)
+ self._inner_evb.connect('key_press_event', self._keydown_cb)
+ self._inner_evb.connect('key_release_event', self._keyup_cb)
+ self._inner_evb.connect('button_press_event', self._mousedown_cb)
+ self._inner_evb.connect('button_release_event', self._mouseup_cb)
+ self._inner_evb.connect('motion-notify-event', self._mousemove_cb)
+ self._inner_evb.connect('expose-event', self._expose_cb)
+ self._inner_evb.connect('configure-event', self._resize_cb)
+
+ # Internal data
+ self.__stopped = False
+ self.__keystate = [0] * 323
+ self.__button_state = [0,0,0]
+ self.__mouse_pos = (0,0)
+ self.__repeat = (None, None)
+ self.__held = set()
+ self.__held_time_left = {}
+ self.__held_last_time = {}
+ self.__tick_id = None
+
+ def hook_pygame(self):
+ pygame.key.get_pressed = self._get_pressed
+ pygame.key.set_repeat = self._set_repeat
+ pygame.mouse.get_pressed = self._get_mouse_pressed
+ pygame.mouse.get_pos = self._get_mouse_pos
+
+ def _expose_cb(self, event, widget):
+ pygame.event.post(pygame.event.Event(pygame.VIDEOEXPOSE))
+ return True
+
+ def _resize_cb(self, widget, event):
+ evt = pygame.event.Event(pygame.VIDEORESIZE,
+ size=(event.width,event.height), width=event.width, height=event.height)
+ pygame.event.post(evt)
+ return False # continue processing
+
+ def _quit_cb(self, data=None):
+ self.__stopped = True
+ pygame.event.post(pygame.event.Event(pygame.QUIT))
+
+ def _keydown_cb(self, widget, event):
+ key = event.keyval
+ if key in self.__held:
+ return True
+ else:
+ if self.__repeat[0] is not None:
+ self.__held_last_time[key] = pygame.time.get_ticks()
+ self.__held_time_left[key] = self.__repeat[0]
+ self.__held.add(key)
+
+ return self._keyevent(widget, event, pygame.KEYDOWN)
+
+ def _keyup_cb(self, widget, event):
+ key = event.keyval
+ if self.__repeat[0] is not None:
+ if key in self.__held:
+ # This is possibly false if set_repeat() is called with a key held
+ del self.__held_time_left[key]
+ del self.__held_last_time[key]
+ self.__held.discard(key)
+
+ return self._keyevent(widget, event, pygame.KEYUP)
+
+ def _keymods(self):
+ mod = 0
+ for key_val, mod_val in self.mod_map.iteritems():
+ mod |= self.__keystate[key_val] and mod_val
+ return mod
+
+ def _keyevent(self, widget, event, type):
+ key = gtk.gdk.keyval_name(event.keyval)
+ if key is None:
+ # No idea what this key is.
+ return False
+
+ keycode = None
+ if key in self.key_trans:
+ keycode = self.key_trans[key]
+ elif hasattr(pygame, 'K_'+key.upper()):
+ keycode = getattr(pygame, 'K_'+key.upper())
+ elif hasattr(pygame, 'K_'+key.lower()):
+ keycode = getattr(pygame, 'K_'+key.lower())
+ elif key == 'XF86Start':
+ # view source request, specially handled...
+ self._mainwindow.view_source()
+ else:
+ print 'Key %s unrecognized' % key
+
+ if keycode is not None:
+ if type == pygame.KEYDOWN:
+ mod = self._keymods()
+ self.__keystate[keycode] = type == pygame.KEYDOWN
+ if type == pygame.KEYUP:
+ mod = self._keymods()
+ ukey = unichr(gtk.gdk.keyval_to_unicode(event.keyval))
+ if ukey == '\000':
+ ukey = ''
+ evt = pygame.event.Event(type, key=keycode, unicode=ukey, mod=mod)
+ self._post(evt)
+
+ return True
+
+ def _get_pressed(self):
+ return self.__keystate
+
+ def _get_mouse_pressed(self):
+ return self.__button_state
+
+ def _mousedown_cb(self, widget, event):
+ self.__button_state[event.button-1] = 1
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONDOWN)
+
+ def _mouseup_cb(self, widget, event):
+ self.__button_state[event.button-1] = 0
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONUP)
+
+ def _mouseevent(self, widget, event, type):
+ evt = pygame.event.Event(type, button=event.button, pos=(event.x, event.y))
+ self._post(evt)
+ return True
+
+ def _mousemove_cb(self, widget, event):
+ # From http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/
+ # if this is a hint, then let's get all the necessary
+ # information, if not it's all we need.
+ if event.is_hint:
+ x, y, state = event.window.get_pointer()
+ else:
+ x = event.x
+ y = event.y
+ state = event.state
+
+ rel = (x - self.__mouse_pos[0], y - self.__mouse_pos[1])
+ self.__mouse_pos = (x, y)
+
+ self.__button_state = [
+ state & gtk.gdk.BUTTON1_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON2_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON3_MASK and 1 or 0,
+ ]
+
+ evt = pygame.event.Event(pygame.MOUSEMOTION,
+ pos=self.__mouse_pos, rel=rel, buttons=self.__button_state)
+ self._post(evt)
+ return True
+
+ def _tick_cb(self):
+ cur_time = pygame.time.get_ticks()
+ for key in self.__held:
+ delta = cur_time - self.__held_last_time[key]
+ self.__held_last_time[key] = cur_time
+
+ self.__held_time_left[key] -= delta
+ if self.__held_time_left[key] <= 0:
+ self.__held_time_left[key] = self.__repeat[1]
+ self._keyevent(None, _MockEvent(key), pygame.KEYDOWN)
+
+ return True
+
+ def _set_repeat(self, delay=None, interval=None):
+ if delay is not None and self.__repeat[0] is None:
+ self.__tick_id = gobject.timeout_add(10, self._tick_cb)
+ elif delay is None and self.__repeat[0] is not None:
+ gobject.source_remove(self.__tick_id)
+ self.__repeat = (delay, interval)
+
+ def _get_mouse_pos(self):
+ return self.__mouse_pos
+
+ def _post(self, evt):
+ try:
+ pygame.event.post(evt)
+ except pygame.error, e:
+ if str(e) == 'Event queue full':
+ print "Event queue full!"
+ pass
+ else:
+ raise e
diff --git a/xophoto.sqlite b/xophoto.sqlite
new file mode 100644
index 0000000..8e1a1c8
--- /dev/null
+++ b/xophoto.sqlite
Binary files differ
diff --git a/xophotoactivity.py b/xophotoactivity.py
new file mode 100644
index 0000000..2a199e2
--- /dev/null
+++ b/xophotoactivity.py
@@ -0,0 +1,289 @@
+#!/usr/bin/env python
+# xophotoactivity.py
+#
+# Copyright (C) 2010 George Hunt
+#
+# 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
+#
+"""
+Top level description of the components of XoPhoto Activity
+xophotoactivity.py --this file, subclasses Activity, Sets up Menus, canvas,
+ fetches the display module.Application class in display.py,
+ passes application object to sugargame.<canvas application runner>
+display.py --contains the xophoto main loop this analyzes keystokes and
+ manipulates the display
+sources.py --obtains images from datastore, folders, cameras and puts relevant
+ information into the sqlite database
+sinks.py --combines information from the sqlite database and the Journal and
+ pumps it to various destinations, folders, email messages, slideshows
+dbphoto.py --interfaces with the sqlite database, provides low level db access
+sugarpygame --pygame platform developed sugarlabs.org see:
+ http://wiki.sugarlabs.org/go/Development_Team/sugargame
+"""
+from gettext import gettext as _
+
+import gtk
+import pygame
+from sugar.activity import activity
+from sugar.graphics.toolbutton import ToolButton
+import gobject
+import sugargame.canvas
+import os
+import shutil
+
+import display
+import photo_toolbar
+
+#Application Globals
+album_column_width = 200
+
+import logging
+_logger = logging.getLogger('xophoto')
+_logger.setLevel(logging.DEBUG)
+console_handler = logging.StreamHandler()
+console_formatter = logging.Formatter('%(name)s %(levelname)s %(funcName)s: %(lineno)d||| %(message)s')
+console_handler.setFormatter(console_formatter)
+#_logger.addHandler(console_handler)
+
+
+class XoPhotoActivity(activity.Activity):
+ def __init__(self, handle):
+ if handle and handle.object_id != '':
+ make_jobject = False
+ else:
+ make_jobject = True
+ activity.Activity.__init__(self, handle, create_jobject = make_jobject)
+ """
+ #for development purposes, copy the sqlite database to the data directory
+ source = os.path.join(os.getcwd(),'xophoto.sqlite')
+ dest = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],'data','xophoto.sqlite')
+ if handle.object_id == None and source != dest:
+ shutil.copy('./xophoto.sqlite',dest)
+ """
+ # Build the activity toolbar.
+ self.build_toolbar()
+
+ # Create the game instance.
+ self.game = display.Application()
+
+ # Build the Pygame canvas.
+ self._pygamecanvas = sugargame.canvas.PygameCanvas(self)
+ # Note that set_canvas implicitly calls read_file when resuming from the Journal.
+ self.set_canvas(self._pygamecanvas)
+
+ # Start the game running.
+ self._pygamecanvas.run_pygame(self.game.run)
+
+ def build_toolbar(self):
+ toolbox = photo_toolbar.ActivityToolbox(self)
+ activity_toolbar = toolbox.get_activity_toolbar()
+ label = gtk.Label(_('New Album Name:'))
+ tool_item = gtk.ToolItem()
+ tool_item.set_expand(False)
+ tool_item.add(label)
+ label.show()
+ activity_toolbar.insert(tool_item, 0)
+ tool_item.show()
+
+ activity_toolbar._add_widget(label)
+ activity_toolbar.keep.props.visible = False
+ #activity_toolbar.share.props.visible = False
+
+ self.edit_toolbar = EditToolbar()
+ toolbox.add_toolbar(_('Edit'), self.edit_toolbar)
+ self.edit_toolbar.connect('do-import',
+ self.edit_toolbar_doimport_cb)
+ self.edit_toolbar.show()
+
+ self.use_toolbar = UseToolbar()
+ toolbox.add_toolbar(_('Use'), self.use_toolbar)
+ self.use_toolbar.connect('do-export',
+ self.use_toolbar_doexport_cb)
+ self.use_toolbar.connect('do-upload',
+ self.use_toolbar_doupload_cb)
+ self.use_toolbar.connect('do-slideshow',
+ self.use_toolbar_doslideshow_cb)
+ self.use_toolbar.show()
+
+ toolbox.show()
+ self.set_toolbox(toolbox)
+
+ def edit_toolbar_doimport_cb(self, view_toolbar):
+ pass
+
+ def use_toolbar_doexport_cb(self,use_toolbar):
+ pass
+
+ def use_toolbar_doupload_cb(self,use_toolbar):
+ pass
+
+ def use_toolbar_doslideshow_cb(self,use_toolbar):
+ pass
+
+ def read_file(self, file_path):
+ sql_file = open(file_path, "rb")
+ local_path = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],'data','xophoto.sqlite')
+ f = open(local_path, 'wb')
+ _logger.debug('reading from %s and writeing to %s'%(file_path,local_path,))
+ try:
+ while sql_file:
+ block = sql_file.read(4096)
+ f.write(block)
+ except IOError, e:
+ _logger.debug('read sqlite file to local error %s'%e)
+ finally:
+ f.close
+ sql_file.close()
+
+ def write_file(self, file_path):
+ try:
+ local_path = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],'data','xophoto.sqlite')
+ self.metadata['filename'] = local_path
+ self.metadata['mime_type'] = mtype
+ #dest = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],'instance',f)
+ _logger.debug('write_file %s to %s'%(local_path,file_path,))
+ shutil.copyfile(local_path,file_path)
+ self.set_file_path(dest)
+ except Exception,e:
+ _logger.debug('write_file exception %s'%e)
+ raise e
+
+class EditToolbar(gtk.Toolbar):
+ __gtype_name__ = 'EditToolbar'
+
+ __gsignals__ = {
+ 'needs-update-size': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'do-import': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([]))
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+ self.doimport = ToolButton()
+ self.doimport.set_stock_id('gtk-open')
+ self.doimport.set_icon_widget(None)
+ self.doimport.set_tooltip(_('Import from SD or USB'))
+ self.doimport.connect('clicked', self.doimport_cb)
+ self.insert(self.doimport, -1)
+ self.doimport.show()
+
+ self.delete_comment = ToolButton()
+ self.delete_comment.set_stock_id('gtk_stock_delete')
+ self.delete_comment.set_tooltip(_("Remove Picture"))
+ self.delete_comment.show()
+ self.insert(self.delete_comment,-1)
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.entry = gtk.Entry()
+ self.entry.set_width_chars(50)
+ tool_item = gtk.ToolItem()
+ tool_item.set_expand(False)
+ tool_item.add(self.entry)
+ self.entry.show()
+ self.insert(tool_item, -1)
+ tool_item.show()
+
+ self.add_comment = ToolButton('list-add')
+ self.add_comment.set_tooltip(_("Add Annotation"))
+ self.add_comment.show()
+ self.insert(self.add_comment,-1)
+
+ self.stop = ToolButton('activity-stop', tooltip=_('Stop'))
+ self.stop.props.accelerator = '<Ctrl>Q'
+ #self.stop.connect('clicked', self.__stop_clicked_cb)
+ self.insert(self.stop, -1)
+ self.stop.show()
+
+
+ def doimport_cb(self, button):
+ self.emit('do-import')
+
+class UseToolbar(gtk.Toolbar):
+ __gtype_name__ = 'UseToolbar'
+
+ __gsignals__ = {
+ 'do-export': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'do-upload': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'do-slideshow': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([]))
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+ self.doexport = ToolButton('view-fullscreen')
+ self.doexport.set_tooltip(_('Export to USB/SD/FileSystem'))
+ self.doexport.connect('clicked', self.doexport_cb)
+ self.insert(self.doexport, -1)
+ self.doexport.show()
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.doupload = ToolButton('view-fullscreen')
+ self.doupload.set_tooltip(_('Fullscreen'))
+ self.doupload.connect('clicked', self.doupload_cb)
+ self.insert(self.doupload, -1)
+ self.doupload.show()
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.doslideshow = ToolButton()
+ self.doslideshow.set_stock_id('gtk-fullscreen')
+ self.doslideshow.set_tooltip(_('SlideShow'))
+ self.doslideshow.connect('clicked', self.doslideshow_cb)
+ self.insert(self.doslideshow, -1)
+ self.doslideshow.show()
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.stop = ToolButton('activity-stop', tooltip=_('Stop'))
+ self.stop.props.accelerator = '<Ctrl>Q'
+ #self.stop.connect('clicked', self.__stop_clicked_cb)
+ self.insert(self.stop, -1)
+ self.stop.show()
+
+ def doexport_cb(self, button):
+ self.emit('do-import')
+
+ def doupload_cb(self, button):
+ self.emit('do-upload')
+
+ def doslideshow_cb(self, button):
+ self.emit('do-slideshow')
+