from __future__ import division from PIL import Image, ImageDraw, ImageFont, ImageOps from trello import TrelloClient import os import requests from requests_oauthlib import OAuth1Session import re mydir = os.path.dirname(os.path.abspath(__file__)) def textBox(text, font, position, limits, colour, draw_black): str = "" limit = [0, 0] limit[0] = limits[0] - position[0] limit[1] = limits[1] - position[1] words = text.split(" ") i = 0 # index of first word sizex, sizey = draw_black.multiline_textsize( str, font) # size of the textbox # while more words to be added to string AND string textbox height < height limit for word in words: sizex, sizey = draw_black.multiline_textsize(str + word, font) sizex2, sizey2 = draw_black.multiline_textsize(str, font) if sizex < limit[0]: # if width of gen textbox < width limit str += word + " " else: sizex, sizey = draw_black.multiline_textsize( str + "\n" + word, font) if(sizey > limit[1]): break else: str = str.rstrip(" ") str += "\n" + word + " " sizex, sizey = draw_black.multiline_textsize(str, font) draw_black.text(position, str, font=font, fill=0) return sizex, sizey def putImage(imagepath, maskpath, position, colour): if type(imagepath) is str: img = Image.open(imagepath) else: img = imagepath if type(imagepath) is str: mask = Image.open(maskpath).convert(mode='1') else: mask = maskpath if colour is "black": image_black.paste(img, position, mask) if colour is "yellow": image_yellow.paste(img, position, mask) black_invert = ImageOps.invert(image_black.convert('L')) draw_yellow.bitmap((0, 0), black_invert, 255) def roundRect(draw, topLeft, bottomRight, arcsize, type): a = 180 arcStart = topLeft arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize) lastLineEnd = (arcStart[0], arcStart[1]+arcsize/2) lineStart = (arcStart[0]+arcsize/2, arcStart[1]) if type is 'outline': draw.arc((arcStart, arcEnd), a, a+90) elif type is 'fill': draw.ellipse(xy=(arcStart, arcEnd), fill=0) a = (a + 90) % 360 arcStart = (bottomRight[0]-arcsize, topLeft[1]) arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize) lineEnd = (arcStart[0]+arcsize/2, arcStart[1]) if type is 'outline': draw.line((lineStart, lineEnd)) elif type is 'fill': box1corner1 = lineStart lineStart = (arcEnd[0], arcEnd[1]-arcsize/2) if type is 'outline': draw.arc((arcStart, arcEnd), a, a+90) elif type is 'fill': draw.ellipse(xy=(arcStart, arcEnd), fill=0) a = (a + 90) % 360 arcStart = (bottomRight[0]-arcsize, bottomRight[1]-arcsize) arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize) lineEnd = (arcEnd[0], arcEnd[1]-arcsize/2) if type is 'outline': draw.line((lineStart, lineEnd)) lineStart = (arcEnd[0]-arcsize/2, arcEnd[1]) if type is 'outline': draw.arc((arcStart, arcEnd), a, a+90) elif type is 'fill': draw.ellipse(xy=(arcStart, arcEnd), fill=0) box2corner1 = lastLineEnd box2corner2 = lineEnd a = (a + 90) % 360 arcStart = (topLeft[0], bottomRight[1]-arcsize) arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize) lineEnd = (arcEnd[0]-arcsize/2, arcEnd[1]) draw.line((lineStart, lineEnd)) if type is 'outline': draw.arc((arcStart, arcEnd), a, a+90) elif type is 'fill': box1corner2 = lineStart draw.ellipse(xy=(arcStart, arcEnd), fill=0) draw.rectangle(xy=(box1corner1, box1corner2), fill=0) draw.rectangle(xy=(box2corner1, box2corner2), fill=0) lineStart = (arcStart[0], arcStart[1]+arcsize/2) draw.line((lineStart, lastLineEnd)) class TextWidget(): # this example widget will have a set size: 3x1 cells (one column) # an instance of this widget class can be created and pasted to canvas def __init__(self, cwidth, cheight, xy=(0, 0), dim=(1, 1), stringIn=""): self.width = dim[0] self.height = dim[1] self.wt = cwidth * dim[0] self.ht = cheight * dim[1] self.cellX = xy[0] self.cellY = xy[1] self.image_yellow = Image.new( '1', (self.wt, self.ht), 255) # 255: clear the frame self.draw_yellow = ImageDraw.Draw(self.image_yellow) self.image_black = Image.new( '1', (self.wt, self.ht), 255) # 255: clear the frame self.draw_black = ImageDraw.Draw(self.image_black) self.mask_black = Image.new('1', (self.wt, self.ht), 255) self.draw_mask_black = ImageDraw.Draw(self.mask_black) self.font = ImageFont.truetype(os.path.join(mydir, 'resources/fonts/DejaVuSans.ttf'), 16) self.stringInput = stringIn self.updateWidget() def drawText(self, str): # define parameters for text placement position = (0, 0) limit = (self.wt, self.ht) # write the string in the widget textBox(str, self.font, position, limit, "black", self.draw_black) def saveImages(self): self.image_black.save(os.path.join(mydir, '../test/imgBlackWidget.bmp')) self.image_yellow.save(os.path.join(mydir, '../test/imgYellowWidget.bmp')) def updateWidget(self): if self.stringInput is not None: self.draw_black.rectangle((0, 0, self.wt, self.ht), fill=255) self.draw_yellow.rectangle((0, 0, self.wt, self.ht), fill=255) self.drawText(self.stringInput) self.saveImages() class ImageWidget(): # this widget displays an image # needs to recieve an image file as input. Then resize and convert to monochrome. def __init__(self, cwidth, cheight, xy=(0, 0), dim=(1, 1), bwMode="mono", scaleMode="fill", img=""): self.width = dim[0] self.height = dim[1] self.wt = cwidth * dim[0] self.ht = cheight * dim[1] self.cellX = xy[0] self.cellY = xy[1] self.image_yellow = Image.new( '1', (self.wt, self.ht), 255) # 255: clear the frame self.draw_yellow = ImageDraw.Draw(self.image_yellow) self.image_black = Image.new( '1', (self.wt, self.ht), 255) # 255: clear the frame self.draw_black = ImageDraw.Draw(self.image_black) self.mask_black = Image.new('1', (self.wt, self.ht), 255) self.draw_mask_black = ImageDraw.Draw(self.mask_black) self.bwMode = bwMode self.scaleMode = scaleMode print(img) self.image = Image.open(img) self.updateWidget() def resizeImg(self, img): # scale image down until whole image fits inside the cell iRatio = img.size[0] / img.size[1] # image ratio cRatio = self.wt / self.ht # canvas ratio if cRatio > iRatio: # height is limiting dimension fixedImg = img.resize((int(self.ht * iRatio), self.ht)) return fixedImg if cRatio < iRatio: # width is limiting dimension fixedImg = img.resize((self.wt, int(self.wt / iRatio))) return fixedImg fixedImg = img return fixedImg def fillImg(self, img): # enlarge the image and crop to fill the widget canvas iRatio = img.size[0] / img.size[1] # image ratio cRatio = self.wt / self.ht # canvas ratio if cRatio > iRatio: fixedImg = img.resize((self.wt, int(self.wt / iRatio))) offset = 0.5 * (fixedImg.size[1] - self.ht) # centre by height cropped = fixedImg.crop((0, offset, self.wt, offset + self.ht - 1)) return cropped if cRatio < iRatio: fixedImg = img.resize((int(self.ht * iRatio), self.ht)) offset = 0.5 * (fixedImg.size[0] - self.wt) # centre by width cropped = fixedImg.crop((offset, 0, offset + self.wt, self.ht - 1)) return cropped return img.resize((self.wt, self.ht)) def autoThresholdLimit(self, img): gscale = img.convert(mode="L") gscale.save(os.path.join(mydir, '../test/greyscale.bmp')) extrema = gscale.getextrema() limit = extrema[0] + 0.4 * (extrema[1] - extrema[0]) self.thresLim = limit def threshold(self, val): if(val > self.thresLim): return 1 else: return 0 def bwImg(self, img): self.autoThresholdLimit(img) if self.bwMode == "mono": return img.convert('L').point(self.threshold, '1') if self.bwMode == "dither": return img.convert(mode='1') def pasteImg(self, img): iRatio = img.size[0] / img.size[1] # image ratio cRatio = self.wt / self.ht # canvas ratio if cRatio > iRatio: # needs to be centred horizontally offset = int(0.5 * (self.wt - img.size[0])) self.image_black.paste(img, (offset, 0)) if cRatio < iRatio: # needs to be centred vertically offset = int(0.5 * (self.ht - img.size[1])) self.image_black.paste(img, (0, offset)) def saveImages(self): self.image_black.save(os.path.join(mydir, '../test/imgBlackWidget.bmp')) self.image_yellow.save(os.path.join(mydir, '../test/imgYellowWidget.bmp')) def updateWidget(self): if self.image is not None: if self.scaleMode == "fill": rsImg = self.fillImg(self.image) if self.scaleMode == "resize": rsImg = self.resizeImg(self.image) if self.scaleMode == "none": rsImg = self.image newImg = self.bwImg(rsImg) self.pasteImg(newImg) self.saveImages() class TrelloWidget(): # displays your trello boards in text form # card function: Name, description in one 'cell' or box with rounded corners def __init__(self, cwidth, cheight, xy=(0, 0), dim=(1, 1), boardName=None, listName=None): self.boardName = boardName self.listName = listName self.width = dim[0] self.height = dim[1] self.wt = cwidth * dim[0] self.ht = cheight * dim[1] self.cellX = xy[0] self.cellY = xy[1] self.image_yellow = Image.new( '1', (self.wt, self.ht), 255) # 255: clear the frame self.draw_yellow = ImageDraw.Draw(self.image_yellow) self.image_black = Image.new( '1', (self.wt, self.ht), 255) # 255: clear the frame self.draw_black = ImageDraw.Draw(self.image_black) self.mask_black = Image.new('1', (self.wt, self.ht), 255) self.draw_mask_black = ImageDraw.Draw(self.mask_black) self.lastY = 0 self.cardLastY = 0 fontpath = os.path.join(mydir, 'resources/fonts/DejaVuSans.ttf') self.font1 = ImageFont.truetype(fontpath, 12) self.font2 = ImageFont.truetype(fontpath, 13) self.font3 = ImageFont.truetype(fontpath, 10) self.updateWidget() def initTrelloClient(self): print("CONNECTING TO TRELLO") self.trello_key = 'a3d8f7c04c266e5c9571f6b7747aa353' self.trello_secret = 'd4ac9968d997aa3e5a0ebf129a627b03701520de6ff19834c81d6d9c56fe7e9b' try: with open('tokenfile.secret', 'r') as file: token = file.readline().rstrip() secret = file.readline().rstrip() self.client = TrelloClient( api_key=self.trello_key, api_secret=self.trello_secret, token=token, token_secret=secret ) all_boards = self.client.list_boards() except Exception as e: print(e) print("token expired or does not exist\nCreating new token") self.access_token, self.resource_owner_key, self.resource_owner_secret = self.create_oauth_token() with open('tokenfile.secret', 'w') as file: file.write(self.access_token["oauth_token"] + "\n" + self.access_token["oauth_token_secret"]) self.client = TrelloClient( api_key=self.trello_key, api_secret=self.trello_secret, token=self.access_token["oauth_token"], token_secret=self.access_token["oauth_token_secret"] ) def create_oauth_token(self, expiration=None, scope=None, key=None, secret=None, name=None, output=True): request_token_url = 'https://trello.com/1/OAuthGetRequestToken' authorize_url = 'https://trello.com/1/OAuthAuthorizeToken' access_token_url = 'https://trello.com/1/OAuthGetAccessToken' expiration = os.environ.get('TRELLO_EXPIRATION', "30days") scope = scope or os.environ.get('TRELLO_SCOPE', 'read,write') #trello_key = key or os.environ['TRELLO_API_KEY'] #trello_secret = secret or os.environ['TRELLO_API_SECRET'] name = name or os.environ.get('TRELLO_NAME', 'py-trello') session = OAuth1Session(client_key=self.trello_key, client_secret=self.trello_secret) response = session.fetch_request_token(request_token_url) resource_owner_key, resource_owner_secret = response.get( 'oauth_token'), response.get('oauth_token_secret') print("Go to the following link in your browser:") print("{authorize_url}?oauth_token={oauth_token}&scope={scope}&expiration={expiration}&name={name}".format( authorize_url=authorize_url, oauth_token=resource_owner_key, expiration=expiration, scope=scope, name=name )) accepted = "n" while accepted.lower() == "n": accepted = raw_input('Have you authorized me? (y/n) ') #oauth_verifier = "" # if input('Have you authorized me? (y/n) ') is "y": # oauth_verifier = input('What is the PIN? ') oauth_verifier = raw_input('What is the PIN? ') session = OAuth1Session(client_key=self.trello_key, client_secret=self.trello_secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret, verifier=oauth_verifier) access_token = session.fetch_access_token(access_token_url) return access_token, resource_owner_key, resource_owner_secret def updateWidget(self): self.initTrelloClient() self.image_black = Image.new('1', (self.wt, self.ht), 255) self.mask_black = Image.new('1', (self.wt, self.ht), 255) card_list = self.cardsFromBoard(self.boardName, self.listName) self.printCards(card_list) self.image_black.save(os.path.join(mydir, '../test/card_image.bmp')) self.mask_black.save(os.path.join(mydir, '../test/mask.bmp')) #self.draw_black.rectangle((0, 0, self.wt, self.ht), fill=255) #inclusive/exclusive dimensions? #self.draw_yellow.rectangle((0, 0, self.wt, self.ht), fill=255) #textBox(content, self.font, (0,0), (self.wt, self.ht), "black", self.draw_black) def printCards(self, card_list): for cardImg in card_list: self.image_black.paste(cardImg[0], (0, self.lastY)) self.mask_black.paste(cardImg[1], (0, self.lastY)) self.lastY += cardImg[0].size[1] self.lastY = 0 def makeCard(self, card): # calculate card dimensions for an image to draw the card on # then draw the card on the widget canvas # card dimensions: wt/width, ht/height (cards as large as one cell) arcsize = 20 #23 offset = 10 #15 padding = 8 #padding = 5*arcsize/16 + offset card_wt = int(self.wt) card_ht = int(self.ht)*5 self.cardLastY = 0 + offset + padding card_image = Image.new('1', (card_wt, card_ht), 255) card_draw = ImageDraw.Draw(card_image) self.drawText(card.name, self.font1, card_draw, card_image, card_wt, offset, padding) self.cardLastY += 4 self.drawText(card.description, self.font3, card_draw, card_image, card_wt, offset, padding) card_ht = self.cardLastY+padding mask_image = Image.new('1', (card_wt, card_ht+1), 255) mask_draw = ImageDraw.Draw(mask_image) roundRect(card_draw, (offset, offset), (card_wt-offset, card_ht), arcsize, 'outline') roundRect(mask_draw, (offset, offset), (card_wt-offset, card_ht), arcsize, 'fill') card_image = card_image.crop((0, 0, card_wt, card_ht+1)) mask_image = ImageOps.invert(mask_image.convert('L')) card_image.save(os.path.join(mydir, '../test/card_image.bmp')) self.cardLastY = 0 return card_image, mask_image def drawText(self, text, font, draw, ima, card_wt, offset, padding): #self.cardLastY += 4 sizex, sizey = textBox(text, font, (offset+padding, self.cardLastY), (card_wt-(offset+padding), ima.size[1]), "black", draw) # find predicted height of textbox and set lastY to it self.cardLastY += sizey def cardsFromBoard(self, boardName, listName): cards = [] all_boards = self.client.list_boards() for board in all_boards: if board.name == boardName: slctd_board = board slctd_board.list_lists() all_lists_in_board = slctd_board.all_lists() for lst in all_lists_in_board: if lst.name == listName: card_list = lst.list_cards() for crd in card_list: cards.append(self.makeCard(crd)) return cards