history.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. # THIS FILE IS A PART OF VCStudio
  2. # PYTHON 3
  3. import os
  4. import datetime
  5. import threading
  6. # GTK module ( Graphical interface
  7. import gi
  8. gi.require_version('Gtk', '3.0')
  9. from gi.repository import Gtk
  10. from gi.repository import GLib
  11. from gi.repository import Gdk
  12. import cairo
  13. # Own modules
  14. from settings import settings
  15. from settings import talk
  16. from project_manager import pm_project
  17. from studio import analytics
  18. from studio import studio_nodes
  19. from studio import studio_dialogs
  20. from studio import story
  21. from studio import analytics
  22. from studio import checklist
  23. #UI modules
  24. from UI import UI_elements
  25. from UI import UI_color
  26. ################################################################################
  27. # This file is going to hande HISTORY. Both recoring and showing. I suppose in
  28. # future when I gonna implement Networking Multiuser support. History will be
  29. # handling some of it too. Will see. ( I might forget about this comment and it's
  30. # already implemented, who knows. )
  31. ################################################################################
  32. def record(win, filename, task, checklist=[] ):
  33. ############################################################################
  34. # This function will write the history file. Or incase connecting to multiuser
  35. # will also notify the server of a task being done. This file probably going
  36. # to change quite usually. Because I might start writting in more or less
  37. # stuff. So keep an eye on it.
  38. # Now since this will touch the multiuser and I can't guarantee that all
  39. # users will have the same version. The multiuser part of this should insure
  40. # stable backward compatibility with all verisons of VCStudio.
  41. # BUT THERE IS NO WARRANTY! So who knows if exidentaly break something for
  42. # the old versions. So keeping up to date quite advisable. Or at least keep
  43. # the same version of VCStudio across all the computers involved in multiuser.
  44. ############################################################################
  45. ################### CHECKLIST OF IMPEMENTED DATATYPES ######################
  46. # [V] [Openned] # WHEN BLEND FILE IS OPENNED [ settings/oscalls.py ]
  47. # [V] [Linked] # AS LINKED ASSET INTO BLEND [ studio/studio_shot_linkLayer.py ]
  48. # [V] [Edited] # WHEN EDITED STORY [ studio/studio_scriptLayer.py ]
  49. # [V] [Updated] # WHEN CONFIGURED STUFF [ studio/studio_asset_configureLayer.py ]
  50. # [V] [Added] # WHEN ADDING A BLEND FILE [ settings/oscalls.py ]
  51. # [V] [Added Asset] # WHEN ADDING AN ASSET [ studio/studio_asset_selectLayer.py ]
  52. # [V] [Checked] # WHEN [V] IN CHECKLIST [ studio/checklist.py ]
  53. # [V] [Un-Checked] # WHEN [ ] IN CHECKLIST [ studio/checklist.py ]
  54. ############################################################################
  55. ############### PARSING THE URLS ##############
  56. print("HISTORY-RAW: ", filename, task, checklist)
  57. # Fisrt of all let's remove the project's url
  58. filename = filename.replace(win.project, "").replace("//", "/")
  59. # Now let's find a type
  60. t = "files"
  61. item = filename
  62. if filename.startswith("/rnd"):
  63. t = "scenes"
  64. item = filename.replace("/rnd", "")
  65. filename = filename[filename.rfind("/"):]
  66. item = item[:item.rfind(filename)]
  67. elif filename.startswith("/ast") or filename.startswith("/dev"):
  68. t = "assets"
  69. if filename.startswith("/ast"):
  70. filename = "[asset_blend]"
  71. item = item.replace(".blend", "").replace("/ast", "")
  72. elif filename.startswith("/dev"):
  73. f = filename[filename.rfind("/"):]
  74. item = filename.replace(f, "").replace("/dev", "")
  75. filename = f
  76. else:
  77. filename = filename[filename.rfind("/"):]
  78. item = item.replace("//", "/")
  79. filename = filename.replace("//", "/")
  80. # Somebody please look at this. Maybe there is more clever way to record
  81. # editing of scenes. I don't know.
  82. if task == "[Edited]":
  83. item = filename
  84. print("HISTORY: ", t, item, filename, task, checklist)
  85. ########### WRITING TO THE ANANLYTICS ###########
  86. new_date_format = "%Y/%m/%d"
  87. date = datetime.datetime.strftime(datetime.datetime.today(), new_date_format)
  88. seconds_format = "%H:%M:%S"
  89. time = datetime.datetime.strftime(datetime.datetime.now(), seconds_format)
  90. username = win.settings["Username"]
  91. # Now since we have our DATE and TIME we can start writing.
  92. if date not in win.analytics["dates"]:
  93. win.analytics["dates"][date] = {}
  94. if t not in win.analytics["dates"][date]:
  95. win.analytics["dates"][date][t] = {}
  96. if item not in win.analytics["dates"][date][t]:
  97. win.analytics["dates"][date][t][item] = []
  98. # APPENDNING
  99. if not checklist:
  100. win.analytics["dates"][date][t][item].append(
  101. [
  102. time,
  103. "history",
  104. filename,
  105. task,
  106. username
  107. ]
  108. )
  109. else:
  110. win.analytics["dates"][date][t][item].append(
  111. [
  112. time,
  113. "history",
  114. filename,
  115. task,
  116. checklist,
  117. username
  118. ]
  119. )
  120. # REFRASHING
  121. analytics.save(win.project, win.analytics)
  122. win.analytics = analytics.load(win.project)
  123. # Multiuser sycning
  124. win.multiuser["request"] = "analytics"
  125. def draw(outlayer, win):
  126. x = 10
  127. y = 70
  128. width = win.current["w"] / 4 - 20
  129. height = win.current["h"] - 80
  130. # Now let's make a layer.
  131. # Making the layer
  132. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
  133. layer = cairo.Context(surface)
  134. layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  135. # Clip
  136. UI_elements.roundrect(layer, win,
  137. 0,
  138. 0,
  139. width,
  140. height,
  141. 10,
  142. fill=False)
  143. layer.clip()
  144. # Background
  145. #UI_color.set(layer, win, "dark_overdrop")
  146. #layer.rectangle(0,0,width, height)
  147. #layer.fill()
  148. new_date_format = "%Y/%m/%d"
  149. today = datetime.datetime.strftime(datetime.datetime.today(), new_date_format)
  150. if win.cur == "/set" :
  151. UI_elements.text(outlayer, win, "current_date_setting",
  152. width-100-(width-6*40),
  153. 15,
  154. (width-6*40),
  155. 40,
  156. set_text=win.current["date"])
  157. if win.text["current_date_setting"]["text"] != win.current["date"]\
  158. and analytics.ifdate(win.text["current_date_setting"]["text"]):
  159. def do():
  160. win.current["date"] = win.text["current_date_setting"]["text"]
  161. win.textactive = ""
  162. UI_elements.roundrect(outlayer, win,
  163. width-140,
  164. 15,
  165. 40,
  166. 40,
  167. 10,
  168. button=do,
  169. icon="ok",
  170. tip=talk.text("checked"))
  171. elif win.current["date"] != today:
  172. def do():
  173. win.current["date"] = today
  174. win.text["current_date_setting"]["text"] = today
  175. win.textactive = ""
  176. UI_elements.roundrect(outlayer, win,
  177. width-140,
  178. 15,
  179. 40,
  180. 40,
  181. 10,
  182. button=do,
  183. icon="cancel",
  184. tip=talk.text("cancel"))
  185. if "history" not in win.scroll:
  186. win.scroll["history"] = 0
  187. current_Y = 0
  188. # OKAY I GUESS SINCE HISTORY IS RELATIVELLY SIMPLE (LOL) I WILL PARSE IT
  189. # STRAIGHT. Without making a whole lot of parsing. Because with schedules
  190. # it resulted in some rather hilarious stuff.
  191. if "history_selection" not in win.current:
  192. win.current["history_selection"] = {
  193. "date":win.current["date"],
  194. "item":False,
  195. "user":False
  196. }
  197. dates = win.analytics["dates"]
  198. for date in dates:
  199. if (not win.cur or win.cur == "/set" )and date != win.current["date"]:
  200. continue
  201. theday = date
  202. date = dates[date]
  203. # Now let's parse throught it. I guess.
  204. for i in ["files", "assets", "scenes"]:
  205. if i in date:
  206. for item in date[i]:
  207. # This is our individual items. I want to create a folder
  208. # with USERS inside it. So you could see who done what.
  209. if win.cur.count("/") > 1:
  210. tmp = win.cur.replace("/","",1).split("/")
  211. acur, name = tmp[0], tmp[1]
  212. else:
  213. name = win.cur[win.cur.rfind("/")+1:]
  214. acur = ""
  215. if win.cur not in item and win.cur != "/set":
  216. continue
  217. name = item[item.rfind("/")+1:]
  218. acur = item.replace(name, "").replace("/", "")
  219. found = {}
  220. for stuff in date[i][item]:
  221. if stuff[1] == "history":
  222. if stuff[-1] not in found:
  223. found[stuff[-1]] = []
  224. if stuff[3] in ["[Checked]", "[Un-Checked]"]:
  225. found[stuff[-1]].append([stuff[0],stuff[2],stuff[3],stuff[4]])
  226. else:
  227. found[stuff[-1]].append([stuff[0],stuff[2],stuff[3],[]])
  228. if found:
  229. # Now let's output our findings
  230. UI_color.set(layer, win, "node_background")
  231. UI_elements.roundrect(layer, win,
  232. 0,
  233. win.scroll["history"] + current_Y,
  234. width,
  235. 50,
  236. 10)
  237. # ICON
  238. if i == "scenes" and win.cur == "/set":
  239. if item.count("/") > 1:
  240. UI_elements.image(layer, win,
  241. "settings/themes/"+win.settings["Theme"]+"/icons/shot.png",
  242. 5, win.scroll["history"] + current_Y+5, 40, 40)
  243. else:
  244. UI_elements.image(layer, win,
  245. "settings/themes/"+win.settings["Theme"]+"/icons/scene.png",
  246. 5, win.scroll["history"] + current_Y+5, 40, 40)
  247. elif i == "assets" and win.cur == "/set":
  248. if os.path.exists(os.getcwd()+"/settings/themes/"+win.settings["Theme"]+"/icons/"+acur+".png"):
  249. UI_elements.image(layer, win,
  250. "settings/themes/"+win.settings["Theme"]+"/icons/"+acur+".png",
  251. 5, win.scroll["history"] + current_Y+5, 40, 40)
  252. else:
  253. UI_elements.image(layer, win,
  254. "settings/themes/"+win.settings["Theme"]+"/icons/"+name+".png",
  255. 5, win.scroll["history"] + current_Y+5, 40, 40)
  256. name = talk.text(name.replace("/", ""))
  257. else:
  258. UI_elements.image(layer, win,
  259. "settings/themes/"+win.settings["Theme"]+"/icons/history.png",
  260. 5, win.scroll["history"] + current_Y+5, 40, 40)
  261. # MAIN TASK
  262. UI_color.set(layer, win, "text_normal")
  263. layer.set_font_size(20)
  264. layer.move_to(
  265. 50,
  266. win.scroll["history"] + current_Y+30,
  267. )
  268. if win.cur == "/set":
  269. if "chr" not in item and "veh" not in item and "loc" not in item and "obj" not in item and "set" not in item:
  270. layer.show_text(item.replace("/","",1).replace("/", " | "))
  271. else:
  272. layer.show_text(name.replace("project.progress", win.analytics["name"]))
  273. else:
  274. layer.show_text(theday)
  275. # Selection button
  276. def do():
  277. win.current["history_selection"]["item"] = item
  278. win.current["history_selection"]["date"] = theday
  279. win.current["history_selection"]["user"] = False
  280. UI_elements.roundrect(layer, win,
  281. 0,
  282. win.scroll["history"] + current_Y,
  283. width,
  284. 50,
  285. 10,
  286. button=do,
  287. fill=False,
  288. offset=[x,y])
  289. layer.stroke()
  290. if win.current["history_selection"]["item"] == item\
  291. and win.current["history_selection"]["date"] == theday:
  292. UI_color.set(layer, win, "progress_background")
  293. UI_elements.roundrect(layer, win,
  294. 0,
  295. win.scroll["history"] + current_Y,
  296. width,
  297. 50,
  298. 10,
  299. fill=False)
  300. layer.stroke()
  301. current_Y = current_Y + 60
  302. # It a history is selected you could open up the list
  303. # of users that worked on a given task.
  304. for user in found:
  305. UI_color.set(layer, win, "node_background")
  306. UI_elements.roundrect(layer, win,
  307. 20,
  308. win.scroll["history"] + current_Y,
  309. width-20,
  310. 50,
  311. 10)
  312. UI_elements.image(layer, win,
  313. "settings/themes/"+win.settings["Theme"]+"/icons/user.png",
  314. 25, win.scroll["history"] + current_Y+5, 40, 40)
  315. # NAME OF THE USER
  316. UI_color.set(layer, win, "text_normal")
  317. layer.set_font_size(20)
  318. layer.move_to(
  319. 70,
  320. win.scroll["history"] + current_Y+30,
  321. )
  322. layer.show_text(user)
  323. # SELECTION BOX
  324. def do():
  325. win.current["history_selection"]["user"] = user
  326. UI_elements.roundrect(layer, win,
  327. 20,
  328. win.scroll["history"] + current_Y,
  329. width-20,
  330. 50,
  331. 10,
  332. button=do,
  333. fill=False,
  334. offset=[x,y])
  335. layer.stroke()
  336. # IF THE USER IS SELECTED
  337. if win.current["history_selection"]["user"] == user:
  338. UI_color.set(layer, win, "progress_background")
  339. UI_elements.roundrect(layer, win,
  340. 20,
  341. win.scroll["history"] + current_Y,
  342. width-20,
  343. 50,
  344. 10,
  345. fill=False)
  346. layer.stroke()
  347. current_Y = current_Y + 60
  348. for stuff in found[user]:
  349. UI_color.set(layer, win, "node_background")
  350. UI_elements.roundrect(layer, win,
  351. 40,
  352. win.scroll["history"] + current_Y,
  353. width-40,
  354. 50,
  355. 10)
  356. # Here if both category and the user are
  357. # selected I need to parse the data of
  358. # the history to present it to you.
  359. operation_icons = {
  360. "[Openned]":"blender",
  361. "[Linked]":"file_link",
  362. "[Edited]":"scene",
  363. "[Updated]":"update",
  364. "[Added]":"new",
  365. "[Added Asset]":"asset_new",
  366. "[Checked]":"checked",
  367. "[Un-Checked]":"unchecked"
  368. }
  369. try:
  370. icon = operation_icons[stuff[2]]
  371. except:
  372. icon = "history"
  373. UI_elements.image(layer, win,
  374. "settings/themes/"+win.settings["Theme"]+"/icons/"+icon+".png",
  375. 45, win.scroll["history"] + current_Y+5, 40, 40)
  376. # Next will be to add some text about
  377. # what exactly was done here. And I know
  378. # that we are already super inside. I
  379. # mean indentation. If somebody want's
  380. # to reduse a few spaces. They are
  381. # welcome to try.
  382. # Let's split the the history entries to
  383. # 2 types. Checklists and everything else.
  384. # CHECKLISTS
  385. if "Checked" in stuff[2]:
  386. # Let's get task and url
  387. try:
  388. task = stuff[-1][-1]
  389. fullurl = ""
  390. for e in stuff[-1][:-1]:
  391. fullurl = fullurl+e+" > "
  392. except:
  393. tasl = ""
  394. fullurl = ""
  395. # MAIN TASK
  396. UI_color.set(layer, win, "text_normal")
  397. layer.set_font_size(20)
  398. layer.move_to(
  399. 80,
  400. win.scroll["history"] + current_Y+24,
  401. )
  402. layer.show_text(task)
  403. # TASK URL INSIDE THE CHECKLIST
  404. if fullurl:
  405. UI_color.set(layer, win, "text_normal")
  406. layer.set_font_size(10)
  407. layer.move_to(
  408. 90+len(task)*12,
  409. win.scroll["history"] + current_Y+24,
  410. )
  411. layer.show_text(fullurl)
  412. else:
  413. # If it's not a checklist. I just want
  414. # to output what it is and what action
  415. # happened to it.
  416. task = stuff[1].replace("/", " ")
  417. if task == "[asset_blend]":
  418. task = " Asset Blend File"
  419. # WHAT
  420. UI_color.set(layer, win, "text_normal")
  421. layer.set_font_size(20)
  422. layer.move_to(
  423. 80,
  424. win.scroll["history"] + current_Y+24,
  425. )
  426. layer.show_text(task)
  427. # WHAT WAS DONE
  428. UI_color.set(layer, win, "text_normal")
  429. layer.set_font_size(10)
  430. layer.move_to(
  431. 90+len(task)*12,
  432. win.scroll["history"] + current_Y+24,
  433. )
  434. layer.show_text(stuff[2])
  435. UI_color.set(layer, win, "text_normal")
  436. layer.set_font_size(15)
  437. layer.move_to(
  438. 92,
  439. win.scroll["history"] + current_Y+41,
  440. )
  441. layer.show_text(stuff[0])
  442. # Okay and the last thing. The button of
  443. # when it's clicked. For example to get
  444. # to the item. Or to find the task in
  445. # the checklist. For what ever reason.
  446. def do():
  447. # Let's make the auto-selecting of
  448. # the checklists. OMG I really have
  449. # no space left to type. OMG. What
  450. # is this.
  451. if "Checked" in stuff[2]:
  452. # AAAAAAAAAAAAAAAAAAAAAAAAAAAA
  453. # How am I supposed to tell thi-
  454. # ngs? OMG.
  455. if "schedule_task_selected" not in win.current:
  456. win.current["schedule_task_selected"] = []
  457. win.current["schedule_task_selected"] = \
  458. [[[stuff[0], "schedule", stuff[1], stuff[2], stuff[3]], 0], item]
  459. # Now since we did this weird
  460. # what ever the mack. Let's make so
  461. # it's going to the asset.
  462. if i == "assets":
  463. goto = "assets"
  464. elif i == "scenes":
  465. goto = "script"
  466. else:
  467. goto = "analytics"
  468. win.url = goto
  469. win.cur = item
  470. win.current["asset_left_panel"] = "history"
  471. if stuff[2] != "[Added Asset]":
  472. UI_elements.roundrect(layer, win,
  473. 40,
  474. win.scroll["history"] + current_Y,
  475. width-40,
  476. 50,
  477. 10,
  478. button=do,
  479. fill=False,
  480. offset=[x,y])
  481. layer.stroke()
  482. current_Y = current_Y + 60
  483. else:
  484. current_Y = current_Y + 60
  485. else:
  486. current_Y = current_Y + 60
  487. # Outputting the layer
  488. outlayer.set_source_surface(surface, x, y)
  489. outlayer.paint()
  490. # Scroll
  491. UI_elements.scroll_area(outlayer, win, "history",
  492. x+0,
  493. y+50,
  494. width,
  495. height-50,
  496. current_Y,
  497. bar=True,
  498. mmb=True)