editor.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. #########################################################
  2. # #
  3. # THIS SOFTWARE IS (C) J.Y.Amihud and contributors 2022 #
  4. # AND IT'S UNDER THE GNU AGPLv3 or any later version. #
  5. # #
  6. #########################################################
  7. # I will try to do something crazy stupid. I will try to
  8. # Make an entire graphical editor using GTK in one python
  9. # script. Which will not be very wise. But I will give it
  10. # a shot.
  11. # A lot of the decisions will be made because of this fact
  12. # so be aware of that.
  13. #########################################################
  14. # #
  15. # IMPORTING VARIOUS MODULES #
  16. # #
  17. #########################################################
  18. import os
  19. import time
  20. import json
  21. import threading
  22. import gi
  23. gi.require_version('Gtk', '3.0')
  24. from gi.repository import Gtk
  25. from gi.repository import GLib
  26. #########################################################
  27. # #
  28. # CONFIGURING THE WINDOW #
  29. # #
  30. #########################################################
  31. win = Gtk.Window()
  32. win.set_size_request(600, 600)
  33. win.connect("destroy", Gtk.main_quit)
  34. scrl = Gtk.ScrolledWindow()
  35. win.add(scrl)
  36. box = Gtk.VBox()
  37. scrl.add(box)
  38. #########################################################
  39. # #
  40. # LOADING PRESETS #
  41. # #
  42. #########################################################
  43. # I want to load all kinds of presets for the auto-fill
  44. # For this we will nee to read all of the files. And it
  45. # will be done before we have a GUI running. So the out
  46. # put of this should be done in terminal it self.
  47. def pbar(now, total):
  48. # Simple terminal progress bar
  49. ################------------------------------
  50. # That's roughly 30% with such a graph.
  51. w, h = os.get_terminal_size()
  52. n = "#"
  53. t = "-"
  54. ns = int(w/total*now)
  55. string = "\r"+(n*ns)+(t*(w-ns))
  56. print(string, end="")
  57. # We assume that it's in a correct folder
  58. print(" ----- LOADING AUTO-FILL DATA ------ ")
  59. win.full = {}
  60. all_apps = list(os.listdir(os.getcwd()+"/apps"))
  61. for n, i in enumerate(all_apps):
  62. pbar(n, len(all_apps))
  63. if i.endswith(".json"):
  64. # we found a file on an app:
  65. try:
  66. with open("apps/"+i) as f:
  67. this_file = json.load(f)
  68. for key in this_file:
  69. if key not in win.full:
  70. win.full[key] = this_file[key]
  71. elif type(this_file[key]) == list:
  72. for item in this_file[key]:
  73. if item not in win.full[key]:
  74. win.full[key].append(item)
  75. elif type(this_file[key]) == dict:
  76. for thekey in this_file[key]:
  77. win.full[key][thekey] = ""
  78. except Exception as e:
  79. pass
  80. print(" ----- DONE LOADING AUTO-FILL DATA ------ ")
  81. #########################################################
  82. # #
  83. # LIST EDITOR ( TAG EDITOR ) #
  84. # #
  85. #########################################################
  86. # This piece of code I wrote for FastLBRY GTK, but it will
  87. # be very handy here. I did a few modifications to remove
  88. # the dependancy on the icon system in FastLBRY GTK
  89. def tags_editor(win, data, return_edit_functions=False, auto_fill=[]):
  90. def update(new_data):
  91. old = data.copy()
  92. for t in old:
  93. data.remove(t)
  94. for t in new_data:
  95. data.append(t)
  96. for i in tagsbox.get_children():
  97. i.destroy()
  98. for tag in data:
  99. add_tag(tag)
  100. tagscont = Gtk.HBox()
  101. tagscrl = Gtk.ScrolledWindow()
  102. tagscrl.set_size_request(40,40)
  103. tagscont.pack_start(tagscrl, True, True, 0)
  104. tagsbox = Gtk.HBox()
  105. tagscrl.add_with_viewport(tagsbox)
  106. def add_tag(tag):
  107. if not tag:
  108. return
  109. if tag not in data:
  110. data.append(tag)
  111. tagb = Gtk.HBox()
  112. tagb.pack_start(Gtk.Label(" "+tag+" "), False, False, 0)
  113. def kill(w):
  114. tagb.destroy()
  115. data.remove(tag)
  116. tagk = Gtk.Button("-")
  117. tagk.connect("clicked", kill)
  118. tagk.set_relief(Gtk.ReliefStyle.NONE)
  119. tagb.pack_start(tagk, False, False, 0)
  120. tagb.pack_start(Gtk.VSeparator(), False, False, 5)
  121. tagsbox.pack_start(tagb, False, False, 0)
  122. tagsbox.show_all()
  123. # Scroll to the last
  124. def later():
  125. time.sleep(0.1)
  126. def now():
  127. a = tagscrl.get_hadjustment()
  128. a.set_value(a.get_upper())
  129. GLib.idle_add(now)
  130. load_thread = threading.Thread(target=later)
  131. load_thread.start()
  132. # The threading is needed, since we want to wait
  133. # while GTK will update the UI and only then move
  134. # the adjustent. Becuase else, it will move to the
  135. # last previous, not to the last last.
  136. addt = Gtk.Button("+")
  137. addt.set_relief(Gtk.ReliefStyle.NONE)
  138. tagscont.pack_end(addt, False, False, 0)
  139. def on_entry(w):
  140. add_tag(tagentry.get_text())
  141. tagentry.set_text("")
  142. tagentry = Gtk.Entry()
  143. if auto_fill:
  144. liststore = Gtk.ListStore(str)
  145. completion = Gtk.EntryCompletion()
  146. completion.set_model(liststore)
  147. completion.set_text_column(0)
  148. for i in auto_fill:
  149. liststore.append((i,))
  150. tagentry.set_completion(completion)
  151. completion.set_minimum_key_length(0)
  152. completion.complete()
  153. tagentry.connect("activate", on_entry)
  154. addt.connect("clicked", on_entry)
  155. tagscont.pack_end(tagentry, False, False, False)
  156. for tag in data:
  157. add_tag(tag)
  158. if not return_edit_functions:
  159. return tagscont
  160. else:
  161. return tagscont, update
  162. #########################################################
  163. # #
  164. # TOOL BAR #
  165. # #
  166. #########################################################
  167. # Tool bar will not going to have icons and things like
  168. # this. It's not about being pretty. It's about giving
  169. # the contributor a handy tool to contribute.
  170. pannel = Gtk.HeaderBar()
  171. pannel.set_show_close_button(True)
  172. win.set_titlebar(pannel)
  173. # We are going to make a little trick on our self ( I do
  174. # that everywhere ). I gonna put important variables into
  175. # the GTK Window.
  176. win.data_is = {
  177. "names":[],
  178. "comment":"",
  179. "links":{},
  180. "licenses":[],
  181. "platforms":[],
  182. "interface":[],
  183. "languages":[],
  184. "networks_read":[],
  185. "networks_write":[],
  186. "formats_read":[],
  187. "formats_write":[],
  188. "generic_name":[],
  189. "issues":[],
  190. }
  191. ##################### NEW BUTTON #######################
  192. def type_min(i):
  193. if type(win.data_is[i]) == list:
  194. return []
  195. elif type(win.data_is[i]) == dict:
  196. return {}
  197. elif type(win.data_is[i]) == str:
  198. return ""
  199. def on_new(w):
  200. for i in win.data_is:
  201. try:
  202. updaters[i](type_min(i))
  203. except Exception as e:
  204. win.data_is[i] = type_min(i)
  205. new_button = Gtk.Button("New")
  206. new_button.set_relief(Gtk.ReliefStyle.NONE)
  207. new_button.connect("clicked", on_new)
  208. pannel.pack_start(new_button)
  209. ##################### OPEN BUTTON #######################
  210. def on_open(w):
  211. dialog = Gtk.FileChooserDialog("Choose a file",
  212. None,
  213. Gtk.FileChooserAction.OPEN,
  214. (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
  215. Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
  216. dialog.set_current_folder(os.getcwd()+"/apps")
  217. filter_sup = Gtk.FileFilter()
  218. filter_sup.set_name("Json Files")
  219. filter_sup.add_pattern("*.json")
  220. dialog.add_filter(filter_sup)
  221. response = dialog.run()
  222. if response == Gtk.ResponseType.OK:
  223. # LOADING THE FILE
  224. with open(dialog.get_filename()) as f:
  225. loaded_file = json.load(f)
  226. for i in win.data_is:
  227. if i in loaded_file and type(win.data_is[i]) == type(loaded_file[i]):
  228. try:
  229. updaters[i](loaded_file.get(i, type_min(i)))
  230. except Exception as e:
  231. win.data_is[i] = loaded_file.get(i, type_min(i))
  232. else:
  233. try:
  234. updaters[i](loaded_file.get(i, type_min(i)))
  235. except:
  236. win.data_is[i] = loaded_file.get(i, type_min(i))
  237. dialog.destroy()
  238. open_button = Gtk.Button("Open")
  239. open_button.set_relief(Gtk.ReliefStyle.NONE)
  240. open_button.connect("clicked", on_open)
  241. pannel.pack_start(open_button)
  242. ##################### SAVE BUTTON #######################
  243. # save_button = Gtk.Button("Save")
  244. # pannel.pack_start(save_button)
  245. #################### SAVE AS BUTTON #####################
  246. def on_save_as(w):
  247. tb = detext.get_buffer()
  248. win.data_is["comment"] = tb.get_text(tb.get_start_iter(), tb.get_end_iter(), True)
  249. dialog = Gtk.FileChooserDialog("Choose a file",
  250. None,
  251. Gtk.FileChooserAction.SAVE,
  252. (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
  253. Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
  254. dialog.set_current_folder(os.getcwd()+"/apps")
  255. filter_sup = Gtk.FileFilter()
  256. filter_sup.set_name("Json Files")
  257. filter_sup.add_pattern("*.json")
  258. dialog.add_filter(filter_sup)
  259. response = dialog.run()
  260. if response == Gtk.ResponseType.OK:
  261. savename = dialog.get_filename()
  262. if not savename.endswith(".json"):
  263. savename = savename+".json"
  264. with open(savename, 'w') as f:
  265. json.dump(win.data_is, f, indent=4, sort_keys=True)
  266. dialog.destroy()
  267. save_as_button = Gtk.Button("Save As")
  268. save_as_button.connect("clicked", on_save_as)
  269. save_as_button.set_relief(Gtk.ReliefStyle.NONE)
  270. pannel.pack_start(save_as_button)
  271. #########################################################
  272. # #
  273. # EDITOR IT SELF #
  274. # #
  275. #########################################################
  276. updaters = {} # Very important to make tags editor work
  277. #################### NAMES / COMMENT ####################
  278. collapsable = Gtk.Expander(label=" Names / Comment: ")
  279. box.pack_start(collapsable, 0,0,5)
  280. term_box = Gtk.VBox()
  281. collapsable.add(term_box)
  282. # The list of names
  283. term_box.pack_start(Gtk.Label("List of Names"),0,0,5)
  284. names_editor, updaters["names"] = tags_editor(win, win.data_is["names"], True)
  285. term_box.pack_start(names_editor,0,0,5)
  286. # The list of names
  287. term_box.pack_start(Gtk.Label("Generic Names ( Features )"),0,0,5)
  288. generic_editor, updaters["generic_name"] = tags_editor(win, win.data_is["generic_name"], True, win.full.get("generic_name",[]))
  289. term_box.pack_start(generic_editor,0,0,5)
  290. term_box.pack_start(Gtk.Label("Comment (HTML)"),0,0,5)
  291. def comment_updater(new):
  292. detext.get_buffer().set_text(new)
  293. win.data_is["comment"] = new
  294. updaters["comment"] = comment_updater
  295. descrl = Gtk.ScrolledWindow()
  296. descrl.set_size_request(100,100)
  297. detext = Gtk.TextView()
  298. detext.set_wrap_mode(Gtk.WrapMode.WORD)
  299. detext.get_buffer().set_text(win.data_is["comment"])
  300. descrl.add(detext)
  301. term_box.pack_start(descrl,0,0,5)
  302. #################### LINKS ####################
  303. collapsable = Gtk.Expander(label=" Links: ")
  304. box.pack_start(collapsable, 0,0,5)
  305. term_box = Gtk.VBox()
  306. collapsable.add(term_box)
  307. # Links are a bit more complicated. It will require a
  308. # special editor.
  309. def add_item_to_view(category, link):
  310. itembox = Gtk.HBox()
  311. itembox.pack_start(Gtk.Label(" "+category+": "), 0,0,0)
  312. itembox.pack_start(Gtk.VSeparator(), 0,0,0)
  313. itembox.pack_start(Gtk.Label(" "+link+" "), 0,0,0)
  314. def on_destroy(w, itembox, category):
  315. itembox.destroy()
  316. del win.data_is["links"][category]
  317. destroy = Gtk.Button("-")
  318. destroy.set_relief(Gtk.ReliefStyle.NONE)
  319. destroy.connect("clicked", on_destroy, itembox, category)
  320. itembox.pack_end(destroy, 0,0,0)
  321. linksbox.pack_end(itembox, 0,0,0)
  322. linksbox.show_all()
  323. def add_item(w):
  324. key = categoryInput.get_text()
  325. value = linkInput.get_text()
  326. if key not in win.data_is["links"]:
  327. add_item_to_view(key, value)
  328. win.data_is["links"][key] = value
  329. categoryInput.set_text("")
  330. linkInput.set_text("")
  331. categoryInput.grab_focus()
  332. def links_updater(new):
  333. for i in linksbox.get_children():
  334. i.destroy()
  335. win.data_is["links"] = {}
  336. for i in new:
  337. add_item_to_view(i, new[i])
  338. win.data_is["links"][i] = new[i]
  339. updaters["links"] = links_updater
  340. inputbox = Gtk.HBox()
  341. term_box.pack_start(inputbox, 1, 0, 5)
  342. inputbox.pack_start(Gtk.Label("Category:"), 0,0,1)
  343. categoryInput = Gtk.Entry()
  344. inputbox.pack_start(categoryInput, 0,0,1)
  345. liststore = Gtk.ListStore(str)
  346. completion = Gtk.EntryCompletion()
  347. completion.set_model(liststore)
  348. completion.set_text_column(0)
  349. for i in list(win.full["links"].keys()):
  350. liststore.append((i,))
  351. categoryInput.set_completion(completion)
  352. completion.set_minimum_key_length(0)
  353. completion.complete()
  354. inputbox.pack_start(Gtk.Label("Link:"), 0,0,1)
  355. linkInput = Gtk.Entry()
  356. linkInput.connect("activate", add_item)
  357. inputbox.pack_start(linkInput, 1,1,1)
  358. addInput = Gtk.Button("+")
  359. addInput.connect("clicked", add_item)
  360. addInput.set_relief(Gtk.ReliefStyle.NONE)
  361. inputbox.pack_end(addInput, 0,0,1)
  362. linksbox = Gtk.VBox()
  363. term_box.pack_start(Gtk.HSeparator(), 1, 0, 5)
  364. term_box.pack_start(linksbox, 1, 0, 5)
  365. #################### LICENSES ####################
  366. collapsable = Gtk.Expander(label=" Licenses: ")
  367. box.pack_start(collapsable, 0,0,5)
  368. term_box = Gtk.VBox()
  369. collapsable.add(term_box)
  370. # The list of licenses
  371. #term_box.pack_start(Gtk.Label("Licenses:"),0,0,5)
  372. licenses_editor, updaters["licenses"] = tags_editor(win, win.data_is["licenses"], True, win.full.get("licenses",[]))
  373. term_box.pack_start(licenses_editor,0,0,5)
  374. #################### NERDY INFO ##################
  375. collapsable = Gtk.Expander(label=" Technical Info: ")
  376. box.pack_start(collapsable, 0,0,5)
  377. term_box = Gtk.VBox()
  378. collapsable.add(term_box)
  379. # The list of Platforms
  380. term_box.pack_start(Gtk.Label("Platforms:"),0,0,5)
  381. platforms_editor, updaters["platforms"] = tags_editor(win, win.data_is["platforms"], True, win.full.get("platforms",[]))
  382. term_box.pack_start(platforms_editor,0,0,5)
  383. # The list of Interfaces
  384. term_box.pack_start(Gtk.Label("Interfaces:"),0,0,5)
  385. interface_editor, updaters["interface"] = tags_editor(win, win.data_is["interface"], True, win.full.get("interface",[]))
  386. term_box.pack_start(interface_editor,0,0,5)
  387. # The list of Languages
  388. term_box.pack_start(Gtk.Label("Languages:"),0,0,5)
  389. languages_editor, updaters["languages"] = tags_editor(win, win.data_is["languages"], True, win.full.get("languages",[]))
  390. term_box.pack_start(languages_editor,0,0,5)
  391. #################### FORMATS / NETWORKS #################
  392. collapsable = Gtk.Expander(label=" Formats / Networks: ")
  393. box.pack_start(collapsable, 0,0,5)
  394. term_box = Gtk.VBox()
  395. collapsable.add(term_box)
  396. # The list of Networs read
  397. term_box.pack_start(Gtk.Label("Networks it can access ( read ):"),0,0,5)
  398. networks_read_editor, updaters["networks_read"] = tags_editor(win, win.data_is["networks_read"], True, win.full.get("networks_read",[]))
  399. term_box.pack_start(networks_read_editor,0,0,5)
  400. # The list of Networs write
  401. term_box.pack_start(Gtk.Label("Networks it can post to ( write ):"),0,0,5)
  402. networks_write_editor, updaters["networks_write"] = tags_editor(win, win.data_is["networks_write"], True, win.full.get("networks_write",[]))
  403. term_box.pack_start(networks_write_editor,0,0,5)
  404. # The list of Opens files
  405. term_box.pack_start(Gtk.Label("File formats it reads:"),0,0,5)
  406. formats_read_editor, updaters["formats_read"] = tags_editor(win, win.data_is["formats_read"], True, win.full.get("formats_read",[]))
  407. term_box.pack_start(formats_read_editor,0,0,5)
  408. # The list of saves files
  409. term_box.pack_start(Gtk.Label("File formats it saves to:"),0,0,5)
  410. formats_write_editor, updaters["formats_write"] = tags_editor(win, win.data_is["formats_write"], True, win.full.get("formats_write",[]))
  411. term_box.pack_start(formats_write_editor,0,0,5)
  412. #################### ANTI-FEATURES #################
  413. collapsable = Gtk.Expander(label=" Anti-Features ( Malware ): ")
  414. box.pack_start(collapsable, 0,0,5)
  415. term_box = Gtk.VBox()
  416. collapsable.add(term_box)
  417. # The list of saves files
  418. issues_editor, updaters["issues"] = tags_editor(win, win.data_is["issues"], True, win.full.get("issues",[]))
  419. term_box.pack_start(issues_editor,0,0,5)
  420. #########################################################
  421. # #
  422. # STARTING EVERYTHING #
  423. # #
  424. #########################################################
  425. win.show_all()
  426. Gtk.main()