studio_analyticsLayer.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. ####################################
  2. # #
  3. # COPYRIGHT NOTICE #
  4. # #
  5. # This file is a part of Victori- #
  6. # ous Children Studio Organizer. #
  7. # Or simply VCStudio. Copyright #
  8. # of J.Y.Amihud. But don't be sad #
  9. # because I released the entire #
  10. # project under a GNU GPL license. #
  11. # You may use Version 3 or later. #
  12. # See www.gnu.org/licenses if your #
  13. # copy has no License file. Please #
  14. # note. Ones I used the GPL v2 for #
  15. # it. It's no longer the case. #
  16. # #
  17. ####################################
  18. import os
  19. import datetime
  20. # GTK module ( Graphical interface
  21. import gi
  22. gi.require_version('Gtk', '3.0')
  23. from gi.repository import Gtk
  24. from gi.repository import GLib
  25. from gi.repository import Gdk
  26. import cairo
  27. # Own modules
  28. from settings import settings
  29. from settings import talk
  30. from settings import fileformats
  31. from settings import oscalls
  32. from project_manager import pm_project
  33. #UI modules
  34. from UI import UI_elements
  35. from UI import UI_color
  36. # story
  37. from studio import story
  38. from studio import checklist
  39. from studio import analytics
  40. from studio import studio_dialogs
  41. from studio import schedule
  42. from studio import history
  43. def datetip(win, date):
  44. # Function that outputs basic analytics about a given date
  45. # in text form
  46. # TODO: This function is in a prototype stage. Meaning it's
  47. # not translated to multiple languages. This should be fixed.
  48. # See settings/talk.py file.
  49. text = date
  50. try:
  51. data = win.analytics["dates"][date]
  52. # Expected
  53. startdate = win.analytics["startdate"]
  54. deadline = win.analytics["deadline"]
  55. duration = win.analytics["duration"]
  56. new_date_format = "%Y/%m/%d"
  57. sd = datetime.datetime.strptime(startdate, new_date_format)
  58. nd = datetime.datetime.strptime(date , new_date_format)
  59. dn = nd - sd
  60. daysin = int(dn.days)
  61. expected = round(100 / duration * daysin, 2)
  62. text = text +"\n\nExpected: "+str(expected)+"%"
  63. # Actual
  64. frac = round(data.get("fractions", {}).get("project")*100, 2)
  65. text = text +"\nActual: "+str(frac)+"%"
  66. # Productivity
  67. productivity = int(round(frac - expected+100))
  68. text = text +"\n\nProductivity: "+str(productivity)+"%"
  69. except Exception as e:
  70. pass
  71. return text
  72. def layer(win):
  73. # This is very important. I makes live easier. LOL.
  74. win.current["shot_left_side_scroll_please_work_omg_wtf"] = True
  75. # Making the layer
  76. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
  77. win.current['h'])
  78. layer = cairo.Context(surface)
  79. #text setting
  80. layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  81. UI_color.set(layer, win, "dark_overdrop")
  82. layer.rectangle(
  83. 0,
  84. 0,
  85. win.current["w"],
  86. win.current["h"],
  87. )
  88. layer.fill()
  89. UI_color.set(layer, win, "node_background")
  90. UI_elements.roundrect(layer, win,
  91. win.current["w"]/4,
  92. 10,
  93. win.current["w"]/2,
  94. win.current["h"]-20,
  95. 10)
  96. ############################################################################
  97. # This is the Analitycs window. This time I want to do something crazy.
  98. # There will be 5 sections on the screen.
  99. ############################################################################
  100. # # # #
  101. # # # #
  102. # # PROGRESS BARS # #
  103. # # # #
  104. # # # #
  105. # # ####################################### # #
  106. # # # #
  107. # SCHEDULING # ANALYTICS GRAPH # MAIN #
  108. # # # CHECKLIS #
  109. # # # #
  110. # # ####################################### # #
  111. # # # #
  112. # # CALENDAR / DAY SELECTOR # #
  113. # # # #
  114. # # X # #
  115. ############################################################################
  116. # The idea is that you could be in Assets or Script Editor and drag the
  117. # tasks from the checklist down to Calendar and leave it there. So when you
  118. # move the task and mouse comes into the center part, you automatically
  119. # transferred to the analytics layer for untill you leave the task somewhere
  120. # or return it back to the checklist.
  121. # I want to do the same with the SCHEDULING. So you could simple drag it to
  122. # the Calendar to change it's date. Let's see how terribly hard will it be
  123. # to implement. Because I'm already fighting with the Scedules all day.
  124. ############################################################################
  125. ############## PROGRESS BARS #############
  126. timepassed = 0.0
  127. projectdone = 0.0
  128. chrdone = 0.0
  129. vehdone = 0.0
  130. locdone = 0.0
  131. objdone = 0.0
  132. rnddone = 0.0
  133. try:
  134. timepassed = win.analytics["timepassed"]
  135. projectdone = win.analytics["fraction"]
  136. chrdone = win.analytics["chr"]
  137. vehdone = win.analytics["veh"]
  138. locdone = win.analytics["loc"]
  139. objdone = win.analytics["obj"]
  140. rnddone = win.analytics["rnd"]
  141. except:
  142. pass
  143. # MAIN PROGRESS
  144. UI_elements.image(layer, win, "settings/themes/"\
  145. +win.settings["Theme"]+"/icons/star.png",
  146. win.current["w"]/4+10,
  147. 15,
  148. 40,
  149. 40)
  150. # Progressbar
  151. UI_color.set(layer, win, "progress_background")
  152. UI_elements.roundrect(layer, win,
  153. win.current["w"]/4+60,
  154. 25,
  155. win.current["w"]/2-80,
  156. 20,
  157. 10,
  158. tip="Today's requirement is: "+str(round(win.analytics.get("needed", 0)*100, 1))+"% ( "+str(round(win.analytics.get("star", 0)*100, 1))+"% of which is done )")
  159. UI_color.set(layer, win, "text_link")
  160. UI_elements.roundrect(layer, win,
  161. win.current["w"]/4+60,
  162. 25,
  163. (win.current["w"]/2-80)*min(win.analytics.get("star", 0), 1),
  164. 20,
  165. 10)
  166. # # Icon
  167. # UI_elements.image(layer, win, "settings/themes/"\
  168. # +win.settings["Theme"]+"/icons/analytics.png",
  169. # win.current["w"]/4+10,
  170. # 15,
  171. # 40,
  172. # 40)
  173. # # Progressbar
  174. # UI_color.set(layer, win, "progress_background")
  175. # UI_elements.roundrect(layer, win,
  176. # win.current["w"]/4+60,
  177. # 25,
  178. # win.current["w"]/2-80,
  179. # 20,
  180. # 10,
  181. # tip=str(round(projectdone*100, 1))+"%")
  182. # # Project Done
  183. # UI_color.set(layer, win, "progress_active")
  184. # UI_elements.roundrect(layer, win,
  185. # win.current["w"]/4+60,
  186. # 25,
  187. # (win.current["w"]/2-80)*projectdone,
  188. # 20,
  189. # 10)
  190. # TIME PASSED
  191. # Icon
  192. UI_elements.image(layer, win, "settings/themes/"\
  193. +win.settings["Theme"]+"/icons/schedule.png",
  194. win.current["w"]/4+10,
  195. 55,
  196. 40,
  197. 40)
  198. UI_color.set(layer, win, "progress_background")
  199. UI_elements.roundrect(layer, win,
  200. win.current["w"]/4+60,
  201. 65,
  202. win.current["w"]/2-80,
  203. 20,
  204. 10,
  205. tip="Time: "+str(round(timepassed*100, 1))+"% Project: "+str(round(projectdone*100, 1))+"%")
  206. # Timepassed
  207. UI_color.set(layer, win, "progress_time")
  208. UI_elements.roundrect(layer, win,
  209. win.current["w"]/4+60,
  210. 65,
  211. (win.current["w"]/2-80)*timepassed,
  212. 20,
  213. 10)
  214. # Project Done
  215. UI_color.set(layer, win, "progress_active")
  216. UI_elements.roundrect(layer, win,
  217. win.current["w"]/4+60,
  218. 65,
  219. (win.current["w"]/2-80)*projectdone,
  220. 20,
  221. 10)
  222. # SCENES DONE ( RND )
  223. # Icon
  224. UI_elements.image(layer, win, "settings/themes/"\
  225. +win.settings["Theme"]+"/icons/shot.png",
  226. win.current["w"]/4+10,
  227. 95,
  228. 40,
  229. 40)
  230. UI_color.set(layer, win, "shot_5")
  231. UI_elements.roundrect(layer, win,
  232. win.current["w"]/4+60,
  233. 105,
  234. win.current["w"]/2-80,
  235. 20,
  236. 10,
  237. tip=str(round(rnddone*100, 1))+"%",
  238. fill=False)
  239. layer.stroke()
  240. # Scenes
  241. UI_color.set(layer, win, "shot_5")
  242. UI_elements.roundrect(layer, win,
  243. win.current["w"]/4+60,
  244. 105,
  245. (win.current["w"]/2-80)*rnddone,
  246. 20,
  247. 10)
  248. # CHR DONE
  249. # Icon
  250. UI_elements.image(layer, win, "settings/themes/"\
  251. +win.settings["Theme"]+"/icons/chr.png",
  252. win.current["w"]/4+10,
  253. 135,
  254. 40,
  255. 40)
  256. UI_color.set(layer, win, "shot_1")
  257. UI_elements.roundrect(layer, win,
  258. win.current["w"]/4+60,
  259. 145,
  260. win.current["w"]/4-80,
  261. 20,
  262. 10,
  263. tip=str(round(chrdone*100, 1))+"%",
  264. fill=False)
  265. layer.stroke()
  266. # progress
  267. UI_color.set(layer, win, "shot_1")
  268. UI_elements.roundrect(layer, win,
  269. win.current["w"]/4+60,
  270. 145,
  271. (win.current["w"]/4-80)*chrdone,
  272. 20,
  273. 10)
  274. # VEH DONE
  275. # Icon
  276. UI_elements.image(layer, win, "settings/themes/"\
  277. +win.settings["Theme"]+"/icons/veh.png",
  278. win.current["w"]/2,
  279. 135,
  280. 40,
  281. 40)
  282. UI_color.set(layer, win, "shot_2")
  283. UI_elements.roundrect(layer, win,
  284. win.current["w"]/2+60,
  285. 145,
  286. win.current["w"]/4-80,
  287. 20,
  288. 10,
  289. tip=str(round(vehdone*100, 1))+"%",
  290. fill=False)
  291. layer.stroke()
  292. # progress
  293. UI_color.set(layer, win, "shot_2")
  294. UI_elements.roundrect(layer, win,
  295. win.current["w"]/2+60,
  296. 145,
  297. (win.current["w"]/4-80)*vehdone,
  298. 20,
  299. 10)
  300. # LOC DONE
  301. # Icon
  302. UI_elements.image(layer, win, "settings/themes/"\
  303. +win.settings["Theme"]+"/icons/loc.png",
  304. win.current["w"]/4+10,
  305. 175,
  306. 40,
  307. 40)
  308. UI_color.set(layer, win, "shot_3")
  309. UI_elements.roundrect(layer, win,
  310. win.current["w"]/4+60,
  311. 185,
  312. win.current["w"]/4-80,
  313. 20,
  314. 10,
  315. tip=str(round(locdone*100, 1))+"%",
  316. fill=False)
  317. layer.stroke()
  318. # progress
  319. UI_color.set(layer, win, "shot_3")
  320. UI_elements.roundrect(layer, win,
  321. win.current["w"]/4+60,
  322. 185,
  323. (win.current["w"]/4-80)*locdone,
  324. 20,
  325. 10)
  326. # OBJ DONE
  327. # Icon
  328. UI_elements.image(layer, win, "settings/themes/"\
  329. +win.settings["Theme"]+"/icons/obj.png",
  330. win.current["w"]/2,
  331. 175,
  332. 40,
  333. 40)
  334. UI_color.set(layer, win, "shot_4")
  335. UI_elements.roundrect(layer, win,
  336. win.current["w"]/2+60,
  337. 185,
  338. win.current["w"]/4-80,
  339. 20,
  340. 10,
  341. tip=str(round(objdone*100, 1))+"%",
  342. fill=False)
  343. layer.stroke()
  344. # progress
  345. UI_color.set(layer, win, "shot_4")
  346. UI_elements.roundrect(layer, win,
  347. win.current["w"]/2+60,
  348. 185,
  349. (win.current["w"]/4-80)*objdone,
  350. 20,
  351. 10)
  352. ############### THE GRAPH ##################
  353. # This graph going to show the entire time of the whole project from
  354. # StartDate till Deadline. It will fill up with data overtime.
  355. # We are going to have a couple of modes.
  356. # Regular mode: Showing actuall values represented on the graph
  357. # it's going to be a diagonal line if the work was done
  358. # linearly.
  359. # Normalized : This this a graph of true values compared to time. So let's
  360. # say at a second day you had to be only at 3%. Being at 3% will
  361. # make graph show 100%. The expected value is linear from 0%
  362. # to 100% over the whole project.
  363. # Pulse mode : This mode will give a graph compared to the previous day.
  364. # if on a given day there was a gib jump in percentage there
  365. # will be a big spike on the graph.
  366. # Let's make a mode selector.
  367. if "analytics_middle_graph_mode" not in win.current:
  368. win.current["analytics_middle_graph_mode"] = "linear"
  369. for num, thing in enumerate(["linear", "analytics", "pulse"]): # By icons
  370. if win.current["analytics_middle_graph_mode"] == thing:
  371. UI_color.set(layer, win, "progress_time")
  372. UI_elements.roundrect(layer, win,
  373. win.current["w"]/4+10+(40*num),
  374. 225,
  375. 40,
  376. 40,
  377. 10)
  378. def do():
  379. win.current["analytics_middle_graph_mode"] = thing
  380. del win.current["graph_cashe"]
  381. UI_elements.roundrect(layer, win,
  382. win.current["w"]/4+10+(40*num),
  383. 225,
  384. 40,
  385. 40,
  386. 10,
  387. do,
  388. thing)
  389. # And before we start a little settings icon.
  390. # Documentation entry
  391. def do():
  392. def after(win, var):
  393. pass
  394. studio_dialogs.help(win, "help", after, SEARCH=talk.text("documentation_analytics"))
  395. UI_elements.roundrect(layer, win,
  396. win.current["w"]/4*3-110,
  397. 225,
  398. 40,
  399. 40,
  400. 10,
  401. do,
  402. "question")
  403. # Settings
  404. def do():
  405. win.url = "settings_layer"
  406. UI_elements.roundrect(layer, win,
  407. win.current["w"]/4*3-60,
  408. 225,
  409. 40,
  410. 40,
  411. 10,
  412. do,
  413. "settings",
  414. talk.text("Settings"))
  415. # Now let's make a filter by type. As you maybe already know appart from
  416. # the main progress I'm reading all the other things too. Every progress
  417. # bar on the screen should have a corrisponding graph. But sometimes if
  418. # paths go too far appart, this aint helpfull. So a filter is required to
  419. # switch a category on and off.
  420. if "analytics_middle_graph_switch" not in win.current:
  421. win.current["analytics_middle_graph_switch"] = {
  422. "project":[True,"analytics", "progress_active"],
  423. "checklist":[True,"checklist", "node_videofile"],
  424. "rnd":[True,"shot", "shot_5"],
  425. "chr":[True,"chr", "shot_1"],
  426. "veh":[True,"veh", "shot_2"], # Name in data : [ Active, Icon name, color ]
  427. "loc":[True,"loc", "shot_3"],
  428. "obj":[True,"obj", "shot_4"]
  429. }
  430. cat = win.current["analytics_middle_graph_switch"]
  431. mode = win.current["analytics_middle_graph_mode"]
  432. for num, thing in enumerate(cat):
  433. if cat[thing][0]:
  434. UI_color.set(layer, win, cat[thing][2])
  435. UI_elements.roundrect(layer, win,
  436. win.current["w"]/4+160+(40*num),
  437. 225,
  438. 38,
  439. 40,
  440. 10,
  441. fill=False)
  442. layer.stroke()
  443. def do():
  444. cat[thing][0] = not cat[thing][0]
  445. del win.current["graph_cashe"]
  446. UI_elements.roundrect(layer, win,
  447. win.current["w"]/4+160+(40*num),
  448. 225,
  449. 38,
  450. 40,
  451. 10,
  452. do,
  453. cat[thing][1])
  454. # Let's set up some very handy values
  455. new_date_format = "%Y/%m/%d"
  456. startdate = win.analytics["startdate"]
  457. deadline = win.analytics["deadline"]
  458. duration = win.analytics["duration"]
  459. # Let's setup the little graph layer. So nothing come out of the frame.
  460. x = win.current["w"]/4
  461. y = 280
  462. width = win.current["w"] / 2
  463. height = 100
  464. # Now let's make a layer.
  465. # CURRENT DATE
  466. today = datetime.datetime.strftime(datetime.datetime.today(), new_date_format)
  467. if "graph_cashe" not in win.current:
  468. # Making the layer
  469. graphsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
  470. node = cairo.Context(graphsurface)
  471. node.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  472. # Background
  473. #UI_color.set(node, win, "dark_overdrop")
  474. #node.rectangle(0,0,width, height)
  475. #node.fill()
  476. # helping line
  477. UI_color.set(node, win, "progress_time")
  478. if mode == "analytics":
  479. node.move_to(0, height/2)
  480. node.line_to(width, height/2)
  481. elif mode == "pulse":
  482. ty = height / duration
  483. node.move_to(0, height/2-ty)
  484. node.line_to(width, height/2-ty)
  485. else:
  486. node.move_to(0, height)
  487. node.line_to(width, 0)
  488. node.stroke()
  489. todayX = 0
  490. for num, thing in enumerate(reversed(cat)):
  491. if cat[thing][0]:
  492. UI_color.set(node, win, cat[thing][2])
  493. if mode == "linear":
  494. node.move_to(0, height)
  495. else:
  496. node.move_to(0, height/2)
  497. pfrac = 0
  498. dates = win.analytics["dates"]
  499. for date in dates:
  500. # Let's calculate the X position of a given part on a graph
  501. sd = datetime.datetime.strptime(startdate, new_date_format)
  502. nd = datetime.datetime.strptime(date , new_date_format)
  503. dn = nd - sd
  504. dn = int(dn.days)
  505. graphX = width / duration * dn
  506. if date == today:
  507. todayX = graphX
  508. # Let's calculate the Y position of a given part on a graph
  509. if "fractions" in dates[date]:
  510. fracs = dates[date]["fractions"]
  511. if mode == "linear":
  512. gfraction = fracs[thing]
  513. graphY = height - height * gfraction
  514. node.line_to(graphX, graphY)
  515. elif mode == "analytics":
  516. gfraction = fracs[thing]
  517. tfraction = dn / duration
  518. gfraction = gfraction / tfraction / 2
  519. graphY = height - height * gfraction
  520. node.line_to(graphX, graphY)
  521. else:
  522. gfraction = fracs[thing]
  523. gfraction = gfraction - pfrac
  524. graphY = height - height * gfraction - height / 2
  525. node.line_to(graphX, graphY)
  526. pfrac = fracs[thing]
  527. node.stroke()
  528. # Today
  529. UI_color.set(node, win, "button_clicked")
  530. node.move_to(todayX, 0)
  531. node.line_to(todayX, height)
  532. node.stroke()
  533. win.current["graph_cashe"] = graphsurface
  534. # Dynamic elements of the graph!
  535. # I sense a possible bug here, since I will draw these on top
  536. # of a prebaked image. And something sometimes might not align
  537. # properly. I don't know how to deal with it quite yet, perhaps
  538. # you can try fixing the issue. LOL.
  539. # Bottom Graph position on the top graph.
  540. try:
  541. posX = width / (duration*50) * win.scroll["days"]
  542. posX2 = (width / (duration*50) * (win.scroll["days"]+width))-posX
  543. except:
  544. posX = 0
  545. posX2 = 20
  546. UI_color.set(layer, win, "dark_overdrop")
  547. UI_elements.roundrect(layer, win,
  548. x-posX,
  549. y,
  550. posX2,
  551. height,
  552. 5,
  553. fill=True)
  554. layer.stroke()
  555. # Mouse drag thingy
  556. if x < win.current["mx"] < x+width\
  557. and y < win.current["my"] < y+height:
  558. if win.current["LMB"]:
  559. win.scroll["days"] = 0- (( win.current["mx"] - x ) / width * (duration*50)) + (width/2)
  560. sd = datetime.datetime.strptime(startdate, new_date_format)
  561. daysin = int(round( duration / width * (x-win.current["mx"])))*-1
  562. td = datetime.timedelta(days=daysin)
  563. hoverdate = sd + td
  564. hoverdate = hoverdate.strftime(new_date_format)
  565. UI_elements.tooltip(win, datetip(win, hoverdate))
  566. UI_color.set(layer, win, "progress_background")
  567. layer.move_to(win.current["mx"], y)
  568. layer.line_to(win.current["mx"], y+height)
  569. layer.stroke()
  570. # Outputting the layer
  571. layer.set_source_surface(win.current["graph_cashe"], x, y)
  572. layer.paint()
  573. # Let's force graph to refresh on each click
  574. if not win.current["LMB"] and win.previous["LMB"]:
  575. try:
  576. del win.current["graph_cashe"]
  577. except:
  578. pass
  579. ############### SCHEDULING / DATE SELECTION ################
  580. # So here I want to put a dialog that in the Blender-Organizer legacy was
  581. # in the center of the frame. I thought here it makes a bit more sense. Tho
  582. # I will remove the graph from it. I have a very good graph on the top from
  583. # it. And in my opinion this is enough. In you think otherwise let me know.
  584. # or fork VCStudio and implement it yourself. See what I can do with free-
  585. # software. I can just tell you to do everything yourself. Imaging doing this
  586. # when developing proprietery software.
  587. # Anyway back to the thing. I do want to draw schedules inside the days cells.
  588. # and by their colors. RED or GREY or PURPLE or GREEN you will have an idea of
  589. # how much stuff is done and how much stuff is not yet done. A kind of second
  590. # graph, so to speak. But representing differnt kind of data.
  591. # OKAY. SETTINGS.
  592. if "schedule_analytics_settings" not in win.current:
  593. win.current["schedule_analytics_settings"] = {
  594. "checked":False,
  595. "multiuser":False
  596. }
  597. for num, button in enumerate(win.current["schedule_analytics_settings"]):
  598. if win.current["schedule_analytics_settings"][button]:
  599. UI_color.set(layer, win, "progress_time")
  600. UI_elements.roundrect(layer, win,
  601. x+10+(40*num),
  602. y+height+10,
  603. 40,
  604. 40,
  605. 10)
  606. def do():
  607. win.current["schedule_analytics_settings"][button] = not win.current["schedule_analytics_settings"][button]
  608. UI_elements.roundrect(layer, win,
  609. x+10+(40*num),
  610. y+height+10,
  611. 40,
  612. 40,
  613. 10,
  614. do,
  615. button)
  616. UI_elements.text(layer, win, "current_date_setting",
  617. x+100,
  618. y+height+10,
  619. 200,
  620. 40,
  621. set_text=win.current["date"])
  622. if win.text["current_date_setting"]["text"] != win.current["date"]\
  623. and analytics.ifdate(win.text["current_date_setting"]["text"]):
  624. def do():
  625. win.current["date"] = win.text["current_date_setting"]["text"]
  626. win.textactive = ""
  627. UI_elements.roundrect(layer, win,
  628. x+260,
  629. y+height+10,
  630. 40,
  631. 40,
  632. 10,
  633. button=do,
  634. icon="ok",
  635. tip=talk.text("checked"))
  636. elif win.current["date"] != today:
  637. def do():
  638. win.current["date"] = today
  639. win.text["current_date_setting"]["text"] = today
  640. win.textactive = ""
  641. UI_elements.roundrect(layer, win,
  642. x+260,
  643. y+height+10,
  644. 40,
  645. 40,
  646. 10,
  647. button=do,
  648. icon="cancel",
  649. tip=talk.text("cancel"))
  650. y = y + height + 60
  651. height = win.current["h"]-500
  652. # Making the layer
  653. graphsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
  654. node = cairo.Context(graphsurface)
  655. node.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  656. if "days" not in win.scroll:
  657. win.scroll["days"] = 0 - win.analytics["dayspassed"]*50 + width / 2 - 25
  658. current_X = 0
  659. prevyear = [startdate.split("/")[0], win.scroll["days"]]
  660. prevmonth = [startdate.split("/")[1], win.scroll["days"]]
  661. prevday = "1997/07/30"
  662. pfrac = {}
  663. for doffset in range(duration+1): # FOR ALL DAYS. NO MATTER IF THEY ARE IN DATA
  664. sd = datetime.datetime.strptime(startdate, new_date_format)
  665. theday = datetime.datetime.strftime(sd+datetime.timedelta(days=doffset), new_date_format)
  666. nowyear = [theday.split("/")[0], current_X+win.scroll["days"]]
  667. nowmonth = [theday.split("/")[1], current_X+win.scroll["days"]]
  668. # Focusing if selected
  669. if win.current["date"] != win.previous["date"] and win.current["date"] == theday:
  670. win.scroll["days"] = 0 - current_X + width / 2 - 25
  671. # YEARS
  672. if nowyear[0] != prevyear[0]:
  673. UI_color.set(node, win, "dark_overdrop")
  674. UI_elements.roundrect(node, win,
  675. 5+prevyear[1]+2,
  676. 0,
  677. nowyear[1] - prevyear[1]-7,
  678. 20,
  679. 10)
  680. UI_color.set(node, win, "text_normal")
  681. node.set_font_size(15)
  682. node.move_to(
  683. max(prevyear[1] + 200, min(width/2-23, nowyear[1]- 200)),
  684. 15,
  685. )
  686. node.show_text(prevyear[0])
  687. prevyear = nowyear
  688. # MONTHS
  689. if nowmonth[0] != prevmonth[0]:
  690. UI_color.set(node, win, "dark_overdrop")
  691. UI_elements.roundrect(node, win,
  692. 5+prevmonth[1]+2,
  693. 22,
  694. nowmonth[1]-prevmonth[1]-7,
  695. 20,
  696. 10)
  697. UI_color.set(node, win, "text_normal")
  698. node.set_font_size(15)
  699. node.move_to(
  700. max(prevmonth[1] + 12, min(width/2-12, nowmonth[1]- 35)),
  701. 38,
  702. )
  703. node.show_text(prevmonth[0])
  704. prevmonth = nowmonth
  705. # DAYS
  706. if -50 < current_X+win.scroll["days"] < width:
  707. UI_color.set(node, win, "dark_overdrop")
  708. UI_elements.roundrect(node, win,
  709. 5+current_X+win.scroll["days"],
  710. 44,
  711. 40,
  712. 20,
  713. 10)
  714. UI_color.set(node, win, "text_normal")
  715. node.set_font_size(15)
  716. node.move_to(
  717. 15+current_X+win.scroll["days"],
  718. 59,
  719. )
  720. node.show_text(theday.split("/")[2])
  721. UI_color.set(node, win, "dark_overdrop")
  722. if theday >= today:
  723. if theday == today:
  724. UI_color.set(node, win, "button_clicked")
  725. UI_elements.roundrect(node, win,
  726. 5+current_X+win.scroll["days"],
  727. 67,
  728. 40,
  729. height-67,
  730. 10)
  731. if win.current["date"] == theday:
  732. UI_color.set(node, win, "progress_background")
  733. UI_elements.roundrect(node, win,
  734. 5+current_X+win.scroll["days"],
  735. 67,
  736. 40,
  737. height-67,
  738. 10,
  739. fill=False)
  740. node.stroke()
  741. # STARTDATE & DEADLINE
  742. elif theday in [startdate, deadline]:
  743. UI_color.set(node, win, "node_badfile")
  744. UI_elements.roundrect(node, win,
  745. 5+current_X+win.scroll["days"],
  746. 67,
  747. 40,
  748. height-67,
  749. 10,
  750. fill=False)
  751. node.stroke()
  752. # SELECTION BUTTON
  753. def do():
  754. if win.current["tool"] == "schedule":
  755. # If it's a scheduling.
  756. path, back, cur, schedulepath, username = win.current["grab_data"].copy()
  757. path = path.replace(win.project, "")
  758. path = path[path.find(cur)+len(cur):]
  759. if theday not in win.analytics["dates"]:
  760. win.analytics["dates"][theday] = {}
  761. name = cur[cur.rfind("/")+1:]
  762. acur = cur.replace(name, "").replace("/", "")
  763. if acur in ["chr", "veh", "loc","obj"]:
  764. itemtype = "assets"
  765. elif not acur:
  766. itemtype = "files"
  767. else:
  768. itemtype = "scenes"
  769. if itemtype not in win.analytics["dates"][theday]:
  770. win.analytics["dates"][theday][itemtype] = {}
  771. if cur not in win.analytics["dates"][theday][itemtype]:
  772. win.analytics["dates"][theday][itemtype][cur] = []
  773. #print("test 1")
  774. win.analytics["dates"][theday][itemtype][cur].append(
  775. ["00:00:00",
  776. "schedule",
  777. path,
  778. "[Un-Checked]",
  779. schedulepath,
  780. username]
  781. )
  782. #print("test 2")
  783. # RETURNING BACK TO NORMAL
  784. win.url = back
  785. win.current["tool"] = "selection"
  786. analytics.save(win.project, win.analytics)
  787. win.analytics = analytics.load(win.project)
  788. win.checklists = {}
  789. # Multiuser sycning
  790. win.multiuser["request"] = "analytics"
  791. #print("test 3")
  792. else:
  793. win.current["date"] = theday
  794. win.text["current_date_setting"]["text"] = theday
  795. UI_elements.roundrect(node, win,
  796. 5+current_X+win.scroll["days"],
  797. 67,
  798. 40,
  799. height-67,
  800. 10,
  801. button=do,
  802. offset=[x,y],
  803. fill=False,
  804. tip=datetip(win, theday))
  805. node.stroke()
  806. ##################### BOTTOM GRAPH #######################
  807. # Blender-Organizer legacy had two graphs. One smaller
  808. # that shows all the project from start to finish
  809. # ( Implemented above ) and one bigger. That shows a
  810. # Zoomed in version of the graph. Back then I made it
  811. # executing the same code twice. Which was fine. Since
  812. # I had to make a change only ones. Now it's a bit of a
  813. # problem to do so. Since the graph above is baked. And
  814. # need to be able to navigate with in the bottom graph.
  815. # So instead I'm going to redo the graph, but since we
  816. # are re-doing it. I orignally wanted to do a different
  817. # design. But it ended up looking confusing. It needs lines!
  818. # Roundrects will not do.
  819. for num, thing in enumerate(reversed(cat)):
  820. if cat[thing][0]:
  821. UI_color.set(node, win, cat[thing][2])
  822. try:
  823. sd = datetime.datetime.strptime(startdate, new_date_format)
  824. nd = datetime.datetime.strptime(theday , new_date_format)
  825. dn = nd - sd
  826. dn = int(dn.days)
  827. fracs = win.analytics["dates"][theday]["fractions"]
  828. Pfracs = win.analytics["dates"][prevday]["fractions"]
  829. gfraction = fracs[thing]
  830. if mode == "linear":
  831. graphY = ((height-80) - (height-80) * gfraction)+60
  832. elif mode == "analytics":
  833. tfraction = dn / duration
  834. gfraction = gfraction / tfraction / 2
  835. graphY = ((height-80) - (height-80) * gfraction)+60
  836. else:
  837. try:
  838. gfraction = fracs[thing]
  839. gfraction = gfraction - Pfracs[thing]
  840. except:
  841. gfraction = 0
  842. graphY = ((height-80) - (height-80) * gfraction - (height-80) / 2)+60
  843. PgraphY = pfrac.get(thing, graphY)
  844. pfrac[thing] = graphY
  845. node.move_to(current_X+win.scroll["days"]-25,
  846. PgraphY)
  847. node.line_to(current_X+win.scroll["days"]+25,
  848. graphY)
  849. node.stroke()
  850. except Exception as e:
  851. if theday < today and theday not in win.analytics["dates"]:
  852. UI_color.set(node, win, "node_badfile")
  853. UI_elements.roundrect(node, win,
  854. 5+current_X+win.scroll["days"],
  855. 67,
  856. 40,
  857. height-67,
  858. 10,
  859. fill=False)
  860. node.stroke()
  861. # Now here I want to draw the representations of scheduled
  862. # tasks that are inside
  863. # Icons of what was done at the day
  864. icons_stuff = {"assets":"obj",
  865. "scenes":"shot",
  866. "files":"checklist"}
  867. icons = []
  868. for t in icons_stuff:
  869. if t in win.analytics["dates"].get(theday, {}) and t != "assets":
  870. icons.append(icons_stuff[t])
  871. elif t in win.analytics["dates"].get(theday, {}):
  872. for at in ["chr", "obj", "loc", "veh"]:
  873. for stuff in win.analytics["dates"].get(theday, {})[t]:
  874. if at in stuff and at not in icons:
  875. icons.append(at)
  876. for nicon, icon in enumerate(icons):
  877. UI_elements.image(node, win, "settings/themes/"\
  878. +win.settings["Theme"]+"/icons/"+icon+".png",
  879. 6+current_X+win.scroll["days"],
  880. height-(50*nicon)-50,
  881. 40,
  882. 40)
  883. # Stars! If the day was exceptional.
  884. try:
  885. if win.analytics["needed"] <= fracs.get("project", 0) - Pfracs.get("project", 0) and theday <= today:
  886. stars = (fracs.get("project", 0) - Pfracs.get("project", 0)) / win.analytics["needed"]
  887. for star in range(int(round(stars))):
  888. UI_elements.image(node, win, "settings/themes/"\
  889. +win.settings["Theme"]+"/icons/star.png",
  890. 6+current_X+win.scroll["days"],
  891. height-(50*(nicon+star-1))-150,
  892. 40,
  893. 40)
  894. except:
  895. pass
  896. # Schedules
  897. sch = []
  898. if theday in win.analytics["dates"]:
  899. date = win.analytics["dates"][theday]
  900. for i in ["files", "assets", "scenes"]:
  901. if i in date:
  902. for item in date[i]:
  903. for stuff in date[i][item]:
  904. if stuff[1] == "schedule":
  905. if not win.current["schedule_analytics_settings"]["multiuser"]:
  906. if win.settings["Username"] != stuff[-1]:
  907. continue
  908. if "[Checked]" in stuff:
  909. if win.current["schedule_analytics_settings"]["checked"]:
  910. sch.append(True)
  911. else:
  912. sch.append(False)
  913. for n, s in enumerate(sch):
  914. UI_color.set(node, win, "node_background")
  915. if theday < today:
  916. UI_color.set(node, win, "node_badfile")
  917. elif theday > today:
  918. UI_color.set(node, win, "node_asset")
  919. if s:
  920. UI_color.set(node, win, "node_blendfile")
  921. UI_elements.roundrect(node, win,
  922. 8+current_X+win.scroll["days"],
  923. 70+13*n,
  924. 35,
  925. 8,
  926. 5)
  927. current_X = current_X + 50
  928. prevday = theday
  929. UI_color.set(node, win, "dark_overdrop")
  930. UI_elements.roundrect(node, win,
  931. 5+prevyear[1]+2,
  932. 0,
  933. nowyear[1] - prevyear[1]-4 + 50,
  934. 20,
  935. 10)
  936. UI_color.set(node, win, "text_normal")
  937. node.set_font_size(15)
  938. node.move_to(
  939. max(prevyear[1] + 200, min(width/2-23, nowyear[1]- 200)),
  940. 15,
  941. )
  942. node.show_text(prevyear[0])
  943. UI_color.set(node, win, "dark_overdrop")
  944. UI_elements.roundrect(node, win,
  945. 5+prevmonth[1]+2,
  946. 22,
  947. nowmonth[1]-prevmonth[1]-4 + 50,
  948. 20,
  949. 10)
  950. UI_color.set(node, win, "text_normal")
  951. node.set_font_size(15)
  952. node.move_to(
  953. max(prevmonth[1] + 12, min(width/2-12, nowmonth[1]- 35)),
  954. 38,
  955. )
  956. node.show_text(prevmonth[0])
  957. # Outputting the layer
  958. layer.set_source_surface(graphsurface, x, y)
  959. layer.paint()
  960. # Scroll
  961. UI_elements.scroll_area(layer, win, "days",
  962. x,
  963. y,
  964. width,
  965. height+30,
  966. current_X,
  967. bar=True,
  968. mmb=True,
  969. sideways=True)
  970. ############## CHECKLIST ################
  971. if win.current["tool"] == "schedule":
  972. # If the tool is scheduling I want to make sure that the user sees where
  973. # he needs to place the task. A higlight.
  974. UI_color.set(layer, win, "progress_background")
  975. UI_elements.roundrect(layer, win,
  976. x,
  977. y+67,
  978. width,
  979. height-67,
  980. 10,
  981. fill=False)
  982. layer.stroke()
  983. path, back, cur, schedulepath, username = win.current["grab_data"].copy()
  984. checklist.draw(layer, win, path, back)
  985. UI_color.set(layer, win, "node_background")
  986. UI_elements.roundrect(layer, win,
  987. win.current["mx"],
  988. win.current["my"],
  989. 40,
  990. 40,
  991. 10)
  992. UI_elements.image(layer, win,
  993. "settings/themes/"+win.settings["Theme"]+"/icons/schedule.png",
  994. win.current["mx"],
  995. win.current["my"],
  996. 40,
  997. 40)
  998. elif os.path.exists(win.project+"/set/project.progress"):
  999. checklist.draw(layer, win, win.project+"/set/project.progress", back=win.url)
  1000. elif os.path.exists(win.project+"/project.progress"):
  1001. checklist.draw(layer, win, win.project+"/project.progress", back=win.url)
  1002. # In the analytics window there will a choise of whether to see schedules. Or to
  1003. # see history.
  1004. if "analytics_left_panel" not in win.current:
  1005. win.current["analytics_left_panel"] = "schedule"
  1006. UI_color.set(layer, win, "node_background")
  1007. UI_elements.roundrect(layer, win,
  1008. 10,
  1009. 10,
  1010. win.current["w"]/4-20,
  1011. 50,
  1012. 10)
  1013. for num, thing in enumerate(["schedule", "history"]):
  1014. if win.current["analytics_left_panel"] == thing:
  1015. UI_color.set(layer, win, "progress_time")
  1016. UI_elements.roundrect(layer, win,
  1017. 20+(40*num),
  1018. 15,
  1019. 40,
  1020. 40,
  1021. 10)
  1022. def do():
  1023. win.current["analytics_left_panel"] = thing
  1024. UI_elements.roundrect(layer, win,
  1025. 20+(40*num),
  1026. 15,
  1027. 40,
  1028. 40,
  1029. 10,
  1030. do,
  1031. thing)
  1032. ##### SCHEDULE ######
  1033. if win.current["analytics_left_panel"] == "schedule":
  1034. schedule.draw(layer, win)
  1035. ##### HISTORY #######
  1036. else:
  1037. history.draw(layer, win)
  1038. # CANCEl
  1039. def do():
  1040. win.url = "story_editor"
  1041. win.assets = {}
  1042. win.current["asset_file_selected"] = ""
  1043. UI_elements.roundrect(layer, win,
  1044. win.current["w"]-40-win.current["w"]/4,
  1045. win.current["h"]-50,
  1046. 40,
  1047. 40,
  1048. 10,
  1049. button=do,
  1050. icon="cancel",
  1051. tip=talk.text("cancel"))
  1052. # Short cut ESC
  1053. if 65307 in win.current["keys"] and not win.textactive:
  1054. do()
  1055. return surface