# -*- coding: iso-8859-1 -*- # # Pile 0.1.0 -- a conceptual visual sorting utility. # # Copyright (C) 2005 Björn Edström # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # try: import psyco psyco.full() except ImportError: None import pygame import wx import sys import random import glob import os import shutil # # wx. these functions are pretty ugly. # class DirDialogWindow(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY) def get(self): dlg = wx.DirDialog(self, message="Choose a dir") ret = "" if dlg.ShowModal() == wx.ID_OK: ret = dlg.GetPath() else: sys.exit() dlg.Destroy() return ret class TextEntryDialogWindow(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY) def get(self, question, title, answer): ret = "" dlg = wx.TextEntryDialog(None, question, title, answer) if dlg.ShowModal() == wx.ID_OK: ret = dlg.GetValue() dlg.Destroy() return ret def get_dir(): app = wx.PySimpleApp() frame = DirDialogWindow() ret = frame.get() frame.Destroy() app.MainLoop() return ret def gui_input(question, title, answer): app = wx.PySimpleApp() frame = TextEntryDialogWindow() ret = frame.get(question, title, answer) frame.Destroy() app.MainLoop() return ret # # pygame # pygame.init() class Pile: """Pile, a conceptual sort sorting utility. No logic-render separation, sorry.""" def __init__(self): self.size = self.width, self.height = 800, 600 self.imagesize = 100 # logic self.running = True # is the program running? self.paths = [] # full path to the images open self.images = [] # image handles self.rects = [] # image geography self.groups = [] # two-dimensional list of groups with indexes self.pickup = False # currently moving an image? self.done = False # done sorting? # window self.screen = pygame.display.set_mode(self.size) self.bgcolor = (255, 255, 255) self.screen.fill(self.bgcolor) self.forecolor = (255, 0, 0) self.status_font = pygame.font.Font(None, 30) pygame.display.set_caption("Pile") self.overlay = pygame.Surface((self.width, self.height)) self.overlay.fill((0, 0, 0)) self.overlay.set_alpha(100) self.output( "Create piles by moving images with your mouse.\n" + "When you are done press [Enter].\n\n" + "This is a slow conceptual utility. Only open folders with small files.\n" + "Select a directory with JPG-files.\n\n" ) pygame.display.flip() self.imagepath = get_dir() self.load() def output(self, text): """Print text centered on screen, supports line breaks.""" text = text.split("\n") if not len(text): return temp, h = self.status_font.size(text[0]) wpos = self.height / 2 - (h / 2) * len(text) for t in text: temp = self.status_font.render(t, 1, self.forecolor) w, h = temp.get_size() x = self.width / 2 - w / 2 wpos = wpos + h self.screen.blit(temp, (x, wpos)) def load(self): """Prepare files.""" pileborder = self.height/2 - self.imagesize self.paths = glob.glob(self.imagepath + "\\*.jpg") for x, p in enumerate(self.paths): temp = pygame.image.load(p).convert() # scale images to max width = self.imagesize or height = self.imagesize w, h = temp.get_size() if w > h: h, w = int(h / float(w) * self.imagesize), self.imagesize else: w, h = int(w / float(h) * self.imagesize), self.imagesize self.images.append( pygame.transform.scale(temp, (w, h)) ) del(temp) # rects rect = self.images[x].get_rect() position = random.randint(pileborder,self.width-self.imagesize-pileborder), \ random.randint(pileborder,self.height-self.imagesize-pileborder) rect = rect.move(position[0], position[1]) self.rects.append(rect) # print loading... \n nn% percent = int(float(x) / len(self.paths) * 100.0) self.output("Loading...\n" + str(percent) + "%") self.show(False) # this is not nice. if pygame.event.get([pygame.QUIT]): self.running = False break def movepile(self, pileindex): """Moves pile pileindex.""" path = gui_input("Please name this pile. This directory will be created in\n" + self.imagepath, "Path?", "") if not path: return dstdir = os.path.join(self.imagepath, path) if not os.path.exists(dstdir): os.mkdir(dstdir) for i in self.groups[pileindex]: filepath, filename = os.path.split(self.paths[i]) dst = os.path.join(dstdir, filename) shutil.copy2(self.paths[i], dst) # ugly but safe for i in self.groups[pileindex]: self.paths[i] = -1 self.images[i] = -1 self.rects[i] = -1 del(self.groups[pileindex]) self.paths = [s for s in self.paths if s != -1] self.images = [s for s in self.images if s != -1] self.rects = [s for s in self.rects if s != -1] # update self.getgroups() def getgroups(self): """Groups images together.""" collide = [] for r in self.rects: collide.append(r.collidelistall(self.rects)) for a in range(len(collide)): for b in range(a + 1,len(collide)): if set(collide[a]) & set(collide[b]): collide[b].extend(collide[a]) collide[a] = [] self.groups = [] for s in collide: if s: self.groups.append(list(set(s))) def show(self, showimages=True): """Paint.""" if showimages: for i in xrange(len(self.images)): self.screen.blit(self.images[i], self.rects[i]) if self.done: self.screen.blit(self.overlay, (0,0)) self.output(str(len(self.groups)) + " pile(s)...\n\nSelect a pile to move by pressing it once") # show pygame.display.flip() self.screen.fill(self.bgcolor) def eventhandler(self, event): """Event loop. Hacky.""" if event.type == pygame.QUIT: self.running = False if not len(self.images): return # no reason to create a function for this. get index of the image at pointer pos if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEBUTTONUP: pos = pygame.mouse.get_pos() for focus in reversed(xrange(len(self.images))): if self.rects[focus].collidepoint(pos): break if not self.rects[focus].collidepoint(pos): return # safe to add events after this if event.type == pygame.MOUSEBUTTONDOWN: if not self.done: # currently moving? if not self.pickup: # put moving image at the end of list for highest z-index self.images.append(self.images.pop(focus)) self.rects.append(self.rects.pop(focus)) self.paths.append(self.paths.pop(focus)) self.pickup = True else: self.pickup = False elif event.type == pygame.MOUSEBUTTONUP: if self.done: for pileindex, group in enumerate(self.groups): if focus in group: self.movepile(pileindex) break elif event.type == pygame.MOUSEMOTION: if self.pickup: pos = pygame.mouse.get_pos() x, y, w, h = self.rects[len(self.rects)-1] self.rects[len(self.rects)-1] = pygame.Rect(pos[0]-w/2, pos[1]-h/2, w, h) elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN: self.done = not self.done if self.done: self.getgroups() def main(self): while self.running: self.eventhandler(pygame.event.wait()) self.show() return 0 main = Pile() sys.exit(main.main())