Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorroot <root@ghunt-desktop.(none)>2010-08-26 04:21:13 (GMT)
committer root <root@ghunt-desktop.(none)>2010-08-26 04:21:13 (GMT)
commite46eba6769a1e12606c79d462bedeeaebae7d856 (patch)
tree73d3a4bfecc5fda76d2d220918d2ed0a47878ca0
parent2d77413f14aa899adb16217362668cf943f2d368 (diff)
a week of bug fixes after version 7 was put up
-rw-r--r--assets/closed_hand.xbm28
-rw-r--r--assets/closed_hand_mask.xbm28
-rw-r--r--dbphoto.py24
-rw-r--r--display.py135
-rw-r--r--help/xophoto.html3
-rwxr-xr-xmkdir_Pictures3
-rw-r--r--sinks.py20
-rw-r--r--xophotoactivity.py17
8 files changed, 216 insertions, 42 deletions
diff --git a/assets/closed_hand.xbm b/assets/closed_hand.xbm
new file mode 100644
index 0000000..d333d8b
--- /dev/null
+++ b/assets/closed_hand.xbm
@@ -0,0 +1,28 @@
+#define closed_hand_width 48
+#define closed_hand_height 48
+static char closed_hand_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC3, 0xE1, 0x00, 0x00,
+ 0x00, 0xC0, 0xE7, 0xF3, 0x71, 0x00, 0x00, 0xC0, 0xE7, 0xF3, 0xF9, 0x00,
+ 0x00, 0xC0, 0xE7, 0xF3, 0xF9, 0x00, 0x00, 0xC0, 0xE7, 0xF3, 0xF9, 0x00,
+ 0x00, 0xC0, 0xE7, 0xF3, 0xF9, 0x00, 0x00, 0xC7, 0xE7, 0xF3, 0xF9, 0x00,
+ 0x80, 0xCF, 0xFF, 0xFF, 0xF9, 0x00, 0x80, 0xCF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xCF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xCF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xCF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
+ 0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
+ 0x00, 0xFE, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x3F, 0x00,
+ 0x00, 0xFC, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
diff --git a/assets/closed_hand_mask.xbm b/assets/closed_hand_mask.xbm
new file mode 100644
index 0000000..0552a4e
--- /dev/null
+++ b/assets/closed_hand_mask.xbm
@@ -0,0 +1,28 @@
+#define closed_hand_mask_width 48
+#define closed_hand_mask_height 48
+static char closed_hand_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xE7, 0xF3, 0x01, 0x00,
+ 0x00, 0xE0, 0xFF, 0xFF, 0xFB, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0x01,
+ 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0x03,
+ 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0x03, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
+ 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
+ 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
+ 0x00, 0xFE, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00,
+ };
diff --git a/dbphoto.py b/dbphoto.py
index f1c7190..180ae48 100644
--- a/dbphoto.py
+++ b/dbphoto.py
@@ -119,6 +119,11 @@ class DbAccess():
cursor.execute('select * from groups where category = ?',('albums',))
return cursor.fetchall()
+ def get_albums_containing(self,jobject_id):
+ cursor = self.connection().cursor()
+ cursor.execute('select * from groups where jobject_id = ?',(jobject_id,))
+ return cursor.fetchall()
+
def get_album_thumbnails(self,album_id,is_journal=False):
if is_journal: #is_journal: #want most recent first, need left join because picture may not exist yet
#sql = """select groups.*, data_cache.picture.* from groups left join data_cache.picture \
@@ -416,6 +421,25 @@ values (?,?,?,?,?,?,?,?)""",(jobject_id,w,h,x_thumb,y_thumb,thumb_binary,transfo
cursor.execute('rollback transaction')
_logger.error('error deleting all references for object:%s. Error: ;%s'%(jobject_id,e,))
+ def set_config(self,name,value):
+ cursor = self.connection().cursor()
+ cursor.execute('select * from config where name = ?',(name,))
+ rows = cursor.fetchall()
+ if len(rows)>0:
+ cursor.execute("update config set value = ? where id = ?",(album_id,rows[0]['id']))
+ else:
+ cursor.execute("insert into config (name,value) values (?,?)",(name,value,))
+ self.con.commit()
+
+ def get_config(self,name):
+ cursor = self.connection().cursor()
+ cursor.execute('select * from config where name = ?',(name,))
+ rows = cursor.fetchall()
+ if len(rows)>0:
+ return rows[0]['value']
+ else:
+ return ''
+
def table_exists(self,table):
try:
sql = 'select * from %s'%table
diff --git a/display.py b/display.py
index 924822d..bee090e 100644
--- a/display.py
+++ b/display.py
@@ -37,6 +37,8 @@ import time
from threading import Timer
import datetime
import gobject
+#from struct import unpack
+from array import array
#application imports
from dbphoto import *
@@ -421,8 +423,11 @@ class OneAlbum():
pygame.display.flip()
def repaint_whole_screen(self):
- self._parent.paint_albums()
- self.repaint()
+ if self.large_displayed:
+ pass
+ else:
+ self._parent.paint_albums()
+ self.repaint()
def release_cycles(self):
while gtk.events_pending():
@@ -471,6 +476,8 @@ class OneAlbum():
"""album name is title stored in groups.jobject_id where category='albums'
This should be called after display thumbnails because the thumbnail routine
counts the number of images which this routine displays to the user
+ comment: this requirement led to Bug 2234 (incorrect count on drop image) -- so
+ count the thumbnails whenever album (left side) is displayed
"""
surf = pygame.Surface((album_column_width,album_height))
@@ -482,7 +489,10 @@ class OneAlbum():
else:
surf.fill(album_background_color)
album_id = rows[index]['subcategory']
- count = rows[index]['seq']
+
+ #bug 2234 thumbnail count incorrect until click on left column
+ count = self.db.get_thumbnail_count(rows[index]['subcategory'])
+
album = rows[index]['jobject_id']
if album_id == trash_id:
@@ -1045,44 +1055,87 @@ class DisplayAlbums():
def start_grab(self,x,y):
global in_grab
- self.start_grab_x = x
- self.start_grab_y = y
+ _logger.debug('start_grab x:%s y:%s in_grab %s'%(x,y,in_grab,))
#change the cursor some way
if in_grab:
in_grab = False
self._activity.window.set_cursor(None)
+ self.drop_image(self.start_grab_x,self.start_grab_y,x,y)
else:
in_grab = True
+ self.start_grab_x = x
+ self.start_grab_y = y
fn = os.path.join(os.getcwd(),'assets','closed_hand.xbm')
- #fn = os.path.join(os.getcwd(),'assets','handcursor.xbm')
- fd = open(fn,'r')
- bitstring = fd.read()
- #_logger.debug('cursordata:%s'%bitstring)
+ bitstring = self.xbm_to_data(fn)
+ if len(bitstring) == 0: return #error state
bitpattern = gtk.gdk.bitmap_create_from_data(None,bitstring,48,48)
fn = os.path.join(os.getcwd(),'assets','closed_hand_mask.xbm')
- #fn = os.path.join(os.getcwd(),'assets','handcursor_mask.xbm')
- maskfn = fn
- fd = open(fn,'r')
- maskstring = fd.read()
+ maskstring = self.xbm_to_data(fn)
+ if len(maskstring) == 0: return #error state
bitmask = gtk.gdk.bitmap_create_from_data(None,maskstring,48,48)
- #a, b, c, d = pygame.cursors.load_xbm(patfn,maskfn)
- #pygame.mouse.set_cursor(a,b,c,d)
-
-
- self._activity.window.set_cursor(gtk.gdk.Cursor(bitpattern,bitmask,gtk.gdk.Color(255,255,255),gtk.gdk.Color(0,0,0),24,24))
- #self._activity.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.DOTBOX))
+ self._activity.window.set_cursor(gtk.gdk.Cursor(bitmask,bitpattern,\
+ gtk.gdk.Color(255,255,255),gtk.gdk.Color(255,255,255),24,24))
+ def xbm_to_data(self,xbm_file):
+ if not os.path.isfile(xbm_file):return ''
+ fd = open(xbm_file,'r')
+ lines = fd.readlines()
+ byte_array = ()
+ w = None
+ h = None
+ index = 0
+ byte_array = array('B')
+ rtn_val = ''
+ for l in lines:
+ if len(l.strip()) == 0: continue
+ if l[0] == '#':
+ lh = l.find('height')
+ if lh > -1:
+ h = int(l[lh+6:])
+ lw = l.find('width')
+ if lw > -1:
+ w = int(l[lw+5:])
+ else:
+ if not (w and h): return ''
+ if index == 0:
+ num_bytes = w * h / 8
+ #examine a case where there might be a hex number after bracket
+ brac = l.find('{')
+ if brac > -1:
+ if brac < len(l)-2: #shift it
+ l = l[brac+1:]
+ else:
+ continue
+ brac = l.find('}')
+ if brac > -1: continue
+ hex_list = l.split(', ')
+ for i in range(len(hex_list)):
+ chunk = hex_list[i].strip()
+ if len(chunk) == 0: continue
+ if chunk[-1:] == ',':
+ chunk = chunk[:-1]
+ try:
+ pass
+ byte_array.append(int(chunk,16))
+ #hex_val = int(chunk,16)
+ except Exception,e:
+ _logger.debug('hex parse error: %s Input: %s line:%s'%(e,chunk,l,))
+ rtn_val += chunk + ', '
+ index += 1
+ return byte_array
+
def drop_image(self, start_x,start_y,drop_x, drop_y):
self._activity.window.set_cursor(None)
+ _logger.debug('drop_image start_x:%s start_y:%s drop_x:%s drop_y:%s'%(start_x,start_y,drop_x,drop_y,))
if drop_x < album_column_width: #we are dropping on album side of screen
jobject_id = self.album_objects[self.selected_album_id].get_jobject_id_at_xy(start_x,start_y)
index = self.get_album_index_at_xy(drop_x, drop_y)
if not index or not jobject_id: return
-
current_album_id = self.get_current_album_identifier()
+ _logger.debug('drop_image index:%s jobject_id:%s current_album_id:%s'%(index,jobject_id,current_album_id,))
#if item dragged from trash, put it back in journal_id as well as target stack
if current_album_id == trash_id:
@@ -1261,8 +1314,11 @@ class Application():
album_id = self.album_collection.selected_album_id
album_object = self.album_collection.album_objects.get(album_id,None)
if not album_object: return
- album_object.repaint_whole_screen()
- _logger.debug('pygame_repaint completed')
+ if album_object.large_displayed:
+ self.vs.display_large()
+ else:
+ album_object.repaint_whole_screen()
+ _logger.debug('pygame_repaint completed')
return False
def do_startup(self):
@@ -1302,8 +1358,11 @@ class Application():
self.util.remove_alert(alert)
#if the picture table is empty, populate it from the journal, and initialize
- if ds_count < 10:
+ #fix for bug 2223 loads images repeatedly into journal
+ start_images_loaded = self.db.get_config('image_init')
+ if ds_count < 10 and start_images_loaded == '':
self.first_run_setup()
+ self.db.set_config('image_init','True')
self.album_collection = DisplayAlbums(self.db, self._activity)
self.album_collection.paint_albums()
@@ -1500,6 +1559,7 @@ class Application():
pygame.display.flip()
def process_mouse_click(self,event):
+ global in_grab
x,y = event.pos
butt = event.button
if butt == 4:
@@ -1509,18 +1569,23 @@ class Application():
self.album_collection.album_objects[self.album_collection.selected_album_id].scroll_down()
else:
l,m,r = pygame.mouse.get_pressed()
- print('mouse single click')
- if x < album_column_width -thick:
- #scroll_x,scroll_y = self.album_collection.album_sb.get_scrolled()
- rtn_val = self.album_collection.click(x,y)
- if not rtn_val:
- #create a new album
- pass
- elif x > album_column_width and x < (screen_w - thick):
- if l:
- self.album_collection.album_objects[self.album_collection.selected_album_id].click(x,y)
- elif r:
- self.album_collection.start_grab(x,y)
+ _logger.debug('mouse single click')
+ if r:
+ self.album_collection.start_grab(x,y)
+ else:
+ if x < album_column_width -thick:
+ #scroll_x,scroll_y = self.album_collection.album_sb.get_scrolled()
+ rtn_val = self.album_collection.click(x,y)
+ if not rtn_val:
+ #create a new album
+ pass
+ elif x > album_column_width and x < (screen_w - thick):
+ if l:
+ if in_grab: #initiated by a right click
+ self._activity.window.set_cursor(None)
+ in_grab = False
+ else:
+ self.album_collection.album_objects[self.album_collection.selected_album_id].click(x,y)
pygame.display.flip()
def process_mouse_double_click(self,event):
diff --git a/help/xophoto.html b/help/xophoto.html
index db08022..7b1edd7 100644
--- a/help/xophoto.html
+++ b/help/xophoto.html
@@ -53,7 +53,8 @@ Images can be removed from the stack by dragging them to the trash. When items
<p>Thereafter, whenever you export a stack of pictures, by default they will be placed in the &quot;/home/olpc/Pictures&quot; folder, and they will be available from to the Browse Activity for upload .</p>
<h3>How to Help Improve XoPhoto . . .Report Bugs </h3>
<p>You can help make XoPhoto better by reporting the Bugs, and suggestions you have for improvement. Fill out a Bug Report at </p>
-<p>https://bugs.sugarlabs.org/newticket?component=XoPhoto</p>
+<p><a href="http://bugs.sugarlabs.org/newticket?component=XoPhoto>
+http://bugs.sugarlabs.org/newticket?component=XoPhoto</a>
<p>or send email with &quot;XoPhoto Bug&quot; in the title to &quot;georgejhunt@gmail.com&quot; describing the problem, and the software Build number from &quot;My Settings&quot; -- &quot;About My Computer&quot; (described in next paragraph). </p>
<p> The author of XoPhoto will make every effort to provide quick turnaround on fixes, and ensure that the software update activity function provides a convenient means of obtaining the improved activity (From the circle home screen, right click on the XO figure in the center, left click on &quot;My Settings&quot;, left click on &quot;softare update&quot; -- on a recent build you need to use the horizontal scroll bar to make &quot;software Update&quot; visible). </p>
<p>&nbsp;</p>
diff --git a/mkdir_Pictures b/mkdir_Pictures
new file mode 100755
index 0000000..59bcbc5
--- /dev/null
+++ b/mkdir_Pictures
@@ -0,0 +1,3 @@
+#bash file to create Pictures folder, and set world writeable permissions
+mkdir -p -m 777 /home/olpc/Pictures
+
diff --git a/sinks.py b/sinks.py
index 6b8a389..eacdd6d 100644
--- a/sinks.py
+++ b/sinks.py
@@ -52,6 +52,7 @@ class ViewSlides():
gobject.timeout_add(1000, self.__timeout)
self.current_time = 1 #set so the first call of timeout will initiate action
self.running = False
+ self.paint = None
#display.screen.fill((0,0,0))
#pygame.display.flip()
@@ -82,11 +83,9 @@ class ViewSlides():
jobject_id = self.rows[self.index]['jobject_id']
if not jobject_id: return
- paint = self.transform_scale_slide(jobject_id)
- if paint:
- display.screen.blit(paint,(0,0))
- pygame.display.flip()
-
+ self.paint = self.transform_scale_slide(jobject_id)
+ if self.paint:
+ self.display_large()
self.index += 1
if self.index == len(self.rows):
if self.loop:
@@ -95,6 +94,12 @@ class ViewSlides():
self.index -= 1
self.album_object.thumb_index = self.index
+ def display_large(self):
+ self.album_object.large_displayed = True
+ display.screen.blit(self.paint,(0,0))
+ pygame.display.flip()
+
+
def transform_scale_slide(self,jobject_id):
"""return surface transformed per database transforms,onto screen sized target"""
_logger.debug('entered transform_scale_slide')
@@ -176,6 +181,10 @@ class ViewSlides():
return target_surf
def run(self):
+ if len(self.rows) == 0:
+ self._parent.util.alert(_('Please select a stack that contains images'),_('Cannot show a slideshow with no images'))
+ self._parent._activity.use_toolbar.set_running(False)
+ return
self.running = True
self.paused = False
_logger.debug('started the run loop')
@@ -222,6 +231,7 @@ class ViewSlides():
def stop(self):
self.running = False
#'gtk.STOCK_MEDIA_STOP'
+ self.album_object.large_displayed = False
self.album_object.repaint_whole_screen()
diff --git a/xophotoactivity.py b/xophotoactivity.py
index a2c6558..a6f2a3e 100644
--- a/xophotoactivity.py
+++ b/xophotoactivity.py
@@ -256,7 +256,22 @@ class XoPhotoActivity(activity.Activity):
self.game.album_collection.delete_album(album_id)
def activity_toolbar_empty_trash_cb(self):
- self.util.confirmation_alert(_('Are you sure you want to proceed?'),\
+ rows = self.DbAccess_object.get_album_thumbnails(trash_id)
+ number_of_references = 0
+ for row in rows:
+ jobject_id = str(row['jobject_id'])
+ album_rows = self.DbAccess_object.get_albums_containing(jobject_id)
+ _logger.debug('album count:%s for jobject_id %s'%(len(album_rows),jobject_id,))
+ for album_row in album_rows:
+ if album_row['category'] == journal_id: continue
+ if album_row['category'] == trash_id: continue
+ number_of_references += 1
+ if number_of_references > 0:
+ number = str(number_of_references)
+ detail = _('These images are used in ') + number + _(' other stacks and will be deleted from them also.')
+ else:
+ detail = _('Are you sure you want to proceed?')
+ self.util.confirmation_alert(detail,\
_('Warning! you are about to completely remove these images from your XO.'),\
self.empty_trash_cb)