#################################### # # # COPYRIGHT NOTICE # # # # This file is a part of Victori- # # ous Children Studio Organizer. # # Or simply VCStudio. Copyright # # of J.Y.Amihud. But don't be sad # # because I released the entire # # project under a GNU GPL license. # # You may use Version 3 or later. # # See www.gnu.org/licenses if your # # copy has no License file. Please # # note. Ones I used the GPL v2 for # # it. It's no longer the case. # # # #################################### import os import urllib3 # GTK module ( Graphical interface import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import GLib from gi.repository import Gdk import cairo # Own modules from settings import settings from settings import talk from settings import oscalls from project_manager import pm_project #UI modules from UI import UI_elements from UI import UI_color ################################################################################ # Markdown. Or .md file format is an easy way to give your simple text documents # a bit of flare. Stuff like links, images and quotes are supported. Also bold # an italic characters. def Open(win, filename, name): # This function will parse a Markdown (.md) file into a readable python # dictionary object. That you can use for various things. try: # If the file is local. AKA exists in the folder. And can be read. # Also it could be not .md if filename.startswith("lbry://"): # Sometimes the link is linking an lbry or https source. # meaning this link is has to be retrieved from the network. # For this we have to retrieve the data from the network. # The LBRY protocol has a little serive called spee.ch that # simply redirects you to the file on the protocol. Let's # try using it. ( the lbry3.vanwanet.com/speech/ is the # actuall link it redircts to ) filename = filename.replace("lbry://", "https://lbry3.vanwanet.com/speech/") http = urllib3.PoolManager() resp = http.request('GET', filename) md = resp.data.decode('utf-8') elif not filename.endswith(".md"): 1/0 # Quick fail switch else: # Sometimes a language File is provided. let's # look for it. l = win.settings["Language"] if os.path.exists(os.getcwd()+"/"+filename[:-3]+"_"+l+".md"): filename = filename[:-3]+"_"+l+".md" md = open(filename) md = md.read() except: # If reading fails. For example if it's a link to something on the # web. Then try just opening it in the standard application. md = "" oscalls.Open(filename) win.current["mdfs"][name] = win.current["mdfs"]["failsafe"] # A few things before I gonna make everything. The - [ ] and the - [X] things. # This is the stuff I want to change to the corresponding icons. md = md.replace("- [ ]", "![](settings/themes/Default/icons/unchecked.png)") md = md.replace("- [X]", "![](settings/themes/Default/icons/checked.png)") md = md.replace("- [x]", "![](settings/themes/Default/icons/checked.png)") md = md.replace("- ", "• ") # This one is risky. I want to remove all the ../ so there will not be a # mistake. md = md.replace("../", "") # Spliting it for the read. md = md.split("\n") # First thing is I was to read the headings and convert it into a tree. tree = [] indent = 1 c = [] for line in md: ty = "text" te = line # Here I want to simply get a type of each line. Later we going to parse # the links and other things. But first. Let's parse stuff based on # lines. if line.startswith("#"): # The titles of the chapter. The Headers are usually written similar # to how here in python you write comments. It's a # , space, and the # text. # The amount of hashes. ## or ### gives different sized text. Officialy # it should support up to 6 hashes. ######. But why not make it more # just in case. ty = line.count("#") # This might give bugs elif line.startswith("> "): # The > sign in the Markdown language is used for quatations. ty = "quote" tree.append([ty, te+"\n"]) # Now the stage 0 is over and we parsed the basic things. Now is the hard # part to parse out all the images and stuff inside them. It's going to be # done per part. And we are going to use the same technique I used for the # conversion of the legacy projects. See : studio/story.py # We are going to itterate over each letter. And decide what to do by that newtree = [] for block in tree: part = "" skip = 0 for n, l in enumerate(block[-1]): if skip > n: continue part = part + l # Here we are going to do something if a give condition is met. # Usually I gonna do something if [part] ends with a given markdown # thing. I don't have a mnual of markdown on me. So please make it # more supported. I guess. I might forget things I rarely use. # Links are made with [stuff you click on](https://example.com) # but similar to it. Images are done ![Tooltip](Image.png) # and even weirder you can put one into the other. Like # [![Tooltip](Image.png)](https://example.com) # Which going to give you a clickable image. # For this version what we are going to do is next. # If we got [![ then it's a clickable image # If we got ![ then it's just image # and if we got [ then it's a link. if part.endswith("[!["): # IMAGE LINK newtree.append([block[0], part[:-3]]) tooltip = "" imageurl = "" url = "" t = False iu = False skip = n for le in block[-1][n:]: # For letters in the rest of text skip = skip + 1 if le == "]": t = True elif le == ")" and t and not iu: iu = True elif le == ")" and t and iu: break elif not t: tooltip = tooltip +le elif t and not iu: imageurl = imageurl + le else: url = url+le tooltip = tooltip[tooltip.find("[")+1:] imageurl = imageurl[imageurl.find("(")+1:] url = url[url.find("(")+1:] apnd = ["image_link", tooltip, imageurl, url] newtree.append(apnd) part = "" elif part.endswith("!["): # IMAGE newtree.append([block[0], part[:-2]]) tooltip = "" url = "" t = False skip = n for le in block[-1][n:]: # For letters in the rest of text skip = skip + 1 if le == "]": t = True elif le == ")" and t: break elif not t: tooltip = tooltip +le else: url = url+le tooltip = tooltip[tooltip.find("[")+1:] url = url[url.find("(")+1:] apnd = ["image", tooltip, url] newtree.append(apnd) part = "" elif part.endswith("[") and not block[-1][n:].startswith('[!['): # LINK newtree.append([block[0], part[:-1]]) tooltip = "" url = "" t = False skip = n for le in block[-1][n:]: # For letters in the rest of text skip = skip + 1 if le == "]": t = True elif le == ")" and t: break elif not t: tooltip = tooltip +le else: url = url+le tooltip = tooltip[tooltip.find("[")+1:] url = url[url.find("(")+1:] apnd = ["link", tooltip, url] newtree.append(apnd) part = "" # Now I want to deal with `, *, ** and ***. If you want to help me you # can implement other types. Such as _, __, ___ and so on. Markdown is # a very rich language. I'm going to use the cut down version I see other # people use. # BTW this is the time. Feb 28. When I switched from Gedit to GNU Emacs. # Interesting feeling using this programm. I kind a love it even tho # so many stuff in not intuitive. Like saving is not Ctrl - S but # Ctrl - X -> Ctrl - S. # Things like Alt-; to comment multiple lines at ones is HUGE. Also it # was built by programmers for programmers. So it's a very good tool. elif part.endswith("**") and not block[-1][n+2:].startswith('*'): # DOUBLE ** newtree.append([block[0], part[:-2]]) if block[0] == "text": block[0] = "text_b" else: block[0] = "text" part = "" elif part.endswith("*") and not block[-1][n+1:].startswith('*'): # SINGLE * newtree.append([block[0], part[:-1]]) if block[0] == "text": block[0] = "text_i" else: block[0] = "text" part = "" elif part.endswith("`"): # SINGLE ` newtree.append([block[0], part[:-1]]) if block[0] == "text": block[0] = "text_c" else: block[0] = "text" part = "" newtree.append([block[0], part]) tree = newtree return(tree) def search_convert(s): l = " ./\|[]{}()?!@#$%^&*`~:;'\"=,<>" s = s.lower().replace(" ","-") r = "" for i in s: if i not in l: r = r + i return r def draw(outlayer, win, name, x, y, width, height): # Now you maybe woundering where is the filname to read from. Well. We are # making it now. if "mdfs" not in win.current: win.current["mdfs"] = {} win.current["mdfs"]["failsafe"] = "" if name not in win.current["mdfs"]: win.current["mdfs"][name] = "" filename = win.current["mdfs"][name] filename = filename.replace("../", "") win.current["mdfs"][name] = filename # The # sing usually calls for search with in the text. if "#" in filename and not "@" in filename: filename, search = filename.split("#") win.current["mdfs"][name] = filename else: search = "" # First we don't want to waste resources to parse the file on each frame # so let's load it ones. if "mds" not in win.current: win.current["mds"] = {} if filename not in win.current["mds"]: win.current["mds"][filename] = Open(win, filename, name) md = win.current["mds"][filename] # This peace of code outputs the parsed object into terminal for reading. # for i in md: # if type(i) == str: # print("'"+i+"' ,") # else: # print(i, ",") # Background UI_color.set(outlayer, win, "node_background") UI_elements.roundrect(outlayer, win, x, y, width, height, 10) outlayer.fill() # The name of the file first. I think it's gonna make sense to make it in # an entry. So people could insert any filename, or link in future. try: UI_elements.text(outlayer, win, "markdown_name", x+10, y+5, int(width/2)-120, 40, set_text=filename, fill=False) if win.text["markdown_name"]["text"] != filename \ and win.textactive != "markdown_name": win.text["markdown_name"]["text"] = filename # Let me make it user editable as well. Because I'm a nerd # and I want to see how it handles various problems. elif win.text["markdown_name"]["text"] != filename: def do(): win.current["mdfs"]["failsafe"] = filename win.current["mdfs"][name] = win.text["markdown_name"]["text"] UI_elements.roundrect(outlayer, win, x+int(width/2)-150, y+5, 40, 40, 10, button=do, icon="ok", tip=talk.text("checked")) except: pass # Users should be able to chose whether automatic downloading of stuff # will be done by the software. This is why I want to add this setting # here too. And not barried somewhere in the settings. if width > 350: download_ok = "unchecked" if win.settings["auto_download_images"]: download_ok = "checked" def do(): win.settings["auto_download_images"] = not win.settings["auto_download_images"] settings.write("auto_download_images", win.settings["auto_download_images"]) UI_elements.roundrect(outlayer, win, x+int(width/2)-100, y+5, int(width/2)-150, 40, 10, button=do, icon=download_ok, tip=talk.text("auto_download_images")) UI_color.set(outlayer, win, "text_normal") outlayer.set_font_size(20) outlayer.move_to(x+int(width/2)-50, y+30) outlayer.show_text(talk.text("auto_download_images")[:int((int(width/2)-150)/9)]) # I want to include 2 button to the top raw besides the adress bar. # A button to open the file in the standard application. ( Edit ) # A button to open the NoABug repository version ( NotABug ) if width > 150 and not filename.startswith("lbry://"): # EDIT def do(): oscalls.Open(filename) UI_elements.roundrect(outlayer, win, x+int(width)-100, y+5, 40, 40, 10, button=do, icon="edit", tip=talk.text("edit_markdown")) # NOTABUG def do(): oscalls.Open("https://notabug.org/jyamihud/VCStudio/src/master/"+filename) UI_elements.roundrect(outlayer, win, x+int(width)-50, y+5, 40, 40, 10, button=do, icon="notabug", tip=talk.text("notabug_markdown")) elif width > 100: # ODYSEE ( LBRY ) open in browser def do(): oscalls.Open(filename.replace("lbry://", "https://odysee.com/")) UI_elements.roundrect(outlayer, win, x+int(width)-50, y+5, 40, 40, 10, button=do, icon="lbry", tip="Odysee.com (LBRY)") # Now let's draw the bastard. We are going to do it the same way as in the # script writer. But rather simplified. For example we are not making an # editor. But only a reader. So we don't need the selection. And we don't # need to calculate every letter preciselly. With something like codes # I can do that every ` object will be drawn in a text entry object. Why # not. # I need to make a new Layer because we are going to clip it for the text. textsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) layer = cairo.Context(textsurface) layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) layer_i = cairo.Context(textsurface) layer_i.select_font_face("Monospace", cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_NORMAL) layer_b = cairo.Context(textsurface) layer_b.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) layer_bi = cairo.Context(textsurface) layer_bi.select_font_face("Monospace", cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_BOLD) UI_elements.roundrect(layer, win, 0,50,width, height-50, 10,fill=False) layer.clip() UI_elements.roundrect(layer_b, win, 0,50,width, height-50, 10,fill=False) layer_b.clip() UI_elements.roundrect(layer_bi, win, 0,50,width, height-50, 10,fill=False) layer_bi.clip() UI_elements.roundrect(layer_i, win, 0,50,width, height-50, 10,fill=False) layer_i.clip() ############################################################################ # HERE WE DRAWING TEXT TO THE SCREEN # ############################################################################ if "markdown" not in win.scroll: win.scroll["markdown"] = 0 current_Y = 70 tyleX = 10 newX = 10 newY = 0 for block in md: # THE # HEADERS if type(block[0]) == int: current_Y = current_Y + newY + 100 UI_color.set(layer_b, win, "text_normal") layer_b.set_font_size(30-(block[0]*4)) layer_b.move_to( 10, current_Y + win.scroll["markdown"] ) layer_b.show_text(block[1].replace("\n", "").replace("#", " ")) current_Y = current_Y + 40 if search and search in search_convert(block[1].replace("\n", "").replace("#", "")): win.scroll["markdown"] = 0 - current_Y + 140 search = "" newX = 10 newY = 0 # IMAGES # This going to be hard. I have an interesting idea about it. # 1.) Make the images whos links are in the internet loadable. # This will require some clever thinking. Because not all # users want to automatically download images from a source # that they don't trust. I'm hosting most of the images # at the moment on NotABug.org with the project's code. # But. That I trust it. Doesn't mean everybody trust it # or should trust it. So Downloading of images should be # optional. We can make a button to download a given image # and a setting in the settings to download them automatically. # # Now thinking about it. The Update system should use something # similar. # # 2.) Warping of text around the image. This is something original # markdown not really supports. On NotABug the images in the text # look a bit ugly because they stretch the line height. # I propose using a kind of warp around system. Let me demonstrate. # ############### This is a sample text about the Image.png. That # # # is going to warp around the image like so. # # Image.png # More text is needed to show the extent of this # # # effect. So I'm going to ramble about something. # # # Hello World! Are you a true hacker? I hope you # ############### are. And not a cracker. Now see what's going to # happen when the image is ended. The next line is written under # the image. Which is a cool look. Now ... # I have no idea what to do if the image is going to be rendered in # the middle of the paragraph. Maybe the image in the middle should # act like in a normal implementation. And only at the start as # what I showed you now. elif "image" in block[0]: # ICONS # Sometimes I link Icons from OldSchool theme to the text. They # look good on a white background in the NotABug.org page. # But. Since they are icons that might have different themes for # the users let's make them theme respecting. if "settings/themes/" in block[2] and "/icons/" in block[2]: iconname = block[2][block[2].rfind('/')+1:] block[2] = "settings/themes/"+win.settings["Theme"]+"/icons/"+iconname UI_elements. image(layer, win, "settings/themes/"\ +win.settings["Theme"]+"/icons/"+iconname, tyleX, current_Y + win.scroll["markdown"]-30) tyleX = tyleX + 40 # OTHER IMAGES # I think the handling of the images loading should be done in the # UI_elements.py file. So I could easy reimplement this feature # else where. else: if newY: current_Y = current_Y + newY + 40 tyleX = 10 newX = 10 newY = 0 UI_elements. image(layer, win, block[2], tyleX, current_Y + win.scroll["markdown"]-15, cell="markdown",offset=[x,y],width=width-40,fit="fit_width") try: imH = win.images["markdown"][block[2]]['image'].get_height() imW = win.images["markdown"][block[2]]['image'].get_width() except: imH = 40 imW = 40 bx = tyleX by = current_Y if imW < width/2: tyleX = tyleX + imW newX = imW + 40 newY = imH - 20 else: tyleX = 10 current_Y = current_Y + imH # LINKED IMAGES if "link" in block[0]: # If the image is linked. We are going to separate it in 2 categoies. # If automatic downloading is on. There is no difference. Else. There # will be a difference. Since the image from the Internet going to be # a button to download it. We need to offset the link button. SO. def do(): if block[3].endswith(".md"): win.current["mdfs"]["failsafe"] = filename win.current["mdfs"][name] = block[3] win.current["current_help_selected"] = "" win.scroll["markdown"] = 0 else: oscalls.Open(block[3]) try: downloadbutton = win.images["markdown"][block[2]]["image"] == "download_button" except: downloadbutton = False if not win.settings["auto_download_images"]\ and block[2].startswith("http")\ and downloadbutton: # Here goes the offsetted link. UI_elements.roundrect(layer, win, tyleX, current_Y + win.scroll["markdown"]-15,40,40,10, icon="internet", button=do, offset=[x,y], tip=block[3]) tyleX = tyleX + 60 else: # Full on image UI_elements.roundrect(layer, win, bx, by + win.scroll["markdown"]-15,imW, imH,10, button=do, offset=[x,y], tip=block[3], fill=False) layer.stroke() # TEXT elif block[0] in ["text", "text_i", "text_b", "text_ib", "text_c", "link"]: for word in block[1].split(" "): if tyleX + len(word)*12+12 > width : tyleX = newX newY = max(0, newY-30) if newY == 0: newX = 10 current_Y = current_Y + 30 # Any type of Text. Whether it's Normal, Bold or Italic gonna # have the same color. if "text" in block[0]: UI_color.set(layer, win, "text_normal") UI_color.set(layer_i, win, "text_normal") UI_color.set(layer_b, win, "text_normal") UI_color.set(layer_bi, win, "text_normal") # Unless it's a link. Then I have a special color for it in the # theme. I tried reusing pre-existing. Non of them works across # multiple themes. else: # Here I gonna introduce the logic of the links. Because # I'm lazy. if "markdown_mouse_link" not in win.current: win.current["markdown_mouse_link"] = "" if win.current["mx"] in range(int(x+tyleX-6), int(x+tyleX-6+len(word)*12+12))\ and win.current["my"] in range(int(y+current_Y + win.scroll["markdown"]-20), int(y+current_Y + win.scroll["markdown"]+5)): win.current["markdown_mouse_link"] = block[2] UI_elements.tooltip(win,block[2]) # AHh... I'll do a clicker here. if not win.current["LMB"] and win.previous["LMB"]: if block[2].startswith("#"): win.current["mdfs"][name] = win.current["mdfs"][name] + block[2] win.current["current_help_selected"] = "" else: win.current["mdfs"]["failsafe"] = filename win.current["mdfs"][name] = block[2] win.current["current_help_selected"] = "" win.scroll["markdown"] = 0 elif win.current["mx"] not in range(win.previous["mx"]-5, win.previous["mx"]+5): win.current["markdown_mouse_link"] = "" # If mouse hovering over a link. Draw a line under the link. if win.current["markdown_mouse_link"] == block[2]: UI_color.set(layer, win, "text_normal") UI_elements.roundrect(layer, win, tyleX-6, current_Y + win.scroll["markdown"], len(word)*12+12, 4, 2) UI_color.set(layer, win, "text_link") # Italic text if "_i" in block[0]: layer_i.set_font_size(20) layer_i.move_to( tyleX, current_Y + win.scroll["markdown"] ) layer_i.show_text(word.replace("\n", "")) # Bold text elif "_b" in block[0]: layer_b.set_font_size(20) layer_b.move_to( tyleX, current_Y + win.scroll["markdown"] ) layer_b.show_text(word.replace("\n", "")) # Any other text else: if "_c" in block[0]: UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, tyleX-6, current_Y + win.scroll["markdown"]-20, len(word)*12+12, 25, 5) UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to( tyleX, current_Y + win.scroll["markdown"] ) layer.show_text(word.replace("\n", "")) # Moving of to the next word. if "\n" in word: tyleX = newX newY = max(0, newY-30) if newY == 0: newX = 10 current_Y = current_Y + 30 else: tyleX = tyleX + len(word)*12+12 ############################################################################ # Scroll UI_elements.scroll_area(outlayer, win, "markdown", x+0, y+0, width+30, height-0, current_Y, bar=True, mmb=True, bar_always=True) # Outputting the layer outlayer.set_source_surface(textsurface, x, y) outlayer.paint()