studio_gtk.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. # THIS FILE IS A PART OF VCStudio
  2. # PYTHON 3
  3. import os
  4. import datetime
  5. # GTK module ( Graphical interface
  6. import gi
  7. gi.require_version('Gtk', '3.0')
  8. from gi.repository import Gtk
  9. from gi.repository import Gdk
  10. import cairo
  11. import datetime
  12. import threading
  13. # Own modules
  14. from settings import settings
  15. from settings import talk
  16. from project_manager import pm_project
  17. # Studio
  18. from studio import analytics
  19. from studio import story
  20. from studio import studio_storyDeletionLayer
  21. from studio import studio_storyLayer
  22. from studio import studio_settingsLayer
  23. from studio import studio_assetLayer
  24. from studio import studio_analyticsLayer
  25. from studio import studio_scriptLayer
  26. from studio import studio_multiuserLayer
  27. from troubleshooter import error_notify
  28. # UI modules
  29. from UI import UI_testing
  30. from UI import UI_color
  31. from UI import UI_elements
  32. # Network
  33. from network import network_renders
  34. from network import network_multiuser
  35. from network import multiuser_terminal
  36. def previous(win):
  37. win.previous = {}
  38. for i in win.current:
  39. if type(win.current[i]) == list or type(win.current[i]) is dict:
  40. win.previous[i] = win.current[i].copy()
  41. else:
  42. win.previous[i] = win.current[i]
  43. # OK let's make a window
  44. def run(project, win):
  45. # In the Blender-Organizer I was putting the version into the title. Not cool.
  46. # Because if you would snap it to the sidebar in Ubuntu. On mouse over it would
  47. # show the first ever version. So there will be a better way to see version.
  48. # I think let's do that like in Blender. Drawn with in the window somewhere.
  49. try:
  50. win.destroy()
  51. except:
  52. pass
  53. # Sometimes the projects could be corrupted. So we need to
  54. # insure that they are alright.
  55. fn = project
  56. try: os.mkdir(fn)
  57. except: pass
  58. try: os.mkdir(fn+"/rnd")
  59. except: pass
  60. try: os.mkdir(fn+"/dev")
  61. except: pass
  62. try: os.mkdir(fn+"/ast")
  63. except: pass
  64. try: os.mkdir(fn+"/pln")
  65. except: pass
  66. try: os.mkdir(fn+"/mus")
  67. except: pass
  68. try: os.mkdir(fn+"/set")
  69. except: pass
  70. for f in ["chr","loc","veh","obj"]:
  71. try: os.mkdir(fn+"/ast/"+f)
  72. except: pass
  73. try: os.mkdir(fn+"/dev/"+f)
  74. except: pass
  75. # Setting up the window
  76. win = Gtk.Window()
  77. win.set_default_size(1200,720)
  78. win.set_position(Gtk.WindowPosition.CENTER)
  79. win.maximize()
  80. win.connect("destroy", Gtk.main_quit)
  81. win.set_title("VCStudio")
  82. win.set_default_icon_from_file("tinyicon.png")
  83. # Setting up the events ( mouse and keyboard handling )
  84. win.connect("button-press-event", mouse_button_press, win)
  85. win.connect("button-release-event", mouse_button_release, win)
  86. win.connect("key-press-event", key_press, win)
  87. win.connect("key-release-event", key_release, win)
  88. # Guess what. The entire time on Blender-Organizer 4 ( 2018 -2020 ) and
  89. # few days of trying connecting the scroll event directly to window or to
  90. # the drawing area. And just now I finally made it work. BY DOING THIS
  91. # Why scroll event is only on ScrolledWindow ? OMG !!!
  92. scroll = Gtk.ScrolledWindow()
  93. scroll.connect("scroll-event", scrolling, win)
  94. # Setting up the global variables. (kinda)
  95. win.animations = {}
  96. win.previous = {}
  97. win.current = {}
  98. win.images = {}
  99. win.imageload = 0
  100. win.text = {}
  101. win.textactive = ""
  102. win.scroll = {}
  103. win.FPS = 0
  104. win.url = "story_editor"
  105. win.cur = "" # This will be used to get precicelly what asset / scene / shot we are in
  106. win.update = {"versions":{}}
  107. win.project = project
  108. win.out_dots = {}
  109. win.assets = {}
  110. win.szone = [[100,100],[100,100]] # Square drawn if selected more then one node.
  111. win.surround = { # And this is the list of the squares. Because it's not
  112. "frame":0, # as easy to do. See UI / UI_math / rectangle_surround()
  113. "rects":[] # for details of this implementation.
  114. }
  115. win.calllayer = False
  116. win.layercashe = {} # Here I gonna store layers that are inactive to speed up stuff
  117. win.checklists = {}
  118. win.blink = False # Cursor blinking thing.
  119. win.renders = {} # List of current active renders.
  120. win.undo_history = []
  121. win.undo_index = 0
  122. win.multiuser = {
  123. "server":False, # Whether we are connected to the server. And the server ID
  124. "userid":"", # The id of current user in the server
  125. "last_request": "", # The last request send to the server. ( for stopping bloat )
  126. "curs":{}, # The information about various items on other users machines.
  127. "request":[["story"]], # Requests done in UI space
  128. "users":{}, # List of users information Names, IPs etc.
  129. "messages":[], # List of messages
  130. "unread":0 , # Amount of unread messages
  131. "terminal":[], # The outputs from the server
  132. "asset_list_check":False, # Whether the initial update happened when connecting to the server
  133. "story_check":False, # Whether the first story check was done.
  134. "analytics_check":False
  135. }
  136. if pm_project.is_legacy(project):
  137. win.story = story.get_legacy(project)
  138. win.analytics = analytics.get_legacy(project)
  139. else:
  140. win.story = story.load(project)
  141. win.analytics = analytics.load(project)
  142. # Try to put a name of the project at the title
  143. win.set_title("VCStudio : "+win.analytics["name"])
  144. # Cashed tables
  145. win.color = UI_color.get_table()
  146. win.settings = settings.load_all()
  147. # Default values
  148. win.current["frame"] = 0
  149. win.current["testing"] = False
  150. win.current["LMB"] = False
  151. win.current["MMB"] = False
  152. win.current["RMB"] = False
  153. win.current["keys"] = []
  154. win.current["key_letter"] = ""
  155. win.current["scroll"] = [0,0]
  156. win.current["project"] = ""
  157. win.current["tool"] = "selection"
  158. win.current["draw_dot"] = "end"
  159. win.current["calls"] = {} # Calls. See sutdio/studio_dialogs.py
  160. win.current["script_find"] = [0,0]
  161. win.current["camera_arrived"] = False
  162. if "pointers" not in win.story:
  163. win.story["pointers"] = {} # List of text pointers per scene
  164. new_date_format = "%Y/%m/%d"
  165. today = datetime.datetime.strftime(datetime.datetime.today(), new_date_format)
  166. win.current["date"] = today # Don't even ask. I'm litteraly tired already.
  167. previous(win)
  168. # Version of the software
  169. win.version = 0.0
  170. try:
  171. vfile = open("settings/update.data")
  172. vfile = vfile.read()
  173. vfile = vfile.split("\n")
  174. for line in vfile:
  175. if line.startswith("VERSION "):
  176. win.version = float(line.replace("VERSION ", ""))
  177. break
  178. except:
  179. win.version = 0.0
  180. # FPS
  181. win.sFPS = datetime.datetime.now()
  182. # Setting the drawable
  183. pmdraw = Gtk.DrawingArea()
  184. pmdraw.set_size_request(1040, 720)
  185. scroll.set_size_request(1040, 720) # This step is because GTK developers are
  186. win.add(scroll) # well. A good, nice, people who knows
  187. scroll.add_with_viewport(pmdraw) # what they are doing. Really.
  188. pmdraw.connect("draw", pmdrawing, win)
  189. # Before we run the UI. Let's run the mutliuser client.
  190. multiuser = threading.Thread(target=network_multiuser.client, args=(win,))
  191. multiuser.setDaemon(True)
  192. multiuser.start()
  193. # And let's start also the multiuser_terminal
  194. multiuser_term = threading.Thread(target=multiuser_terminal.listen, args=(win,))
  195. multiuser_term.setDaemon(True)
  196. multiuser_term.start()
  197. #run
  198. win.show_all()
  199. Gtk.main()
  200. def pmdrawing(pmdrawing, main_layer, win):
  201. # This function draws the actuall image. I'm doing full frames redraws. It's
  202. # a bit simpler then making some kind of dynamic draw call system that might
  203. # be used in such an application. But to hell with it. I did the same on the
  204. # Blender-Organizer altho with way less cairo. And it works well enought.
  205. try:
  206. # FPS counter
  207. win.fFPS = datetime.datetime.now()
  208. win.tFPS = win.fFPS - win.sFPS
  209. if win.current["frame"] % 5 == 0:
  210. win.blink = not win.blink # Iterating the blink
  211. if win.current["frame"] % 10 == 0:
  212. win.FPS = int ( 1.0 / ( win.tFPS.microseconds /1000000))
  213. if "Auto_De-Blur" not in win.settings:
  214. win.settings["Auto_De-Blur"] = True
  215. # Fail switch for Graphics.
  216. if win.FPS < 10 and win.settings["Auto_De-Blur"]:
  217. win.settings["Blur"] = False
  218. win.sFPS = datetime.datetime.now()
  219. # Current frame (for animations and things like this)
  220. win.current["frame"] += 1
  221. if win.current["frame"] == 10:
  222. win.cur = "/set"
  223. win.url = "analytics"
  224. if not "scale" in win.settings:
  225. settings.write("scale", 1) # Writing to file
  226. win.settings = settings.load_all()
  227. # Getting data about the frame
  228. win.current['mx'] = int(round(win.get_pointer()[0] / win.settings["scale"]))
  229. win.current['my'] = int(round(win.get_pointer()[1] / win.settings["scale"]))
  230. win.current['w'] = int(round(win.get_size()[0] / win.settings["scale"] ))
  231. win.current['h'] = int(round(win.get_size()[1] / win.settings["scale"] ))
  232. if 65451 in win.current["keys"]:
  233. settings.write("scale", win.settings["scale"]+0.2)
  234. win.settings = settings.load_all()
  235. win.current["keys"] = []
  236. elif 65453 in win.current["keys"]:
  237. settings.write("scale", win.settings["scale"]-0.2)
  238. win.settings = settings.load_all()
  239. win.current["keys"] = []
  240. win.cursors = {
  241. "arrow":Gdk.Cursor.new(Gdk.CursorType.ARROW),
  242. "watch":Gdk.Cursor.new(Gdk.CursorType.WATCH),
  243. "text" :Gdk.Cursor.new(Gdk.CursorType.XTERM),
  244. "hand" :Gdk.Cursor.new(Gdk.CursorType.HAND1),
  245. "cross":Gdk.Cursor.new(Gdk.CursorType.CROSS)
  246. }
  247. win.current["cursor"] = win.cursors["arrow"]
  248. # Attemt to make things straight when pressing Ctrl.
  249. if 65507 in win.current["keys"] and win.current["LMB"]:
  250. # Let's see what's got more distance. X or Y motion of the mouse.
  251. dx = win.current["LMB"][0] - win.current["mx"]
  252. dx = max(dx, dx*-1)
  253. dy = win.current["LMB"][1] - win.current["my"]
  254. dy = max(dy, dy*-1)
  255. # If X has more ditance. Then Y should be the same as begining.
  256. if dx > dy:
  257. win.current["my"] = win.current["LMB"][1]
  258. else:
  259. win.current["mx"] = win.current["LMB"][0]
  260. # Attempt at making the mouse cursor a bit more usefull for stuff.
  261. if win.previous["LMB"] and len(win.previous["LMB"]) > 2 and win.current["LMB"]:
  262. win.current["LMB"] = win.previous["LMB"]
  263. #Background color
  264. UI_color.set(main_layer, win, "background")
  265. main_layer.rectangle(
  266. 0,
  267. 0,
  268. win.current['w']*win.settings["scale"],
  269. win.current['h']*win.settings["scale"])
  270. main_layer.fill()
  271. # Tooltips and other junk has to be defined here. And then drawn later to
  272. # the screen. So here we get a special layer. That will be drawn to during
  273. # the time of drawing. And later composeted over everything.
  274. win.tooltip_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
  275. win.current['h'])
  276. win.tooltip = cairo.Context(win.tooltip_surface)
  277. win.tooltip.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  278. # Layers. Order of them matter
  279. Layers = []
  280. if win.url == "story_editor":
  281. Layers.append([studio_storyLayer.layer(win),"story_editor"])
  282. if "story_editor" in win.layercashe:
  283. del win.layercashe["story_editor"]
  284. else:
  285. if "story_editor" not in win.layercashe:
  286. win.layercashe["story_editor" ] = studio_storyLayer.layer(win)
  287. Layers.append([win.layercashe["story_editor" ],"story_editor"])
  288. if win.url == "story_deletion_dialog":
  289. Layers.append([studio_storyDeletionLayer.layer(win),"story_deletion_dialog"])
  290. elif win.url == "settings_layer":
  291. Layers.append([studio_settingsLayer.layer(win),"settings_layer"])
  292. elif win.url == "analytics":
  293. Layers.append([studio_analyticsLayer.layer(win),"analytics"])
  294. elif win.url == "assets":
  295. Layers.append([studio_assetLayer.layer(win),"assets"])
  296. elif win.url == "script":
  297. Layers.append([studio_scriptLayer.layer(win), "script"])
  298. elif win.url == "multiuser":
  299. Layers.append([studio_multiuserLayer.layer(win), "multiuser"])
  300. # Call layers. See studio/studio_dialogs.py for explanation. It's wild.
  301. win.calllayer = False
  302. remlater = []
  303. for call in list(win.current["calls"].keys()):
  304. if win.current["calls"][call]["var"] == None:
  305. Layers.append([win.current["calls"][call]["draw"](win, call)])
  306. win.url = win.current["calls"][call]["url"]
  307. win.calllayer = True
  308. else:
  309. win.url = win.current["calls"][call]["back"]
  310. win.current["calls"][call]["call"](win, win.current["calls"][call]["var"])
  311. win.textactive = ""
  312. remlater.append(call)
  313. for call in remlater:
  314. try:
  315. del win.current["calls"][call]
  316. except:
  317. pass
  318. Layers.append([UI_testing.layer(win)])
  319. Layers.append([win.tooltip_surface])
  320. if win.settings["scale"] != 1:
  321. main_layer.scale(win.settings["scale"],
  322. win.settings["scale"])
  323. # Combining layers
  324. for layer in Layers:
  325. if len(layer) > 1:
  326. layer, url = layer
  327. blur = UI_elements.animate(url+"_blur", win, 50)
  328. if (win.url != url or win.calllayer):
  329. if win.current["tool"] not in ["schedule", "grab"]:
  330. blur = UI_elements.animate(url+"_blur", win, blur, 50, 2, True)
  331. else:
  332. blur = UI_elements.animate(url+"_blur", win, 50, 50, 0, True)
  333. else:
  334. if win.current["tool"] not in ["schedule", "grab"]:
  335. blur = UI_elements.animate(url+"_blur", win, blur, 0, 2, True)
  336. else:
  337. blur = UI_elements.animate(url+"_blur", win, 0, 0, 0, True)
  338. layer = UI_elements.blur(layer, win, blur)
  339. else:
  340. layer = layer[0]
  341. main_layer.set_source_surface(layer, 0 , 0)
  342. main_layer.paint()
  343. win.get_root_window().set_cursor(win.current["cursor"])
  344. # If you press ESC you get back from any window to the main menu.
  345. if 65307 in win.current["keys"] and win.url != "install_updates":
  346. win.url = "story_editor"
  347. win.story["selected"] = []
  348. win.current["tool"] = "selection"
  349. win.current["keys"] = []
  350. win.textactive = ""
  351. # For the rendering I will need to read render info at each frame and parse
  352. # it in various windows.
  353. network_renders.read_renders(win)
  354. # There is a but in the Gnome I guess. That autopresses the Shift and Ctrl
  355. # keys when you scroll to different windows. So here is a little fix.
  356. if not win.is_active():
  357. win.current["keys"] = []
  358. elif "is_active" in win.previous and not win.previous["is_active"]:
  359. win.multiuser["last_request"] = ""
  360. UI_elements.reload_images(win)
  361. win.current["is_active"] = win.is_active()
  362. # Saving data about this frame for the next one. A bit hard to get WTF am I
  363. # doing here. Basically trying to avoid current and previous data to be links
  364. # of the same data.
  365. previous(win) # Moved it into a seprate function for obvoius reasons
  366. # Refreshing those that need to be refrashed
  367. win.current["scroll"] = [0,0]
  368. # Refreshing the frame automatically
  369. pmdrawing.queue_draw()
  370. except:
  371. Gtk.main_quit()
  372. error_notify.show()
  373. # This program will have things like mouse and keyboard input. And this setup
  374. # Will be done in both PM and the actuall Project window. ( Also in the render
  375. # Window. Basically all separate windows will have to have this setup separatelly.
  376. # Mouse
  377. def mouse_button_press(widget, event, win):
  378. # This function marks activation of the button. Not it's deactivation.
  379. # I'm going to attempt something quite disturbing. Basically I want to save
  380. # the state of the mouse as the press begun untill it's released. And I'm
  381. # going to do in a slightly weird way. Because I'm bored I guess. The prob-
  382. # lem is that it will require to check whether the data even exists in the
  383. # first place. If x. Before parsing it. Because it might be False.
  384. for i, button in enumerate(["LMB", "MMB", "RMB"]):
  385. if i+1 == int(event.get_button()[1]):
  386. win.current[button] = [event.x, event.y]
  387. # If you folowed the code. By checking for example if win.current["LMB"]
  388. # You can know if it's even pressed to begin with. Because if it's not
  389. # It's False.
  390. def mouse_button_release(widget, event, win):
  391. # This function reverses the effects of the mouse_button_press() function.
  392. for i, button in enumerate(["LMB", "MMB", "RMB"]):
  393. if i+1 == int(event.get_button()[1]):
  394. win.current[button] = False
  395. # I guess it's time to make something similar for the keyboard keys as well.
  396. # I'm going to reuse the old system from the Blender-Organizer. Just a list of
  397. # pressed keys. Maybe as well a strting thingy. Because I want to type in this
  398. # app.
  399. def key_press(widget, event, win):
  400. if event.keyval not in win.current["keys"]:
  401. win.current["keys"].append(event.keyval)
  402. win.current["key_letter"] = event.string
  403. def key_release(widget, event, win):
  404. try:
  405. win.current["keys"].remove(event.keyval)
  406. except:
  407. win.current["keys"] = []
  408. # I also want to clean the key letter. Because other wise in the
  409. # script writer I had weird key presses all the time.
  410. if not win.current["keys"]:
  411. win.current["key_letter"] = ""
  412. def scrolling(widget, event, win):
  413. e, x, y = event.get_scroll_deltas()
  414. win.current["scroll"] = [x,y]