# THIS FILE IS A PART OF VCStudio # PYTHON 3 import os import cairo #UI modules from UI import UI_elements from UI import UI_color #settings from settings import talk def get_list(filepath): # This fucntion converts text documents. (.progress) into a more machine # friendly recursive dict. # In the original organizer evaluation of the checklist was done separatelly # in a different function. Which made it very messy for recursive stuff. # I will attemp to combine it. And make it more clean as a result. checklist = { "fraction":0.0, # The percentage of a checklist. From 0.0 to 1.0 "string":filepath, "editing":False,# Whether the string is during editing. UI. "selected":False,# Whether the current task is selected. "open":True, # Whether to draw the suptasks. UI. "subtasks":[] # List of subtastks. (In the same format as the checklist) } data = open(filepath) data = data.read() data = data.split("\n") # Let's filter out all the comments. Lines starting with #. For some reason # in the old organizer. I just thought it wasn't important. LOL. And just # started reading from the 10th line. # Here is an example of the first 9 lines. # 1 #### Blender orgainizer checklist format # 2 #### INDINTATION (4 SPACES LONG) # 3 #### STR means Start date of the ASSET # 4 #### FIN means Finish deadline of the asset # 5 #### [ ] means that task is on list # 6 #### [V] means that tast is finished # 7 #### DO NOT USE EMPTY LINES # 8 STR 24/02/2020 # 9 FIN 01/05/2021 # You can see a trace from a very long time ago. From the first versions # of the blender organizer. The STR and FIN values. Which are start and # deadline of the project. # I guess we need to filter it out a bit differently. Checking whether a # given line starts with a [ or with a number of spaces and [. This will # make the file a little more open to editing by hand. Without too much # worrying that something will break. cleandata = [] for line in data: # So not to mangle the line. tmp = line while tmp.startswith(" "): tmp = tmp[1:] # Checking if tmp.startswith("[ ]") or tmp.startswith("[V]"): cleandata.append(line) # Now since we have the cleandata. We can try to parse it somehow into a # checklist thing. For this we need a reqursion. I gonna use a method from # the blender-organizer. By running the function with in itself. It's not # very wise. I know. In python3 it will give you no more then 996 recursions # untill it will declare an error. BUT. It's 996 layers of a subtaks. # For now I don't see a need in so many subtasks. Let's think of layers of # subtasks as of memory. This laptop has only 8 GB of RAM. I can't put more # data into it even if I wanted. def convert(part, indent=0): # If a thing have subtasks it should not even be a checkbox. So trying # To evaluate it's fraction by whether it's a [ ] or [V] shoun't be # done. subtask = [] for num, line in enumerate(part): # Let's get the NEXT line. I'm not kidding. We gonna work with the # next line to see whether to count this lines [V] if line[indent:].startswith("["): thisline = { "fraction":0.0, "string":line[line.find("]")+2:], "editing":False, "selected":False, "open":False, "subtasks":[] } try: nextline = part[num+1] except: nextline = "" if not line[line.find("]")+1] == ".": thisline["open"] = True if nextline.find("[")-1 <= indent: if line[indent:].startswith("[V]"): thisline["fraction"] = 1.0 else: subpart = [] subdent = indent for n, l in enumerate(part[num+1:]): if n == 0: subdent = l.find("[") if l.find("[")-1 <= indent: break else: subpart.append(l) #print(subpart) thisline["subtasks"] = convert(subpart, subdent) fracs = [] for task in thisline["subtasks"]: if not task["string"].startswith("#"): fracs.append(task["fraction"]) try: thisline["fraction"] = sum(fracs) / len(fracs) except: thisline["fraction"] = 0.0 # Sometime it was showing 99% when infect it's 100% if thisline["fraction"] == 0.9999: thisline["fraction"] = 1.0 subtask.append(thisline) return subtask checklist["subtasks"] = convert(cleandata) fracs = [] for task in checklist["subtasks"]: if not task["string"].startswith("#"): fracs.append(task["fraction"]) try: checklist["fraction"] = sum(fracs) / len(fracs) except: checklist["fraction"] = 0.0 # Sometime it was showing 99% when infect it's 100% if checklist["fraction"] == 0.9999: checklist["fraction"] = 1.0 return checklist def get_fraction(win, path): ############################################################################ # This function will return a fraction of a given checklist. It will ignore # complitelly what is the asset / shot / project the checklist is from. # For sake of making this function actually somewhat useful, aka not bloated # I will use it to cashe the whole checklist data objects. So they could be # easily accesable later on. # This is usefull so I would not need to create 2 cash data structures. One # for fractions and for the checklists them selves. ############################################################################ if path not in win.checklists: # Let's check if path exists in the project first. if os.path.exists(win.project+"/"+path): win.checklists[path] = get_list(win.project+"/"+path) else: win.checklists[path] = get_list(path) # Let's now return back the fraction return win.checklists[path]["fraction"] def filter_tasks(data): ############################################################################ # This function going to remove all selections and all edits from a given # checklist. This is nessesary for being able to select one task without # manually deselcting the other. And since the checklist is recursive it's # not a trivial task. ############################################################################ data["selected"] = False data["editing"] = False if data["subtasks"]: for i in data["subtasks"]: filter_tasks(i) def save(path, data): ############################################################################ # This funtion will save the checklist into a .progress file. Formatted # similarly as in the old Blender-Organizer. But as easy to understand. # # NOTE: I will remove some stuff from the file. Mainly the comments in the # beginning and the STR and END data. Which are no longer needed in a # VCStudio project. # # But since some part of Legacy Organizer relies on those, it's not adviced # to open the projects that you are planning to work on with Blender-Organizer # in VCStudio. ############################################################################ indent = [0] lines = [] def writetasks(tasks): for task in tasks: if task["fraction"] and not task["subtasks"]: v = "[V]" else: v = "[ ]" if task["open"]: o = " " else: o = "." lines.append(" "*indent[0]+v+o+task["string"]) if task["subtasks"]: indent[0] = indent[0] + 4 writetasks(task["subtasks"]) indent[0] = indent[0] - 4 writetasks(data["subtasks"]) # Writting to file w = open(path, "w") for i in lines: w.write(i+"\n") w.close() def draw(outlayer, win, path): ############################################################################ # This function will draw the checklists to the screen. # You probably noticed that there is no x, y, width or height values that # you can input. This due to one idea that I have in mind. # I'm planning to make scheduling a kind of interactive process rather then # a boring date selection. Something that's going to be somewhat fun to do. # The idea is to grab the task and to drug it outside the checklist. Which # will call for an analytics window to show up, where you can put the # schedules into a given date. # For this reason the checklist should always be in the same spot on the # screen. Which is a (window width) / 4 at the right side. ############################################################################ if path not in win.checklists: # Let's check if path exists in the project first. if os.path.exists(win.project+"/"+path): win.checklists[path] = get_list(win.project+"/"+path) else: win.checklists[path] = get_list(path) x = win.current["w"] / 4 * 3 + 10 y = 10 width = win.current["w"] / 4 - 20 height = win.current["h"] - 20 # Now let's make a layer. # Making the layer surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) layer = cairo.Context(surface) layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) # Clip UI_elements.roundrect(layer, win, 0, 0, width, height, 10, fill=False) layer.clip() # Checklist icon. UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, 0, 0, width, 50, 10) UI_elements.image(layer, win, "settings/themes/"+win.settings["Theme"]+"/icons/checklist.png", 5, 5, 40, 40) # Fraction fraction = win.checklists[path]["fraction"] UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, 50, 17, width - 60, 0, 7) UI_color.set(layer, win, "progress_active") UI_elements.roundrect(layer, win, 50, 17, (width - 60 )*fraction, 0, 7) # Clip UI_elements.roundrect(layer, win, 0, 60, width, height, 10, fill=False) layer.clip() ########################################################################### # NOW THIS IS THE HARD RECURSION PART! THERE IS GOING TO BE SOME REDIC! ########################################################################### tileX = 0 current_Y = 70 # There is some bullshit regarding the Global variables. I gonna do a bit # trickier. Since lists are only links to lists. Let's do this. cXY = [tileX, current_Y] if "checklist" not in win.scroll: win.scroll["checklist"] = 0 if "moving_task_now" not in win.current: win.current["moving_task_now"] = False def draw_tasks(tasks, cXY): for num , task in enumerate(tasks): # Delete if 65535 in win.current["keys"] and task["selected"]: del tasks[num] win.current["keys"] = [] # Saving save(path, win.checklists[path]) win.checklists = {} # Grab if win.current["LMB"]\ and int(win.current["LMB"][0]) in range(int(x+cXY[0]), int(x+cXY[0]+width))\ and int(win.current["LMB"][1]) in range(int(y+cXY[1]), int(y+cXY[1]+height))\ and win.current["tool"] == "selection" and task["selected"]\ and int(win.current["LMB"][0]) not in range(int(win.current["mx"]-1), int(win.current["mx"]+1))\ and int(win.current["LMB"][1]) not in range(int(win.current["my"]-1), int(win.current["my"]+1)): win.current["tool"] = "grab" win.current["moving_task_now"] = False if not win.current["LMB"] and win.previous["LMB"] and win.current["tool"] == "grab"\ and task["selected"]: win.current["moving_task_now"] = tasks.pop(num) sx = cXY[0] sy = win.scroll["checklist"] + cXY[1] thismoving = False somemoving = False someXY = [0,0] if win.current["tool"] == "grab": somemoving = True someXY = [ win.current["mx"]-x, win.current["my"]-y ] if task["selected"]: sx = win.current["mx"] - x + 5 sy = win.current["my"] - y + 5 task["open"] = False thismoving = True inside = False between = False if sy-10 < someXY[1] < sy+10 and somemoving and not task["selected"]: if win.current["moving_task_now"]: tasks.insert(num, win.current["moving_task_now"]) win.current["tool"] = "selection" win.current["LMB"] = False win.previous["LMB"] = False # Saving save(path, win.checklists[path]) win.checklists = {} elif win.current["LMB"]: UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, cXY[0], win.scroll["checklist"] + cXY[1], width - cXY[0], 40, 10, fill=False) layer.stroke() cXY[1] = cXY[1] + 50 sx = cXY[0] sy = win.scroll["checklist"] + cXY[1] between = True somemoving = False elif sy < someXY[1] < sy + 40 and somemoving and not task["selected"]: if win.current["moving_task_now"]: task["subtasks"].append(win.current["moving_task_now"]) win.current["tool"] = "selection" win.current["LMB"] = False win.previous["LMB"] = False # Saving save(path, win.checklists[path]) win.checklists = {} elif win.current["LMB"]: inside = True # Selection if win.textactive != "editing_task" and win.current["tool"] == "selection": def do(): ed = task["selected"] and not task["editing"] filter_tasks(win.checklists[path]) task["selected"] = True if ed: task["editing"] = True UI_elements.roundrect(layer, win, sx+40, sy, width - cXY[0]-40, 40, 10, button=do, offset=[x,y], fill=False) layer.stroke() # Background if not task["subtasks"]: UI_color.set(layer, win, "node_background") if task["string"].startswith("#"): UI_color.set(layer, win, "dark_overdrop") UI_elements.roundrect(layer, win, sx, sy, width - cXY[0], 40, 10) if inside: UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, sx, sy, width - cXY[0], 40, 10, fill=False) layer.stroke() if task["selected"]: UI_color.set(layer, win, "text_normal") UI_elements.roundrect(layer, win, sx, sy, width - cXY[0], 40, 10, fill=False) layer.stroke() # Line to see how deep you are in for line in range(int(cXY[0]/20)): line = line * 20 + 10 UI_color.set(layer, win, "node_background") layer.move_to(line, win.scroll["checklist"] + cXY[1]-10) layer.line_to(line, win.scroll["checklist"] + cXY[1]+40) layer.stroke() if not task["string"].startswith("#"): if task["fraction"]: im = "checked" else: im = "unchecked" # CHECK BUTTON def do(): if task["fraction"]: task["fraction"] = 0.0 else: task["fraction"] = 1.0 # Saving save(path, win.checklists[path]) win.checklists = {} UI_elements.roundrect(layer, win, sx, sy, 40, 40, 10, button=do, icon=im, offset=[x,y]) else: UI_color.set(layer, win, "node_background") if task["string"].startswith("#"): UI_color.set(layer, win, "dark_overdrop") UI_elements.roundrect(layer, win, sx, sy, width - cXY[0], 60, 10) if inside: UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, sx, sy, width - cXY[0], 60, 10, fill=False) layer.stroke() if task["selected"]: UI_color.set(layer, win, "text_normal") UI_elements.roundrect(layer, win, sx, sy, width - cXY[0], 60, 10, fill=False) layer.stroke() # Line to see how deep you are in for line in range(int(cXY[0]/20)): line = line * 20 + 10 UI_color.set(layer, win, "node_background") layer.move_to(line, win.scroll["checklist"] + cXY[1]-10) layer.line_to(line, win.scroll["checklist"] + cXY[1]+60) layer.stroke() # Fraction if not task["string"].startswith("#"): fraction = task["fraction"] UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, sx+10, sy+45, width-20 - cXY[0], 0, 5) UI_color.set(layer, win, "progress_active") UI_elements.roundrect(layer, win, sx+10, sy+45, (width -20 - cXY[0])*fraction, 0, 5) if task["open"]: im = "open" else: im = "closed" def do(): task["open"] = not task["open"] # Saving save(path, win.checklists[path]) win.checklists = {} UI_elements.roundrect(layer, win, sx, sy, 40, 40, 10, button=do, icon=im, offset=[x,y]) # TEXT # ECS if 65307 in win.current["keys"] and task["editing"]: # It's here because Text entry has it's own ESC win.textactive = "" task["editing"] = False del win.text["editing_task"] win.current["keys"] = [] # Saving save(path, win.checklists[path]) win.checklists = {} if not task["editing"]: layer.set_font_size(20) layer.move_to( sx+50, sy+25 ) if task["string"].startswith("#"): UI_color.set(layer, win, "progress_background") if not task["subtasks"]: layer.move_to( sx+10, sy+25 ) layer.show_text(task["string"][1:]) else: UI_color.set(layer, win, "text_normal") layer.show_text(task["string"]) else: UI_elements.text(layer, win, "editing_task", sx+39, sy, width - cXY[0] - 40, 40, set_text=task["string"], offset=[x,y]) win.textactive = "editing_task" # Assigning the text def do(): task["string"] = win.text["editing_task"]["text"] win.textactive = "" filter_tasks(win.checklists[path]) del win.text["editing_task"] # Saving save(path, win.checklists[path]) def button(): do() win.checklists = {} UI_elements.roundrect(layer, win, sx+39 + width - cXY[0] - 80, sy, 40, 40, 10, button=button, icon="ok", tip=talk.text("checked"), offset=[x,y]) # Enter if 65293 in win.current["keys"]: button() win.current["keys"] = [] # Tab if 65289 in win.current["keys"]: do() tasks.append( { "fraction":0.0, "string":"", "editing":True, "selected":True, "open":False, "subtasks":[] } ) win.current["keys"] = [] # RECURSION if not thismoving: if not task["subtasks"]: cXY[1] = cXY[1] + 50 else: cXY[1] = cXY[1] + 70 cXY[0] = cXY[0] + 20 # THERE IS YOUR RECURSION if task["open"]: draw_tasks(task["subtasks"], cXY) cXY[0] = cXY[0] - 20 # Adding subtasks if ((task["subtasks"] and task["open"])\ or (task["selected"] and not task["subtasks"]))\ and win.textactive != "editing_task"\ and win.current["tool"] == "selection": cXY[0] = cXY[0] + 20 def do(): task["open"] = True filter_tasks(win.checklists[path]) task["subtasks"].append( { "fraction":0.0, "string":"", "editing":True, "selected":True, "open":False, "subtasks":[] } ) UI_elements.roundrect(layer, win, cXY[0], win.scroll["checklist"] + cXY[1], width - cXY[0], 40, 10, button=do, icon="new", offset=[x,y]) UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, cXY[0], win.scroll["checklist"] + cXY[1], width - cXY[0], 40, 10, fill=False) layer.stroke() layer.set_font_size(20) layer.move_to( cXY[0]+50, win.scroll["checklist"] + cXY[1]+25 ) layer.show_text(talk.text("add_new_subtask")) for line in range(int(cXY[0]/20)): line = line * 20 + 10 UI_color.set(layer, win, "node_background") layer.move_to(line, win.scroll["checklist"] + cXY[1]-10) layer.line_to(line, win.scroll["checklist"] + cXY[1]+40) layer.stroke() cXY[0] = cXY[0] - 20 cXY[1] = cXY[1] + 50 draw_tasks(win.checklists[path]["subtasks"], cXY) # Go to the bottom. if win.current["tool"] == "grab"\ and win.current["my"] - y > cXY[1]: if win.current["moving_task_now"]: win.checklists[path]["subtasks"].append(win.current["moving_task_now"]) win.current["tool"] = "selection" win.current["LMB"] = False win.previous["LMB"] = False elif win.current["LMB"]: UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, cXY[0], win.scroll["checklist"] + cXY[1], width - cXY[0], 40, 10, fill=False) layer.stroke() # Adding a task. if win.textactive != "editing_task"\ and win.current["tool"] == "selection": def do(): filter_tasks(win.checklists[path]) win.checklists[path]["subtasks"].append( { "fraction":0.0, "string":"", "editing":True, "selected":True, "open":False, "subtasks":[] } ) UI_elements.roundrect(layer, win, cXY[0], win.scroll["checklist"] + cXY[1], width - cXY[0], 40, 10, button=do, icon="new", offset=[x,y]) UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, cXY[0], win.scroll["checklist"] + cXY[1], width - cXY[0], 40, 10, fill=False) layer.stroke() layer.set_font_size(20) layer.move_to( cXY[0]+50, win.scroll["checklist"] + cXY[1]+25 ) layer.show_text(talk.text("add_new_task")) tileX, current_Y = cXY # Outputting the layer outlayer.set_source_surface(surface, x, y) outlayer.paint() # Scroll UI_elements.scroll_area(outlayer, win, "checklist", x+0, y+50, width, height-50, current_Y, bar=True, mmb=True)