checklist.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264
  1. # THIS FILE IS A PART OF VCStudio
  2. # PYTHON 3
  3. import os
  4. import cairo
  5. try:
  6. from gi.repository import Gtk
  7. from gi.repository import Gdk
  8. except:
  9. pass
  10. #UI modules
  11. from UI import UI_elements
  12. from UI import UI_color
  13. #settings
  14. from settings import talk
  15. #studio
  16. from studio import analytics
  17. from studio import history
  18. def get_list(filepath):
  19. # This fucntion converts text documents. (.progress) into a more machine
  20. # friendly recursive dict.
  21. # In the original organizer evaluation of the checklist was done separatelly
  22. # in a different function. Which made it very messy for recursive stuff.
  23. # I will attemp to combine it. And make it more clean as a result.
  24. checklist = {
  25. "fraction":0.0, # The percentage of a checklist. From 0.0 to 1.0
  26. "string":filepath,
  27. "editing":False,# Whether the string is during editing. UI.
  28. "selected":False,# Whether the current task is selected.
  29. "open":True, # Whether to draw the suptasks. UI.
  30. "subtasks":[] # List of subtastks. (In the same format as the checklist)
  31. }
  32. data = open(filepath)
  33. data = data.read()
  34. data = data.split("\n")
  35. # Let's filter out all the comments. Lines starting with #. For some reason
  36. # in the old organizer. I just thought it wasn't important. LOL. And just
  37. # started reading from the 10th line.
  38. # Here is an example of the first 9 lines.
  39. # 1 #### Blender orgainizer checklist format
  40. # 2 #### INDINTATION (4 SPACES LONG)
  41. # 3 #### STR means Start date of the ASSET
  42. # 4 #### FIN means Finish deadline of the asset
  43. # 5 #### [ ] means that task is on list
  44. # 6 #### [V] means that tast is finished
  45. # 7 #### DO NOT USE EMPTY LINES
  46. # 8 STR 24/02/2020
  47. # 9 FIN 01/05/2021
  48. # You can see a trace from a very long time ago. From the first versions
  49. # of the blender organizer. The STR and FIN values. Which are start and
  50. # deadline of the project.
  51. # I guess we need to filter it out a bit differently. Checking whether a
  52. # given line starts with a [ or with a number of spaces and [. This will
  53. # make the file a little more open to editing by hand. Without too much
  54. # worrying that something will break.
  55. cleandata = []
  56. for line in data:
  57. # So not to mangle the line.
  58. tmp = line
  59. while tmp.startswith(" "):
  60. tmp = tmp[1:]
  61. # Checking
  62. if tmp.startswith("[ ]") or tmp.startswith("[V]"):
  63. cleandata.append(line)
  64. # Now since we have the cleandata. We can try to parse it somehow into a
  65. # checklist thing. For this we need a reqursion. I gonna use a method from
  66. # the blender-organizer. By running the function with in itself. It's not
  67. # very wise. I know. In python3 it will give you no more then 996 recursions
  68. # untill it will declare an error. BUT. It's 996 layers of a subtaks.
  69. # For now I don't see a need in so many subtasks. Let's think of layers of
  70. # subtasks as of memory. This laptop has only 8 GB of RAM. I can't put more
  71. # data into it even if I wanted.
  72. def convert(part, indent=0):
  73. # If a thing have subtasks it should not even be a checkbox. So trying
  74. # To evaluate it's fraction by whether it's a [ ] or [V] shoun't be
  75. # done.
  76. subtask = []
  77. for num, line in enumerate(part):
  78. # Let's get the NEXT line. I'm not kidding. We gonna work with the
  79. # next line to see whether to count this lines [V]
  80. if line[indent:].startswith("["):
  81. thisline = {
  82. "fraction":0.0,
  83. "string":line[line.find("]")+2:],
  84. "editing":False,
  85. "selected":False,
  86. "open":False,
  87. "subtasks":[]
  88. }
  89. try:
  90. nextline = part[num+1]
  91. except:
  92. nextline = ""
  93. if not line[line.find("]")+1] == ".":
  94. thisline["open"] = True
  95. if nextline.find("[")-1 <= indent:
  96. if line[indent:].startswith("[V]"):
  97. thisline["fraction"] = 1.0
  98. else:
  99. subpart = []
  100. subdent = indent
  101. for n, l in enumerate(part[num+1:]):
  102. if n == 0:
  103. subdent = l.find("[")
  104. if l.find("[")-1 <= indent:
  105. break
  106. else:
  107. subpart.append(l)
  108. #print(subpart)
  109. thisline["subtasks"] = convert(subpart, subdent)
  110. fracs = []
  111. for task in thisline["subtasks"]:
  112. if not task["string"].startswith("#"):
  113. fracs.append(task["fraction"])
  114. try:
  115. thisline["fraction"] = sum(fracs) / len(fracs)
  116. except:
  117. thisline["fraction"] = 0.0
  118. # Sometime it was showing 99% when infect it's 100%
  119. if thisline["fraction"] == 0.9999:
  120. thisline["fraction"] = 1.0
  121. subtask.append(thisline)
  122. return subtask
  123. checklist["subtasks"] = convert(cleandata)
  124. fracs = []
  125. for task in checklist["subtasks"]:
  126. if not task["string"].startswith("#"):
  127. fracs.append(task["fraction"])
  128. try:
  129. checklist["fraction"] = sum(fracs) / len(fracs)
  130. except:
  131. checklist["fraction"] = 0.0
  132. # Sometime it was showing 99% when infect it's 100%
  133. if checklist["fraction"] == 0.9999:
  134. checklist["fraction"] = 1.0
  135. return checklist
  136. def get_fraction(win, path):
  137. ############################################################################
  138. # This function will return a fraction of a given checklist. It will ignore
  139. # complitelly what is the asset / shot / project the checklist is from.
  140. # For sake of making this function actually somewhat useful, aka not bloated3
  141. # I will use it to cashe the whole checklist data objects. So they could be
  142. # easily accesable later on.
  143. # This is usefull so I would not need to create 2 cash data structures. One
  144. # for fractions and for the checklists them selves.
  145. ############################################################################
  146. if path not in win.checklists:
  147. # Let's check if path exists in the project first.
  148. if os.path.exists(win.project+"/"+path):
  149. win.checklists[path] = get_list(win.project+"/"+path)
  150. else:
  151. win.checklists[path] = get_list(path)
  152. # Let's now return back the fraction
  153. return win.checklists[path]["fraction"]
  154. def get_task_by_path(tasks, path, p=[]):
  155. ############################################################################
  156. # This function will give the information about a given task from a non
  157. # recursive URL such as ["Task", "Sub-task", "Sub-task2"]. This will look
  158. # the recursive list and output the given task. In this case "Sub-task2"
  159. # with all the information inside it.
  160. ############################################################################
  161. for task in tasks:
  162. pa = p.copy()
  163. pa.append(" "+task["string"])
  164. if pa == path:
  165. return task
  166. if task["subtasks"]:
  167. t = get_task_by_path(task["subtasks"], path, pa)
  168. if t:
  169. return t
  170. return False
  171. def filter_tasks(data):
  172. ############################################################################
  173. # This function going to remove all selections and all edits from a given
  174. # checklist. This is nessesary for being able to select one task without
  175. # manually deselcting the other. And since the checklist is recursive it's
  176. # not a trivial task.
  177. ############################################################################
  178. data["selected"] = False
  179. data["editing"] = False
  180. if data["subtasks"]:
  181. for i in data["subtasks"]:
  182. filter_tasks(i)
  183. def save(path, data):
  184. ############################################################################
  185. # This funtion will save the checklist into a .progress file. Formatted
  186. # similarly as in the old Blender-Organizer. But as easy to understand.
  187. #
  188. # NOTE: I will remove some stuff from the file. Mainly the comments in the
  189. # beginning and the STR and END data. Which are no longer needed in a
  190. # VCStudio project.
  191. #
  192. # But since some part of Legacy Organizer relies on those, it's not adviced
  193. # to open the projects that you are planning to work on with Blender-Organizer
  194. # in VCStudio.
  195. ############################################################################
  196. indent = [0]
  197. lines = []
  198. def writetasks(tasks):
  199. for task in tasks:
  200. if task["fraction"] and not task["subtasks"]:
  201. v = "[V]"
  202. else:
  203. v = "[ ]"
  204. if task["open"]:
  205. o = " "
  206. else:
  207. o = "."
  208. lines.append(" "*indent[0]+v+o+task["string"])
  209. if task["subtasks"]:
  210. indent[0] = indent[0] + 4
  211. writetasks(task["subtasks"])
  212. indent[0] = indent[0] - 4
  213. writetasks(data["subtasks"])
  214. if path:
  215. # Writting to file
  216. w = open(path, "w")
  217. for i in lines:
  218. w.write(i+"\n")
  219. w.close()
  220. ret = ""
  221. for i in lines:
  222. ret = ret+"\n"+i
  223. ret = ret[1:]
  224. return ret
  225. def draw(outlayer, win, path, back="story_editor"):
  226. ############################################################################
  227. # This function will draw the checklists to the screen.
  228. # You probably noticed that there is no x, y, width or height values that
  229. # you can input. This due to one idea that I have in mind.
  230. # I'm planning to make scheduling a kind of interactive process rather then
  231. # a boring date selection. Something that's going to be somewhat fun to do.
  232. # The idea is to grab the task and to drug it outside the checklist. Which
  233. # will call for an analytics window to show up, where you can put the
  234. # schedules into a given date.
  235. # For this reason the checklist should always be in the same spot on the
  236. # screen. Which is a (window width) / 4 at the right side.
  237. ############################################################################
  238. if path not in win.checklists:
  239. # Let's check if path exists in the project first.
  240. if os.path.exists(win.project+"/"+path):
  241. win.checklists[path] = get_list(win.project+"/"+path)
  242. elif os.path.exists(win.project+"/rnd"+path):
  243. win.checklists[path] = get_list(win.project+"/rnd"+path)
  244. else:
  245. win.checklists[path] = get_list(path)
  246. x = win.current["w"] / 4 * 3 + 10
  247. y = 10
  248. width = win.current["w"] / 4 - 20
  249. height = win.current["h"] - 20
  250. # Now let's make a layer.
  251. # Making the layer
  252. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
  253. layer = cairo.Context(surface)
  254. layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  255. # Clip
  256. UI_elements.roundrect(layer, win,
  257. 0,
  258. 0,
  259. width,
  260. height,
  261. 10,
  262. fill=False)
  263. layer.clip()
  264. # Checklist icon.
  265. UI_color.set(layer, win, "node_background")
  266. UI_elements.roundrect(layer, win,
  267. 0,
  268. 0,
  269. width,
  270. 50,
  271. 10)
  272. UI_elements.image(layer, win,
  273. "settings/themes/"+win.settings["Theme"]+"/icons/checklist.png",
  274. 5, 5, 40, 40)
  275. # If not in the analytics window. I want to have a button to go to analyitcs.
  276. if win.url != "analytics":
  277. reducing = 60
  278. def do():
  279. win.cur = "/set"
  280. win.url = "analytics"
  281. UI_elements.roundrect(layer, win,
  282. width - 55,
  283. 5,
  284. 40,
  285. 40,
  286. 10,
  287. do,
  288. offset=[x,y],
  289. icon="analytics")
  290. else:
  291. reducing = 0
  292. # Fraction
  293. fraction = win.checklists[path]["fraction"]
  294. UI_color.set(layer, win, "progress_background")
  295. UI_elements.roundrect(layer, win,
  296. 50,
  297. 17,
  298. width - 60 -reducing,
  299. 0,
  300. 7)
  301. UI_color.set(layer, win, "progress_active")
  302. UI_elements.roundrect(layer, win,
  303. 50,
  304. 17,
  305. (width - 60 -reducing)*fraction,
  306. 0,
  307. 7)
  308. # Clip
  309. UI_elements.roundrect(layer, win,
  310. 0,
  311. 60,
  312. width,
  313. height,
  314. 10,
  315. fill=False)
  316. layer.clip()
  317. ###########################################################################
  318. # NOW THIS IS THE HARD RECURSION PART! THERE IS GOING TO BE SOME REDIC!
  319. ###########################################################################
  320. tileX = 0
  321. current_Y = 70
  322. # There is some bullshit regarding the Global variables. I gonna do a bit
  323. # trickier. Since lists are only links to lists. Let's do this.
  324. cXY = [tileX, current_Y]
  325. if "checklist" not in win.scroll:
  326. win.scroll["checklist"] = 0
  327. if "moving_task_now" not in win.current:
  328. win.current["moving_task_now"] = False
  329. def draw_tasks(tasks, cXY, schedulep):
  330. #########################################
  331. # #
  332. # THIS IS THE RECURSIVE FUNCTION #
  333. # #
  334. #########################################
  335. # I will try to explain what is going on here. But it will require me
  336. # understanding the code myself. And it's a bit redic. There was a hell
  337. # of a lot of copy-paste, edit value, continue.
  338. for num , task in enumerate(tasks):
  339. # This is the code that will be done for each task in the list. Or
  340. # subtask if a task have them.
  341. # Let's get a schedule path. A folder like structure.
  342. # Exacmple:
  343. # Instead of:
  344. # Task
  345. # Sub-Task
  346. # Sub-Task 2
  347. # We get:
  348. # [" Task", " Sub-Task", " Sub-Task 2"]
  349. schedulepath = schedulep.copy()
  350. schedulepath.append(" "+task["string"])
  351. ###### SCHEDULING STUFF BACKWARD ######
  352. # This is a set of stuff to make schedules work with checklists.
  353. if "schedule_task_selected" not in win.current:
  354. win.current["schedule_task_selected"] = False
  355. if win.current["schedule_task_selected"] and win.cur == win.current["schedule_task_selected"][-1]\
  356. or win.current["schedule_task_selected"] and win.cur == "/set" and win.current["schedule_task_selected"][-1] == "":
  357. csl = win.current["schedule_task_selected"][0][0][4]
  358. if " "+task["string"] in csl and not task["open"]:
  359. task["open"] = True
  360. if schedulepath == csl and not task["selected"]:
  361. filter_tasks(win.checklists[path])
  362. task["selected"] = True
  363. win.scroll["checklist"] = 0 - cXY[1] + height/2
  364. #### DELETE SHORT KEY ####
  365. # There is probably a reason to put it here. Probably something
  366. # breaks if you put it anywhere else. IDK actually. Test it. I
  367. # don't remember
  368. if 65535 in win.current["keys"] and task["selected"] and not win.current["schedule_task_selected"]:
  369. del tasks[num]
  370. win.current["keys"] = []
  371. # Saving
  372. save(path, win.checklists[path])
  373. win.checklists = {}
  374. win.assets = {}
  375. win.analytics = analytics.load(win.project)
  376. win.multiuser["last_request"] = ""
  377. #### THE GRABBING FUNCTION ####
  378. # This is the activation of the grab tool. It uses the same
  379. # win.current["tool"] = "grab" as the story editor. Because it makes
  380. # sense to reuse it if it already exists. For sake of conviniense.
  381. # It doesn't mean that it uses the same code. It's a bit different.
  382. # due to the nature of lists vs free positioning items.
  383. # Now let's set up a few positional variables. So I could move the tasks
  384. # while moving the currently selected task. And in the same time will not
  385. # screw the data.
  386. # So I copy the X and the Y locations like this.
  387. sx = cXY[0]
  388. sy = win.scroll["checklist"] + cXY[1]
  389. grabY = 40
  390. if task["subtasks"]:
  391. grabY = 60
  392. # Grab
  393. if win.current["LMB"]\
  394. and int(win.current["LMB"][0]) in range(int(x+sx), int(x+sx+width))\
  395. and int(win.current["LMB"][1]) in range(int(y+sy), int(y+sy+grabY))\
  396. and win.current["tool"] == "selection"\
  397. and int(win.current["LMB"][0]) not in range(int(win.current["mx"]-2), int(win.current["mx"]+2))\
  398. and int(win.current["LMB"][1]) not in range(int(win.current["my"]-2), int(win.current["my"]+2)):
  399. # So here we set up the grab tool and creating a little variable.
  400. # This variable (moving_task_now) will consist of 2 parts on release.
  401. # 1. The entire task ( with subtasks ) using pop. Which is not simply
  402. # a copy. But also removing the task from the checklist's list.
  403. # 2. The current frame of poping. So to offset insertion for 1 frame.
  404. # This insures that you insert the task in the correct spot.
  405. filter_tasks(win.checklists[path])
  406. task["selected"] = True
  407. win.current["schedule_task_selected"] = False
  408. win.current["tool"] = "grab"
  409. win.current["moving_task_now"] = False
  410. # Now let's actually setup the pop. So when the mouse is now pressed, but
  411. # was on a previous framae, and our current tool is grab.
  412. if not win.current["LMB"] and win.previous["LMB"] and win.current["tool"] == "grab"\
  413. and task["selected"]:
  414. # We remove the task from the list. And write it to the variable. With
  415. # the current frame.
  416. win.current["moving_task_now"] = [tasks.pop(num), win.current["frame"]]
  417. # Now I can touch the sx and sy without screwing up the cXY or scroll.
  418. thismoving = False # This is going to be True is this is the task that's
  419. # moving currently
  420. somemoving = False # This is going to be True if any task is moving
  421. someXY = [0,0] # And this is the mouse position translated to location
  422. # of the checklist. (x, y coordinates of the whole
  423. # widget). Or in other words position of the task.
  424. # Then comes some logic to determen those 3 values.
  425. if win.current["tool"] == "grab":
  426. # Okay so here. If grab first we assume that it's some other
  427. # task. And now currently selected one.
  428. somemoving = True
  429. someXY = [
  430. win.current["mx"]-x,
  431. win.current["my"]-y
  432. ]
  433. # Now this is the editing of the sx and sy values. To be at
  434. # the position of the mouse.
  435. if task["selected"]:
  436. # Calculating so the mouse will end up about in the centre
  437. # of the task while the task is at motion.
  438. sx = win.current["mx"] - x - (width - cXY[0])/2
  439. sy = win.current["my"] - y - 10
  440. task["open"] = False
  441. thismoving = True
  442. somemoving = False
  443. # Now if the mouse is outside the frame of the checklist
  444. # it will activate the scheduling.
  445. ############################################################
  446. # SCHEDULING PART #
  447. ############################################################
  448. if win.current["mx"] < x:
  449. win.url = "analytics"
  450. win.current["grab_data"] = [path, back, win.cur, schedulepath, win.settings["Username"]]
  451. win.current["tool"] = "schedule"
  452. #
  453. #
  454. #
  455. # SEE studio/studio_analyticsLayer.py
  456. # for more details about scheduling
  457. #
  458. # And if back into frame it will return to normal grab mode.
  459. elif win.current["tool"] == "schedule" and win.current["mx"] > x:
  460. win.url = back
  461. win.current["tool"] = "grab"
  462. ####################################################################
  463. # SCHEDULING PART END #
  464. ####################################################################
  465. inside = False
  466. between = False
  467. if sy-10 < someXY[1] < sy+10 and somemoving and not task["selected"]:
  468. if win.current["moving_task_now"] and win.current["moving_task_now"][1] != win.current["frame"]:
  469. tasks.insert(num, win.current["moving_task_now"][0])
  470. win.current["tool"] = "selection"
  471. win.current["LMB"] = False
  472. win.previous["LMB"] = False
  473. # Saving
  474. save(path, win.checklists[path])
  475. win.checklists = {}
  476. win.assets = {}
  477. win.analytics = analytics.load(win.project)
  478. win.multiuser["last_request"] = ""
  479. elif win.current["LMB"]:
  480. for line in range(int(cXY[0]/20)):
  481. line = line * 20 + 10
  482. UI_color.set(layer, win, "node_background")
  483. layer.move_to(line, win.scroll["checklist"] + cXY[1]-10)
  484. layer.line_to(line, win.scroll["checklist"] + cXY[1]+40)
  485. layer.stroke()
  486. cXY[1] = cXY[1] + 50
  487. sx = cXY[0]
  488. sy = win.scroll["checklist"] + cXY[1]
  489. between = True
  490. somemoving = False
  491. elif sy < someXY[1] < sy + 40 and somemoving and not task["selected"]:
  492. if win.current["moving_task_now"] and win.current["moving_task_now"][1] != win.current["frame"]:
  493. task["subtasks"].append(win.current["moving_task_now"][0])
  494. win.current["tool"] = "selection"
  495. win.current["LMB"] = False
  496. win.previous["LMB"] = False
  497. # Saving
  498. save(path, win.checklists[path])
  499. win.checklists = {}
  500. win.assets = {}
  501. win.analytics = analytics.load(win.project)
  502. win.multiuser["last_request"] = ""
  503. elif win.current["LMB"]:
  504. inside = True
  505. # Selection
  506. if win.textactive != "editing_task" and win.current["tool"] == "selection":
  507. def do():
  508. ed = task["selected"] and not task["editing"]
  509. filter_tasks(win.checklists[path])
  510. win.current["schedule_task_selected"] = False
  511. task["selected"] = True
  512. if ed:
  513. task["editing"] = True
  514. UI_elements.roundrect(layer, win,
  515. sx+40,
  516. sy,
  517. width - cXY[0]-40,
  518. grabY,
  519. 10,
  520. button=do,
  521. offset=[x,y],
  522. fill=False)
  523. layer.stroke()
  524. # Background
  525. if not task["subtasks"]:
  526. UI_color.set(layer, win, "node_background")
  527. if task["string"].startswith("#"):
  528. UI_color.set(layer, win, "dark_overdrop")
  529. UI_elements.roundrect(layer, win,
  530. sx,
  531. sy,
  532. width - cXY[0],
  533. 40,
  534. 10)
  535. if inside:
  536. UI_color.set(layer, win, "progress_background")
  537. UI_elements.roundrect(layer, win,
  538. sx,
  539. sy,
  540. width - cXY[0],
  541. 40,
  542. 10,
  543. fill=False)
  544. layer.stroke()
  545. if task["selected"]:
  546. UI_color.set(layer, win, "text_normal")
  547. UI_elements.roundrect(layer, win,
  548. sx,
  549. sy,
  550. width - cXY[0],
  551. 40,
  552. 10,
  553. fill=False)
  554. layer.stroke()
  555. # Line to see how deep you are in
  556. for line in range(int(cXY[0]/20)):
  557. line = line * 20 + 10
  558. UI_color.set(layer, win, "node_background")
  559. layer.move_to(line, win.scroll["checklist"] + cXY[1]-10)
  560. layer.line_to(line, win.scroll["checklist"] + cXY[1]+40)
  561. layer.stroke()
  562. if not task["string"].startswith("#"):
  563. if task["fraction"]:
  564. im = "checked"
  565. else:
  566. im = "unchecked"
  567. # CHECK BUTTON
  568. def do():
  569. if task["fraction"]:
  570. task["fraction"] = 0.0
  571. history.record(win, path, "[Un-Checked]", schedulepath)
  572. win.textactive = ""
  573. try: del win.text["editing_task"]
  574. except: pass
  575. else:
  576. task["fraction"] = 1.0
  577. history.record(win, path, "[Checked]", schedulepath)
  578. win.textactive = ""
  579. try: del win.text["editing_task"]
  580. except: pass
  581. # Saving
  582. save(path, win.checklists[path])
  583. win.checklists = {}
  584. win.assets = {}
  585. win.multiuser["last_request"] = ""
  586. win.analytics = analytics.load(win.project)
  587. UI_elements.roundrect(layer, win,
  588. sx,
  589. sy,
  590. 40,
  591. 40,
  592. 10,
  593. button=do,
  594. icon=im,
  595. offset=[x,y])
  596. else:
  597. UI_color.set(layer, win, "node_background")
  598. if task["string"].startswith("#"):
  599. UI_color.set(layer, win, "dark_overdrop")
  600. UI_elements.roundrect(layer, win,
  601. sx,
  602. sy,
  603. width - cXY[0],
  604. 60,
  605. 10)
  606. if inside:
  607. UI_color.set(layer, win, "progress_background")
  608. UI_elements.roundrect(layer, win,
  609. sx,
  610. sy,
  611. width - cXY[0],
  612. 60,
  613. 10,
  614. fill=False)
  615. layer.stroke()
  616. if task["selected"]:
  617. UI_color.set(layer, win, "text_normal")
  618. UI_elements.roundrect(layer, win,
  619. sx,
  620. sy,
  621. width - cXY[0],
  622. 60,
  623. 10,
  624. fill=False)
  625. layer.stroke()
  626. # Line to see how deep you are in
  627. for line in range(int(cXY[0]/20)):
  628. line = line * 20 + 10
  629. UI_color.set(layer, win, "node_background")
  630. layer.move_to(line, win.scroll["checklist"] + cXY[1]-10)
  631. layer.line_to(line, win.scroll["checklist"] + cXY[1]+60)
  632. layer.stroke()
  633. # Fraction
  634. if not task["string"].startswith("#"):
  635. fraction = task["fraction"]
  636. UI_color.set(layer, win, "progress_background")
  637. UI_elements.roundrect(layer, win,
  638. sx+10,
  639. sy+45,
  640. width-20 - cXY[0],
  641. 0,
  642. 5)
  643. UI_color.set(layer, win, "progress_active")
  644. UI_elements.roundrect(layer, win,
  645. sx+10,
  646. sy+45,
  647. (width -20 - cXY[0])*fraction,
  648. 0,
  649. 5)
  650. if task["open"]:
  651. im = "open"
  652. else:
  653. im = "closed"
  654. def do():
  655. task["open"] = not task["open"]
  656. win.current["schedule_task_selected"] = False
  657. # Saving
  658. save(path, win.checklists[path])
  659. win.multiuser["last_request"] = ""
  660. UI_elements.roundrect(layer, win,
  661. sx,
  662. sy,
  663. 40,
  664. 60,
  665. 10,
  666. button=do,
  667. icon=im,
  668. offset=[x,y])
  669. # TEXT
  670. # ECS
  671. if 65307 in win.current["keys"] and task["editing"]:
  672. # It's here because Text entry has it's own ESC
  673. win.textactive = ""
  674. task["editing"] = False
  675. del win.text["editing_task"]
  676. win.current["keys"] = []
  677. # Saving
  678. save(path, win.checklists[path])
  679. win.checklists = {}
  680. win.assets = {}
  681. win.analytics = analytics.load(win.project)
  682. win.multiuser["last_request"] = ""
  683. if not task["editing"]:
  684. layer.set_font_size(20)
  685. layer.move_to(
  686. sx+50,
  687. sy+25
  688. )
  689. if task["string"].startswith("#"):
  690. UI_color.set(layer, win, "progress_background")
  691. if not task["subtasks"]:
  692. layer.move_to(
  693. sx+10,
  694. sy+25
  695. )
  696. layer.show_text(task["string"][1:])
  697. else:
  698. UI_color.set(layer, win, "text_normal")
  699. layer.show_text(task["string"])
  700. else:
  701. UI_elements.text(layer, win, "editing_task",
  702. sx+39,
  703. sy,
  704. width - cXY[0] - 40,
  705. 40,
  706. set_text=task["string"],
  707. offset=[x,y])
  708. win.textactive = "editing_task"
  709. # Assigning the text
  710. def do():
  711. task["string"] = win.text["editing_task"]["text"]
  712. win.textactive = ""
  713. filter_tasks(win.checklists[path])
  714. del win.text["editing_task"]
  715. # Saving
  716. save(path, win.checklists[path])
  717. win.multiuser["last_request"] = ""
  718. def button():
  719. do()
  720. win.checklists = {}
  721. win.assets = {}
  722. UI_elements.roundrect(layer, win,
  723. sx+39 + width - cXY[0] - 80,
  724. sy,
  725. 40,
  726. 40,
  727. 10,
  728. button=button,
  729. icon="ok",
  730. tip=talk.text("checked"),
  731. offset=[x,y])
  732. # Enter
  733. if 65293 in win.current["keys"]:
  734. button()
  735. win.current["keys"] = []
  736. # Tab
  737. if 65289 in win.current["keys"]:
  738. do()
  739. tasks.append(
  740. {
  741. "fraction":0.0,
  742. "string":"",
  743. "editing":True,
  744. "selected":True,
  745. "open":False,
  746. "subtasks":[]
  747. }
  748. )
  749. win.current["keys"] = []
  750. # RECURSION
  751. if not thismoving:
  752. if not task["subtasks"]:
  753. cXY[1] = cXY[1] + 50
  754. else:
  755. cXY[1] = cXY[1] + 70
  756. cXY[0] = cXY[0] + 20
  757. # THERE IS YOUR RECURSION
  758. if task["open"]:
  759. draw_tasks(task["subtasks"], cXY, schedulepath)
  760. cXY[0] = cXY[0] - 20
  761. # Adding subtasks
  762. if ((task["subtasks"] and task["open"] and task["selected"])\
  763. or (task["selected"] and not task["subtasks"]))\
  764. and win.textactive != "editing_task"\
  765. and win.current["tool"] == "selection":
  766. cXY[0] = cXY[0] + 20
  767. def do():
  768. task["open"] = True
  769. filter_tasks(win.checklists[path])
  770. task["subtasks"].append(
  771. {
  772. "fraction":0.0,
  773. "string":"",
  774. "editing":True,
  775. "selected":True,
  776. "open":False,
  777. "subtasks":[]
  778. }
  779. )
  780. UI_elements.roundrect(layer, win,
  781. cXY[0],
  782. win.scroll["checklist"] + cXY[1],
  783. width - cXY[0],
  784. 40,
  785. 10,
  786. button=do,
  787. icon="new",
  788. offset=[x,y])
  789. UI_color.set(layer, win, "progress_background")
  790. UI_elements.roundrect(layer, win,
  791. cXY[0],
  792. win.scroll["checklist"] + cXY[1],
  793. width - cXY[0],
  794. 40,
  795. 10,
  796. fill=False)
  797. layer.stroke()
  798. layer.set_font_size(20)
  799. layer.move_to(
  800. cXY[0]+50,
  801. win.scroll["checklist"] + cXY[1]+25
  802. )
  803. layer.show_text(talk.text("add_new_subtask"))
  804. for line in range(int(cXY[0]/20)):
  805. line = line * 20 + 10
  806. UI_color.set(layer, win, "node_background")
  807. layer.move_to(line, win.scroll["checklist"] + cXY[1]-10)
  808. layer.line_to(line, win.scroll["checklist"] + cXY[1]+40)
  809. layer.stroke()
  810. cXY[0] = cXY[0] - 20
  811. cXY[1] = cXY[1] + 50
  812. schedulepath = []
  813. draw_tasks(win.checklists[path]["subtasks"], cXY, schedulepath)
  814. # Go to the bottom.
  815. if win.current["tool"] == "grab"\
  816. and win.current["my"] - y > win.scroll["checklist"] + cXY[1]:
  817. if win.current["moving_task_now"] and win.current["moving_task_now"][1] != win.current["frame"]:
  818. win.checklists[path]["subtasks"].append(win.current["moving_task_now"][0])
  819. win.current["tool"] = "selection"
  820. win.current["LMB"] = False
  821. win.previous["LMB"] = False
  822. # Saving
  823. save(path, win.checklists[path])
  824. win.checklists = {}
  825. win.assets = {}
  826. win.analytics = analytics.load(win.project)
  827. win.multiuser["last_request"] = ""
  828. # Adding a task.
  829. if win.textactive != "editing_task"\
  830. and win.current["tool"] == "selection":
  831. def do():
  832. filter_tasks(win.checklists[path])
  833. win.checklists[path]["subtasks"].append(
  834. {
  835. "fraction":0.0,
  836. "string":"",
  837. "editing":True,
  838. "selected":True,
  839. "open":False,
  840. "subtasks":[]
  841. }
  842. )
  843. UI_elements.roundrect(layer, win,
  844. cXY[0],
  845. win.scroll["checklist"] + cXY[1],
  846. width - cXY[0],
  847. 40,
  848. 10,
  849. button=do,
  850. icon="new",
  851. offset=[x,y])
  852. UI_color.set(layer, win, "progress_background")
  853. UI_elements.roundrect(layer, win,
  854. cXY[0],
  855. win.scroll["checklist"] + cXY[1],
  856. width - cXY[0],
  857. 40,
  858. 10,
  859. fill=False)
  860. layer.stroke()
  861. layer.set_font_size(20)
  862. layer.move_to(
  863. cXY[0]+50,
  864. win.scroll["checklist"] + cXY[1]+25
  865. )
  866. layer.show_text(talk.text("add_new_task"))
  867. cXY[1] = cXY[1] + 100
  868. def do():
  869. raw = save(False, win.checklists[path])
  870. clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
  871. clipboard.set_text( raw , -1)
  872. UI_elements.roundrect(layer, win,
  873. cXY[0],
  874. win.scroll["checklist"] + cXY[1],
  875. width - cXY[0],
  876. 40,
  877. 10,
  878. button=do,
  879. icon="copy_file",
  880. offset=[x,y])
  881. UI_color.set(layer, win, "progress_background")
  882. UI_elements.roundrect(layer, win,
  883. cXY[0],
  884. win.scroll["checklist"] + cXY[1],
  885. width - cXY[0],
  886. 40,
  887. 10,
  888. fill=False)
  889. layer.stroke()
  890. layer.set_font_size(20)
  891. layer.move_to(
  892. cXY[0]+50,
  893. win.scroll["checklist"] + cXY[1]+25
  894. )
  895. layer.show_text(talk.text("copy_checklist_to_clipboard"))
  896. cXY[1] = cXY[1] + 50
  897. def do():
  898. clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
  899. cliptext = str(clipboard.wait_for_text())
  900. savetext = ""
  901. for i in cliptext.split("\n"):
  902. if "[ ]" in i or "[V]" in i or "[v]" in i:
  903. savetext = savetext + "\n"+i.replace("[V]", "[ ]").replace("[v]", "[ ]")
  904. else:
  905. savetext = savetext + "\n[ ] #"+i
  906. cliptext = savetext[1:]
  907. s = open(path, 'ab')
  908. s.write(cliptext.encode("utf-8"))
  909. s.close()
  910. win.checklists[path] = get_list(path)
  911. UI_elements.roundrect(layer, win,
  912. cXY[0],
  913. win.scroll["checklist"] + cXY[1],
  914. width - cXY[0],
  915. 40,
  916. 10,
  917. button=do,
  918. icon="copy_file",
  919. offset=[x,y])
  920. UI_color.set(layer, win, "progress_background")
  921. UI_elements.roundrect(layer, win,
  922. cXY[0],
  923. win.scroll["checklist"] + cXY[1],
  924. width - cXY[0],
  925. 40,
  926. 10,
  927. fill=False)
  928. layer.stroke()
  929. layer.set_font_size(20)
  930. layer.move_to(
  931. cXY[0]+50,
  932. win.scroll["checklist"] + cXY[1]+25
  933. )
  934. layer.show_text(talk.text("copy_to_checklist_from_clipboard"))
  935. tileX, current_Y = cXY
  936. # So there would not be jumps of stuff. Let's add heigh to the scroll while
  937. # in grab.
  938. if win.current["tool"] == "grab":
  939. current_Y = current_Y + height
  940. # Outputting the layer
  941. outlayer.set_source_surface(surface, x, y)
  942. outlayer.paint()
  943. # Scroll
  944. UI_elements.scroll_area(outlayer, win, "checklist",
  945. x+0,
  946. y+50,
  947. width,
  948. height-50,
  949. current_Y,
  950. bar=True,
  951. mmb=True)