checklist.py 48 KB

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