######################################################### # # # THIS SOFTWARE IS (C) J.Y.Amihud and contributors 2022 # # AND IT'S UNDER THE GNU AGPLv3 or any later version. # # # ######################################################### # I will try to do something crazy stupid. I will try to # Make an entire graphical editor using GTK in one python # script. Which will not be very wise. But I will give it # a shot. # A lot of the decisions will be made because of this fact # so be aware of that. ######################################################### # # # IMPORTING VARIOUS MODULES # # # ######################################################### import os import time import json import threading from gi.repository import Gtk from gi.repository import GLib ######################################################### # # # CONFIGURING THE WINDOW # # # ######################################################### win = Gtk.Window() win.set_size_request(600, 600) win.connect("destroy", Gtk.main_quit) scrl = Gtk.ScrolledWindow() win.add(scrl) box = Gtk.VBox() scrl.add(box) ######################################################### # # # LOADING PRESETS # # # ######################################################### # I want to load all kinds of presets for the auto-fill # For this we will nee to read all of the files. And it # will be done before we have a GUI running. So the out # put of this should be done in terminal it self. def pbar(now, total): # Simple terminal progress bar ################------------------------------ # That's roughly 30% with such a graph. w, h = os.get_terminal_size() n = "#" t = "-" ns = int(w/total*now) string = "\r"+(n*ns)+(t*(w-ns)) print(string, end="") # We assume that it's in a correct folder print(" ----- LOADING AUTO-FILL DATA ------ ") win.full = {} all_apps = list(os.listdir(os.getcwd()+"/apps")) for n, i in enumerate(all_apps): pbar(n, len(all_apps)) if i.endswith(".json"): # we found a file on an app: try: with open("apps/"+i) as f: this_file = json.load(f) for key in this_file: if key not in win.full: win.full[key] = this_file[key] elif type(this_file[key]) == list: for item in this_file[key]: if item not in win.full[key]: win.full[key].append(item) elif type(this_file[key]) == dict: for thekey in this_file[key]: win.full[key][thekey] = "" except Exception as e: pass print(" ----- DONE LOADING AUTO-FILL DATA ------ ") ######################################################### # # # LIST EDITOR ( TAG EDITOR ) # # # ######################################################### # This piece of code I wrote for FastLBRY GTK, but it will # be very handy here. I did a few modifications to remove # the dependancy on the icon system in FastLBRY GTK def tags_editor(win, data, return_edit_functions=False, auto_fill=[]): def update(new_data): old = data.copy() for t in old: data.remove(t) for t in new_data: data.append(t) for i in tagsbox.get_children(): i.destroy() for tag in data: add_tag(tag) tagscont = Gtk.HBox() tagscrl = Gtk.ScrolledWindow() tagscrl.set_size_request(40,40) tagscont.pack_start(tagscrl, True, True, 0) tagsbox = Gtk.HBox() tagscrl.add_with_viewport(tagsbox) def add_tag(tag): if not tag: return if tag not in data: data.append(tag) tagb = Gtk.HBox() tagb.pack_start(Gtk.Label(" "+tag+" "), False, False, 0) def kill(w): tagb.destroy() data.remove(tag) tagk = Gtk.Button("-") tagk.connect("clicked", kill) tagk.set_relief(Gtk.ReliefStyle.NONE) tagb.pack_start(tagk, False, False, 0) tagb.pack_start(Gtk.VSeparator(), False, False, 5) tagsbox.pack_start(tagb, False, False, 0) tagsbox.show_all() # Scroll to the last def later(): time.sleep(0.1) def now(): a = tagscrl.get_hadjustment() a.set_value(a.get_upper()) GLib.idle_add(now) load_thread = threading.Thread(target=later) load_thread.start() # The threading is needed, since we want to wait # while GTK will update the UI and only then move # the adjustent. Becuase else, it will move to the # last previous, not to the last last. addt = Gtk.Button("+") addt.set_relief(Gtk.ReliefStyle.NONE) tagscont.pack_end(addt, False, False, 0) def on_entry(w): add_tag(tagentry.get_text()) tagentry.set_text("") tagentry = Gtk.Entry() if auto_fill: liststore = Gtk.ListStore(str) completion = Gtk.EntryCompletion() completion.set_model(liststore) completion.set_text_column(0) for i in auto_fill: liststore.append((i,)) tagentry.set_completion(completion) completion.set_minimum_key_length(0) completion.complete() tagentry.connect("activate", on_entry) addt.connect("clicked", on_entry) tagscont.pack_end(tagentry, False, False, False) for tag in data: add_tag(tag) if not return_edit_functions: return tagscont else: return tagscont, update ######################################################### # # # TOOL BAR # # # ######################################################### # Tool bar will not going to have icons and things like # this. It's not about being pretty. It's about giving # the contributor a handy tool to contribute. pannel = Gtk.HeaderBar() pannel.set_show_close_button(True) win.set_titlebar(pannel) # We are going to make a little trick on our self ( I do # that everywhere ). I gonna put important variables into # the GTK Window. win.data_is = { "names":[], "comment":"", "links":{}, "licenses":[], "platforms":[], "interface":[], "languages":[], "networks_read":[], "networks_write":[], "formats_read":[], "formats_write":[], "generic_name":[], "issues":[], } ##################### OPEN BUTTON ####################### def on_open(w): dialog = Gtk.FileChooserDialog("Choose a file", None, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_current_folder(os.getcwd()+"/apps") filter_sup = Gtk.FileFilter() filter_sup.set_name("Json Files") filter_sup.add_pattern("*.json") dialog.add_filter(filter_sup) response = dialog.run() if response == Gtk.ResponseType.OK: # LOADING THE FILE with open(dialog.get_filename()) as f: loaded_file = json.load(f) def type_min(i): if type(win.data_is[i]) == list: return [] elif type(win.data_is[i]) == dict: return {} elif type(win.data_is[i]) == str: return "" for i in win.data_is: if i in loaded_file and type(win.data_is[i]) == type(loaded_file[i]): try: updaters[i](loaded_file.get(i, type_min(i))) except Exception as e: win.data_is[i] = loaded_file.get(i, type_min(i)) raise() else: try: updaters[i](loaded_file.get(i, type_min(i))) except: win.data_is[i] = loaded_file.get(i, type_min(i)) dialog.destroy() open_button = Gtk.Button("Open") open_button.set_relief(Gtk.ReliefStyle.NONE) open_button.connect("clicked", on_open) pannel.pack_start(open_button) ##################### SAVE BUTTON ####################### # save_button = Gtk.Button("Save") # pannel.pack_start(save_button) #################### SAVE AS BUTTON ##################### def on_save_as(w): tb = detext.get_buffer() win.data_is["comment"] = tb.get_text(tb.get_start_iter(), tb.get_end_iter(), True) dialog = Gtk.FileChooserDialog("Choose a file", None, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_current_folder(os.getcwd()+"/apps") filter_sup = Gtk.FileFilter() filter_sup.set_name("Json Files") filter_sup.add_pattern("*.json") dialog.add_filter(filter_sup) response = dialog.run() if response == Gtk.ResponseType.OK: savename = dialog.get_filename() if not savename.endswith(".json"): savename = savename+".json" with open(savename, 'w') as f: json.dump(win.data_is, f, indent=4, sort_keys=True) dialog.destroy() save_as_button = Gtk.Button("Save As") save_as_button.connect("clicked", on_save_as) save_as_button.set_relief(Gtk.ReliefStyle.NONE) pannel.pack_start(save_as_button) ######################################################### # # # EDITOR IT SELF # # # ######################################################### updaters = {} # Very important to make tags editor work #################### NAMES / COMMENT #################### collapsable = Gtk.Expander(label=" Names / Comment: ") box.pack_start(collapsable, 0,0,5) term_box = Gtk.VBox() collapsable.add(term_box) # The list of names term_box.pack_start(Gtk.Label("List of Names"),0,0,5) names_editor, updaters["names"] = tags_editor(win, win.data_is["names"], True) term_box.pack_start(names_editor,0,0,5) # The list of names term_box.pack_start(Gtk.Label("Generic Names ( Features )"),0,0,5) generic_editor, updaters["generic_name"] = tags_editor(win, win.data_is["generic_name"], True, win.full.get("generic_name",[])) term_box.pack_start(generic_editor,0,0,5) term_box.pack_start(Gtk.Label("Comment (HTML)"),0,0,5) def comment_updater(new): detext.get_buffer().set_text(new) win.data_is["comment"] = new updaters["comment"] = comment_updater descrl = Gtk.ScrolledWindow() descrl.set_size_request(100,100) detext = Gtk.TextView() detext.set_wrap_mode(Gtk.WrapMode.WORD) detext.get_buffer().set_text(win.data_is["comment"]) descrl.add(detext) term_box.pack_start(descrl,0,0,5) #################### LINKS #################### collapsable = Gtk.Expander(label=" Links: ") box.pack_start(collapsable, 0,0,5) term_box = Gtk.VBox() collapsable.add(term_box) # Links are a bit more complicated. It will require a # special editor. def add_item_to_view(category, link): itembox = Gtk.HBox() itembox.pack_start(Gtk.Label(" "+category+": "), 0,0,0) itembox.pack_start(Gtk.VSeparator(), 0,0,0) itembox.pack_start(Gtk.Label(" "+link+" "), 0,0,0) def on_destroy(w, itembox, category): itembox.destroy() del win.data_is["links"][category] destroy = Gtk.Button("-") destroy.set_relief(Gtk.ReliefStyle.NONE) destroy.connect("clicked", on_destroy, itembox, category) itembox.pack_end(destroy, 0,0,0) linksbox.pack_end(itembox, 0,0,0) linksbox.show_all() def add_item(w): key = categoryInput.get_text() value = linkInput.get_text() if key not in win.data_is["links"]: add_item_to_view(key, value) win.data_is["links"][key] = value categoryInput.set_text("") linkInput.set_text("") categoryInput.grab_focus() def links_updater(new): for i in linksbox.get_children(): i.destroy() for i in new: add_item_to_view(i, new[i]) win.data_is["links"][i] = new[i] updaters["links"] = links_updater inputbox = Gtk.HBox() term_box.pack_start(inputbox, 1, 0, 5) inputbox.pack_start(Gtk.Label("Category:"), 0,0,1) categoryInput = Gtk.Entry() inputbox.pack_start(categoryInput, 0,0,1) liststore = Gtk.ListStore(str) completion = Gtk.EntryCompletion() completion.set_model(liststore) completion.set_text_column(0) for i in list(win.full["links"].keys()): liststore.append((i,)) categoryInput.set_completion(completion) completion.set_minimum_key_length(0) completion.complete() inputbox.pack_start(Gtk.Label("Link:"), 0,0,1) linkInput = Gtk.Entry() linkInput.connect("activate", add_item) inputbox.pack_start(linkInput, 1,1,1) addInput = Gtk.Button("+") addInput.connect("clicked", add_item) addInput.set_relief(Gtk.ReliefStyle.NONE) inputbox.pack_end(addInput, 0,0,1) linksbox = Gtk.VBox() term_box.pack_start(Gtk.HSeparator(), 1, 0, 5) term_box.pack_start(linksbox, 1, 0, 5) #################### LICENSES #################### collapsable = Gtk.Expander(label=" Licenses: ") box.pack_start(collapsable, 0,0,5) term_box = Gtk.VBox() collapsable.add(term_box) # The list of licenses #term_box.pack_start(Gtk.Label("Licenses:"),0,0,5) licenses_editor, updaters["licenses"] = tags_editor(win, win.data_is["licenses"], True, win.full.get("licenses",[])) term_box.pack_start(licenses_editor,0,0,5) #################### NERDY INFO ################## collapsable = Gtk.Expander(label=" Technical Info: ") box.pack_start(collapsable, 0,0,5) term_box = Gtk.VBox() collapsable.add(term_box) # The list of Platforms term_box.pack_start(Gtk.Label("Platforms:"),0,0,5) platforms_editor, updaters["platforms"] = tags_editor(win, win.data_is["platforms"], True, win.full.get("platforms",[])) term_box.pack_start(platforms_editor,0,0,5) # The list of Interfaces term_box.pack_start(Gtk.Label("Interfaces:"),0,0,5) interface_editor, updaters["interface"] = tags_editor(win, win.data_is["interface"], True, win.full.get("interface",[])) term_box.pack_start(interface_editor,0,0,5) # The list of Languages term_box.pack_start(Gtk.Label("Languages:"),0,0,5) languages_editor, updaters["languages"] = tags_editor(win, win.data_is["languages"], True, win.full.get("languages",[])) term_box.pack_start(languages_editor,0,0,5) #################### FORMATS / NETWORKS ################# collapsable = Gtk.Expander(label=" Formats / Networks: ") box.pack_start(collapsable, 0,0,5) term_box = Gtk.VBox() collapsable.add(term_box) # The list of Networs read term_box.pack_start(Gtk.Label("Networks it can access ( read ):"),0,0,5) networks_read_editor, updaters["networks_read"] = tags_editor(win, win.data_is["networks_read"], True, win.full.get("networks_read",[])) term_box.pack_start(networks_read_editor,0,0,5) # The list of Networs write term_box.pack_start(Gtk.Label("Networks it can post to ( write ):"),0,0,5) networks_write_editor, updaters["networks_write"] = tags_editor(win, win.data_is["networks_write"], True, win.full.get("networks_write",[])) term_box.pack_start(networks_write_editor,0,0,5) # The list of Opens files term_box.pack_start(Gtk.Label("File formats it reads:"),0,0,5) formats_read_editor, updaters["formats_read"] = tags_editor(win, win.data_is["formats_read"], True, win.full.get("formats_read",[])) term_box.pack_start(formats_read_editor,0,0,5) # The list of saves files term_box.pack_start(Gtk.Label("File formats it saves to:"),0,0,5) formats_write_editor, updaters["formats_write"] = tags_editor(win, win.data_is["formats_write"], True, win.full.get("formats_write",[])) term_box.pack_start(formats_write_editor,0,0,5) #################### ANTI-FEATURES ################# collapsable = Gtk.Expander(label=" Anti-Features ( Malware ): ") box.pack_start(collapsable, 0,0,5) term_box = Gtk.VBox() collapsable.add(term_box) # The list of saves files issues_editor, updaters["issues"] = tags_editor(win, win.data_is["issues"], True, win.full.get("issues",[])) term_box.pack_start(issues_editor,0,0,5) ######################################################### # # # STARTING EVERYTHING # # # ######################################################### win.show_all() Gtk.main()