pm_gtk.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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. from gi.repository import GLib
  11. import cairo
  12. import threading
  13. # Own modules
  14. from settings import settings
  15. from settings import talk
  16. from project_manager import pm_project
  17. from project_manager import pm_mainLayer
  18. from project_manager import pm_newprojectLayer
  19. from project_manager import pm_scanLayer
  20. from project_manager import pm_helpLayer
  21. from project_manager import pm_updateLayer
  22. from project_manager import update_reader
  23. from project_manager import pm_installUpdatesLayer
  24. from project_manager import pm_settingsLayer
  25. from troubleshooter import error_notify
  26. # UI modules
  27. from UI import UI_testing
  28. from UI import UI_color
  29. from UI import UI_elements
  30. def previous(win):
  31. win.previous = {}
  32. for i in win.current:
  33. if type(win.current[i]) == list or type(win.current[i]) is dict:
  34. win.previous[i] = win.current[i].copy()
  35. else:
  36. win.previous[i] = win.current[i]
  37. # OK let's make a window
  38. def run():
  39. # In the Blender-Organizer I was putting the version into the title. Not cool.
  40. # Because if you would snap it to the sidebar in Ubuntu. On mouse over it would
  41. # show the first ever version. So there will be a better way to see version.
  42. # I think let's do that like in Blender. Drawn with in the window somewhere.
  43. # Setting up the window
  44. win = Gtk.Window()
  45. win.set_default_size(1200,720)
  46. win.set_position(Gtk.WindowPosition.CENTER)
  47. # I used to do win.maximize() here. But people complained that the software is slow.
  48. # so Instead I gonna keep it relativelly small when you open it. And slow only if you
  49. # go full screen.
  50. # I might figure out how to make it faster with various resulutions. Not now.
  51. win.connect("destroy", Gtk.main_quit)
  52. win.set_title("VCStudio : "+talk.text("project-manager"))
  53. win.set_default_icon_from_file("tinyicon.png")
  54. # Setting up the events ( mouse and keyboard handling )
  55. win.connect("button-press-event", mouse_button_press, win)
  56. win.connect("button-release-event", mouse_button_release, win)
  57. win.connect("key-press-event", key_press, win)
  58. win.connect("key-release-event", key_release, win)
  59. # Guess what. The entire time on Blender-Organizer 4 ( 2018 -2020 ) and
  60. # few days of trying connecting the scroll event directly to window or to
  61. # the drawing area. And just now I finally made it work. BY DOING THIS
  62. # Why scroll event is only on ScrolledWindow ? OMG !!!
  63. scroll = Gtk.ScrolledWindow()
  64. scroll.connect("scroll-event", scrolling, win)
  65. # Setting up the global variables. (kinda)
  66. win.animations = {}
  67. win.previous = {}
  68. win.current = {}
  69. win.images = {}
  70. win.imageload = 0
  71. win.text = {}
  72. win.textactive = ""
  73. win.scroll = {}
  74. win.FPS = 0
  75. win.url = "project_manager"
  76. win.update = {"versions":{}}
  77. win.projects = {}
  78. win.calllayer = False
  79. win.blink = False
  80. t2 = threading.Thread(target=update_reader.get_update_info, args=(win,))
  81. t2.setDaemon(True)
  82. t2.start()
  83. if not settings.read("Username"):
  84. settings.write("Username", "VCStudio-User")
  85. # Cashed tables
  86. win.color = UI_color.get_table()
  87. win.settings = settings.load_all()
  88. # Default values
  89. win.current["frame"] = 0
  90. win.current["testing"] = False
  91. win.current["LMB"] = False
  92. win.current["MMB"] = False
  93. win.current["RMB"] = False
  94. win.current["keys"] = []
  95. win.current["key_letter"] = ""
  96. win.current["scroll"] = [0,0]
  97. win.current["project"] = ""
  98. win.current["calls"] = {} # Calls. See sutdio/studio_dialogs.py
  99. previous(win)
  100. # Version of the software
  101. win.version = 0.0
  102. try:
  103. vfile = open("settings/update.data")
  104. vfile = vfile.read()
  105. vfile = vfile.split("\n")
  106. for line in vfile:
  107. if line.startswith("VERSION "):
  108. win.version = float(line.replace("VERSION ", ""))
  109. break
  110. except:
  111. win.version = 0.0
  112. # FPS
  113. win.sFPS = datetime.datetime.now()
  114. # Setting the drawable
  115. pmdraw = Gtk.DrawingArea()
  116. pmdraw.set_size_request(815, 500)
  117. scroll.set_size_request(815, 500) # This step is because GTK developers are
  118. win.add(scroll) # well. A good, nice, people who knows
  119. scroll.add_with_viewport(pmdraw) # what they are doing. Really.
  120. pmdraw.connect("draw", pmdrawing, win)
  121. #run
  122. win.show_all()
  123. Gtk.main()
  124. def pmdrawing(pmdrawing, main_layer, win):
  125. # This function draws the actuall image. I'm doing full frames redraws. It's
  126. # a bit simpler then making some kind of dynamic draw call system that might
  127. # be used in such an application. But to hell with it. I did the same on the
  128. # Blender-Organizer altho with way less cairo. And it works well enought.
  129. try:
  130. # FPS counter
  131. win.fFPS = datetime.datetime.now()
  132. win.tFPS = win.fFPS - win.sFPS
  133. if win.current["frame"] % 5 == 0:
  134. win.blink = not win.blink # Iterating the blink
  135. if win.current["frame"] % 10 == 0:
  136. win.FPS = int ( 1.0 / ( win.tFPS.microseconds /1000000))
  137. if "Auto_De-Blur" not in win.settings:
  138. win.settings["Auto_De-Blur"] = True
  139. # Fail switch for Graphics.
  140. if win.FPS < 10 and win.settings["Auto_De-Blur"]:
  141. win.settings["Blur"] = False
  142. win.sFPS = datetime.datetime.now()
  143. win.cursors = {
  144. "arrow":Gdk.Cursor.new(Gdk.CursorType.ARROW),
  145. "watch":Gdk.Cursor.new(Gdk.CursorType.WATCH),
  146. "text" :Gdk.Cursor.new(Gdk.CursorType.XTERM),
  147. "hand" :Gdk.Cursor.new(Gdk.CursorType.HAND1),
  148. "cross":Gdk.Cursor.new(Gdk.CursorType.CROSS)
  149. }
  150. win.current["cursor"] = win.cursors["arrow"]
  151. # CHECKING FOR UPDATES
  152. # I don't want updates to be checking automatically if the user
  153. # doesn't want to. I think by default they will be automatic. But
  154. # you can definitely argue with me about it.
  155. if "check-for-updates" not in win.settings:
  156. settings.write("check-for-updates", True)
  157. win.settings["check-for-updates"] = True
  158. if win.settings["check-for-updates"]:
  159. GLib.timeout_add(1 , update_reader.get_update_info, win)
  160. # Current frame (for animations and things like this)
  161. win.current["frame"] += 1
  162. # Scaling of the UI
  163. if not "scale" in win.settings:
  164. settings.write("scale", 1) # Writing to file
  165. win.settings = settings.load_all()
  166. # Getting data about the frame
  167. win.current['mx'] = int(round(win.get_pointer()[0] / win.settings["scale"]))
  168. win.current['my'] = int(round(win.get_pointer()[1] / win.settings["scale"]))
  169. win.current['w'] = int(round(win.get_size()[0] / win.settings["scale"] ))
  170. win.current['h'] = int(round(win.get_size()[1] / win.settings["scale"] ))
  171. if 65451 in win.current["keys"]:
  172. settings.write("scale", win.settings["scale"]+0.2)
  173. win.settings = settings.load_all()
  174. win.current["keys"] = []
  175. elif 65453 in win.current["keys"]:
  176. settings.write("scale", win.settings["scale"]-0.2)
  177. win.settings = settings.load_all()
  178. win.current["keys"] = []
  179. #Background color
  180. UI_color.set(main_layer, win, "background")
  181. main_layer.rectangle(
  182. 0,
  183. 0,
  184. win.current['w']*win.settings["scale"],
  185. win.current['h']*win.settings["scale"])
  186. main_layer.fill()
  187. # Tooltips and other junk has to be defined here. And then drawn later to
  188. # the screen. So here we get a special layer. That will be drawn to during
  189. # the time of drawing. And later composeted over everything.
  190. win.tooltip_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
  191. win.current['h'])
  192. win.tooltip = cairo.Context(win.tooltip_surface)
  193. win.tooltip.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
  194. # Layers. Order of them matter
  195. Layers = []
  196. Layers.append([pm_mainLayer.layer(win),"project_manager"])
  197. # Call layers. See studio/studio_dialogs.py for explanation. It's wild.
  198. win.calllayer = False
  199. remlater = []
  200. for call in list(win.current["calls"].keys()):
  201. if win.current["calls"][call]["var"] == None:
  202. Layers.append([win.current["calls"][call]["draw"](win, call)])
  203. win.url = win.current["calls"][call]["url"]
  204. win.calllayer = True
  205. else:
  206. win.url = win.current["calls"][call]["back"]
  207. win.current["calls"][call]["call"](win, win.current["calls"][call]["var"])
  208. win.textactive = ""
  209. remlater.append(call)
  210. for call in remlater:
  211. del win.current["calls"][call]
  212. if win.url == "new_project":
  213. Layers.append([pm_newprojectLayer.layer(win), "new_project"])
  214. elif win.url == "scan_projects":
  215. Layers.append([pm_scanLayer.layer(win), "scan_projects"])
  216. elif win.url == "help_layer":
  217. Layers.append([pm_helpLayer.layer(win), "help_layer"])
  218. elif win.url == "update_layer":
  219. Layers.append([pm_updateLayer.layer(win), "update_layer"])
  220. elif win.url == "install_updates":
  221. Layers.append([pm_installUpdatesLayer.layer(win), "install_updates"])
  222. elif win.url == "settings_layer":
  223. Layers.append([pm_settingsLayer.layer(win), "settings_layer"])
  224. Layers.append([UI_testing.layer(win)])
  225. Layers.append([win.tooltip_surface])
  226. if win.settings["scale"] != 1:
  227. main_layer.scale(win.settings["scale"],
  228. win.settings["scale"])
  229. # Combining layers
  230. for layer in Layers:
  231. if len(layer) > 1:
  232. layer, url = layer
  233. blur = UI_elements.animate(url+"_blur", win, 50)
  234. if win.url != url:
  235. blur = UI_elements.animate(url+"_blur", win, blur, 50, 2, True)
  236. else:
  237. blur = UI_elements.animate(url+"_blur", win, blur, 0, 2, True)
  238. layer = UI_elements.blur(layer, win, blur)
  239. else:
  240. layer = layer[0]
  241. main_layer.set_source_surface(layer, 0 , 0)
  242. main_layer.paint()
  243. win.get_root_window().set_cursor(win.current["cursor"])
  244. # If you press ESC you get back from any window to the main menu.
  245. if 65307 in win.current["keys"] and win.url != "install_updates":
  246. win.url = "project_manager"
  247. win.current["project"] = ""
  248. win.textactive = ""
  249. # Saving data about this frame for the next one. A bit hard to get WTF am I
  250. # doing here. Basically trying to avoid current and previous data to be links
  251. # of the same data.
  252. previous(win) # Moved it into a seprate function for obvoius reasons
  253. # Refreshing those that need to be refrashed
  254. win.current["scroll"] = [0,0]
  255. # Refreshing the frame automatically
  256. pmdrawing.queue_draw()
  257. except:
  258. Gtk.main_quit()
  259. error_notify.show()
  260. # This program will have things like mouse and keyboard input. And this setup
  261. # Will be done in both PM and the actuall Project window. ( Also in the render
  262. # Window. Basically all separate windows will have to have this setup separatelly.
  263. # Mouse
  264. def mouse_button_press(widget, event, win):
  265. # This function marks activation of the button. Not it's deactivation.
  266. # I'm going to attempt something quite disturbing. Basically I want to save
  267. # the state of the mouse as the press begun untill it's released. And I'm
  268. # going to do in a slightly weird way. Because I'm bored I guess. The prob-
  269. # lem is that it will require to check whether the data even exists in the
  270. # first place. If x. Before parsing it. Because it might be False.
  271. for i, button in enumerate(["LMB", "MMB", "RMB"]):
  272. if i+1 == int(event.get_button()[1]):
  273. win.current[button] = [event.x, event.y]
  274. # If you folowed the code. By checking for example if win.current["LMB"]
  275. # You can know if it's even pressed to begin with. Because if it's not
  276. # It's False.
  277. def mouse_button_release(widget, event, win):
  278. # This function reverses the effects of the mouse_button_press() function.
  279. for i, button in enumerate(["LMB", "MMB", "RMB"]):
  280. if i+1 == int(event.get_button()[1]):
  281. win.current[button] = False
  282. # I guess it's time to make something similar for the keyboard keys as well.
  283. # I'm going to reuse the old system from the Blender-Organizer. Just a list of
  284. # pressed keys. Maybe as well a strting thingy. Because I want to type in this
  285. # app.
  286. def key_press(widget, event, win):
  287. if event.keyval not in win.current["keys"]:
  288. win.current["keys"].append(event.keyval)
  289. win.current["key_letter"] = event.string
  290. def key_release(widget, event, win):
  291. try:
  292. win.current["keys"].remove(event.keyval)
  293. except:
  294. win.current["keys"] = []
  295. def scrolling(widget, event, win):
  296. e, x, y = event.get_scroll_deltas()
  297. win.current["scroll"] = [x,y]