# THIS FILE IS A PART OF VCStudio
# PYTHON 3

# This a console project manager.

import os
import math
import urllib3
import hashlib
import threading

# GTK module ( Graphical interface
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import GdkPixbuf
import cairo

# Own modules
from settings import settings
from settings import talk
from settings import fileformats

# UI
from UI import UI_color

def roundrect(layer, win, x, y, width, height, r, button=False, icon=False, 
              tip="", fill=True, url="", clip=False, offset=[0,0], text=False):
    
    # This function draws a rectangle with rounded edges.
    
    # A button variable is a calable for the button action. Basically it's a
    # function. Roundrect will act as a button.
    if button:
        
        #if not current url in the software
        if url and url != win.url:
            
            # So icons would not disappear LOL
            if icon:
                image(layer, win, "settings/themes/"\
                +win.settings["Theme"]+"/icons/"+icon+".png", x, y)
            
            return
        
        # If UI testing is on preview. Buttons.
        if win.current["testing"]:
            UI_color.set(layer, win, "testing_banner")
            layer.rectangle(x,y,width,height)
            layer.stroke()
        
        
        if  win.current['mx'] in range(int(x+offset[0]), int(x+width+offset[0])) \
        and win.current['my'] in range(int(y+offset[1]), int(y+height+offset[1])) :
            do = True
            
            if clip:
                if  win.current['mx'] in range(int(clip[0]), int(clip[0]+clip[2])) \
                and win.current['my'] in range(int(clip[1]), int(clip[1]+clip[3])) :
                    do = True
                else:
                    do = False
        
        else:
            do = False
        
        
            
        if do:
        
            # cursor
            if text:
                win.current["cursor"] = win.cursors["text"]
            else:
                win.current["cursor"] = win.cursors["hand"]
            
            # If holding click
            if win.current["LMB"]:
                UI_color.set(layer, win, "button_clicked")
            else:
                UI_color.set(layer, win, "button_active")
            # If clicked
            if win.previous["LMB"] and not win.current["LMB"]:
                win.current["cursor"] = win.cursors["watch"]
                button()
            
            # Button might have a tooltip as well
            if tip:
                tooltip(win, tip)
        
        
    else:
        do = True
    
    if do:
        # Making sure that round rectangle will not be smaller then it's round-
        # ness. Also with width and height zero, it's going to draw a circle.
        
        if width < r*2:
            width = r*2
        if height < r*2:
            height = r*2
        
        # I just out of blue decided that I want to have a setting to restrict
        # the amount of roundness for every roundrect. If the user want let him
        # or her have ugly squares everywhere.
        
        if "Roundness" not in win.settings:
            win.settings["Roundness"] = 1.0
            settings.write("Roundness", 1.0)
        
        r = r*win.settings["Roundness"]
        
        # actuall drawing
        layer.move_to(x,y+r)
        layer.arc(x+r, y+r, r, math.pi, 3*math.pi/2)
        layer.arc(x+width-r, y+r, r, 3*math.pi/2, 0)
        layer.arc(x+width-r, y+height-r, r, 0, math.pi/2)
        layer.arc(x+r, y+height-r, r, math.pi/2, math.pi)
        layer.close_path()
        if fill:
            layer.fill()
    
    # Icon is a continuation of the button part. Because you need a way to see
    # that that the button is even there to begin with.
    if icon:
        image(layer, win, "settings/themes/"\
        +win.settings["Theme"]+"/icons/"+icon+".png", x, y)
    
def animate(name, win, v1=0, v2=None, time=10, force=False):
    
    # This function will make animating values over time possible. For smooth
    # Transisions and things like this it's going to be very usefull.
    
    
    
    # Let's clear mess in case they I'm lazy to make all the things
    if v2 == None:
        v2 = v1
        
    # Set up the animation into the animations. If it's not there yet.
    if name not in win.animations or force:
        win.animations[name] = [
            v1,
            v2,
            time,
            win.current["frame"]
            ]
   
    # Let's get data out of the win.animation[name]
    v1    = win.animations[name][0]
    v2    = win.animations[name][1]
    time  = win.animations[name][2]
    start = win.animations[name][3]
    frame = win.current["frame"]
    
    if time == 0:
        return v2
    
    # If animation is over.
    if start + time < frame:
        return v2
    
    # If v1 and v2 are the same. I'm doing it here. In case the value would be
    # Animated later. So it will create the animation instance.
    if v1 == v2:
        return v2
    
    if v1 < v2:
        vN = v1 + ((v2 - v1)/time*(frame-start))
    else:
        vN = v1 - ((v1 - v2)/time*(frame-start))
    
    return vN    
    
def blur(surface, win, amount):
    
    # This function will blur a given layer by scaling it down and scaling it 
    # back up. It will be doing it only when a given blur setting it active.
    
    # To avoid all kinds of Zero devision problems. And speed up the draw if
    # using animated blur values.
    if amount < 3: # When Blue value was less then 3 it felt sharp but not enough
        return surface # Which caused sense of uneasiness. 
    
    # Setting up initial Blur
    if not "Blur" in win.settings:
        settings.write("Blur", True)       # Writing to file
        win.settings = settings.load_all() # Loading file back to RAM
    
    # If to active blur. Will be changed in the graphics settings. 
    if win.settings["Blur"]:
        # scaling down
        surface1 = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
                                                           win.current['h'])
        slacedownlayer = cairo.Context(surface1)
        slacedownlayer.scale(1/amount, 
                             1/amount)
        
        slacedownlayer.set_source_surface(surface, 0 , 0)
        slacedownlayer.paint()
        
        #scaling back up
        surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'],
                                                           win.current['h'])
        slaceuplayer = cairo.Context(surface2)
        slaceuplayer.scale(amount, 
                           amount)
        
        slaceuplayer.set_source_surface(surface1, 0 , 0)
        slaceuplayer.paint()
        
        return surface2
    else:
        return surface

def hash_file(f):
    
    # This function will give me MD5 hashes of various files
    
    try:
        BLOCKSIZE = 65536
        hasher = hashlib.md5()
        with open(f, 'rb') as afile:
            buf = afile.read(BLOCKSIZE)
            while len(buf) > 0:
                hasher.update(buf)
                buf = afile.read(BLOCKSIZE)
        return str(hasher.hexdigest())
    except:
        return "FOLDER"

def loadimage(layer, win ,path, x, y, width, height, fit, cell=0):

    # Before we load an image we have to know whether the image is local
    # in the Internet. Markdown files for example can have links to a
    # picture in the internet. And so if a given Image URL is not local
    # we have to be able to download it.

    # For reasons we all can relate to. I don't want such downloading to
    # happen automatically. Unless the user is going to enable auto
    # download in the settings.

    # Let's set up a setting. I'm doing it here because Images drawn
    # first. On the main screen. And the setting does not exists in
    # the earlier settings file from the earlier version.

    if "auto_download_images" not in win.settings:
        win.settings["auto_download_images"] = False
        settings.write("auto_download_images", win.settings["auto_download_images"])
        
    filename = "/tmp/"+path.replace("/","_")
    # It could an image data without an image .png thing
    found = False
    for f in fileformats.images:
        if filename.endswith(f):
           found = True
    if not found:
        filename = filename+".png"

    tmppath = ""

    if path.startswith("http") and not os.path.exists(filename):

            
        if win.images[cell][path]["image"]  != "downloading":

            # If the button is not pressed yet. Then we need to
            # put a downlod button. Look down where implement
            # the drawing of the image for the button it self.
            
            win.images[cell][path]["loading"] = False
            win.images[cell][path]["image"]   = "download_button"
            return

        else:
            
            # Now somebody either pressed the button. Or it set to
            # automatic downloads. Then, let's get the file.

            win.images[cell][path]["image"]   = None
            
            http = urllib3.PoolManager()
            r = http.request('GET', path, preload_content=False)
            with open(filename, 'wb') as out:
                while True:
                    data = r.read(1024)
                    if not data:
                        break
                    out.write(data)

            r.release_conn()
            

    elif path.startswith("http"):
        tmppath = path
        path = filename

        
           
    # Also I don't want downloading to happen here. Since if it's not
    # downloaded 
    
    # So multiple types of the image could be loaded. It could be either
    # an image file. Like png or jpeg. Either a video file. Or a blend file
    # each with it's own loading problems. 
        
    # While cairo can read pngs directly. It's not the best way of doing it
    # since it's not capable of reading jpegs and other files. So let's do
    # something about it.
    
    foundformat = False
    
    # IMAGEFILES
    for f in fileformats.images:
        if path.endswith(f):
            
            foundformat = True
            
            # So this parth of the code is for loading all simple images. From
            # pngs Jpegs to anything else.
            
            # We are going to use Gtk's GdkPixbuf for this. We will need to 
            # convert it to cairo surface later
            
            try:
                load1 = GdkPixbuf.Pixbuf.new_from_file(path)
            except:
                
                try:
                    load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/image.png")
                except:
                    load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/image.png")
                
    # VIDEOFILES
    for f in fileformats.videos:
        if path.endswith(f):
            
            foundformat = True
            
            # Now if it's a video. It's going to be a little harder. We will need
            # to use totem. It's a video player that exists in many GNU/Linux 
            # systems. Unfortunatly not everybody could see video previews.
            
            try:
                
                # This is going to require a few steps.
                
                # 1. Making a filename for our totem to output. 
                part = path.replace("/", "_").replace(" ", "_")
                
                # 2. Calling totem. And telling him that we want a thumbnail. 
                os.system("totem-video-thumbnailer -s "+str(width)+" "+path\
                                +" /tmp/vcstudio_thumbnail"+part+".png")
                
                # 3. Loading this thumbnail.
                load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_thumbnail"+part+".png")
                
                # 4. Cleaning the thumbnail from the OS.
                try:
                    os.remove("/tmp/vcstudio_thumbnail"+part+".png")
                except:
                    pass
                
            except:
                try:
                    load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/video.png")
                except:
                    load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/video.png")
    # BLEND FILES
    
    for f in ["blend", "blend1"]:
        if path.endswith(f):
            
            foundformat = True
            
            # Similarly to the Video. Blends files has their own thumbnailer. This
            # time it's inside of the code of VCStudio. But I've copied it from
            # the blender's installation folder. So you can find it there too
            # it's called blender-thumbnailer.py. Which is a python script. Which
            # is cool.
            
            # Because thumbnailer is developed to work regardless of whether blender
            # is installed or not. As it's reading the blend file directly. And
            # not using some bpy script. We can copy that over to VCStudio and use
            # it to give previews to users who do not have blender installed.
            
            try:
                
                # This is going to require a few steps.
                
                # 1. Making a filename for our thumbnailer to output. 
                part = path.replace("/", "_").replace(" ", "_")
                
                # 2. Calling thumbnailer. And telling him that we want a thumbnail. 
                os.system("python3 "+os.getcwd()+"/UI/blender-thumbnailer.py "\
                            +path+" /tmp/vcstudio_thumbnail"+part+".png")
                
                # 3. Loading this thumbnail.
                load1 = GdkPixbuf.Pixbuf.new_from_file("/tmp/vcstudio_thumbnail"+part+".png")
                
                # 4. Cleaning the thumbnail from the OS.
                try:
                    os.remove("/tmp/vcstudio_thumbnail"+part+".png")
                except:
                    pass
                
            except:
                
                try:
                    load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/blender.png")
                except:
                    load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/blender.png")
     
    if not foundformat:
        
        # If you can't find any format. Just use the file icon then
        try:
            load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/"+win.settings["Theme"]+"/icons/file.png")
        except:
            load1 = GdkPixbuf.Pixbuf.new_from_file("settings/themes/Default/icons/file.png")

        
        
    # Then to convert the pixbuf to a cairo surface
    Px = load1.get_width()
    Py = load1.get_height()
    load2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, Px, Py)
    imagedraw = cairo.Context(load2)
    Gdk.cairo_set_source_pixbuf( imagedraw, load1, 0, 0)
    imagedraw.paint()
    
    # If I want to resize the image for an icon or something. There is            
    # gonna be the folowing algorythm.
    
    if width or height:
        
        dx = 0
        dy = 0
        
        
        imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
        imagedraw = cairo.Context(imagesurface)
        
        # Crop effect. Really hard on my brains.
        
        if fit == 'crop':
            if height > load2.get_height()\
            or width  > load2.get_width():
                
                dx = (width/2) -(load2.get_width() /2)
                dy = (height/2)-(load2.get_height()/2)
                
            else:
                factor = 1
                if (load2.get_height()*(width/load2.get_width()))\
                    < height:
                    factor = height / load2.get_height()
                else:
                    factor = width / load2.get_width()
                #factor = 0.1
                imagedraw.scale(factor, factor)    
                dx = (width/2)/factor -(load2.get_width() /2)
                dy = (height/2)/factor -(load2.get_height()/2)

        # Finally I need to implement something but the Crop.
        # Fit Width I want to use in MarkDowns. Maybe also in the
        # text of the script. Makes sense.
        
        elif fit == "fit_width" and load2.get_width() > width:
            factor = width / load2.get_width()
            dx = 0
            dy = 0
            imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(load2.get_height()*factor))
            imagedraw = cairo.Context(imagesurface)
            imagedraw.scale(factor, factor)
        elif fit == "fit_width":
            dx = 0
            dy = 0
            imagesurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(load2.get_width()), int(load2.get_height()))
            imagedraw = cairo.Context(imagesurface)
            
            
        # Let's make an ability for theme makers to simply color the standard
        # icons into any shade.
                
        imagedraw.set_source_surface(load2, dx, dy)    
        imagedraw.paint()  
    else:
        imagesurface = load2
        
    # Saving it into the win.images
    if tmppath:
        path = tmppath
    win.images[cell][path]["loading"] = False
    win.images[cell][path]["image"]   = imagesurface
    win.images[cell][path]["hash"]    = hash_file(path)
    
    win.imageload -= 1

def reload_images(win, force = False, only_cell = None):
    
    # This function will force the images to refrash. For things like sync
    # or simply where a file is changed. Or for any other reason. Now we 
    # probably don't need to do this on every image. So instead we are going
    # to compare the hashes of the current images to the ones of the files
    # and if they changed. We are going to update those.
    
    for cell in list(win.images.keys()):
        if only_cell != None and only_cell != cell:
            continue
        for image in list(win.images[cell].keys()):
            if (hash_file(image) != win.images[cell][image]["hash"]) or force:
                win.images[cell][image]["loading"] = True
    
    pass

def image(layer, win ,path, x, y, width=0, height=0, fit="crop", cell=0, offset=[0,0]):
    
    # This module will handle drawing images to the layers. It's not that hard
    # to do in cairo by default. But i'm doing it at every frame. And so it
    # means a system of images should exist to take out the load. Basically
    # it will make sure the images is loaded only ones. And for the next draw
    # calls it will forward the old image.
    
    # Attempt of optimization. Keeping in mind the nature of the programm. Basi-
    # cally I will not load image unless x and y are in frame. Kind a ugly. I
    # know. But I don't want to even bother checking the resolution of the image
    # if it's not in a frame. Roughly speaking. Will see maybe I will make do
    # something to make it better.
    
    if cell not in win.images:
        win.images[cell] = {}
    
    if int(x) not in range(int(0-width ), int(win.current["w"])) or \
       int(y) not in range(int(0-height-win.current["h"]), int(win.current["h"])) :
        return
    
    # If you ran this software you probably noticed that images are loading
    # dynamically. I did it using this following crazy algorythm borowed from
    # the old organizer. 
    
    # Here I want to load images. But I also want to give myself an ability to
    # load refrash images. Without refrashing all the images. For this I'm going
    # to use hashes. Specificly MD5 but it doesn't matter really. The idea is
    # that on a special redraw call. We are going to check hashes storred with 
    # those from the file. And only if a file really changed. Then update the image.
    
    
    # First the icon might not exist in the icon pack. Since I want to make 
    # packs using
    if win.settings["Theme"] in path and not os.path.exists(path):
        path = path.replace(win.settings["Theme"], "Default")
    
    if path not in win.images[cell] or win.images[cell][path]["loading"]: 
        
        # If this is the first time this image is draw we want to create it a data
        # structure. So we could load the image.
        
        win.images[cell][path] = {
            "loading": True, # Whether the image is currently loading.
            "image"  : None, # The image data it self.
            "hash"   : ""    # The hash of the image file.
            }
            
        # Then we want to insure to load only a given amount of images in the same
        # time. For this we are going to use the next variable.
        
        MAXLOAD = 10
        
        if win.imageload < MAXLOAD:
            win.imageload += 1
        
        
            # Now we want to load the image. But we are going to do that in a separate thread
            # not to freeze the UI while the image is loading.
            
            # See: loadimage() function for the continuation of this. 
            
            t = threading.Thread(target=loadimage, args=(layer, win ,path, x, y, width, height, fit, cell))
            t.start()
            
    #loading it back
    else:
        if win.images[cell][path]["image"] and win.images[cell][path]["image"] != "download_button":
            imagesurface = win.images[cell][path]["image"]
            
            # Writting the image to the screen
            
            
            try:
                if "icons" in win.color and "settings/themes/" in path:

                    UI_color.set(layer, win, "icons")
                    layer.rectangle(x,y,0,0)
                    layer.fill()

                    layer.mask_surface(imagesurface, x, y)

                    UI_color.set(layer, win, "icons")
                    layer.fill()
                else: 
                    layer.set_source_surface(imagesurface, x, y)
                    layer.paint()
            except:
                pass
            
            # And if testing
            
            if win.current["testing"]:
                UI_color.set(layer, win, "testing_image")
                layer.rectangle(x,y,imagesurface.get_width(),imagesurface.get_height())
                layer.stroke()
                
        elif win.images[cell][path]["image"] == "download_button":

            # If the image is online. By default it will not load it unless the
            # user presses a download button. So here it is.
    
            def do():
                win.images[cell][path]["image"] = "downloading"
                loadimage(layer, win ,path, x, y, width, height, fit, cell)
                win.images[cell][path]["loading"] = True

                # Some link image will be automatically clicked. This
                # fixes the problem.
                win.current["LMB"] = False
                win.previous["LMB"] = False

                
            if win.settings["auto_download_images"]:
                t = threading.Thread(target=do)
                t.start()
            else:
                
                roundrect(layer, win,  x,y,40,40,10,
                          icon="image_link",
                          button=do,
                          offset=offset,
                          tip=path)
                

def tooltip(win, text):
    
    layer = win.tooltip
    
    # This function draws a tooltip helper window. 
    
    # Just in case
    text = str(text)
    
    # Let's get dimantions of the cube first.
    lines = 0
    maxletters = 0
    
    for line in text.split("\n"):
        lines += 1
        
        if len(line) > maxletters:
            maxletters = len(line)
    
    
    # Now when we now the mount of lines and the max lenght of a line. We can
    # start actually drawing something.
    
    # Let's try to make so it's not out of the frame.
    sx = win.current["mx"]
    sy = win.current["my"]
    
    if sx+(maxletters*9)+40 > win.current["w"]:
        sx = win.current["w"] - ((maxletters*9)+40)
    if sy+(lines*20)+10 > win.current["h"]:
        sy = win.current["h"] - ((lines*20)+10)   
    if win.current['my'] > win.current['h'] - 50:
        sy -= 50

    # Rectangle
    UI_color.set(layer, win, "node_background")
    roundrect(layer, win,
    sx,
    sy,
    (maxletters*9)+40,
    (lines*20)+10,
    10)
    
    
    # Text it self
    layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL,
                                        cairo.FONT_WEIGHT_NORMAL)
    layer.set_font_size(15)
    UI_color.set(layer, win, "text_normal")
    
    for num, line in enumerate(text.split("\n")):
        layer.move_to(sx+20,
                      sy+20+(20*num) )
        layer.show_text(line)
        
def scroll_area(layer, win, name, x, y, width, height, maxlength, 
                           bar=False,sideways=False, mmb=False, mmb_only=False,
                           url="", strenght=50, bar_always=False, smooth=True):
    
    # This function going to handle all the scrolling windows. Making it so it's
    # relativelly simple to set up big widgets with in small areas. 
    # It will handle only the value of the scroll stored in win.scroll[name]
    

    if 'Scroll_smoothness' not in win.settings:
           win.settings['Scroll_smoothness'] = True
           settings.write('Scroll_smoothness', True)

    if not win.settings['Scroll_smoothness']:
        smooth = False
    

    if maxlength == 0:
        maxlength = 1
    
    x = int(x)
    y = int(y)
    width = int(width)
    height = int(height)
    
    # First let's set one up if it's not setup
    if name not in win.scroll:
        win.scroll[name] = 0
        return
    
    # Getting scroll amount based on all kinds of settings. AT THIS MOMENT
    # IT'S IN AN ALPHA BECAUSE I'M LAZY. I GONNA IMPLEMENT THING AS I NEED THEM
    # Or you can do that. IDK...
    
    amount = 0.0
    if  win.current['mx'] in range(x, x+width) \
    and win.current['my'] in range(y, y+height) :
        if not mmb_only:

            amount = win.current["scroll"][1]*strenght
            
                
        if mmb_only:
            mmb = True
        
        # Middle mouse button scroll, or using a graphical tablet.
        if mmb:
            if win.current["MMB"]:
                if not sideways:
                    amount = 0 - ( win.current["my"] - win.previous["my"] )
                else:
                    amount = 0 - ( win.current["mx"] - win.previous["mx"] )

   
                    
    # I guess I need to separate the logic into a separate minifunction.
    # I will use later for the scroll bar thingy. So not to rewrite the code
    # Here is a function thingy.
    
    def logic(smooth=False):
        if url and url != win.url:
            return
        # Scroll logic
        if smooth and (win.current["scroll"][1] or win.current["MMB"] or win.current["LMB"]):
            animate("scroll_"+name, win, v1=win.scroll[name],
                    v2=win.scroll[name]-amount, time=5, force=True)
            
        if smooth and not win.current["MMB"] and not win.current["LMB"]:
            win.scroll[name] = animate("scroll_"+name, win)
        else:
            win.scroll[name] -= amount
        
        if not sideways:
            # If too low  
            if win.scroll[name] < (1-maxlength+height):
                win.scroll[name] = (1-maxlength+height)
            # If too high
            if win.scroll[name] > 0:
                win.scroll[name] = 0    
        else:
            # If too low  
            if win.scroll[name] < (1-maxlength+width):
                win.scroll[name] = (1-maxlength+width)
            # If too high
            if win.scroll[name] > 0:
                win.scroll[name] = 0 
    logic(smooth)
    # Not BAR. Which is going to be drawn at a side of what ever content there
    # Basically a scrollbar. But implemented from scratch. Because I'm crazy.
    # and have nothing better to do now.
    
    if bar:
        
        # For now I'm going to implement only vertical bar. I gonna implement
        # the horisontal one when the time comes.
        
        if not sideways:
            
            # Fist let's make some math in front. Because things like this
            # require ton of maths. And it's good to have some predone.
            
            tobreak = False # Also if we can abort the operation early with it.
            fraction = height / maxlength # Similar to progress bar for now
            if fraction > 1:
                tobreak = True
                fraction = 1
            
            # To break parameter basically says. To draw it the bar only when 
            # it's actully needed. When content aka maxlength is bigger then
            # our viewport.
            
            if not tobreak or bar_always:
                
                # Now the offset value. That will move our progress bar with 
                # the scroll value.
                
                offset = (height-60)*( (1-win.scroll[name])  / maxlength )        
                
                # Background bar
                
                UI_color.set(layer, win, "background")
                roundrect(layer, win,
                (x+width)-20,
                y+30,
                10,
                height-60,
                5
                )
                
                # Active bar
                
                UI_color.set(layer, win, "button_active")
                
                # Let's save a little bit more math because it's going to be
                # vild. And I love it.
                
                Lx  = (x+width)-20
                LSx = 10
                Ly  = y+30+offset
                LSy = (height-60)*fraction
                
                # Mouse over thingy. To make the bat a tiny bit brighter. 
                # indicating to the user that it's now could be used.
                
                if  win.current['mx'] in range(int(Lx), int(Lx+LSx)) \
                and win.current['my'] in range(int(Ly), int(Lx+LSy)) :
                    UI_color.set(layer, win, "button_clicked")
                
                # Now separatelly we gonna check for if the mouse pressed. 
                # Remember if it's not pressed it's False. It's written in one
                # of the files. Basically I want to be able to move the mouse
                # outside the bar while moving. And so it won't cancel the motion.
                
                # It seems like I did something wrong. Anyways it works.
                
                if win.current["LMB"]:
                    if  int(win.current['LMB'][0]) in range(int(Lx), int(Lx+LSx)) \
                    and int(win.current['LMB'][1]) in range(int(y), int(y+(height-60))) :
                        
                        UI_color.set(layer, win, "button_clicked")
                        
                        # A bit more math to set the value back.
                        amount = ( win.current["my"] - win.previous["my"] ) / \
                                   (height-60) * maxlength
                        smooth = False
                        logic() # Yeah. Look a few lines back.
                
                # And only after all of this nonsense we can draw the cube. Or
                # should I say roundrect? A button? Aaaaaa.... 
                
                roundrect(layer, win,
                Lx,
                Ly,
                LSx,
                LSy,
                5
                )

        else:
            
            # Fist let's make some math in front. Because things like this
            # require ton of maths. And it's good to have some predone.
            
            tobreak = False # Also if we can abort the operation early with it.
            fraction = width / maxlength # Similar to progress bar for now
            if fraction > 1:
                tobreak = True
                fraction = 1
            
            # To break parameter basically says. To draw it the bar only when 
            # it's actully needed. When content aka maxlength is bigger then
            # our viewport.
            
            if not tobreak or bar_always:
                
                # Now the offset value. That will move our progress bar with 
                # the scroll value.
                
                offset = (width-60)*( (1-win.scroll[name])  / maxlength )        
                
                # Background bar
                
                UI_color.set(layer, win, "background")
                roundrect(layer, win,
                x+30,
                (y+height)-20,
                width-60,
                10,
                5
                )
                
                # Active bar
                
                UI_color.set(layer, win, "button_active")
                
                # Let's save a little bit more math because it's going to be
                # vild. And I love it.
                
                Lx  = (y+height)-20
                LSx = 10
                Ly  = x+30+offset
                LSy = (width-60)*fraction
                
                # Mouse over thingy. To make the bat a tiny bit brighter. 
                # indicating to the user that it's now could be used.
                
                if  win.current['my'] in range(int(Lx), int(Lx+LSx)) \
                and win.current['mx'] in range(int(Ly), int(Lx+LSy)) :
                    UI_color.set(layer, win, "button_clicked")
                
                # Now separatelly we gonna check for if the mouse pressed. 
                # Remember if it's not pressed it's False. It's written in one
                # of the files. Basically I want to be able to move the mouse
                # outside the bar while moving. And so it won't cancel the motion.
                
                # It seems like I did something wrong. Anyways it works.
                
                if win.current["LMB"]:
                    if  int(win.current['LMB'][1]) in range(int(Lx), int(Lx+LSx)) \
                    and int(win.current['LMB'][0]) in range(int(x), int(x+(width-60))) :
                        
                        UI_color.set(layer, win, "button_clicked")
                        
                        # A bit more math to set the value back.
                        amount = ( win.current["mx"] - win.previous["mx"] ) / \
                                   (width-60) * maxlength
                        smooth = False
                        logic() # Yeah. Look a few lines back.
                
                # And only after all of this nonsense we can draw the cube. Or
                # should I say roundrect? A button? Aaaaaa.... 
                
                roundrect(layer, win,
                Ly,
                Lx,
                LSy,
                LSx,
                5
                )

def text(outlayer, win, name, x, y, width, height, set_text="", parse=False, fill=True,
        editable=True, multiline=False , linebreak=False, centered=False, tip="",
        offset=[0,0]):
    
    # This function will handle all the text writting in the software.
    # I'm not sure about how parsing going to work for script files later. 
    # But if it's currently works, means that I already implemented it into 
    # the program. 
    
    # Making the layer
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
    layer = cairo.Context(surface)
    layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
    layer.set_font_size(20)
    
    # Some challenges that it will have is how to correctly store data about
    # the text in the system. I think we can use the win.text variable to store
    # directories of the data.
    
    if name not in win.text:
        
        # I need to get something done before I can pu scroll in.
        scrollname = name
        while scrollname in win.scroll:
            scrollname = scrollname+"_text"
        
        win.text[name] = {
            "text"  :set_text,  # Actuall text you are editing.
            "cursor":[len(str(set_text)),len(str(set_text))], # Cursor
            "insert":False, # Whether the insert mode is on
            "scroll":scrollname # If multiline. The pointer for the scroll value.
            }
    
    # Background
    if fill:
        UI_color.set(layer, win, "darker_parts")
        roundrect(layer, win, 
            0,
            0,
            width,
            height,
            10)
        layer.fill()
    
    # Now after filling it up. I want to clip everything. SO no text will get
    # out of a given area. 
    roundrect(layer, win, 
        0,
        0,
        width,
        height,
        10, 
        fill=False)
    layer.clip()
    
    # Now I want to give a preview of the text it self. BUT. I need to be sure 
    # that if the text longer then a given width and there is no multiline or a
    # linebreak. Then it scrolls sideways to the cursor. 
    
    # Automatic scroll system: Based on the position of the cursor.
    offsetX = 0
    cursor2location = win.text[name]["cursor"][1]*12 + offsetX
    while cursor2location > width - 5:
        offsetX -= 1
        cursor2location = win.text[name]["cursor"][1]*12 + offsetX
        
    
    # Text selection. AKA cursor
    # So here we draw the cursor
    if editable:
        UI_color.set(layer, win, "node_blendfile")
        if win.text[name]["cursor"][0] == win.text[name]["cursor"][1]: 
            if win.blink:
                layer.rectangle(
                    win.text[name]["cursor"][0]*12+5 +offsetX,
                    5,
                    (win.text[name]["cursor"][1]*12)-(win.text[name]["cursor"][0]*12)+2,
                    30
                    )
        else:
            roundrect(layer, win, 
                win.text[name]["cursor"][0]*12+5 +offsetX,
                5,
                (win.text[name]["cursor"][1]*12)-(win.text[name]["cursor"][0]*12)+2,
                30,
                5,
                fill=False
                )
        if win.textactive == name:
            layer.fill()
        elif win.text[name]["cursor"][0] != win.text[name]["cursor"][1]:
            layer.stroke()
    
    
    # Making sure that cursor is correct. Because a lot of bugs are happening
    # with it and it's not cool.
    
    # Mouse select
    if win.current["LMB"]:
        if int(win.current["mx"]-offset[0]) in range(int(x), int(x+width))\
        and int(win.current["my"]-offset[1]) in range(int(y), int(y+height)):
            win.text[name]["cursor"][0] = int((win.current["LMB"][0]-x-offsetX-offset[0])/12)
            win.text[name]["cursor"][1] = int((win.current["mx"]-x-offsetX-offset[0])/12)
            
    # If second part of selection ends up bigger then the first. Reverse them.
    if win.text[name]["cursor"][0] > win.text[name]["cursor"][1]:
        win.text[name]["cursor"] = [
            win.text[name]["cursor"][1],
            win.text[name]["cursor"][0]]
    
    # If any part ends up beyond the text. Clip them in.
    if win.text[name]["cursor"][0] < 0:
        win.text[name]["cursor"][0] = 0
    if win.text[name]["cursor"][1] < 0:
        win.text[name]["cursor"][1] = 0
    if win.text[name]["cursor"][0] > len(str(win.text[name]["text"])):
        win.text[name]["cursor"][0] = len(str(win.text[name]["text"]))
    if win.text[name]["cursor"][1] > len(str(win.text[name]["text"])):
        win.text[name]["cursor"][1] = len(str(win.text[name]["text"]))
    
    
    
    # Drawing the text
    
    UI_color.set(layer, win, "text_normal")
    layer.move_to(5+offsetX, height/2+5)
    if centered:
        layer.move_to(width/2-len(str(win.text[name]["text"]))*12/2, height/2+5)
    layer.show_text(str(win.text[name]["text"]))
    
    # Editing the text
    if win.current["keys"] and editable and name == win.textactive:
        # Let's filter the input first. 
        # For example
        if not multiline: #Removing enter key press
            if 65293 in win.current["keys"] or 65421 in win.current["keys"]\
            or 65289 in win.current["keys"]:  # TAB
                win.current["key_letter"] = ""
            
        
        prevlen = len(win.text[name]["text"])
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        regularclean = True
        ORD = 0
        try:
            ORD = ord(win.current["key_letter"])
        except:
            pass
        backremove = False # Whether to make selection go to 0 width thing
        #print(ORD, win.text[name]["cursor"][0])
        
        
        # Backspace
        if 65288 in win.current["keys"]:
            if win.text[name]["cursor"][0] != 0 and win.text[name]["cursor"][0]\
                                           == win.text[name]["cursor"][1]:
                
                win.text[name]["text"] = win.text[name]["text"]\
                [:win.text[name]["cursor"][0]-1]+\
                 win.text[name]["text"]\
                [win.text[name]["cursor"][1]:]
                
            elif win.text[name]["cursor"][1] != 0 and win.text[name]["cursor"][0]\
                                           != win.text[name]["cursor"][1]:
                                           
                win.text[name]["text"] = win.text[name]["text"]\
                [:win.text[name]["cursor"][0]]+\
                 win.text[name]["text"]\
                [win.text[name]["cursor"][1]:]
                backremove = True
            
        # Ctrl - C
        elif ORD == 3:
            clipboard.set_text( win.text[name]["text"]\
            [win.text[name]["cursor"][0]:win.text[name]["cursor"][1]], -1)
        
        # Ctrl - V
        elif ORD == 22:
            
            cliptext = str(clipboard.wait_for_text())
            
            win.text[name]["text"] = win.text[name]["text"]\
            [:win.text[name]["cursor"][0]]\
             + cliptext +\
            win.text[name]["text"]\
            [win.text[name]["cursor"][1]:]
            win.text[name]["cursor"][0] = win.text[name]["cursor"][1]
        
        # Ctrl - A
        elif ORD == 1:
            win.text[name]["cursor"][0] = 0
            win.text[name]["cursor"][1] = len(win.text[name]["text"])
        
        # To clear up the Controll
        elif 65507 in win.current["keys"]:
            pass
        
        # Shift
        elif 65506 in win.current["keys"]:
            # Right
            if 65363 in win.current["keys"]:
                win.text[name]["cursor"][1] = win.text[name]["cursor"][1] + 1
                #win.current["keys"].remove(65363)
            
            # Left
            elif 65361 in win.current["keys"]:
                if win.text[name]["cursor"][1] > win.text[name]["cursor"][0]:
                    win.text[name]["cursor"][1] = win.text[name]["cursor"][1] - 1
                #win.current["keys"].remove(65361)
                
        # Right button
        elif 65363 in win.current["keys"]:
            win.text[name]["cursor"][0] = win.text[name]["cursor"][0] + 1
            win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
            win.current["keys"].remove(65363)
        
        # Left button
        elif 65361 in win.current["keys"]:
            win.text[name]["cursor"][0] = win.text[name]["cursor"][0] - 1
            win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
            win.current["keys"].remove(65361)
        
        # Escape
        elif 65307 in win.current["keys"]:
            win.textactive = ""
            win.current["keys"].remove(65307)
            
        else:
            win.text[name]["text"] = win.text[name]["text"]\
            [:win.text[name]["cursor"][0]]\
             + win.current["key_letter"]+\
            win.text[name]["text"]\
            [win.text[name]["cursor"][1]:]
            
               
        # Auto moving the cursor    
        nowlen = len(win.text[name]["text"])
        if win.text[name]["cursor"][0] == win.text[name]["cursor"][1]:
            win.text[name]["cursor"][0] = win.text[name]["cursor"][0] + (nowlen - prevlen)
            win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
        
        elif backremove:
            win.text[name]["cursor"][1] = win.text[name]["cursor"][0]
        
        
        if nowlen != prevlen and regularclean:
            # Deleting all the keys from the keys. So yeah.
            win.current["keys"] = [] 
            
    
    
    
    # Outputing to the outlayer. 
    outlayer.set_source_surface(surface, x, y)
    outlayer.paint() 
    
    # Button if editable.
    if editable:
        def do():
            win.textactive = name
        roundrect(outlayer, win, 
            x,
            y,
            width,
            height,
            10, 
            fill=False,
            button=do,
            tip=tip,
            offset=offset,
            text=True)
        outlayer.stroke()
    
    if win.textactive == name:
        UI_color.set(outlayer, win, "button_active")
        roundrect(outlayer, win, 
            x,
            y,
            width,
            height,
            10, 
            fill=False)
        outlayer.stroke()

