Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Containers for photos, with scroll, drag&drop between them
-
Hello folks,
I am not happy with the various solutions to manage my photos on the ipad. I would like to have a panel with
- a container with a thumbnail of my photos, in 1 d, scollable
- several containers in which i can d&d the photos I want to assemble
- for each of those a page in wich i can arrange the selected photos in the layout i want
- save the layout in camera roll when i am happy with it
- save all the containers data so i can rework my choices.
ideally all these is real time. This is to prepare my printed photoalbums before ordering them (the online tools are way too slow and cumbersome and unaccurate to do the preparation work on them - I usually use ‘Print To Size’ app for that).
I think pythonista is able to do that. But i am not (yet).Could you guys point out for me some existing code bricks you know about that I could re-use and assemble for this project?
I already heave found some excellent gesture repos, but not the scrollable photo container. I could do it myself but considering my poor python skills it is going to take me 2 or 3 weeks, so if i can use already available code, that’s good to spare the effort for the overall app. And i have noticed it is much easier for me to start from someone else good code and bend it to my will, rather than starting from scratch myself.Please help me!
Thanks -
@jmv38 Have à look to this, far from perfect, little script
# todo # - ImageView zoom and scroll # - limit au min from Gestures import Gestures from objc_util import * import os import photos import ui class PhotosPickerTableView(ui.View): def __init__(self,w,h,d): self.width = w self.height = h self.selected = None ok = ui.ButtonItem() ok.image = ui.Image.named('iob:ios7_checkmark_outline_32') ok.action = self.ok_action self.right_button_items = (ok,) tbl = ui.TableView() tbl.frame = (0,0,d,h) tbl.row_height = d self.assets = photos.get_assets() tbl.data_source = ui.ListDataSource(items=range(len(self.assets))) tbl.separator_color=(1,0,0,0) tbl.delegate = self tbl.data_source.tableview_cell_for_row = self.tableview_cell_for_row tbl.background_color = (0,0,0,0) self.add_subview(tbl) pha =ui.View(name='photo_area') pha.frame = (d,0,w-d,h) self.add_subview(pha) img = ui.ImageView(name='selected_photo') img.frame = (0,0,w-d,h) img.content_mode=ui.CONTENT_SCALE_ASPECT_FIT img.background_color = (0.5,0.5,0.5,1) pha.add_subview(img) self.tableview_did_select(tbl,0,0) self.photo_scale = 1 self.g = Gestures() self.pinch = self.g.add_pinch(img,self.did_pinch) self.pan = self.g.add_pan(img,self.did_pan,1,1) def did_pinch(self, data): if data.state == 1: self.scale_at_begin = self.photo_scale s = data.scale s_new = self.photo_scale * s #print('did_pinch: data.scale=',s,'scale:',self.photo_scale,'-->',s_new,'data.state=',data.state) if s_new < 1 and self.scale_at_begin >= 1: # minimum size 1 s_new = 1 s = 1 / self.photo_scale #if s_new > 20: # return self.photo_scale = s_new #print('scale',s_new) self['photo_area']['selected_photo'].transform = self['photo_area']['selected_photo'].transform.concat(self['photo_area']['selected_photo'].transform.scale(s,s)) self.pinch.scale = 1.0 def did_pan(self, data): #print('before',self['photo_area']['selected_photo'].frame) o = data.translation o = o * self.photo_scale # to accelerate move if zoom #print('translation',o) if ((self['photo_area']['selected_photo'].frame[0]+o[0]) > 0) or ((self['photo_area']['selected_photo'].frame[1]+o[1]) > 0): return self['photo_area']['selected_photo'].transform = self['photo_area']['selected_photo'].transform.concat(self['photo_area']['selected_photo'].transform.translation(*o)) self.p = CGPoint(0,0) self.pan.setTranslation_inView_(self.p,self.pan.view()) #print('after',self['photo_area']['selected_photo'].frame) def ok_action(self,sender): self.close() def tableview_did_select(self, tableview, section, row): ui_image = self.assets[row].get_ui_image() self['photo_area']['selected_photo'].image = ui_image self.selected = row def tableview_cell_for_row(self,tableview, section, row): cell = ui.TableViewCell() v = 0.4 + (row % 2)*0.4 cell.bg_color = (v,v,v,v) selected_cell = ui.View() #selected_cell.bg_color = 'blue' selected_cell.border_width = 2 selected_cell.border_color = 'blue' cell.selected_background_view = selected_cell photo = ui.ImageView() photo.frame = (0,0,tableview.row_height,tableview.row_height) photo.image = self.assets[row].get_ui_image() photo.content_mode = ui.CONTENT_SCALE_ASPECT_FIT cell.content_view.add_subview(photo) return cell def main(): # Hide script w,h = ui.get_screen_size() mi = min(w,h)*0.9 my_back = PhotosPickerTableView(mi,mi,100) my_back.background_color='white' my_back.name = 'Photos Picker via TableView' my_back.present('sheet',hide_title_bar=False) my_back.wait_modal() # Protect against import if __name__ == '__main__': main()
-
@cvp wow! that was fast!!!
thanks a lot. This is pretty much exactly the first container i needed.
Now i should add other empty containers (perpendicular) and the drag and drop mechanism.
This really a good kickstart, thanks! -
@jmv38 That has been fast because I already had written it 😂
-
@jmv38 Little code, extracted from a big program, to show how to select a grid type.
Some code may seem strange because used by removed parts.
My big program, not yet ended, is used by my wife to join pictures in several different grids with moving lines. Program not completely operational and with' French messages.
import ImageFont import ui def my_list_popover(elements,val=None,x=None,y=None,color='white',multiple=False,title=None,selectable=True): global grids class my_list(ui.View): def __init__(self,selected): # In multiple selection mode, an "ok" button is needed if multiple: ok_button = ui.ButtonItem() ok_button.title = 'ok' ok_button.action = self.ok_button_action self.right_button_items = [ok_button] # ListDataSource for clients TableView elements_lst = ui.ListDataSource(items=lst) # ListDataSource has no attribute "name" elements_lst.delete_enabled = False elements_lst.move_enabled = False elements_txt = ui.TableView(name='elements_txt') elements_txt.allows_multiple_selection = multiple elements_txt.allows_selection = selectable elements_txt.text_color = 'black' elements_txt.font= ('Courier',12) if elements == grids: elements_txt.row_height = 128 else: elements_txt.row_height = elements_txt.font[1] * 2 h = len(lst) * elements_txt.row_height font = ImageFont.truetype('Courier',16) w = 0 if elements == grids: ws, hs = ui.get_screen_size() w = int(elements_txt.row_height*ws/hs) h_ele = elements_txt.row_height h_max = h_screen*0.9 else: for element in lst: w_ele,h_ele = font.getsize(element) if w_ele > w: w = w_ele w = w + 32 h_max = h_screen*0.8 elements_txt.content_size = (w,h) if h > h_max: h = h_max w_max = w_screen - 20 if w > w_max: w = w_max elements_txt.frame = (4,4,w,h) #elements_txt.border_color = 'blue' #elements_txt.border_width = 3 elements_txt.data_source = elements_lst elements_txt.data_source.tableview_cell_for_row = self.tableview_cell_for_row if not multiple: if selected >= 0: elements_txt.selected_row = (0,selected) else: if selected_lst != []: elements_txt.selected_rows = selected_lst elements_txt.delegate = self self.add_subview(elements_txt) self.width = elements_txt.width + 8 self.height = elements_txt.height + 8 self.corner_radius = 20 self.border_color = 'blue' self.border_width = 2 self.background_color = color if title and multiple: # title bar displayed in multiple self.name = title self.pending = [] def ok_button_action(self,sender): lst.append(str(multiples)) # '['xxxx','yyyy']' v.close() def tableview_did_deselect(self, tableview, section, row): # Called when a row was deselected (multiple only) if multiple: multiples.remove(lst[row]) #print(multiples) def tableview_did_select(self, tableview, section, row): # Called when a row was selected if multiple: multiples.append(lst[row]) # ['xxxx','yyyy'] #print(multiples) else: lst.append(lst[row]) v.close() def tableview_accessory_button_tapped(self, tableview, section, row): # Called when the accessory (ex: info) tapped if multiple: multiples.append('tapped'+lst[row]) lst.append(str(multiples)) # '['xxxx','yyyy','tapped.....']' else: lst.append('tapped'+lst[row]) # 'tapped...' v.close() def tableview_cell_for_row(self, tableview, section, row): cell = ui.TableViewCell() selected_cell = ui.View() selected_cell.border_color = 'black' selected_cell.border_width = 2 selected_cell.corner_radius = 10 data = tableview.data_source.items[row] if elements == grids: selected_cell.bg_color = 'lightgray' else: selected_cell.bg_color = 'cornflowerblue' cell.selected_background_view = selected_cell cell.text_label.text = data cell.text_label.alignment = ui.ALIGN_LEFT ch1 = cell.text_label.text[0] # 1st char of text if ch1 == '🈸': g = ui.Button() g.background_color = 'white' g.image = ui.Image.named('iob:grid_32') g.width = 28 g.height = 28 g.x = 12 g.y = 0 cell.content_view.add_subview(g) cell.text_label.text_color = 'black' cell.bg_color = color if elements == grids: cell.text_label.text = '' h = tableview.row_height w = tableview.width rows = data.split('/') ny = len(rows) x0 = 2 y0 = 5 dy = int((h-2*y0)/ny) y = y0 bs = {} for iy in range(0,ny): # loop on rows x = x0 nx = len(rows[iy]) # should be equal on each row dx = int((w-2*x0)/nx) for ix in range(0,nx): c = rows[iy][ix] # cell n° if c not in bs: b = ui.Button() b.enabled = False if ix < (nx-1): wx = dx else: wx = (w-2*x0) - x # force all last right same b.border_width = 1 b.border_color = 'blue' b.title = c b.tint_color ='blue' b.frame = (x,y,wx,dy) else: # split cell on several rows or columns: dx or dy will change b = bs[c] if b.y == y: b.width = (x + wx - 1) - b.x + 1 # same row else: b.height = (y + dy - 1) - b.y + 1 # same col bs[c] = b k = 'Photo ' + c cell.content_view.add_subview(b) x = x + dx - 1 y = y + dy - 1 del bs return cell lst = [] i = 0 selected = -1 if type(elements) is dict: elements_list = sorted(elements.keys()) elif type(elements) is list: elements_list = elements if multiple: # val = ['xxx','yyy'] if val: # not None val_lst = ast.literal_eval(val) # convert str -> dict else: val_lst = [] selected_lst = [] multiples = [] for element in elements_list: lst.append(element) if not multiple: if element == val: selected = i else: if element in val_lst: selected_lst.append((0,i)) multiples.append(element) i = i + 1 w_screen,h_screen = ui.get_screen_size() if not x: x = int(w_screen/2) if not y: y = int(h_screen/2) v = my_list(selected) # lst already copied into elements_lst, thus we can change lst # to contain iniital value in case of tap outside if selected == -1: lst.append('') else: lst.append(lst[selected]) if not multiple: v.present('popover',popover_location=(x,y),hide_title_bar=True) else: v.present('popover',popover_location=(x,y),hide_title_bar=False) v.wait_modal() return lst[len(lst)-1] # return last def main(): global grids w,h = ui.get_screen_size() mi = min(w,h)*0.9 mv = ui.View() mv.frame = (0,0,mi,mi) mv.name = 'Example of grids button' mv.background_color = 'white' grid = ui.ButtonItem() grid.image = ui.Image.named('iob:grid_32') grids = ['1', '12', '1/2','123', '11/23', '12/33', '1/2/3', '12/32', '12/13', '1234', '111/234', '12/34', '123/444', '11/22/34', '12/33/44', '11/23/44', '1/2/3/4', '12/13/14', '12/32/42', '11/23/43', '123/143', '123/425', '1234/5678/9abc/defg', '12345/67890/abcde/fghij/klmno'] #, '5', '1/4', '4/1', '2/3', f'3/2', '1/1/3', '1/3/1', '2/1/2', '2/2/1', '1/2/2', '3/1/1','1/1/1/2', '1/1/2/1', '1/2/1/1', '2/1/1/1', '1/1/1/1/1'] def grid_action(sender): x = mi - 50 y = 50 g = my_list_popover(grids,-1,x=x,y=y,color='white') grid.action = grid_action mv.right_button_items = (grid,) mv.present('sheet') # Protect against import if __name__ == '__main__': main()
-
I could agree to put in GitHub my program but I don't want to provide any support because it often crashes with big or multiple photos 😢 and I don't have enough free time to debug it.
-
See also PhotosPickerView.py
-
@cvp thanks for the 2nd example, it is inspiring.
Actually i dont use grids i prefer to place and zoom and cut each photo manually.
If you put your whole program in github i certainly wont demand support!
I think I’ll post my work step by step in gists and this forum as I progress. Not sure how fast I’ll go though...
Btw, I am french too (Grenoble) so no pb with the langage. I’ll continue anyway to write in english by respect for people in this forum.
Thanks.
-
@cvp from the doc, it seems the tableView is vertical only. Is there a way to have is horizontal too? Initially i wanted my camera roll container to be vertical, but the photo group containers to be horizontal (and themselves inside a scrollable vertical container too).
So i should pbly use a scrollview instead for my base photo container? -
@cvp mmm PhotosPickerView.py seems a bit over my head, with all this objC functions...
-
-
@jmv38 said:
seems a bit over my head, with all this objC functions...
It is not so complex as it seems...
-
@cvp thanks for juxtapositionDePhotos.py
all the bricks i need are there
=> this is really going to help me.
thanks. -
@jmv38 quick and dirty horizontal scroll view
import photos import ui def main(): assets = photos.get_assets() #assets = photos.pick_asset(assets=assets,multi=True) d = 100 wsv = min(400,d * len(assets)) sv = ui.ScrollView() sv.frame = (0,0,wsv,d) x = 0 for i in range(0,len(assets)): b = ui.Button(name=str(i)) #b = ui.ImageView(name=str(i)) b.border_color = 'blue' b.border_width = 1 ui_image = assets[i].get_ui_image().with_rendering_mode(ui.RENDERING_MODE_ORIGINAL) w,h = ui_image.size w = d * w/h b.frame = (x,0,w,d) b.image = ui_image def b_action(sender): sender.superview.name = 'tapped=' + sender.name b.action = b_action sv.add_subview(b) x = x + w sv.content_size = (x,d) sv.present('sheet') if __name__ == '__main__': main()
-
@cvp excellent! thanks.
It is nice to have the function in a really small piece of code.
I had to limit the number of photos because i have 1600 and that crashes my app. I’ll manage that.Another thing: for the interractions between panels i learned (from coding in Codea) that it is much easier to wire things together via a register/trigger message system. Do you have something available?
-
@jmv38 said:
it is much easier to wire things together via a register/trigger message system. Do you have something available?
No, never done like that. I always use normal way of ui. Good luck
-
-
@cvp here an example of a very simple event class i found on google and modified for my needs. I also added an example of usage.
class EventHook(object): def __init__(self): self.__handlers = [] def __iadd__(self, handler): self.__handlers.append(handler) return self def __isub__(self, handler): self.__handlers.remove(handler) return self def __call__(self, *args, **keywargs): for handler in self.__handlers: handler(*args, **keywargs) def clearObjectHandlers(self, inObject): for theHandler in self.__handlers: if theHandler.im_self == inObject: self -= theHandler if __name__ == '__main__': class Watcher(): def __init__(self): self.incoming = EventHook() self.leaving = EventHook() class Greeter(): def __init__(self,me): self.me = me def sayHello(self, name): print('{}: hello Mister {}'.format(self.me,name)) def sayGoodbye(self, name): print('{}: goobye Mister {}'.format(self.me,name)) class Maid(): def __init__(self,me): self.me = me def sayHello(self, name): print('{}: hello Mister {}'.format(self.me,name)) def sayGoodbye(self, name): if name != 'DSK': print('{}: goobye Mister {}'.format(self.me,name)) else: print('{}: You f... s.. o. a b...!'.format(self.me)) from console import clear clear() Isee = Watcher() deskMan = Greeter('desk man Georges') maid = Maid('maid Sandra') # make the connections: Isee.incoming += lambda name: print('\nM. {} is comming...'.format(name)) Isee.incoming += deskMan.sayHello Isee.incoming += maid.sayHello Isee.leaving += lambda name: print('\nM. {} is leaving...'.format(name)) Isee.leaving += maid.sayGoodbye Isee.leaving += deskMan.sayGoodbye # remove listener from the event # fire event Isee.incoming('Clinton') Isee.leaving('Clinton')
-
@jmv38 Too complex for me 😀
-
@cvp i can understand what you feel: it is exactly what i thought the first time saw this! It took me several months to start to understand it, but once i did, bam!, it opened a huge box of possibilties because it made a complex project so much simpler. But I wont try to convert you, you’ll see for yourself as i use it in my project if you think it is usefull...
Thanks.