Blender-Pipeline/UI/UI_Markdown.py

904 lines
30 KiB
Python

####################################
# #
# 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()