Skip to content

Instantly share code, notes, and snippets.

@Sebastian-Nielsen
Created April 15, 2018 20:55
Show Gist options
  • Save Sebastian-Nielsen/7d84874f962b4eec6fb9f69c7ab03fcf to your computer and use it in GitHub Desktop.
Save Sebastian-Nielsen/7d84874f962b4eec6fb9f69c7ab03fcf to your computer and use it in GitHub Desktop.
import tkinter as tk
from tkinter import *
from tkinter import messagebox
from tkinter.ttk import Combobox, Progressbar, Separator, Style
import tkinter.font as font
import sys, os, re, platform, bisect, pyperclip, requests, logging, webbrowser, random, getpass, locale
from random import randint
from bs4 import BeautifulSoup
from time import sleep
from itertools import cycle
from collections import namedtuple, OrderedDict
from PIL import ImageTk, Image
# Change the locale formatting
locale.setlocale(locale.LC_ALL, '') # danish digit grouping TODO Slet alt med digit grouping at gøre
# from settings import *
###########################Currentpath = sys.argv[0].rsplit('/', 1)[0] + '/'
# from openpyxl.utils import get_column_letter, column_index_from_string
# from openpyxl.styles import Color, PatternFill, Font, Border
# from openpyxl.styles import colors
# from openpyxl.cell import Cell
# import openpyxl
img_data = {'fullscreen':'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAM1BMVEX///8lb7tFmN5Fmd4AAAAlb7tbtvlVrvFVrfFNo+dFmd9Fmd4+kNY3h884h88xgMj///+0+7hKAAAABXRSTlMAAAAAGaYArnEAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2wUSFikA2jAWAQAAAGtJREFUGNONj0sSgDAIQ0GNLfR3/9tawLrQjVll3pAARB/BxETsxsCZwAYY+XSQRWOiSHKAUts06FUjwujDwGjTkmV5lbJ1/ZN3PRFsaFHaoxTQ2g20WmJtkhIRlRynZ+y+HilOv39azx0vXaiMA1kuoX+tAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA5LTE3VDE1OjIzOjE1KzA4OjAwxMn81QAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wNS0xOFQyMjo0MTowMCswODowMM2uG7AAAABNdEVYdHNvZnR3YXJlAEltYWdlTWFnaWNrIDcuMC4xLTYgUTE2IHg4Nl82NCAyMDE2LTA5LTE3IGh0dHA6Ly93d3cuaW1hZ2VtYWdpY2sub3Jn3dmlTgAAABh0RVh0VGh1bWI6OkRvY3VtZW50OjpQYWdlcwAxp/+7LwAAABd0RVh0VGh1bWI6OkltYWdlOjpIZWlnaHQAMTYdr15vAAAAFnRFWHRUaHVtYjo6SW1hZ2U6OldpZHRoADE25QCe4gAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxMzA1NzI5NjYwUrri7QAAABJ0RVh0VGh1bWI6OlNpemUAMy4xMktCKchJ5wAAAFt0RVh0VGh1bWI6OlVSSQBmaWxlOi8vL2hvbWUvd3d3cm9vdC9zaXRlL3d3dy5lYXN5aWNvbi5uZXQvY2RuLWltZy5lYXN5aWNvbi5jbi9zcmMvNDAzLzQwMzE1LnBuZzwbqXIAAAAASUVORK5CYII=','right_blueArrow': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAFzUkdCAK7OHOkAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1MzmNZGAwAAABV0RVh0Q3JlYXRpb24gVGltZQAyLzE3LzA4IJyqWAAABBF0RVh0WE1MOmNvbS5hZG9iZS54bXAAPD94cGFja2V0IGJlZ2luPSIgICAiIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNC4xLWMwMzQgNDYuMjcyOTc2LCBTYXQgSmFuIDI3IDIwMDcgMjI6MTE6NDEgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhhcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4YXA6Q3JlYXRvclRvb2w+QWRvYmUgRmlyZXdvcmtzIENTMzwveGFwOkNyZWF0b3JUb29sPgogICAgICAgICA8eGFwOkNyZWF0ZURhdGU+MjAwOC0wMi0xN1QwMjozNjo0NVo8L3hhcDpDcmVhdGVEYXRlPgogICAgICAgICA8eGFwOk1vZGlmeURhdGU+MjAwOC0wMy0yNFQxOTowMDo0Mlo8L3hhcDpNb2RpZnlEYXRlPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNR1SZAAAAI1JREFUOE9j/P//PwMlgAlKkw1oYwBj3NTNQEyU33C5wIchKBRk0H9GRkYGEMYF8HshMISBIXYK2CW4DCEcBu5ucEOwAfwG/PnDwPD1KwODvg5OQwi7gADAbwALCwMDNzcDw8UrDAyLc7AGAmEX7NyFUzMI4Ddg/Rq8mkEAlwFbGNatJqgZBIZ8ZmJgAAB69SK/wethjgAAAABJRU5ErkJggg==',
'left_blueArrow': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAFzUkdCAK7OHOkAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1MzmNZGAwAAABV0RVh0Q3JlYXRpb24gVGltZQAyLzE3LzA4IJyqWAAABBF0RVh0WE1MOmNvbS5hZG9iZS54bXAAPD94cGFja2V0IGJlZ2luPSIgICAiIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNC4xLWMwMzQgNDYuMjcyOTc2LCBTYXQgSmFuIDI3IDIwMDcgMjI6MTE6NDEgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhhcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4YXA6Q3JlYXRvclRvb2w+QWRvYmUgRmlyZXdvcmtzIENTMzwveGFwOkNyZWF0b3JUb29sPgogICAgICAgICA8eGFwOkNyZWF0ZURhdGU+MjAwOC0wMi0xN1QwMjozNjo0NVo8L3hhcDpDcmVhdGVEYXRlPgogICAgICAgICA8eGFwOk1vZGlmeURhdGU+MjAwOC0wMy0yNFQxOTowMDo0Mlo8L3hhcDpNb2RpZnlEYXRlPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNR1SZAAAAKFJREFUOE9j/P//PwMlgAlKkw1oYwAjIyMqjpv6H4g3Q6VRAGEXxE75zxAUCmL5gPloAL8BIM2BIVAOdoDbAJBmdzcoBzfAbgBIs74OA8PXrwwMf/5ABbEDwmFAAGA3YHEOI8PFKwwM3NwMDCwsUEHsALcLQIbs3AXl4AZYDYAnb5Ah69dA2DgATheADAHjRdmMDOtWg4S2gCXQwJDPTAwMAGenLLMBUntVAAAAAElFTkSuQmCC',
'afk': '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',
'plus': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEUAAAAAbfAAbfAAAAD9vR1+AAAAAnRSTlMAuLMp9oYAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4QsXFxsxSlNC5QAAAB1JREFUCNdjYGB0YGBgYGrAQ2StWrUSQuBXBzIKAMSvC2nyDAplAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTExLTIzVDIzOjI3OjQ5KzAxOjAwH6vYMwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0xMS0yM1QyMzoyNzo0OSswMTowMG72YI8AAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC',
'trash': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAnFBMVEUAAADXACjYACfYACfZACbXACjZACbfACDYACfYACfYACfYACfXACjYACfWACnYACfaACXZACbYACfcACPYACfbACTZACbMADPYACfYACe/AEDZACbYACfYACfXACjYACfYACfXACjVACvYACfZACbZACbZACbXACjYACfZACbXACjXACjZACbYACfYACfMADPZACbYACfYACcAAABkgXASAAAAMnRSTlMAZ/j6cqdxEGjg9e1NwDi4N6DKHdoVeAX+6wTo+/PODeK0HtGZL39Ar2VTekrf6gqt/coYhKoAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4QsXFyIMHL+DTgAAAKBJREFUGNNFzccCgjAURNFYEBIEBeUhHZUiSHP+/+PEGPHu5myGsU+rNbDZsn/aTtd32m8ZnAuTc1NwbkjYW0t7CfZhyZZwhHDgunAEjhJOOHsggnfGSYJ/CcIPhMHF/95EMUuIEhZH6jdFlhPlGVIFV9zuRPcbrgoKlBaRVaJQUKF+ED1qVAoatM7z6bRoFHToxTCIHp2CcXph7jWN83gDLBwRcbJLtrYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTEtMjNUMjM6MzQ6MTIrMDE6MDB/9eZuAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTExLTIzVDIzOjM0OjEyKzAxOjAwDqhe0gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=',
'write': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAgVBMVEUAAAB5s013sE12sU13qlV2sE13sE13r0x2sU11sE1ttkl2sEx2sU52sE12sE55rkp2sE12sE11s0x2sE54sU53sE12sE6AgIB2sE12sE13sEt1sU52sE13sE10sU50rkx3sE15rlF2sk12r012sU13sE12sE13r056sU52sE0AAAAd3AxlAAAAKXRSTlMAKNW2D8nIndClDrEn57gm5uUlzyTk4wLW80c78qouOfETOHDArMR2F9yf2h0AAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4QsXFy44kb449wAAAHtJREFUGNNVz9kWgjAMBNBQlIKiuLAIIhREcf7/B420p4Z5yp3kJUQ+gQo3JLMFolWjIZsgTnZ7bkLn9AAcf42yzk685EZHmfUZSxJ3f3HG1TovnMt8cXWrV6ZG31tpenTUG+HB8DQab3ryrtPKmyagfTXV/6XpPX/ki1+1aw3T95ovRQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0xMS0yM1QyMzo0Njo1NiswMTowMCI+CTkAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMTEtMjNUMjM6NDY6NTYrMDE6MDBTY7GFAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAABJRU5ErkJggg==',
'ove': ''}
class CreateToolTip:
"""
create a tooltip for a given widget
"""
def __init__(self, widget=None, imgPath=False, data=False, text=False, wraplength=180):
self.wraplength = wraplength # pixels
self.widget = widget
self.imgPath = imgPath
self.data = data
self.text = text
if data:
try:
self.image = PhotoImage(data=data)
except (FileNotFoundError, AttributeError):
logging.debug('failed to load pic exiting...')
return
elif imgPath:
# If the tool tip is an img, prepare the image
try:
# Check if the img exists
img = Image.open(self.imgPath)
self.image = ImageTk.PhotoImage(img)
except (FileNotFoundError, AttributeError):
# AttributeError is raised if a nonexistent path is entered
logging.debug('failed to load pic exiting...')
return
"""# Resize img
basewidth = 350
wPercent = (basewidth / float(img.size[0]))
hSize = int((float(img.size[1]) * float(wPercent)))
# !!!! We are not currently resizing the image !!!!#
# img = img.resize((basewidth, hSize), Image.ANTIALIAS)"""
# waittime before the tooltop appeares when hovering the chosen widget
self.waittime = 150 # miliseconds
# Bind the widget
self.init_bind()
def init_bind(self):
"""Called along with the __init__ method"""
# Bind the widget to the tool tip
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Leave>", self.leave)
self.widget.bind("<ButtonPress>", self.leave)
self.id = None
self.tw = None
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.widget.after(self.waittime, self.showtip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
def showtip(self, event=None):
x = y = 0
x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() + 25
y += self.widget.winfo_rooty() + 20
# creates a toplevel window
self.tw = tk.Toplevel(self.widget)
# Leaves only the label and removes the app window
self.tw.wm_overrideredirect(True)
self.tw.wm_geometry("+%d+%d" % (x, y))
label = Label(self.tw, justify='left', background="#ffffff", relief='solid',
borderwidth=1, wraplength=self.wraplength)
if self.imgPath or self.data:
# The tooltip is an img
label.config(image = self.image)
elif self.text:
# The tooltip is text
label.config(text = self.text)
label.pack(ipadx=1)
def hidetip(self):
tw = self.tw
self.tw = None
if tw:
tw.destroy()
def encrypt(plaintext, n, key1, key2):
"""Encrypt the string and return the ciphertext"""
result = ''
for l in plaintext[:int(len(plaintext)/2)]:
try:
i = (key1.index(l) + n) % len(key1)
result += key1[i]
except ValueError:
result += l
for l in plaintext[int(len(plaintext)/2):]:
try:
i = (key2.index(l) + n) % len(key2)
result += key2[i]
except ValueError:
result += l
return result
def decrypt(ciphertext, n, key1, key2):
"""Encrypt the string and return the ciphertext"""
result = ''
for l in ciphertext[:int(len(ciphertext)/2)]:
try:
i = (key1.index(l) - n) % len(key1)
result += key1[i]
except ValueError:
result += l
for l in ciphertext[int(len(ciphertext)/2):]:
try:
i = (key2.index(l) - n) % len(key2)
result += key2[i]
except ValueError:
result += l
return result
class License:
def __init__(self, root, grey):
self.grey = grey
self.tw = tk.Toplevel(root)
self.tw.resizable(False, False)
# Set span location
x = root.winfo_rootx()
y = root.winfo_rooty()
self.tw.wm_geometry("+%d+%d" % (x, y))
# Set dimensions of window
self.tw.geometry('300x75')
# Initialize the window
self.init_window()
def init_window(self):
self.frame = Frame(self.tw, bg=self.grey)
self.mid_frame = Frame(self.frame, bg=self.grey)
self.bot_frame = Frame(self.frame, bg=self.grey)
userInput = StringVar(root)
self.label = Label(self.frame, text='Indtast license kode', bg=self.grey)
self.entry = Entry(self.mid_frame, textvariable=userInput, width=35)
self.status = Label(self.mid_frame, bg=self.grey)
self.checkBut = Button(self.bot_frame, text='Check', command=lambda: self.check(
licCode=userInput.get().strip()
))
self.cancel = Button(self.bot_frame, text='Cancel', command=lambda: self.tw.destroy())
# Grid the widgets
self.frame.grid(row=0, column=0, sticky='nsew')
self.label.grid(row=0, column=0, sticky='nw', padx=(45,0))
self.mid_frame.grid(row=1, column=0, sticky='we')
self.entry.grid(row=1, column=0, sticky='n', padx=(2,0), pady=(0,5))
self.status.grid(row=1, column=2, sticky='we', pady=(0,3))
self.bot_frame.grid(row=2, column=0, sticky='ew')
self.checkBut.grid(row=0, column=0, sticky='ew')
self.cancel.grid(row=0, column=1, sticky='ew')
# Configure row and column
self.tw.grid_rowconfigure(0, weight=1)
self.tw.grid_columnconfigure(0, weight=1)
self.frame.grid_rowconfigure(0, weight=1)
self.frame.grid_columnconfigure(0, weight=1)
self.bot_frame.columnconfigure((0,1), weight=1)
self.mid_frame.grid_columnconfigure(1, weight=1)
def check(self, licCode):
"""Function that handles the check button in the license popup."""
#print('AT CHECK FUNCTION: "{licCode}"')
if self.check_license_code(licCode):
self.create_license_file()
self.display_succes_msg()
else:
self.display_fail_msg()
def display_succes_msg(self):
"""Updates the status label to display: 'Succes!'"""
self.status.config(bg='lightgreen', fg='green', text='Success!')
self.status.grid(padx=(0,14))
def display_fail_msg(self):
"""Updates the status label to display: 'Ugyldig license kode'"""
self.status.config(bg='pink', fg='black', text='Ugyldig kode')
self.status.grid(padx=(0,4))
def license_file_info_popup(self):
"""A popup that displays some important info to the user.
One of the things it talks about is for instance how the user have
to keep the license file in the same directory as the executeable file."""
messagebox.showinfo('Success', "Sørg for altid at have license filen i samme mappe som VøBot.exe filen!\n\n"\
" Du rådes til ikke at ændrer på nogle systemvariabler, da VøBot benytter disse til at bestemme om du har delt " \
" licensenskoden med andre personer. Hvis du alligevel skulle gøre dette, vil din license sandsynligvis ikke virke. "\
"I såfald kan du kontakt os for at få en ny - den gamle vil blive stemplet ugyldig.\n\n""")
# TODO Skriv hvor lang tid koden er gyldig i
def check_license_code(self, licCode):
"""Checks if the license code is valid or not
returns a boolean depending on the validity of the license code the user typed"""
NT_options = sponserW_instance.options
for _ in range(*(int(x) for x in NT_options.numberOfTries.split(' '))):
licCode = decrypt(ciphertext=licCode, n=NT_options.encryptionOffset,
key1=NT_options.charset_1,
key2=NT_options.charset_2
)
# DEBUG: print(f'{_-3} decrypt: {licCode}')
if licCode == NT_options.basisKode:
#print(f'The message was broken after {_-5} decrypts')
return True
else:
return False
def create_file_content(self):
"""Writes the content of the license.txt file"""
content = 'Always keep this file in the same directoy as the VøBot.exe file. Do NOT delete this file.\n'
# Add random characters to confuse the end user
for _ in range(45):
content += chr(random.randint(48,123))
for n in (9,34,22,16,12,6):
# Add the encrypted version of the systems username
content += encrypt(getpass.getuser(), n=n,
key1='aA0!bBcC"1dDeE2f#FgG3%hHi&I4/jJ(kK)5l=Lm?M6@nNoO7pPqQ8rRsS9tTuUvVwWxXyYzZ',
key2='1aAbBc2CdDeE3fFgG4hHjJ5kKlLm6iIwWMnNo7OpPq8QrRsS9tTuUvVxXyYzZ'
)
# Continue adding rdm characters
for _ in range(67):
content += chr(random.randint(48,123))
return content
def create_license_file(self):
"""This function gets called whenever a correct license code has been entered."""
with open('VøBot License', 'w') as file:
content = self.create_file_content()
file.write(content)
@staticmethod
def check_license_file():
"""Returns a boolean dependent on whether the license file is valid or not"""
try:
file = open('VøBot License', 'r')
content = file.readlines()[1]
except FileNotFoundError:
return False # The file could not be located
pivot = 0
usern_L = len(getpass.getuser())
for n in (9,34,22,16,12,6):
encrypted_username = content[45 + pivot:45 + usern_L + pivot]
username = decrypt(encrypted_username, n=n,
key1='aA0!bBcC"1dDeE2f#FgG3%hHi&I4/jJ(kK)5l=Lm?M6@nNoO7pPqQ8rRsS9tTuUvVwWxXyYzZ',
key2='1aAbBc2CdDeE3fFgG4hHjJ5kKlLm6iIwWMnNo7OpPq8QrRsS9tTuUvVxXyYzZ'
)
pivot += usern_L
if username != getpass.getuser():
print('<<<<<<FAILED')
continue
return False
else:
print('<<<<<<<SUCCESFUL')
return True
return True if (username == getpass.getuser()) else False
class CreateToolTip_dpRectangle(CreateToolTip):
def __init__(self, widget, imgPath=False, data=False, text=False, wraplength=180, enable=True, box=None, DPcanvas=None):
super().__init__()
self.enable = enable
self.box = box
self.DPcanvas = DPcanvas
def init_bind(self):
# Du Pont pyramid rectangles
DPcanvas.tag_bind(self.box, '<Button-1>', self.enter)
DPcanvas.tag_bind(self.box, '<Enter>', self.enter)
DPcanvas.tag_bind(self.box, '<Leave>', self.leave)
self.id = None
self.tw = None
def enter(self, event=None):
if enable:
self.schedule()
def showtip(self, event=None):
x = y = 0
x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() + 25
y += self.widget.winfo_rooty() + 20
# creates a toplevel window
self.tw = tk.Toplevel(self.widget)
# Leaves only the label and removes the app window
self.tw.wm_overrideredirect(True)
self.tw.wm_geometry("+%d+%d" % (x, y))
label = Label(self.tw, justify='left', background="#ffffff", relief='solid',
borderwidth=1, wraplength=self.wraplength)
if self.imgPath or self.data:
# The tooltip is an img
label.config(image=self.image)
elif self.text:
# The tooltip is text
label.config(text=self.text)
class SponserWindow:
"""1. Look for the image in the dir first, if not found ->
2. download the image with request, if it's not possible to download img ->
3. show sponsorWindow anyway"""
def __init__(self, root, adDuration):
self.adDuration = adDuration
# request options fra github
self.options = self.req_options()
print('License file is:', License.check_license_file(), 'at startup.')
# Failed to req (no int) github options and the license file was not located or invalid
if (self.options == None):
if (License.check_license_file() == False):
logging.info('>> Invalid license file and no internet.. exiting')
#exit()
else:
# Show root Window
root.deiconify()
return
try:
# If an update is available, display a popup with the message
if self.options.updateMSG != 'None':
self.updateAvailable()
# Overwrite the adDuartion with the custom one just downloaded
self.adDuration = self.options.adDuration
except AttributeError:
logging.warning('AttributeError: self.options mangler nogle options eller attribute navne er forkerte')
logging.info('adDuration: {}'.format(self.adDuration))
if self.options.showAd:
# Load the image and initialize window
self.image = self.load_image()
self.init_window()
else:
# if showAd is disable don't start sponsorwindow; Instead show root Window
logging.info('showAd = False; skipping sponsorWindow')
root.deiconify()
def load_image(self):
"""Load the image, if that fails it tries to download the image
and load it. If anything should fail it returns None"""
# if img already in path
if 'ad.jpg' in os.listdir():
try:
# Load the image
img = ImageTk.PhotoImage(file=os.getcwd() + '/ad.jpg')
logging.info('Loaded ad')
return img
except:
logging.warning('Failed to load img')
# image isn't downloaded
self.download_image()
try:
# load the picture
img = ImageTk.PhotoImage(file=os.getcwd() + '/ad.jpg')
logging.info('Loaded the saved ad')
return img
except:
logging.warning('Failed to load ad')
def req_options(self):
"""Henter options fra github og gemmer dem i en namedtuple"""
# namedtuple - Options # # # # # # # # # # # # # # # # # # # # # # #
#1 # imgURL # link til download af img
#2 # updateMSG # Displays messagebox with the updateMSG text
#3 # showAd # hvis False så skippes sponsorWindow
#4 # adDuration # antal milisekunder reklamen skal vare
#5 # basisKode # kode som skal kunne findes frem til ved at decrypt med charset + offset (se 2 næste)
#6 # encryptionOffset # Offset til encryption algoritme
#7 # charset_1 # Charset key one til encryption algoritme
#8 # charset_2 # (key two) - || -
#9 # numberOfTries # Number of times to try and decrypt the license code
#10+# usedCodes # liste med allerede aktiverede koder for at holde styr på,
# # # at en kode ikke bruges af flere personer.
Options = namedtuple('Options', 'imgURL updateMSG showAd adDuration basisKode '
'encryptionOffset charset_1 charset_2 '
'numberOfTries usedCodes')
gistURL = 'https://gist.github.com/Sebastian-Nielsen/3a717a178d4581983fb711cc35dc90b1'
try:
# Requests github html
html = requests.get(gistURL)
soup = BeautifulSoup(html.text, 'html.parser')
logging.info('>>Github html received')
except:
logging.warning('Failed to GET request github options')
return None
try:
# sorter options fra soup
gen = (int(x.string) if x.string.isdigit() else x.string
for x in soup.findAll("td", {"class": "blob-code blob-code-inner js-file-line"}))
# Create the namedtuple 'options' by the options collected from github
options = Options(*gen)
logging.info(str(options))
except:
logging.warn('Failed to handle options and create a namedtuple')
return None
return options
def updateAvailable(self):
"""Creates a toplevel window telling the user an update is out"""
messagebox.showinfo('Update available', self.options.updateMSG)
def download_image(self):
"""Downloads the image"""
# Download the image
try:
img = requests.get(self.options.imgURL)
logging.info('Downloaded ad')
except:
logging.warning('Failed to download ad')
return
# Save the image
self.save_img(img)
def save_img(self, img):
"""Saves the image"""
saveLocation = os.getcwd()
name = '/ad.jpg'
try:
with open(saveLocation + name, 'wb') as f:
f.write(img.content)
logging.info('Saved ad')
except:
logging.warning('Failed to save ad img')
def init_window(self):
"""Initializes sponsorWindow and starts timer to show mainWindow and to hide sponsorWindow"""
vidste_du = ['Man behøver ikke gøre brug af "digit grouping" \nsom 43.213, du kan skrive 43213 i stedet.',
'Test 123']
# Init tw and start countdown to the deletion of the ad
self.tw = tk.Toplevel()
# Skjul "kryds felt" så ad'en ikke lukkes
self.tw.wm_overrideredirect(True)
# Luk ned for sponserWindow efter "adDuration"
self.tw.after(self.adDuration, self.tw.destroy)
# Start op for rootWindow efter "adDuration"
root.after(self.adDuration, root.deiconify)
# Initialize sponsorWindow layout
font = 'arial 20 bold'
try:
photo = Label(self.tw, image=self.image)
photo.image = self.image
logging.info('Uploaded ad to sponsorWindow')
try:
# Billedet er succesful uploaded, count en person har set en ad
os = platform.system()
if os == 'Windows':
headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
requests.get('https://c.statcounter.com/11546229/0/86e98146/1/', headers=headers)
logging.info('Ad viewer count - increased +1')
except:
logging.warning('Error - Something went wrong; Failed to update ad viewer count')
except:
photo = Label(self.tw, text='FAILED TO LOAD IMAGE', font='arial 40 bold')
logging.warning('Failed to upload ad to sponsorWindow')
photo.grid(row=1, column=0)
# Vi gør brug af en progress bar i stedet for en label
self.topLab = Label(self.tw, font=font, text='Vores sponsorer: {}'.format(int(self.adDuration / 1000)))
self.topLab.grid(row=0, column=0, sticky='w')
self.botLab = Label(self.tw, font='courier 11', text='Vidste du: ' + random.choice([vidste_du[0]]))
self.botLab.grid(row=3, column=0, sticky='w')
# Progressbar
self.pBar = Progressbar(self.tw, orient="horizontal", mode="determinate")
self.pBar.grid(row=2, column=0, sticky='wse')
self.pBar["value"] = 0
self.pBar["maximum"] = self.adDuration
# Times = hvor mange bider pBar skal opdateres i på adDuration tiden
self.times = round(self.adDuration / 6)
self.update_progress()
self.update_countdown()
def update_countdown(self):
# Decrease ad duration by one before updating countdown
self.adDuration -= 1000
self.topLab.config(text=' Vores sponsorer {:2d}sek. '.format(round(self.adDuration / 1000)))
# Update countdown again after 1 second
self.tw.after(1000, self.update_countdown)
def update_progress(self):
self.pBar["value"] += self.times
self.tw.after(self.times, self.update_progress)
class ExpandoText(tk.Text):
"""Used in 'nøgletal_analyse"""
def insert(self, *args, **kwargs):
result = tk.Text.insert(self, *args, **kwargs)
self.update()
self.reset_height()
def reset_height(self):
height = self.tk.call((self._w, "count", "-update", "-displaylines", "1.0", "end"))
self.configure(height=height)
class CollapsibleFrame(Frame):
def __init__(self, master, root, text=None, row=0, column=0, borderwidth=2, width=0, height=16, font=None, interior_padx=0, interior_pady=8, background=None, caption_separation=4,
caption_font=None, caption_builder=None, icon_x=5):
Frame.__init__(self, master)
self.root = root
self.row = row
self.column = column
if background is None:
background = self.cget("background")
self.configure(background=background)
self._is_opened = False
self._interior_padx = interior_padx
self._interior_pady = interior_pady
self._iconOpen = PhotoImage(data="iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAFzUkdCAK7OHOkAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1MzmNZGAwAAABV0RVh0Q3JlYXRpb24gVGltZQAyLzE3LzA4IJyqWAAABBF0RVh0WE1MOmNvbS5hZG9iZS54bXAAPD94cGFja2V0IGJlZ2luPSIgICAiIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNC4xLWMwMzQgNDYuMjcyOTc2LCBTYXQgSmFuIDI3IDIwMDcgMjI6MTE6NDEgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhhcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4YXA6Q3JlYXRvclRvb2w+QWRvYmUgRmlyZXdvcmtzIENTMzwveGFwOkNyZWF0b3JUb29sPgogICAgICAgICA8eGFwOkNyZWF0ZURhdGU+MjAwOC0wMi0xN1QwMjozNjo0NVo8L3hhcDpDcmVhdGVEYXRlPgogICAgICAgICA8eGFwOk1vZGlmeURhdGU+MjAwOC0wMy0yNFQxOTowMDo0Mlo8L3hhcDpNb2RpZnlEYXRlPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNR1SZAAAAI1JREFUOE9j/P//PwMlgAlKkw1oYwBj3NTNQEyU33C5wIchKBRk0H9GRkYGEMYF8HshMISBIXYK2CW4DCEcBu5ucEOwAfwG/PnDwPD1KwODvg5OQwi7gADAbwALCwMDNzcDw8UrDAyLc7AGAmEX7NyFUzMI4Ddg/Rq8mkEAlwFbGNatJqgZBIZ8ZmJgAAB69SK/wethjgAAAABJRU5ErkJggg==")
self._iconClose = PhotoImage(data="iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAFzUkdCAK7OHOkAAAAEZ0FNQQAAsY8L/GEFAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1MzmNZGAwAAABV0RVh0Q3JlYXRpb24gVGltZQAyLzE3LzA4IJyqWAAABBF0RVh0WE1MOmNvbS5hZG9iZS54bXAAPD94cGFja2V0IGJlZ2luPSIgICAiIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNC4xLWMwMzQgNDYuMjcyOTc2LCBTYXQgSmFuIDI3IDIwMDcgMjI6MTE6NDEgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhhcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4YXA6Q3JlYXRvclRvb2w+QWRvYmUgRmlyZXdvcmtzIENTMzwveGFwOkNyZWF0b3JUb29sPgogICAgICAgICA8eGFwOkNyZWF0ZURhdGU+MjAwOC0wMi0xN1QwMjozNjo0NVo8L3hhcDpDcmVhdGVEYXRlPgogICAgICAgICA8eGFwOk1vZGlmeURhdGU+MjAwOC0wMy0yNFQxOTowMDo0Mlo8L3hhcDpNb2RpZnlEYXRlPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNR1SZAAAAJZJREFUOE9j/P//PwMlgAlKkw2GgQEogcgYN3UzkPKB8HCCLf8XZftC2RgG/GcICoXycIB1qxmABjBCeWgGMALFY6f8ZwgMgQj8+QOhWVgg9Po1KJpBANMAEAAZ4u7GwPD1K4TPzc3AsHMXA8PiHIx0g90AEAAZoq8DYV+8AtYMYhJvAAiADAEBHJpBAMNJpILRpMzAAABzmUMBeP5cOQAAAABJRU5ErkJggg==")
height_of_icon = max(self._iconOpen.height(), self._iconClose.height())
width_of_icon = max(self._iconOpen.width(), self._iconClose.width())
containerFrame_pady = ( height_of_icon //2) +1
self._height = height
self._width = width
self._containerFrame = Frame(self, borderwidth=borderwidth, width=width, height=height, relief=RIDGE, background=background)
self._containerFrame.grid(row=row, column=column, pady=(containerFrame_pady, 0))
self.interior = Frame(self._containerFrame, background=background)
self._collapseButton = Label(self, borderwidth=0, bg=background, image=self._iconOpen, relief=RAISED)
self._collapseButton.bind("<Enter>", lambda *_: self._collapseButton.config(bg='#a2c0ef'))
self._collapseButton.bind("<Leave>", lambda *_: self._collapseButton.config(bg=background))
self._collapseButton.place(in_=self._containerFrame, x=icon_x, y=-(height_of_icon // 2), anchor=N + W, bordermode="ignore")
self._collapseButton.bind("<Button-1>", lambda event: self.toggle())
if caption_builder is None:
if font != None:
self._captionLabel = Label(self, anchor=W, borderwidth=1, bg=background, text=text, font=font)
else:
self._captionLabel = Label(self, anchor=W, borderwidth=1, bg=background, text=text)
self._captionLabel.bind("<Enter>", lambda *_: self._captionLabel.config(bg='#a2c0ef'))
self._captionLabel.bind("<Leave>", lambda *_: self._captionLabel.config(bg=background))
self._captionLabel.bind("<Button-1>", lambda event: self.toggle())
if caption_font is not None:
self._captionLabel.configure(font=caption_font)
else:
self._captionLabel = caption_builder(self)
if not isinstance(self._captionLabel, Widget):
raise Exception("'caption_builder' doesn't return a tkinter widget")
self.after(0, lambda: self._place_caption(caption_separation, icon_x, width_of_icon))
def update_width(self, width=None):
# Update could be devil
# http://wiki.tcl.tk/1255
self.after(0, lambda width=width: self._update_width(width))
def _place_caption(self, caption_separation, icon_x, width_of_icon):
self.update()
x = caption_separation + icon_x + width_of_icon
y = -(self._captionLabel.winfo_reqheight() // 2)
self._captionLabel.place(in_=self._containerFrame, x=x, y=y, anchor=N + W, bordermode="ignore")
def _update_width(self, width):
self.update()
if width is None:
width = self.interior.winfo_reqwidth()
if isinstance(self._interior_pady, (list, tuple)):
width += self._interior_pady[0] + self._interior_pady[1]
else:
width += 2 * self._interior_pady
width = max(self._width, width)
self._containerFrame.configure(width=width)
def open(self):
self._collapseButton.configure(image=self._iconClose)
self._containerFrame.configure(height=self.interior.winfo_reqheight())
self.interior.grid(row=0, column=0, sticky='ew', padx=self._interior_padx, pady=self._interior_pady)
self._is_opened = True
if not (self.root.winfo_height() >= 850):
self.root.geometry('{}x{}'.format(self.root.winfo_width(), self.root.winfo_height()+self.interior.winfo_reqheight()))
def close(self):
self.interior.grid_forget()
self._containerFrame.configure(height=self._height)
self._collapseButton.configure(image=self._iconOpen)
self._is_opened = False
# Resizing of root window size when closing a collapsibleFrame
try:
# Du Pont Pyramid is not shown - (DDP is not shown if (DPP_switch == True) )
if not next(MainApp.DPP_switch):
root_height = self.root.winfo_height()
inte_height = self.interior.winfo_reqheight()
if root_height - inte_height > 650:
self.root.geometry('{}x{}'.format(self.root.winfo_width(), self.root.winfo_height()-self.interior.winfo_reqheight()))
next(MainApp.DPP_switch)
return
next(MainApp.DPP_switch)
except AttributeError:
pass
#print('There does not exist an intance named "MainApp" of the "Nøgletals_analyse" class ')
self.root.geometry('{}x{}'.format(self.root.winfo_width(), self.root.winfo_height() - self.interior.winfo_reqheight()))
def toggle(self):
if self._is_opened:
self.close()
else:
self.open()
class MainApplication:
def __init__(self, root):
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)
# create all of the main containers
self.top_frame = Frame(root, width=200, height=75, bg='forest green')
self.ctr_frame = Frame(root, width=200, bg='black')
self.bot_frame = Frame(root, height=50, bg='cyan')
# layout all of the main containers
self.top_frame.grid(row=0, sticky='ew')
self.ctr_frame.grid(row=1, sticky='nsew')
self.bot_frame.grid(row=2, sticky='nsew')
self.ctr_frame.grid_rowconfigure(0, weight=1)
self.ctr_frame.grid_columnconfigure((0, 1), weight=1)
# create all child containers
self.left_child = Frame(self.ctr_frame, bg='purple')
self.right_child = Frame(self.ctr_frame, bg='red')
# layout all child containers
self.left_child.grid(row=0, column=0, sticky='nsew')
self.right_child.grid(row=0, column=1, sticky='nsew')
""" def __init__(self, root):
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)
# create all of the main containers
self.top_frame = Frame(root, width=200, height=100, bg='forest green')
self.ctr_frame = Frame(root, width=200, bg='black')
self.bot_frame = Frame(root, height=50, bg='cyan')
# layout all of the main containers
self.top_frame.grid(row=0, sticky='ew' )
self.ctr_frame.grid(row=1, sticky='nsew')
self.bot_frame.grid(row=2, sticky='nsew')
self.ctr_frame.grid_rowconfigure (0, weight=1)
self.ctr_frame.grid_columnconfigure(1, weight=1)
# create the minor containers
self.ctr_left = Frame(self.ctr_frame, width=50, height=200, bg='red' )
self.ctr_mid = Frame(self.ctr_frame, width=600, height=200, bg='light green')
self.ctr_right = Frame(self.ctr_frame, width=50, height=200, bg='blue' )
# layout all of the minor containers
self.ctr_left.grid( row=0, column=0, sticky='nsew')
self.ctr_mid.grid( row=0, column=1, sticky='nsew')
self.ctr_right.grid(row=0, column=2, sticky='ns' )
self.ctr_mid.grid_columnconfigure(0, weight=1)
self.ctr_mid.grid_rowconfigure((0,1), weight=1)
# create objects to - ctr_frame -
self.rentabilitet = Button(self.ctr_mid, text='Remtabilitet', padx=35)
self.choose_bt = Button(self.ctr_mid, text='Regnskab', padx=35, width=35)
# layout all of the ctr_frame objects
#self.rentabilitet.grid(row=0, column=0, sticky='nsew')
#self.choose_bt.grid( row=1, column=0, sticky='nsew', columnspan=2)"""
class Nøgletal_Analyse:
def __init__(self, root):
self.root = root
self.root.withdraw()
self.nøgletal_labels = ('Afkastningsgrad', 'Overskudsgrad', 'Aktivernes omsætningshastighed',
'Gældsrente', 'Egenkapitalens forrentning', 'Gearing', 'Konklusion', 'Indtjeningsevne',
'Soliditetsgrad', 'Likviditetsgrad')
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=1) # super(Analyse_Window, self).__init__()
self.grey = 'gray90'
self.ggrey = 'grey86'
self.bColor_enter = 'grey60' # Button color on enter
self.bColor_leave = 'grey70' # Button color on leave
self.root.config(bg=self.grey)
# Blue colored buttons
self.blue_active = "#%02x%02x%02x" % (160, 194, 247) #'SkyBlue1'#'SteelBlue3'
self.blue_enter = "#%02x%02x%02x" % (132, 173, 237) #'DodgerBlue2'#'SteelBlue2'
self.blue_leave = "#%02x%02x%02x" % (100, 151, 229) #'Dodgerblue1'#'SteelBlue1'
# create all of the main containers
self.left_frame = Frame(self.root, bg=self.grey)
self.right_frame = Frame(self.root, bg=self.grey)
self.bot_frame = Frame(self.root, bg=self.ggrey) # Here goes the statusbar label
# layout all of the main containers
self.left_frame.grid( row=0, column=0, sticky='nsew')
self.right_frame.grid(row=0, column=1, sticky='nsew', rowspan=2)
self.bot_frame.grid( row=1, column=0, sticky='we')
self.right_frame.grid_columnconfigure(0, weight=1)
self.right_frame.grid_rowconfigure(1, weight=1)
self.bot_frame.grid_columnconfigure(0, weight=1)
self.bot_frame.grid_remove()
# Statusbar
self.statusbar = Label(self.bot_frame, text='status bar text here', bg=self.ggrey)
self.statusbar.grid(sticky='w')
# create all of the subcontainers
self.top_child = Frame(self.left_frame, bg=self.grey)
self.left_child = Frame(self.left_frame, bg=self.grey)
self.right_child = Frame(self.left_frame, bg=self.grey)
self.bot_child = Frame(self.left_frame, bg=self.grey)
# layout all of the subcontainers
self.top_child.grid(row=0, column=0, columnspan=2, sticky='we')
self.left_child.grid(row=1, column=0, sticky='n')
self.right_child.grid(row=1, column=1, sticky='n')
self.bot_child.grid(row=3, columnspan=2, sticky='ew')
self.bot_child.grid_columnconfigure(0, weight=1)
# create labels
self.firmanavnL = Label(self.top_child, bg=self.grey, text='Virksomhedens navn: ')
self.årL = Label(self.left_child, bg=self.grey, text='År: (sidste til første)')
self.cbut = Checkbutton(self.top_child, bg=self.grey, text='Nyetableret', activebackground=self.grey)
# create firma label and trace it
self.firmanavn = StringVar(self.top_child)
self.firmaE = Entry(self.top_child, textvariable=self.firmanavn)
self.firmaE.insert(END, 'FIRMANAVN')
self.firmanavn.trace('w', lambda *_: self.update_firmanavn())
# Optionmenu for selecting the business type (produktions-/handelsvirksomhed)
options = ['Produktionsvirksomhed', 'Handelsvirksomhed']
self.virkType = StringVar()
self.virkType.set('Handelsvirksomhed')
temp = OptionMenu(self.top_child, self.virkType, *options)
temp.config(bg='grey88', indicatoron=5, width=22, activebackground='grey83')
temp.grid(row=0, column=2, sticky='ew', pady=(0,0), padx=(5,0))
#from tkinter import font
#f = font.Font(temp, temp.cget("font"))
#f.configure(underline=True)
# create the 3 entries for the årLabel in a dictionary for itself
self.entriesÅr = {}
self.svaÅr = {}
self.init_årEntries()
font = 'courier 9' # TODO <- not used yet - kan slettes
# We need two widget in one column, so create a frame to put it in.
subFrame = Frame(self.left_child, bg=self.grey)
self.overskrift = Label(subFrame, bg=self.grey, width=21, underline="5", text='Rentabilitet', font='Helvetica 8 bold')
self.helpBut = Button(subFrame, text='?', width=2, bg=self.grey, font='simsun 9', relief='raised', bd=1, command=self.help_popup)
BG1 = Label(self.left_child, bg=self.ggrey, width=35, relief='flat')
self.afkastningsgL = Label(self.left_child, bg=self.ggrey, text='Afkastningsgrad,% -------------------------->')
self.overskudsgL = Label( self.left_child, bg=self.ggrey, text='Overskudsgrad,% ---------------------------->')
self.omsætningshL = Label( self.left_child, bg=self.ggrey, text='Aktivernes omsætningshastighed,gange ---->')
self.gældsrenteL = Label( self.left_child, bg=self.ggrey, text='Gældsrente,% ------------------------------->')
self.forrentningL = Label( self.left_child, bg=self.ggrey, text='Egenkapitalens forrentning,%---------------->')
self.GearingL = Label( self.left_child, bg=self.ggrey, text='Gearing,gange ------------------------------->')
BG2 = Label(self.left_child, bg=self.ggrey, width=35, relief='flat')
self.overskrift2 = Label( self.left_child, bg=self.grey, width=21, text=' Soliditet og likviditet', font='Helvetica 8 bold')
self.solditetsgL = Label( self.left_child, bg=self.ggrey, text='Soliditetsgrad,%')
self.likviditetsgL = Label(self.left_child, bg=self.ggrey, text='Likviditetsgrad,%')
# create toolstips for each label
path = os.getcwd()
self.TEST = CreateToolTip(self.afkastningsgL, data = img_data['afk'])
CreateToolTip(self.overskudsgL, path + '/formel img/ove.png')
CreateToolTip(self.omsætningshL, path + '/formel img/oms.png')
CreateToolTip(self.gældsrenteL, path + '/formel img/gæl.png')
CreateToolTip(self.forrentningL, path + '/formel img/ege.png')
CreateToolTip(self.GearingL, path + '/formel img/gea.png')
CreateToolTip(self.solditetsgL, path + '/formel img/sol.png')
CreateToolTip(self.likviditetsgL, path + '/formel img/lik.png')
# layout all of the labels and entries
self.firmanavnL.grid(row=0, column=0, pady=(0, 16))
self.firmaE.grid(row=0, column=1, sticky='nw', pady=(4,0))
self.årL.grid(row=0, column=0)
#self.cbut.grid(row=0, column=2, sticky='w', pady=(2,16), padx=(0,80))
CreateToolTip(self.cbut, wraplength=300,
text='Er virksomheden nyetableret?\nÆndrer bl.a. på:\n- Acceptabelt for en nyetableret virksomhed at \n have en lav'
' soliditetsgrad.\n')
# Layout subFrame - we need two in one column
subFrame.grid(row=1, column=0, sticky='nsw')
self.helpBut.grid(row=1, column=1, sticky='s')
self.overskrift.grid(row=1, column=0, sticky='nsw', pady=(10, 1))
self.afkastningsgL.grid(row=2, column=0, sticky='nsw', pady=(2, 0))
self.overskudsgL.grid( row=3, column=0, sticky='nsw', pady=(3, 0))
self.omsætningshL.grid( row=4, column=0, sticky='nsw', pady=(3, 0))
self.gældsrenteL.grid( row=5, column=0, sticky='nsw', pady=(2, 0))
self.forrentningL.grid( row=6, column=0, sticky='nsw', pady=(2, 0))
self.GearingL.grid( row=7, column=0, sticky='nsw', pady=(3, 0))
self.overskrift2.grid( row=8, column=0, sticky='nsw', pady=(12, 0), padx=(5,0))
self.solditetsgL.grid( row=9, column=0, sticky='nsw', pady=(3, 0))
self.likviditetsgL.grid(row=10, column=0,sticky='nsw', pady=(2, 3))
# Background for labels
BG1.grid(row=2, column=0, sticky='nsw', columnspan=2, rowspan=6)
BG2.grid(row=9, column=0, sticky='nsw', columnspan=2, rowspan=2)
# Make 3 entries for each "nøgletal" Label (except headline labels)
self.entries = {}
self.sva = {}
self.init_entries()
# Push all entries down to allign them with the labels
for i in range(1, 4):
self.entries['Afkastningsgrad,%' + str(i)].grid_configure(pady=(4, 4))
# push 6 last entries further down
self.entries['Soliditetsgrad,%' + str(i)].grid_configure(pady=(4, 5))
# Create top right frame for buttons such as "expand all"
self.right_child_top = Frame(self.right_frame, bg=self.grey, width=700)
self.right_child_top.grid(row=0, column=0, sticky='ew')
self.right_child_top.grid_columnconfigure(0, weight=1)
# Create canvas and scrollbar
self.canvas = Canvas(self.right_frame, highlightthickness=0, borderwidth=10, bg='RoyalBlue4', height=700, width=700)
self.can_frame = Frame(self.canvas, background=self.grey)
self.scrollbar = Scrollbar(self.root, orient='vertical', command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.grid_rowconfigure((0, 1, 2, 3), weight=0)
self.scrollbar.grid(row=0, column=3, sticky='ns')
# canvas
self.canvas.grid(row=1, column=0, sticky='nsew')
self.can_frame.grid(row=0, column=0, sticky='nsew')
self.can_frame.grid_columnconfigure(0, weight=1)
self.can_frame.grid_rowconfigure(0, weight=1)
self._frame_id = self.canvas.create_window((0, 0), window=self.can_frame, anchor='nw', tags="self.frame")
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
self.canvas.bind("<Configure>", self._resize_frame_width)
self.can_frame.bind("<Configure>", self._onFrameConfigure)
# Create mini frame for the self.right_child_top
self.toolbFrame = Frame(self.right_child_top, bg=self.grey)
self.toolbFrame.grid(row=0, column=0, sticky='ew')
self.toolbFrame.grid_columnconfigure((0,1), weight=1)
# canvas options buttons
switch = cycle((True, False))
switch2 = cycle((False, True))
self.co_ex_but = Button(self.toolbFrame, text='Collapse all', bg=self.bColor_leave, command=lambda: self.__collaps_expand_all(next(switch)))
self.co_ex_but.grid(row=0, column=0, sticky='we')
reorder = Button(self.toolbFrame, text='Reorder text', command=self.__resize_textWidget_height, bg=self.bColor_leave)
reorder.grid(row=0, column=1, sticky='we')
# Hovor colors of buttons
reorder.bind("<Enter>", lambda e: reorder.config(bg=self.bColor_enter))
reorder.bind("<Leave>", lambda e: reorder.config(bg=self.bColor_leave))
self.co_ex_but.bind("<Enter>", lambda e: self.co_ex_but.config(bg=self.bColor_enter))
self.co_ex_but.bind("<Leave>", lambda e: self.co_ex_but.config(bg=self.bColor_leave))
# Initialize the left frame's mid section, here we find: markedsrente; copyBut; collapsible frames
self.init_leftFrame_mid_section()
# Create and grid "text widgets" and corrosponding buttons to 'Analyse tekst'
self._make_TextWidgets()
# Initialize(write) 'analyse tekst' and display the 'analyse tekst' in the text widgets in the canvas
self.init_analyse_text()
self._display_text()
# make sure that the text starts "reordered"
self.root.after(1500, self.__resize_textWidget_height)
self.root.after(4700, self.__resize_textWidget_height)
self.canvas.yview_moveto(0)
# Stores "self.root.after" id's in order to cancel if needed - used in 'display_statusbar' method
self.after_calls = []
def init_DP_collapsibleFrame_and_canvas(self):
"""The DP collapsibleFrame is initialized in a function on its own for the sake of simplicity
From here it'll:
-> make a call to draw the Du Pont Pyramid (in the right_frame -> canvas)
-> make a call to initialize the DP entries (in the right_frame -> DP_bot)
"""
# Du Pont collapsible Frame
self.duPontFrame = CollapsibleFrame(self.bot_child, root=self.root, row=1, column=0, width=408, background=self.grey, text='Du Pont Pyramide', font='Ariel 10 bold')
self.duPontFrame.grid(row=3, column=0, padx=(2,2), sticky='ew')
self.DPP_switch = cycle((True, False))
duPontFrame_topFrame = Frame(self.duPontFrame.interior, bg=self.grey)
duPontFrame_topFrame.grid(row=0, column=0, columnspan=2)
self.dpButton = Button(duPontFrame_topFrame, text='Vis Du Pont Pyramide', width=20, bg=self.blue_leave,
activebackground=self.blue_active, command=lambda *_: self._on_showDP_but(show=next(self.DPP_switch)))
self.dpButton.grid(row=0, column=0, sticky='w', padx=(4,250), pady=(10,2))
self.dpButton.bind("<Enter>", lambda e: self.dpButton.config(bg=self.blue_enter))
self.dpButton.bind("<Leave>", lambda e: self.dpButton.config(bg=self.blue_leave))
# Du Pont canvas and options (stuff at self.right_frame)
self.DPcanvas = Canvas(self.right_frame, highlightthickness=0)# 'grey84')
self.toolbFrameDP = Frame(self.right_frame, bg=self.grey)
switch4 = cycle((True, False))
img = PhotoImage(data=img_data['fullscreen'])
self.expRightFrame = Button(self.toolbFrameDP, bg=self.ggrey, image=img,
command= lambda *_: self.on_expFrame_but(next(switch4)) )#self.left_frame.grid_remove() if next(switch4) else self.left_frame.grid(row=0, column=0, sticky='nsew')) TODO - undo
self.expRightFrame.image = img
# DPcanvas buttons, frames and entries
self.DP_cVar1 = IntVar()
self.DP_cVar2 = IntVar()
self.DP_botFrame = Frame(self.right_frame, bg='grey85')
self.DP_botFrame_but = Button(self.right_frame, text='Hide', bg=self.ggrey, command=self.on_DP_botFrame_but)
self.DP_cb1 = Checkbutton(self.duPontFrame.interior, bg=self.grey, activebackground=self.grey, text='Visualiser tal i Du Pont', variable=self.DP_cVar1, command=self.on_DP_cb1)
self.DP_cb2 = Checkbutton(self.duPontFrame.interior, bg=self.grey, activebackground=self.grey, text='Disable colors', variable=self.DP_cVar2, command=self.on_DP_cb2)
self.DP_botFrame_but.bind("<Enter>", lambda e: self.DP_botFrame_but.config(bg='grey80'))
self.DP_botFrame_but.bind("<Leave>", lambda e: self.DP_botFrame_but.config(bg=self.ggrey))
options = ['år1', 'år2', 'år3', 'Udvikling fra: år1 -> år2', 'Udvikling fra: år2 -> år3', 'Udvikling fra: år1 -> år3']
self.DP_dropVar = StringVar()
self.DP_dropVar.set('år1')
self.DP_dropmenu = OptionMenu(self.DPcanvas, self.DP_dropVar, *options, command=lambda *_: self.on_DP_dropmenu())
self.DP_dropmenu.config(bg='grey98', indicatoron=5)
self.DP_dropmenu.grid(row=0, column=0)
# trace 'disable colors' cb
self.DP_cVar2.trace('w', self.on_DP_disable_colors_cb)
# Draw the Du Pont Pyramid
self.draw_duPont()
# Initialize Du Pont entries
self.init_DP_entries()
# TODO: where should this be done? - handle clipboard stuff
# Bind custom 'ctrl+v' to årstals entries
for k, v in self.entriesÅr.items():
v.bind('<<Paste>>', lambda *_, key=k: self.handle_clipboard(key, år_entry=True))
# Bind custom 'ctrl+v' to entries
index = 0
for k, v in self.entries.items():
# Check om k er en GEN i så fald set gen_entry til True, ellers set nøgletal til True
for label in ('Gennemsnitlig egenkap', 'Gennemsnitlige forpligtel', 'Gennemsnitlige aktiv', 'Resultat'):
if k.startswith(label):
# gen entry
# DEBUG: print(k, 'GEN')
v.bind('<<Paste>>', lambda *_, key=k: self.handle_clipboard(key, gen_entry=True))
break
else:
# None of them matched, it have to be a 'Nøgletals' entry
# DEBUG: print(k, 'NØGLETAL')
v.bind('<<Paste>>', lambda *_, key=k: self.handle_clipboard(key, nøgletal=True))
def on_DP_disable_colors_cb(self, *_):
"""Whenever the disable colors cb is toggled"""
state = self.DP_cVar2.get()
if state:
for label in self.DP_box_labels:
self.DPcanvas.itemconfig(self.boxes[label], fill='light blue')
else:
item = self.DP_dropVar.get()
if len(item) == 3:
# Hvis kun et år skal vises (fx.'år2') - det vælges i dropdown menuen
# Lad 'nr' hvad end året der skal vises er peje til det
self.update_analyse_args(Ltext='Everything', nr=item[-1], update='DP')
else:
self.update_analyse_args(nr=item[-1], Ltext='Everything', update='DP')
def on_DP_dropmenu(self):
"""Runs a functions depending on the chosen item in the OptionMenu"""
# Get the chosen item in the Du Pont optionMenu
item = self.DP_dropVar.get()
if len(item) == 3:
# Hvis kun et år (fx.'år2') skal vises
self.update_DP_entries_text(årx=item[-1])
else:
# Hvis et år skal vises i forhold til et andet (fx år3-år1)
årx = item[17]
åry = item[-1]
self.update_DP_entries_text(årx=årx, åry=åry)
def on_expFrame_but(self, switch):
x = self.right_frame.winfo_rootx()
y = self.right_frame.winfo_rooty()
if switch:
self.left_frame.grid_remove()
self.root.wm_geometry("+%d+%d" % (x - 6, y - 30))
root.geometry('{}x{}'.format(1015, 700))
else:
self.root.wm_geometry("+%d+%d" % (x - 440, y - 30))
self.left_frame.grid(row=0, column=0, sticky='nsew')
self.root.geometry('{}x{}'.format(1425, 700))
def on_DP_cb1(self):
"""If the Du Pont pyramid is currently shown;
show/hide DP_botFrame and DP_botFrame_but"""
state = self.DP_cVar1.get()
if self.DPcanvas.winfo_exists:
# if Du Pont is shown
if state:
self.DP_cb2.config(state=NORMAL)
self.DP_botFrame_but.grid(row=2, column=0, sticky='ew')
self.DP_botFrame.grid(row=3, column=0, sticky='ew')
self.DP_dropmenu.config(state=NORMAL)
# Run 'on_DP_dropmenu'
self.on_DP_dropmenu()
else:
self.DP_botFrame_but.grid_remove()
self.DP_botFrame.grid_remove()
self.DP_cb2.config(state=DISABLED)
self.DP_dropmenu.config(state=DISABLED)
self.draw_duPont()
def on_DP_cb2(self):
"""(This checkbutton can only be activated if cb1 is enabled)
Disables/enables DP colors"""
def on_DP_botFrame_but(self):
"""Show/hide 'DP_bot_frame depending on the state of 'show'"""
if self.DP_botFrame.winfo_ismapped():
# If already gridded; hide it
self.DP_botFrame.grid_forget()
self.DP_botFrame_but.config(text='Show')
else:
# Show it
self.DP_botFrame.grid(row=3, column=0, sticky='ew')
self.DP_botFrame_but .config(text='Hide')
def update_DP_entries_text(self, årx, åry=None):
"""Update the DP_entries' text in the boxes. (see 'init_DP_entries')
It does NOT update all boxes' text, only the standard onces!
The standard boxes are all the boxes that has to be known in order to fill out the remaining ones."""
# Dict to store value: "årx and åry" with key: "respective box"
self.ÅrxÅry = {}
if åry:
# Der skal bruges 2 entries for hver label
for label in self.DP_standard_labels:
numx = self._get(label + årx, DP=True)
numy = self._get(label + åry, DP=True)
label = label[:3].lower()
# grad = difference between årx num and åry num in % (indekstal forskel)
grad = self.DP_grad(numx, numy)
# Forskel mellem tallene
num = self.DP_forskel(float(numx), float(numy))
symbol = ('% ⬆ rp' if num > 0 else
'% ⬇ rp' if num < 0 else '% rp')
if label == 'net': # Nettoomsætning
# Da der er 3 nettoom. skal der opdateres 3 tekster med samme tal
for i in range(3):
self.DPcanvas.itemconfig(self.DP_textID['net' + str(i)], text=self._punctuate_num(str(num)))
self.DPcanvas.itemconfig(self.DP_textID_grad['net' + str(i)], text=grad + symbol)
self.DP_colorize_box('net' + str(i), num)
# Store the value
self.ÅrxÅry['net' + str(i)] = (numx, numy)
else:
self.DPcanvas.itemconfig(self.DP_textID[label], text=self._punctuate_num(str(num)))
self.DPcanvas.itemconfig(self.DP_textID_grad[label], text=grad + symbol)
self.DP_colorize_box(label, num)
# Store the value
self.ÅrxÅry[label] = (numx, numy)
# Update the box one step higher in the pyramid -> 'Dækningsbidrag'
self.DP_update_dækningsbidrag(forhold=True)
else:
# Udfyld hvert standard box med den tilhørende entry tal
for label in self.DP_standard_labels:
# Get the number written in the entry
num = self._get(label + årx, DP=True)
label = label[:3].lower()
if label == 'net': # Nettoomsætning
# Da der er 3 boxes med label: "nettoom."
for i in range(3):
self.DPcanvas.itemconfig(self.DP_textID['net' + str(i)], text=self._punctuate_num(str(num)))
self.DPcanvas.itemconfig(self.DP_textID_grad['net' + str(i)], text='')
# Colorize the box depending on the number
self.DP_colorize_box(label + str(i), num)
else:
self.DPcanvas.itemconfig(self.DP_textID[label], text=self._punctuate_num(str(num)))
self.DPcanvas.itemconfig(self.DP_textID_grad[label], text='')
# Colorize the box depending on the number
self.DP_colorize_box(label, num)
# Update the box one step higher in the pyramid -> 'Dækningsbidrag'
self.DP_update_dækningsbidrag()
def DP_update_dækningsbidrag(self, forhold=False):
"""Update dækningsbidrag text and make a call to update resultat af primær drift"""
# Vis årx i forhold til åry (åry - årx)
if forhold:
# Udregn dækningsbidrag
dækX = self.ÅrxÅry['net0'][0] - self.ÅrxÅry['var'][0]
dækY = self.ÅrxÅry['net0'][1] - self.ÅrxÅry['var'][1]
self.ÅrxÅry['dæk'] = (dækX, dækY)
# grad = difference between årx num and åry num in % (indekstal forskel)
grad = self.DP_grad(dækX, dækY)
# Forskel mellem tallene
dæk = self.DP_forskel(dækX, dækY)
# Opdater text
self.DPcanvas.itemconfig(self.DP_textID['dæk'], text=self._punctuate_num(str(dæk)))
self.DPcanvas.itemconfig(self.DP_textID_grad['dæk'], text=grad + self.DP_getSymbol(dæk))
self.DP_colorize_box(label='dæk', num=dæk)
self.DP_update_RaPD(forhold=True)
else:
# Udregn dækningsbidrag
dæk = self._DPget_text(self.DP_textID['net0']) - self._DPget_text(self.DP_textID['var'])
self.DP_entries['dæk'] = dæk
# Colorize dækningsbidrag box
self.DP_colorize_box(label='dæk', num=dæk)
# Update box number
self.DPcanvas.itemconfig(self.DP_textID['dæk'], text=self._punctuate_num(str(dæk)))
self.DPcanvas.itemconfig(self.DP_textID_grad['dæk'], text='')
self.DP_update_RaPD()
def DP_update_RaPD(self, forhold=False):
"""Update resultat af primær drift and make a call to update Overskudsgrad"""
# Vis årx i forhold til åry (åry - årx)
if forhold:
# Udregn resultat af primær drift
resX = self.ÅrxÅry['dæk'][0] - self.ÅrxÅry['kap'][0]
resY = self.ÅrxÅry['dæk'][1] - self.ÅrxÅry['kap'][1]
self.ÅrxÅry['res'] = (resX, resY)
# grad = difference between årx num and åry num in % (indekstal forskel)
grad = self.DP_grad(resX, resY)
# Forskel mellem tallene
res = self.DP_forskel(resX, resY)
# Opdater text
self.DPcanvas.itemconfig(self.DP_textID['res'], text=self._punctuate_num(str(res)))
self.DPcanvas.itemconfig(self.DP_textID_grad['res'], text=grad + self.DP_getSymbol(res))
self.DP_colorize_box(label='res', num=res)
self.DP_update_ove(forhold=True)
else:
# Udregn RaPD
res = self._DPget_text(self.DP_textID['dæk']) - self._DPget_text(self.DP_textID['kap'])
self.DP_entries['res'] = res
# Colorize RaPD box
self.DP_colorize_box(label='res', num=res)
# Update box number
self.DPcanvas.itemconfig(self.DP_textID['res'], text=self._punctuate_num(str(res)))
self.DPcanvas.itemconfig(self.DP_textID_grad['res'], text='')
# Update the box one step higher in the pyramid -> 'Overskudsgrad'
self.DP_update_ove()
def DP_update_ove(self, forhold=False):
"""Update 'Overskudsgrad' and make a call to update 'Afkastningsgrad' """
# Vis årx i forhold til åry (åry - årx)
if forhold:
# Udregn overskudsgrad
oveX = self.ÅrxÅry['res'][0] / self.ÅrxÅry['net0'][0]
oveY = self.ÅrxÅry['res'][1] / self.ÅrxÅry['net0'][1]
self.ÅrxÅry['ove'] = (oveX, oveY)
# grad = difference between årx num and åry num in % (indekstal forskel)
grad = self.DP_grad(oveX, oveY)
# Forskel mellem tallene
ove = self.DP_forskel(oveX, oveY)
# Opdater text
self.DPcanvas.itemconfig(self.DP_textID['ove'], text=self._punctuate_num(str(ove)) + ' pp')
self.DPcanvas.itemconfig(self.DP_textID_grad['ove'], text=grad + self.DP_getSymbol(ove))
self.DP_colorize_box(label='ove', num=ove)
self.DP_update_omsA(forhold=True)
else:
# Udregn overskudsgrad
ove = self._DPget_text(self.DP_textID['res']) / self._DPget_text(self.DP_textID['net0'])
self.DP_entries['ove'] = ove
# Colorize ove box
self.DP_colorize_box(label='ove', num=ove)
# Update box number
self.DPcanvas.itemconfig(self.DP_textID['ove'], text=self._punctuate_num(str(ove)) + '%')
self.DPcanvas.itemconfig(self.DP_textID_grad['ove'], text='')
# Update the number of the box 'omsætningsaktiver' (omsA)
self.DP_update_omsA()
def DP_update_omsA(self, forhold=False):
"""Update 'Omsætningsaktiver' and make a call to update 'Aktiver' """
# Vis årx i forhold til åry (åry - årx)
if forhold:
# Udregn omsætningsaktiver
omsAX = self.ÅrxÅry['til'][0] + self.ÅrxÅry['lik'][0]
omsAY = self.ÅrxÅry['til'][1] + self.ÅrxÅry['lik'][1]
self.ÅrxÅry['omsA'] = (omsAX, omsAY)
# grad = difference between årx num and åry num in % (indekstal forskel)
grad = self.DP_grad(omsAX, omsAY)
# Forskel mellem tallene
omsA = self.DP_forskel(omsAX, omsAY)
# Opdater text
self.DPcanvas.itemconfig(self.DP_textID['omsA'], text=self._punctuate_num(str(omsA)))
self.DPcanvas.itemconfig(self.DP_textID_grad['omsA'], text=grad + self.DP_getSymbol(omsA))
self.DP_colorize_box(label='omsA', num=omsA)
self.DP_update_akt(forhold=True)
else:
# Udregn omsætningsaktiver
omsA = self._DPget_text(self.DP_textID['til']) + self._DPget_text(self.DP_textID['lik'])
self.DP_entries['omsA'] = omsA
# Colorize omsA box
self.DP_colorize_box(label='omsA', num=omsA)
# Update box number
self.DPcanvas.itemconfig(self.DP_textID['omsA'], text=self._punctuate_num((str(omsA))))
self.DPcanvas.itemconfig(self.DP_textID_grad['omsA'], text='')
# Update the box one step higher in the pyramid -> 'Aktiver'
self.DP_update_akt()
def DP_update_akt(self, forhold=False):
"""Update 'aktiver' and make a call to update 'Aktiver' """
# Vis årx i forhold til åry (åry - årx)
if forhold:
# Udregn aktiver
aktX = self.ÅrxÅry['anl'][0] + self.ÅrxÅry['omsA'][0]
aktY = self.ÅrxÅry['anl'][1] + self.ÅrxÅry['omsA'][1]
self.ÅrxÅry['akt'] = (aktX, aktY)
# grad = difference between årx num and åry num in % (indekstal forskel)
grad = self.DP_grad(aktX, aktY)
# Forskel mellem tallene
akt = self.DP_forskel(aktX, aktY)
# Opdater text
self.DPcanvas.itemconfig(self.DP_textID['akt'], text=self._punctuate_num(str(akt)))
self.DPcanvas.itemconfig(self.DP_textID_grad['akt'], text=grad + self.DP_getSymbol(akt))
self.DP_colorize_box(label='akt', num=akt)
self.DP_update_oms(forhold=True)
else:
# Udregn aktiver
akt = self._DPget_text(self.DP_textID['anl']) + self._DPget_text(self.DP_textID['omsA'])
self.DP_entries['akt'] = akt
# Colorize akt box
self.DP_colorize_box(label='akt', num=akt)
# Update box number
self.DPcanvas.itemconfig(self.DP_textID['akt'], text=self._punctuate_num(str(akt)))
self.DPcanvas.itemconfig(self.DP_textID_grad['akt'], text='')
# Update the box one step higher in the pyramid -> 'Aktivernes omsætningshastigheder' (oms)
self.DP_update_oms()
def DP_update_oms(self, forhold=False):
"""Update 'Omsætningsaktiver' and make a call to update 'Aktiver' """
# Vis årx i forhold til åry (åry - årx)
if forhold:
# Udregn aktivernes omsætningshastighed
omsX = self.ÅrxÅry['net0'][0] / self.ÅrxÅry['akt'][0]
omsY = self.ÅrxÅry['net0'][1] / self.ÅrxÅry['akt'][1]
self.ÅrxÅry['oms'] = (omsX, omsY)
# grad = difference between årx num and åry num in % (indekstal forskel)
grad = self.DP_grad(omsY, omsX)
# Forskel mellem tallene
oms = self.DP_forskel(omsX, omsY)
# Opdater text
self.DPcanvas.itemconfig(self.DP_textID['oms'], text=self._punctuate_num(str(oms)) + ' pp')
self.DPcanvas.itemconfig(self.DP_textID_grad['oms'], text=grad + self.DP_getSymbol(oms))
self.DP_colorize_box(label='oms', num=oms)
# Update afkastningsgraden (the final box that hasn't been updated)
self.DP_update_afk(forhold=True)
else:
# Udregn aktivernes omsætningshastigheder
oms = self._DPget_text(self.DP_textID['net0']) / self._DPget_text(self.DP_textID['akt'])
self.DP_entries['akt'] = oms
# Colorize oms box
self.DP_colorize_box(label='oms', num=oms)
# Update box number
self.DPcanvas.itemconfig(self.DP_textID['oms'], text=self._punctuate_num(str(oms)) + 'g')
self.DPcanvas.itemconfig(self.DP_textID_grad['oms'], text='')
# Update afkastningsgraden (the final box that hasn't been updated)
self.DP_update_afk()
def DP_update_afk(self, forhold=False):
"""Update 'Afkastningsgrad' and make a call to update 'Omsætningsaktiver' """
# Vis årx i forhold til åry (åry - årx)
if forhold:
# Udregn afkastningsgraden
afkX = self.ÅrxÅry['ove'][0] * self.ÅrxÅry['oms'][0]
afkY = self.ÅrxÅry['ove'][1] * self.ÅrxÅry['oms'][1]
self.ÅrxÅry['afk'] = (afkX, afkY)
# grad = difference between årx num and åry num in percentage (indekstal forskel)
grad = self.DP_grad(afkX, afkY)
# Forskel mellem tallene
afk = self.DP_forskel(afkX, afkY)
# Opdater text
self.DPcanvas.itemconfig(self.DP_textID['afk'], text=self._punctuate_num(str(afk)) + ' pp')
self.DPcanvas.itemconfig(self.DP_textID_grad['afk'], text=grad + self.DP_getSymbol(afk))
self.DP_colorize_box(label='afk', num=afk)
else:
# Udregn afkastningsgrad
ove = float(self.DPcanvas.itemcget(self.DP_textID['ove'], 'text')[:-1].replace('.','').replace(',','.'))
oms = float(self.DPcanvas.itemcget(self.DP_textID['oms'], 'text')[:-1].replace('.','').replace(',','.'))
afk = ove * oms
self.DP_entries['afk'] = afk
# Colorize afk box
self.DP_colorize_box(label='afk', num=afk)
self.DPcanvas.itemconfig(self.DP_textID_grad['afk'], text='')
# Update box number
self.DPcanvas.itemconfig(self.DP_textID['afk'], text=self._punctuate_num(str(afk)) + '%')
def DP_forskel(self, x : float, y : float) -> float:
"""returns the difference between x and y (y-x)"""
try:
if (x < 0 and y < 0):
return abs(x) - abs(y)
else:
return y - x
except ZeroDivisionError:
return 0
def DP_grad(self, x : float, y : float) -> str:
"""returns rp between x and y"""
try:
if (x < 0):
return str(round(-(y*100 / x - 100), 2))
else:
return str(round(y*100 / x - 100, 2))
except ZeroDivisionError:
return '0'
def DP_colorize_box(self, label : str, num : float):
"""Farv en box alt efter om num er positiv eller negativ"""
# If colors are disabled
if self.DP_cVar2.get():
color = 'light blue' # Default DP pyramid color
else:
# Color = green or red depending on 'num'
color = 'light green' if num > 0 else '#e26a84' #(0,self.green,0) if num > 0 else (124,self.green,0)
if label == 'Nettoomsætning':
for i in range(3):
self.DPcanvas.itemconfig(self.boxes['net' + str(i)], fill=color)#'#%02x%02x%02x' % color)
else:
self.DPcanvas.itemconfig(self.boxes[label], fill=color)#'#%02x%02x%02x' % color )
def _on_showDP_but(self, show):
"""When 'grafisk' button is pressed
shown = bool: Du Pont is currently shown"""
self.root.geometry('{}x{}'.format(1425, self.root.winfo_height())) # 1425))
#print(self.root.winfo_width())
#print(self.root.winfo_height()) # TODO, resizing wwhen pressing "Vis du pont"
# SHOW DU PONT PYRAMID
if show:
# forget the text analyse canvas
self.canvas.grid_forget()
self.toolbFrame.grid_forget()
self.scrollbar.grid_forget()
# display expand/collapse right_frame/left_frame
self.expRightFrame.grid(row=0, column=0, sticky='nsew')
# display dp checkbuttons
self.DP_cb1.grid(row=1, column=0)
self.DP_cb2.grid(row=1, column=1)
# display du pont canvas
self.toolbFrameDP.grid(row=0, column=0, sticky='ew')
self.DPcanvas.grid( row=1, column=0, sticky='nsew')
if self.DP_cVar1.get():
self.DP_botFrame_but.grid(row=2, column=0, sticky='ew')
self.DP_botFrame.grid(row=3, column=0, sticky='ew')
# Change the text of the button
self.dpButton.config(text='Vis analyse text')
# HIDE DU PONT PYRAMID
elif show == False:
# forget the currently shown du pont canvas
self.toolbFrameDP.grid_forget()
self.DPcanvas.grid_forget()
self.DP_botFrame_but.grid_forget()
self.DP_botFrame.grid_forget()
self.expRightFrame.grid_forget()
# forget dp checkbuttons
self.DP_cb1.grid_forget()
self.DP_cb2.grid_forget()
# display the text analyse canvas
root.geometry('{}x{}'.format(850, self.root.winfo_height()))
self.toolbFrame.grid(row=0, column=0, sticky='ew')
self.canvas.grid( row=1, column=0, sticky='nsew')
self.scrollbar.grid( row=0, column=3, sticky='ns')
# Change the text of the button
self.dpButton.config(text='Vis Du Pont Pyramide')
def draw_duPont(self):
"""Draw objects to DPcanvas and bind the rectangles to a function"""
color = 'lightblue'
c2 = 'white'
pady = 120
height = round(650 / 2)
width = round(1000 / 2)
width3 = round(1000 / 3) # 1/3 af window width
self.boxes = []
# 1. række
self.boxes.append(self.DPcanvas.create_rectangle(width - 70, 20 + (pady*0), width + 70, 100 + (pady*0), fill=color, activewidth=2, activeoutline=c2)) # R1B1
# 2. række
self.DPcanvas.create_line(width, 100 + (pady*0), width3, 20 + (pady*1))
self.DPcanvas.create_line(width, 100 + (pady*0), (width3*2), 20 + (pady*1))
self.boxes.append(self.DPcanvas.create_rectangle(width3 - 70, 20 + (pady*1), width3 + 70, 100 + (pady*1), fill=color, activewidth=2, activeoutline=c2)) # R2B1
self.boxes.append(self.DPcanvas.create_rectangle((width3 * 2) - 100, 20 + (pady*1), (width3*2) + 134, 100 + (pady*1), fill=color, activewidth=2, activeoutline=c2)) # R2B2
# 3. række
self.DPcanvas.create_line(width3, 100 + (pady*1), (width3) - 90, 20 + (pady*2)) # R2B1 -> R3B1
self.DPcanvas.create_line(width3, 100 + (pady*1), (width3) + 90, 20 + (pady*2)) # R2B1 -> R3B2
self.DPcanvas.create_line((width3 * 2), 100 + (pady*1), (width3*2) - 90, 20 + (pady*2))
self.DPcanvas.create_line((width3 * 2), 100 + (pady*1), (width3*2) + 90, 20 + (pady*2))
self.boxes.append(self.DPcanvas.create_rectangle((width3) - 230, 20 + (pady*2), (width3) - 60, 100 + (pady*2), fill=color, activewidth=2, activeoutline=c2)) # R3B1
self.boxes.append(self.DPcanvas.create_rectangle((width3) + 120, 20 + (pady*2), (width3) - 20, 100 + (pady*2), fill=color, activewidth=2, activeoutline=c2)) # R3B2
self.boxes.append(self.DPcanvas.create_rectangle((width3 * 2) - 120, 20 + (pady*2), (width3*2) + 20, 100 + (pady*2), fill=color, activewidth=2, activeoutline=c2)) # R3B3
self.boxes.append(self.DPcanvas.create_rectangle((width3 * 2) + 200, 20 + (pady*2), (width3*2) + 60, 100 + (pady*2), fill=color, activewidth=2, activeoutline=c2)) # R3B4
R3B1 = (width3) - 105 # Række 3, Box 1: boxens midte width
R3B4 = (width3 * 2) + 90 # Række 4, Box 4: boxens midte width
# 4. række
self.DPcanvas.create_line(R3B1, 100 + (pady*2), R3B1 - 90, 20 + (pady*3))
self.DPcanvas.create_line(R3B1, 100 + (pady*2), R3B1 + 90, 20 + (pady*3))
self.DPcanvas.create_line(R3B4, 100 + (pady*2), R3B4 - 90, 20 + (pady*3))
self.DPcanvas.create_line(R3B4, 100 + (pady*2), R3B4 + 90, 20 + (pady*3))
self.boxes.append(self.DPcanvas.create_rectangle(R3B1 - 160, 20 + (pady*3), R3B1 - 20, 100 + (pady*3), fill=color, activewidth=2, activeoutline=c2)) # R4B1
self.boxes.append(self.DPcanvas.create_rectangle(R3B1 + 196, 20 + (pady*3), R3B1 + 20, 100 + (pady*3), fill=color, activewidth=2, activeoutline=c2)) # R4B2
self.boxes.append(self.DPcanvas.create_rectangle(R3B4 - 170, 20 + (pady*3), R3B4 - 20, 100 + (pady*3), fill=color, activewidth=2, activeoutline=c2)) # R4B3
self.boxes.append(self.DPcanvas.create_rectangle(R3B4 + 170, 20 + (pady*3), R3B4 + 20, 100 + (pady*3), fill=color, activewidth=2, activeoutline=c2)) # R4B4
R4B1 = R3B1 - 90
R4B4 = R3B4 + 90
# 5. række
self.DPcanvas.create_line(R4B1, 100 + (pady*3), R4B1 - 90, 20 + (pady*4)) # R4B1 -> R5B1
self.DPcanvas.create_line(R4B1, 100 + (pady*3), R4B1 + 90, 20 + (pady*4)) # R4B1 -> R5B2
self.DPcanvas.create_line(R4B4, 100 + (pady*3), R4B4 - 90, 20 + (pady*4))
self.DPcanvas.create_line(R4B4, 100 + (pady*3), R4B4 + 90, 20 + (pady*4))
self.boxes.append(self.DPcanvas.create_rectangle(R4B1 - 100, 20 + (pady*4), R4B1 + 40, 100 + (pady*4), fill=color, activewidth=2, activeoutline=c2)) # R5B1
self.boxes.append(self.DPcanvas.create_rectangle(R4B1 + 245, 20 + (pady*4), R4B1 + 80, 100 + (pady*4), fill=color, activewidth=2, activeoutline=c2)) # color
self.boxes.append(self.DPcanvas.create_rectangle(R4B4 - 325, 20 + (pady*4), R4B4 - 80, 100 + (pady*4), fill=color, activewidth=2, activeoutline=c2)) # R5B3
self.boxes.append(self.DPcanvas.create_rectangle(R4B4 + 130, 20 + (pady*4), R4B4 - 40, 100 + (pady*4), fill=color, activewidth=2, activeoutline=c2)) # R5B4
# Text setting
f = 'Purisa 11'
f2 = 'Purisa 10'
padxT = 10
padyT = 20
# R1
self.DPcanvas.create_text(width - 70 + padxT, (20 + (pady*0)) + padyT, font=f, anchor='w', text="Afkastningsgrad") # item 30
# R2
self.DPcanvas.create_text((width3 - 70) + padxT, (20 + (pady*1)) + padyT, font=f, anchor='w', text="Overskudsgrad") # item 31
self.DPcanvas.create_text((width3 * 2 - 100) + padxT, (20 + (pady*1)) + padyT, font=f, anchor='w', text="Aktivernes omsætningshastighed") # item 32
# R3
self.DPcanvas.create_text((width3 - 230) + padxT, (20 + (pady*2)) + padyT, font=f, anchor='w', text="Resultat af primær drift") # item 33
self.DPcanvas.create_text((width3 - 20) + padxT, (20 + (pady*2)) + padyT, font=f, anchor='w', text="Nettoomsætning") # item 34
self.DPcanvas.create_text((width3 * 2 - 120) + padxT,(20 + (pady*2)) + padyT, font=f, anchor='w', text="Nettoomsætning") # item 35
self.DPcanvas.create_text((width3 * 2 + 60) + padxT, (20 + (pady*2)) + padyT, font=f, anchor='w', text="Aktiver") # item 36
# R4
self.DPcanvas.create_text(R3B1 - 160 + padxT, (20 + (pady*3)) + padyT, font=f, anchor='w', text="Dækningsbidrag") # item 37
self.DPcanvas.create_text(R3B1 + 20 + padxT, (20 + (pady*3)) + padyT, font=f, anchor='w', text="Kapacitetsomkostninger") # item 38
self.DPcanvas.create_text(R3B4 - 170 + padxT, (20 + (pady*3)) + padyT, font=f, anchor='w', text="Anlægsaktiver") # item 39
self.DPcanvas.create_text(R3B4 + 20 + padxT, (20 + (pady*3)) + padyT, font=f, anchor='w', text="Omsætningsaktiver") # item 40
# R5
self.DPcanvas.create_text(R4B1 - 100 + padxT, (20 + (pady*4)) + padyT, font=f, anchor='w', text="Nettoomsætning") # item 41
self.DPcanvas.create_text(R4B1 + 80 + padxT, (20 + (pady*4)) + padyT, font=f, anchor='w', text="Variable omkostninger") # item 42
self.DPcanvas.create_text(R4B4 - 325 + padxT, (20 + (pady*4)) + padyT, font=f, anchor='w', text="Tilgodehavender+varebeholdninger") # item 43
self.DPcanvas.create_text(R4B4 - 40 + padxT, (20 + (pady*4)) + padyT, font=f, anchor='w', text="Likvide beholdninger") # item 44
# regnetegn
self.DPcanvas.create_text(width, 56 + (pady * 1), font="Purisa 17 ", text='x') # item 45
self.DPcanvas.create_text(width3 - 40, 56 + (pady * 2), font="Purisa 15 bold", text=':') # item 46
self.DPcanvas.create_text(width3 * 2 + 40, 56 + (pady * 2), font="Purisa 15 bold", text=':') # item 47
self.DPcanvas.create_text(R3B1, 57 + (pady * 3), font="Purisa 16 bold", text='‒') # item 48
self.DPcanvas.create_text(R3B4, 60 + (pady * 3), font="Purisa 17", text='+') # item 49
self.DPcanvas.create_text(R4B1 + 60, 57 + (pady * 4), font="Purisa 16 bold", text='‒') # item 50
self.DPcanvas.create_text(R4B4 - 60, 60 + (pady * 4), font="Purisa 17", text='+') # item 51
# Help box
self.DP_helpBox = 9999 # Create a dummy to avoid attributeError
self.DPcanvas.tag_bind('helpBox', "<Button-1>", self.on_DP_helpBox_click)
self.DP_helpBox_lab = self.DPcanvas.create_text((width + 155), 25, anchor='w', font=f2, text='')
self.DP_helpBox_årxLab = self.DPcanvas.create_text((width + 155), 45, anchor='w', font='consolas 10', text='')
self.DP_helpBox_åryLab = self.DPcanvas.create_text((width + 155), 67, anchor='w', font='consolas 10', text='')
#self.DP_helpBox_forskelLab = self.DPcanvas.create_text((width + 155), 89, anchor='w', font=f2, text='Forskel: 23.312.123 kr.')
self.DP_labels = ('Afkastningsgrad', 'Overskudsgrad', 'Aktivernes omsætningshastighed', 'Resultat af primær drift', 'Nettoomsætning', 'Nettoomsætning',
'Aktiver', 'Dækningsbidrag', 'Kapacitetsomkostninger', 'Anlægsaktiver', 'Omsætningsaktiver', 'Nettoomsætning', 'Variable produktionsomkostninger',
'Tilgodehavender+varebeholdninger', 'Likvide beholdninger')
self.DP_textID = {}
self.DP_textID_grad = {}
self.DP_boxTooltips = {}
self.DP_box_labels = ('afk', 'ove', 'oms', 'res', 'net0', 'net1', 'akt', 'dæk', 'kap', 'anl', 'omsA', 'net2', 'var', 'til', 'lik')
self.boxes = {lab: box for lab, box in zip(self.DP_box_labels, self.boxes)}
i = 0
for key, box in self.boxes.items():
coords = self.DPcanvas.coords(box)
self.DP_textID[key] = self.DPcanvas.create_text(coords[0] + padxT, coords[1] + padyT*2, font=f, anchor='w', text='')
self.DP_textID_grad[key] = self.DPcanvas.create_text(coords[0] + padxT, coords[1] + padyT*2 + 20, font=f, anchor='w', text='')
self.DPcanvas.tag_bind(box, "<Enter>", lambda e, key=key, label=self.DP_labels[i]: self.on_box_enter(Ltext=key, label=label))
i += 1
def on_DP_helpBox_click(self, e):
"""Remove 'DP_helpbox' rectangle from 'DPcanvas' and set the text inside the rectangle to nothing (invisible)"""
self.DPcanvas.delete(self.DP_helpBox)
self.DPcanvas.itemconfig(self.DP_helpBox_lab, text='')
self.DPcanvas.itemconfig(self.DP_helpBox_årxLab, text='')
self.DPcanvas.itemconfig(self.DP_helpBox_åryLab, text='')
def on_box_enter(self, Ltext, label):
"""If dropmenu is set to compare years -> draw helpbox and write text to it"""
item = self.DP_dropVar.get()
if len(item) != 3:
# Der er blevet valgt en option med sammenligning af år fra "dropmenu"
if self.DPcanvas.coords(self.DP_helpBox) == []:
# helpBox is not drawn; draw it now and store the id
self.DP_helpBox = self.DPcanvas.create_rectangle(650, 15, 980, 120, fill='grey84', activewidth=2, activeoutline='red', tag='helpBox')
self.DPcanvas.tag_lower(self.DP_helpBox)
# Get år der skal sammenlignes (eg. år1 år3)
årx = item[15:18]
åry = item[-3:]
# Nummeret for hvert år til den tilhørende Ltext
årxNum, åryNum = self.ÅrxÅry[Ltext]
t1 = 'År ' + self.entriesÅr[årx].get() + ': ' + locale.format("%16.2f", årxNum, grouping=True)
t2 = 'År ' + self.entriesÅr[åry].get() + ': ' + locale.format("%16.2f", åryNum, grouping=True)
self.DPcanvas.itemconfig(self.DP_helpBox_lab, text=label)
self.DPcanvas.itemconfig(self.DP_helpBox_årxLab, text=t1)
self.DPcanvas.itemconfig(self.DP_helpBox_åryLab, text=t2)
def init_leftFrame_mid_section(self):
"""'init_leftFrame_mid_section' initialized in a function on its own for the sake of simplicity
In the 'LeftFrame's mid section' we find:
# analyse tekst = AT
left_frame right_frame
------------------------------------------------------------------------------------------------
| <- top_child (AT) -> | canva stuff |
|---------------------------------------------------| |
| left_child (AT labels) | right_child (AT entries) | |
| | | |
------------- |------------------------|--------------------------| |
mid section ---> | left_frame_mid | |
| (markedsrente, copyBut etc.) | |
------------- |---------------------------------------------------| |
bot section ---> | bot_child | |
| (collapsibleFrames such as: årsager, DP) | |
------------ |---------------------------------------------------| |
| bot_frame (for statusbar) | |
------------------------------------------------------------------------------------------------
Markedsrente - label, entry og markedsr. button --------------------------------------> self.left_frame_mid
CopyButton - Knap til at kopier analyse teksten. ------------------------------------> self.left_frame_mid
CollapsibleFrame - Du Pont Pyramide - Visualiserer tal i en Du Pont pyramide ----------> self.bot_child
CollapsibleFrame - årsager - hvis aktiveret skriver årsager til analyse teksten.
# Gennemsnitlig(e) -egenkapital, -forplitegelser og -aktiver.
"""
# Create the 'mid_frame' in the 'left_frame' - (mid section)
self.left_frame_mid = Frame(self.bot_child, bg=self.grey)
self.left_frame_mid.grid(row=0, column=0, padx=(2,2), sticky='ew', pady=(0,5))
self.left_frame_mid.grid_columnconfigure(0, weight=1)
# Populate the mid section #
# Create the markedsrente section
mrFrame = Frame(self.left_frame_mid, bg=self.ggrey)
sv = StringVar(mrFrame)
mrLab1 = Label(mrFrame, bg=self.ggrey, text='Markedsrente: ')
mrLab2 = Label(mrFrame, bg=self.ggrey, text='%')
CreateToolTip(mrLab1, wraplength=310, text="Markedsrenten skal på gymnasieniveau sammenlignes med en markedsrente på 4-5% samt et risikotillæg på 1-2%")
self.mrEntry = Entry(mrFrame, width=9, textvariable=sv)
self.mrEntry.insert(END, '4.00')
self.entries['markedsrente'] = self.mrEntry
self.markedsr = Button(mrFrame, text='Oversigt over markedsrenter', width=23, bg=self.blue_leave, activebackground=self.blue_active, command=self.markedsrente_window)
# Trace markedsrente entry
sv.trace('w', lambda name, *_, sv=sv: self._check_markedsrente(sv, name, 'markedsrente'))
# Hover color
self.markedsr.bind("<Enter>", lambda event, button=self.markedsr: self.markedsr.config(bg=self.blue_enter))
self.markedsr.bind("<Leave>", lambda event, button=self.markedsr: self.markedsr.config(bg=self.blue_leave))
# Grid markedsrente
mrFrame.grid( row=0, column=0, pady=(12,0), sticky='ew')
mrLab1.grid( row=0, column=0, pady=(3,3), padx=(0,0))
self.mrEntry.grid( row=0, column=1, pady=(3,3), padx=(0,0), sticky='w')
mrLab2.grid( row=0, column=2, pady=(3,3))
self.markedsr.grid(row=0, column=3, pady=(3,3), padx=(92,2))
# Create frame to have "aktLic" og "copy" knapperne i samme column
tempFrame = Frame(self.left_frame_mid, background='grey82') #self.grey)
tempFrame.grid(row=18, column=0, padx=(20, 20), pady=(10, 10), sticky='we')
# Create the 'kopier analyse tekst' button
copy = Button(tempFrame, text='Kopier analyse tekst', activebackground='light green', width=16, bg=self.blue_leave, font='arial 10', bd=2, command=self.copy_analysetext_to_clipboard)
# Grid copy button
copy.grid(row=18, column=0, padx=(18,77), pady=(11, 11), sticky='w')
# copyBut hover colors
copy.bind("<Enter>", lambda e: copy.config(bg=self.blue_enter))
copy.bind("<Leave>", lambda e: copy.config(bg=self.blue_leave))
#pw = PanedWindow(self.left_frame_mid, bg='black')
#pw.grid(row=1, column=0, sticky='we')
# Activate License button
actLic = Button(tempFrame, text="Aktiver license kode", activebackground=self.blue_active, width=16,
bg=self.blue_leave, font='arial 10', bd=1, command=lambda: License(root, self.grey))
actLic.grid(row=18, column=1)
# actLic hover colors
actLic.bind("<Enter>", lambda e: actLic.config(bg=self.blue_enter))
actLic.bind("<Leave>", lambda e: actLic.config(bg=self.blue_leave))
# Populate the bot section #
# # Create the 'CollapsibleFrame' for 'årsager' (gennemsnitlige entries)
self.årsagsFrame = CollapsibleFrame(self.bot_child, root=self.root, width=408, background=self.grey, text='Årsager til nøgletallenes ændringer', font='Ariel 10 bold')
self.årsagsFrame.grid(row=1, column=0, padx=(2, 2), sticky='ew')
# Create a darker grey background
BG1 = Label(self.årsagsFrame.interior, bg=self.grey)
BG1.grid(row=1, column=0, columnspan=4, rowspan=4, sticky='nsew')
# Initialize gen. entries
self.init_gen_entries()
# create checkbox for gen. and trace it
f = Frame(self.årsagsFrame.interior, bg=self.grey)
self.iv_gen = IntVar()
self.iv_gen2 = IntVar()
self.iv_gen.trace( 'w', self.on_gen_checkbox)
self.iv_gen2.trace('w', self.on_iv_gen2_RaPD_kendes_ikke)
self.gen_cb = Checkbutton(f, text='OFF ', variable=self.iv_gen, bg=self.grey, fg='firebrick2', activebackground=self.grey)
self.gen_cb2 = Checkbutton(f, text='Resultat af primær drift kendes ikke', variable=self.iv_gen2, bg=self.grey, activebackground=self.grey)
f.grid(row=0, column=0, columnspan=4, sticky='w')
self.gen_cb.grid( row=0, column=0, padx=(45,0), sticky='w')
self.gen_cb2.grid(row=0, column=1, padx=(65,0))
# # Create the 'CollapsibleFrame' for 'indtjenignsevnen'
self.indtjeningsFrame = CollapsibleFrame(self.bot_child, root=self.root, width=408, background=self.grey, text='Indtjeningsevne', font='Ariel 10 bold')
self.indtjeningsFrame.grid(row=2, column=0, padx=(2, 2), sticky='ew')
# Create an intvariable, trace it, and bind
f = Frame(self.indtjeningsFrame.interior, bg=self.grey, width=408)
f.grid(row=0, column=0, columnspan=7, sticky='nsew')
self.iv_indt = IntVar()
self.iv_indt.trace('w', self.on_indt_cb)
self.indt_cb = Checkbutton(f, text='OFF ', variable=self.iv_indt, bg='grey89', fg='firebrick2', activebackground=self.grey)
self.indt_cb.grid(row=0, column=0, padx=(45,0))
indeks_lab = Label(f, text='Indekstal', bg='grey89', font='Ariel 9')
indeks_lab.grid(row=0, column=1, padx=(220,28)) # 45 + 190 = 235 -> 254 = 19
# Initialize labels and respective entries for indekstal
self.init_indtjenings_entries()
# # Create the 'CollapsibleFrame for
self.init_DP_collapsibleFrame_and_canvas()
def on_indt_cb(self, *_):
"""Triggers whenever the checkbutton is toggled 'OFF/ON' """
state = self.iv_indt.get()
# Change color and text
if state:
self.indt_cb.config(fg='green', text='ON ')
else:
self.indt_cb.config(fg='firebrick2', text='OFF ')
# TODO
# TODO
# TODO THIS NEEDS TO BE DONE FIRST!
# TODO
# TODO
# Create (evt. update) indtjeningsevne text
self.args['Indtjeningsevne']['additional'] = self.create_indtjeningsevne_text()
# Display nu ændringerne i args
self._display_text()
def _paste_nums(self, key_of_focused_entry):
... # TODO reduce the handle_clipboard function by adding some of its funcationality to this function
def handle_clipboard(self, key_of_focused_entry, år_entry=False, gen_entry=False, nøgletal=False):
"""Function to destribute the clipboard data into seperate entries
key_of_focused_entry er fx.:
hvis år_entry = False
# Overskudsgrad,%3, Overskudsgrad,%2, Overskudsgrad,%1
hvis år_entry = True
# år3, år2, år1
"""
if '\n' not in root.clipboard_get():
# If there is only one line in clipboard, paste it all in the focused cell
return
# count number of lines (cells) there is in the clipboard
clipboard_cells = (cell.strip() for line in root.clipboard_get().split('\n')[:-1]
for cell in line.split('\t'))
if år_entry:
# Vi starter fra den fokuserede cell, og indsætter ind til der ikke er flere entries
n = int(key_of_focused_entry[-1])
for entry in (self.entriesÅr['år' + str(n)] for n in range(n, 0, -1)):
try:
cell = next(clipboard_cells)
entry.delete(0, 'end')
entry.insert(0, cell)
except StopIteration:
# There is not more to paste
# but still more entries to paste
pass
# En årsags-entry
elif gen_entry:
if self.iv_gen2.get():
# RaPD kendes ikke
skip = '^Resultat af' # RaPD
else:
skip = '^Resultat før' # RfFO
# Vi starter fra den fokuserede cell, og indsætter ind til der ikke er flere entries
index = self.gen_labels.index(key_of_focused_entry)
for entry in (self.entries[entry] for entry in self.gen_labels[index:] if not re.search(skip, entry)):
try:
cell = next(clipboard_cells)
entry.delete(0, 'end')
entry.insert(0, cell)
except StopIteration:
# There is not more to paste
# but still more entries to paste
pass
# En nøgletals entry
elif nøgletal:
# Vi starter fra den fokuserede cell, og indsætter ind til der ikke er flere entries
index = self.nøgletal.index(key_of_focused_entry)
for entry in (self.entries[entry] for entry in self.nøgletal[index:]):
try:
cell = next(clipboard_cells)
entry.delete(0, 'end')
entry.insert(0, cell)
except StopIteration:
# There is not more to paste
# but still more entries to paste
pass
# DuPont entry
else:
# Vi starter fra den fokuserede cell, og indsætter ind til der ikke er flere entries
index = self.DP_entries_list.index(key_of_focused_entry)
for entry in (self.DP_entries[entry] for entry in self.DP_entries_list[index:]):
try:
cell = next(clipboard_cells)
entry.delete(0, 'end')
entry.insert(0, cell)
except StopIteration:
# There is not more to paste
# but still more entries to paste
pass
# Return "break" in order to stop the main "paste" in executing
return "break"
def markedsrente_window(self):
"""Markedsrente window"""
try:
if self.tw.winfo_exists():
self.tw.destroy()
return
except:
# expected to pass on first run, since tw2 isnt initalized yet
pass
renter = {2016: '2.0',
2015: '2.0', 2014: '2.0', 2013: '2.0', 2012: '2.0', 2011: '2.075', 2010: '2.075', 2009: '3.0',
2008: '5.5', 2007: '6.0', 2006: '5.5', 2005: '4.25', 2004: '4.0', 2003: '4.0', 2002: '4.75',
2001: '5.25', 2000: '6.75', 1999: '5.0', 1998: '5.5', 1997: '5.5', 1996: '5.25', 1995: '6.25',
1994: '7.0', 1993: '8.25', 1992: '11.5', 1991: '11.5', 1990: '10.5', 1989: '9.0', 1988: '9.0',
1987: '9.0'}
self.tw = tk.Toplevel(root)
self.tw.resizable(False, False)
# Set span location
x = self.markedsr.winfo_rootx() + 250
y = self.markedsr.winfo_rooty() - 300
self.tw.wm_geometry("+%d+%d" % (x, y))
# Set dimensions of window
self.tw.geometry('250x702')
link = 'http://nationalbanken.statistikbank.dk/nbf/98214'
lab1 = Label(self.tw, bg='Burlywood3', text='Nationalbankens Diskonto + 2%', font='system 7 bold')
lab2 = Label(self.tw, bg='Burlywood3', text=link, font='arial 8 underline')
lab1.grid(row=0, column=0, sticky='we')
lab2.grid(row=1, column=0, sticky='we')
# Make link (lab2) clickable
lab2.bind("<Enter>", lambda e: lab2.config(fg='blue'))
lab2.bind("<Leave>", lambda e: lab2.config(fg='black'))
lab2.bind("<Button-1>", lambda e: webbrowser.open(link))
row = 2
for år in range(2016, 1986, -1):
label = Label(self.tw, relief='solid', font='arial 10',
text='Årstal: {a}\t-\t{b:5f} %'.format(a=år, b=float(renter[år])))
label.grid(row=row, column=0, sticky='we')
row += 1
# Set colors when focusing/not focusing at the labels
label.bind("<Enter>", lambda event, label=label: label.config(bg='light green'))
if row % 2 == 1:
label.config(bg='Wheat2')
label.bind("<Leave>", lambda e, label=label: label.config(bg='Wheat2'))
else:
label.config(bg='snow')
label.bind("<Leave>", lambda e, label=label: label.config(bg='snow'))
# If a label is clicked
label.bind("<Button-1>", lambda e, label=label: self.on_MR_window_label(label))
def on_MR_window_label(self, label):
"""Luk ned for markedsrente window og set markedsrente entry i root window
til den valgte labels rentesats."""
# Store the text value first
text = label.cget('text')
# destroy markedsrente window
self.tw.destroy()
# Clear markedsrente entry
self.mrEntry.delete(0, 'end')
# Sorter markedsrenten fra resten af teksten
rente_str = re.search(r'\d\.\d+', text).group()
rente = round(float(rente_str), 2)
# Insert renten i markedsrente entrien
self.mrEntry.insert(END, rente)
# Show statusbar
self.display_statusbar(msg='Markedsrenten er ændret til rentesatsen i år {} -> {} %'.format(re.search(r'\d+', text).group(),
rente))
def on_iv_gen2_RaPD_kendes_ikke(self, *_):
"""checkbutton that is pressed if RaPD is unknown"""
if self.iv_gen2.get():
# Gem 'Resultat af primær drift'
self.gen_RaPD_label.grid_forget()
for entry in (self.entries['Resultat af primær drift' + str(i)] for i in range(3, 0, -1)):
entry.grid_forget()
# Show 'Resultat før finansielle omkostninger'
self.gen_RfFO_label.grid(row=1, column=0, sticky='w')
for i, entry in enumerate(self.entries['Resultat før finansielle omkost.' + str(i)] for i in range(3, 0, -1)):
entry.grid(row=1, column=i+1, padx=(0,2))
else:
# Gem 'Resultat før finansielle omkostninger
self.gen_RfFO_label.grid_forget()
for entry in (self.entries['Resultat før finansielle omkost.' + str(i)] for i in range(3, 0, -1)):
entry.grid_forget()
# Show 'Resultat af primær drift
self.gen_RaPD_label.grid(row=1, column=0, sticky='w', padx=(0,15))
for i, entry in enumerate(self.entries['Resultat af primær drift' + str(i)] for i in range(3, 0, -1)):
entry.grid(row=1, column=i+1, padx=(0,2))
# Opdater analyse tekst til at vise årsag til RfFO's stigning/fald
self.args['Afkastningsgrad']['årsag'] = self._årsag_afkastningsgrad()
self.args['Overskudsgrad']['årsag'] = self._årsag_overskudsgrad()
# display den opdaterede tekst
root.after(1000, self._display_text)
def help_popup(self):
"""Forklaring af nøgletal window"""
try:
if self.tw2.winfo_exists():
self.tw2.destroy()
return
except:
# expected to pass on first run, since tw2 isnt initalized
pass
self.tw2 = Toplevel(self.root, bg='Snow')
self.tw2.resizable(False, False)
self.tw2.wm_geometry("+1200+1000")
# Set spawn location
x = self.helpBut.winfo_rootx() + 275
y = self.helpBut.winfo_rooty() - 100
self.tw2.wm_geometry("+{}+{}".format(x, y))
headl = 'Ariel 10 bold'
text = 'Ariel 10'
bg1 = 'Wheat2'
bg2 = 'Wheat2'
helpText = [Label(self.tw2, font=headl, bg=bg1, text='Afkastningsgrad'),
Label(self.tw2, font=text, bg=bg2, text='Viser, hvor meget man opnår i forrentning af den investerede kapital.'),
Label(self.tw2, font=headl, bg=bg1, text='Overskudsgrad'),
Label(self.tw2, font=text, bg=bg2, text='Viser, hvor stor en del af omsætningen, der bliver tilbage i indtjening, når virksomhedens driftsomkostninger er dækket.'),
Label(self.tw2, font=headl, bg=bg1, text='Aktivernes omsætningshastighed'),
Label(self.tw2, font=text, bg=bg2, text='Viser, hvor stor en aktivitet (omsætning) der er skabt i virksomheden på grundlag af den investerede kapital (aktiverne).'),
Label(self.tw2, font=headl, bg=bg1, text='Egenkapitalens forrentning'),
Label(self.tw2, font=text, bg=bg2, text='Viser, hvor meget ejerne har opnået i forrentning af den kapital, de har investeret i virksomheden.'),
Label(self.tw2, font=headl, bg=bg1, text='Gældsrente'),
Label(self.tw2, font=text, bg=bg2, text='Viser den rente, som virksomheden i gennemsnit betaler af sine gældsforpligtelser.'),
Label(self.tw2, font=headl, bg=bg1, text='Gearing'),
Label(self.tw2, font=text, bg=bg2, text='Viser forholdet mellem gældsforpligtelser og egenkapital.'),
]
for i, label in enumerate(helpText):
if i % 2:
label.grid(row=i, column=0, sticky='ew', pady=(0,10))
else:
label.grid(row=i, column=0, sticky='ew')
def on_gen_checkbox(self, *_):
"""triggeres whenever the advance options section: 'gennemsnitlig(e) -egenkapital, -forplitegelser og -aktiver'
checkbutton is pressed. Remove/show all 'årsager' to the corressponding state"""
state = self.iv_gen.get()
# Change color and text
if state:
self.gen_cb.config(fg='green', text='ON ')
else:
self.gen_cb.config(fg='firebrick2', text='OFF ')
# Updater alle args i hvert "gennemsnitlige", de vil blive vist/skjult afhænigigt af state
self.update_analyse_args(Ltext='Gennemsnitlig egenkapital', nr=None, update='gennemsnitlig')
self.update_analyse_args(Ltext='Gennemsnitlige forpligtelser', nr=None, update='gennemsnitlig')
self.update_analyse_args(Ltext='Gennemsnitlige aktiver', nr=None, update='gennemsnitlig')
self.update_analyse_args(Ltext='Resultat af primær drift', nr=None, update='gennemsnitlig')
# Display nu ændringerne i args
self._display_text()
def _show_hide_bot_child(self, show):
"""Shows/hides bot child (advanced options frame) depending on the show variable"""
if show:
self.bot_child.grid()
self.vis_opt.config(text='v Skjul valgfrie options v')
else:
self.bot_child.grid_remove()
self.vis_opt.config(text='> Vis valgfrie options >')
def display_statusbar(self, msg, color='light green', duration=4):
"""Displays the statusbar for a given duration"""
try:
# If there is already an statusbar running cancel the 'root.after'
self.root.after_cancel(self.after_calls.pop())
except:
pass
self.bot_frame.grid()
self.bot_frame.config(bg=color)
self.statusbar.config(bg=color, text=msg)
# Hide the statusbar after x miliseconds
self.after_calls.append(self.root.after(duration*1000, lambda: self.bot_frame.grid_remove()))
def copy_analysetext_to_clipboard(self):
"""Kopter analyse text til clipboard
#Important! - vent et stykke tid før der lukkes for tkinter winduet - bug?
"""
self.root.clipboard_clear()
variable = ''
for nøgletal in self.nøgletal_labels:
variable += nøgletal + '\n' + self.string_templates[nøgletal].format(**self.args[nøgletal]) + '\n\n'
pyperclip.copy(variable)
messagebox.showinfo('info', 'Analysen er blevet kopieret til clipboard\n\'Ctrl+v\' for at indsætte analysen.')
def _resize_frame_width(self, e):
# 'e.width-25' da vi gerne vil ypad så widgets'ne ikke skal nå helt ud.
self.canvas.itemconfig(self._frame_id, width=e.width - 25)
view = self.canvas.yview()
if view[0] == 0 and view[1] == 1:
self.canvas.bind_all("<MouseWheel>", "break")
else:
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _onFrameConfigure(self, event):
# update scrollregion after starting 'mainloop'
# when all widgets are in canvas
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
# TODO er det nødvendigt med 2 "onFrameConfigure" ?
# Referer til self._resize_frame
# print('onframe:', event)
def __resize_textWidget_height(self):
"""Resizes all text widgets' height in order to perfectly fit its content within"""
for widget in self.textWidgets.values():
widget.reset_height()
# update root so that view is updated
self.root.update()
# If there is nothing to scroll turn off global mousewheel
view = self.canvas.yview()
if view[0] == 0 and view[1] == 1:
self.canvas.bind_all("<MouseWheel>", "break")
else:
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), 'units')
def on_indt_writeBut(self, row : int):
"""When the "writeicon" button is pressed (in the "indtjeningsevne collapsible Frame")
Display/remove "indt_årsag_entry" depending on if the entry is currently shown.
self.indtj_writeMore = [(writeMoreFrame, text), (writeMoreFrame, text), and so on..]
self.indtj_writeMore[0][0] == writeMoreFrame # The frame in which we eg. grid the text widget (explained below)
self.indtj_writeMore[0][1] == text # The text widget to write an explanation as to why the index numbers increased/decreased as they did
"""
# Minus 1 fra row, da vi starter med at tælle fra 0 ikke 1
# Divider med 2, da vi stiger med 2 rækker for hver gang vi adder en ny writeMoreFrame
# grunden til '//' i stedet for '/' skyldes, at når man dividere får man en float - vi ønsker en 'int'
# (row-1)//3)
index = (row-1)//3
frame = self.indtjenings_list[index]['writeMore'][0]
widget = self.indtjenings_list[index]['writeMore'][1]
if frame.winfo_ismapped():
# If the writeMoreFrame was shown when the button was pressed - hide
frame.grid_remove()
else:
# If the writeMoreFrame was NOT shown when the button was pressed - show
frame.grid()
def on_indt_deleteBut(self, row : int):
"""When the "trash icon" button is pressed (in the "indtjeningsevne collapsible Frame")
Removes everything on the row at which the "trash icon" was clicked. """
#print('Deleting row:', row)
# convert row number to corresponding index number in list.
# eg. row=1 corressponds to index 0
# eg. row=7 corressponds to index 3
# The index number can be derrived from the following formula:
index = (row-1)//3
rowContent = self.indtjenings_list[index]
# Delete everything on the row
rowContent['label'].destroy()
rowContent['writeMore'][0].destroy()
rowContent['toDelete'][0].destroy()
rowContent['toDelete'][1].destroy()
for i in range(1, 4):
rowContent['år' + str(i)].destroy()
def _indt_create_newline(self, row, label=None):
"""To internal use in 'on_indt_addBut' and 'init_indtjenings_entries',
this method is entirely dedicated to avoiding DRY code."""
# Create delete button
trash_img = PhotoImage(data=img_data['trash'])
trash_but = Button(self.indtjeningsFrame.interior, relief='flat', bg='grey89', takefocus=False, image=trash_img,
command=lambda row=row: self.on_indt_deleteBut(row=row))
trash_but.grid(row=row, column=0, padx=(2, 0), sticky='w')
trash_but.img = trash_img
# Create write årsag button
write_img = PhotoImage(data=img_data['write'])
write_but = Button(self.indtjeningsFrame.interior, relief='flat', bg='grey89', takefocus=False, image=write_img, command=lambda *_, row=row: self.on_indt_writeBut(row))
write_but.grid(row=row, column=1, padx=(2, 0), sticky='w')
write_but.img = write_img
# Create the "write more entry" - It will be gridded the row below (row +=1)
# Create a frame for the "write more entry" and lab in order to not disturb the gridding
writeMoreFrame = Frame(self.indtjeningsFrame.interior, bg='grey89')
writeMoreLab = Label(writeMoreFrame, text='└─', bg='grey89')
text_widget = Text(writeMoreFrame, height=2, width=43, wrap=WORD, font='arial 9')
writeMoreFrame.grid(row=row + 1, column=0, columnspan=7, sticky='ew')
writeMoreLab.grid(row=row, column=0, padx=(20, 0))
text_widget.grid(row=row, column=1)
# Write more framed should not be visable at init
writeMoreFrame.grid_remove()
# Create the 'label' entry
label_entry = Entry(self.indtjeningsFrame.interior, width=40)
label_entry.grid(row=row, column=2, sticky='w')
if label: # only None when new lines are added by the user
label_entry.insert(END, label)
# Store this entire row in a namedtuple and add it to 'indjenings_list'
self.indtjenings_list.append(
{'rowNr': row, 'label': label_entry,
'writeMore': (writeMoreFrame, text_widget),
'toDelete': (trash_but, write_but)}
)
år = 3
# We need 3 entries to row
for col in range(3, 6):
# Create stringvar in order to trace entry
sv = StringVar(self.indtjeningsFrame.interior)
# Create 'indekstal' entry
entry = Entry(self.indtjeningsFrame.interior, width=5, textvariable=sv)
entry.insert(END, '100')
entry.grid(row=row, column=col)
self.indtjenings_list[-1]['år'+str(år)] = entry
# Trace entry - (triggers a cuntion whenever the sv (entry) is being edited)
sv.trace('w', lambda name, *_, sv=sv, Ltext=label: self._check_indtjeningsevne_entries(sv, name, Ltext)) # TODO - trace entry
år -= 1
def on_indt_addBut(self):
"""Adds a new row, to enter additional information eg. 'labelEntry': 'indekstalEntry3', 'indekstalEntry2', indekstalEntry1' """
row = len(self.indtjenings_list)*3 + 1 # The row nr to add to the additional information to ^^
self._indt_create_newline(row)
def create_indtjeningsevne_text(self):
"""If indtjeningsevne is 'ON'; Creates the body text of the indtjeningsevne based on the information provided;
eg. label, indekstal, additonal (writeMore) text """
# Hvis optional options ikke er slået til return
if self.iv_indt.get() == False:
return '.' # The dot is on purpose
x, y = False, False
# Locate nettoomsætning
for dictionary in self.indtjenings_list:
label = dictionary['label'].get()
if re.search(r'(netto)?omsætning', label, re.IGNORECASE):
oms_1 = float(dictionary['år1'].get())
oms_3 = float(dictionary['år3'].get())
oms_rp = oms_3 - oms_1
x = True
elif re.search(r'(vareforbrug|produktionsomkostninger)', label, re.IGNORECASE):
omk_label = 'Vareforbruget' if ('vareforbrug' in label) else 'Produktionsomkostningerne'
omk_index = (dictionary['rowNr'] - 1) // 3
omk_1 = float(dictionary['år1'].get())
omk_3 = float(dictionary['år3'].get())
omk_rp = omk_3 - omk_1
y = True
else:
if not y:
print('CRITICAL: either "vareforbrug" or "produktionsomkostninger" have not been declared! ----')
if not (x and y):
...
# 'Omsætning' was not found, warn the user that it is mandatory to declare 'omsætning'
# TODO
# TODO Warn user omsætnings is mandatory to
# TODO
# Precalculate certain variables to make code more readable and to better the performance
pp_ove = self._pp('Overskudsgrad,%', 1, 3)
result = ("; dette skyldes, at omsætningen er steget procentuelt {adj} end omkostningerne. "
"Det fremgår ud fra indekstallene, at omsætningen er gået fra indeks {oms_1} i {år1} til "
"indeks {oms_3} i {år3}, hvilket er {sub} på {rp}%. ").format(
adj = 'mere' if pp_ove else 'mindre',
sub = 'en stigning' if pp_ove else 'et fald',
år1=self.entriesÅr['år1'].get().strip(), oms_1=oms_1,
år3=self.entriesÅr['år3'].get().strip(), oms_3=oms_3,
rp = oms_rp # da det er indekstal er rp år3 fratrukket år1
)
## Kommentere på omkostningsindekstallenes udvikling ##
# Spring den først i listen over, da vi kun kommenter på omkostninger
result += ("{label} er {adj} med {rp}%, hvilket er {grad}. "
"Det har dermed påvirket overskudsgraden {påvirket}. ").format(
label=omk_label, rp=omk_rp,
påvirket=self.__påvirkelse(omk_rp - oms_rp, 'indtjeningsevne'),
adj = self.__binary_stigning_verbum(omk_rp, førdatid=True),
grad = self.__tillægsord_gradbøjet(værdi=omk_rp - oms_rp,
tillægsord=['en del mindre end omsætningens stigningstakt', 'en smule mindre end omsætningens stigningstakt', 'den samme stigningstakt som omsætningen',
'en anelse mere end omsætningens stigningstakt', 'en del mere end omsætningens stigningstakt'],
milestones=[-9, -0.001, 0.001, 9]
)
)
for i in range(1, len(self.indtjenings_list)):
if (omk_index == i): # Skip produktionsomkostnings/vareforbug label; er allerede kommenteret på ^^
continue
# Da vi har med indekstal at gøre er 'rp' "(indekstal år3 - indekstal år1) = rp"
rp = float(self.indtjenings_list[i]['år1'].get()) - float(self.indtjenings_list[i]['år3'].get())
# Get the label to the current line
label = self.indtjenings_list[i]['label'].get().capitalize()
# Convert label to 'bestemt ental' if it ends on 'omkostninger'
label = self._indt_label_to_bestemt_flertal(label)
# Varier sætningsopstilling
if (i % 2 == 0):
result += ("{label} er {adj} med {rp}%. Det har altså påvirket overskudsgraden {påvirket}, idet stigningstakten er {adj2} end "
"omsætningens stigningstakt. ").format(
label=label, rp=rp,
adj=self.__binary_stigning_verbum(rp, førdatid=True),
adj2= 'større' if (rp - oms_rp) > 0 else 'mindre',
påvirket=self.__påvirkelse(rp, 'indtjeningsevne')
)
else:
result += ("{label} er {adj} med {rp}%, hvilket er {grad}. {label} har altså {retning}. ").format(
label=label, rp=rp,
adj=self.__binary_stigning_verbum(rp, førdatid=True),
grad=self.__tillægsord_gradbøjet(værdi=omk_rp - oms_rp,
tillægsord=['en del mindre end stigningen i omsætningen', 'en smule mindre end stigningen i omsætningen',
'den samme stigningstakt som omsætningen', 'en anelse mere end stigningen i omsætningen',
'en markant mere end stigningen i omsætningen'],
milestones=[-9, -0.001, 0.001, 9]
),
retning=( 'påvirket overskudsgraden i negativ retning' if (omk_rp - oms_rp) > 0
else 'påvirket overskudsgraden i positiv retning' if (omk_rp - oms_rp) < 0
else 'ikke påvirket overskudsgraden')
)
return result
def _indt_label_to_bestemt_flertal(self, label):
"""Converts a given word if it ends on 'omkostninger' to 'omkostningerne' (bestemt flertal)
else return either 'virksomhedens {label}' or '{ejefald(firma)} {label}' """
if label.endswith('nger') or label.endswith('omkostninger'):
label += 'ne'
else:
# if unsure on how to convert the label to 'bestemt flertal', add eg 'FIRMANAVNET(s)/virksomhedens label'
label = random.choice(('Virksomhedens', self._firmanavn_ejefald(self.firma.get().strip()).capitalize())) \
+ ' ' \
+ label
return label
def init_indtjenings_entries(self): # TODO: Complete this method
"""Initilization - one time use
Makes and places entries in the "advance options" section
In the collapsible frame: 'Indtjeningsevne'
'self.indtj_maxRow' -> is the max row number of
"""
self.indtjenings_labels = ['Nettoomsætning', 'Produktionsomkostninger', 'Salgs- og distributionsomkostninger', 'Indeks for administartionsomkostninger']
# For when we need to extract the data
self.indtjenings_list = [] # {'rowNr': 3, 'label': '', 'år3': 100, 'år2': 100, 'år1': 100, 'WriteMore': (writeMoreFrame, text_widget), 'toDelete': (trash_but, write_but, indeks_entries)}
row = 1
for label in self.indtjenings_labels:
self._indt_create_newline(row, label=label)
row += 3
# Nettoomsætning should be a dropdown menu
#options = ['år1', 'år2', 'Udvikling fra: år1 -> år3']
#self.qr = StringVar()
#self.qr.set('år1')
#self.a = OptionMenu(self.indtjeningsFrame.interior, self.qr, *options)
#self.a.config(bg='grey98', indicatoron=5, width=40, font="Courier 10")
#self.a.grid(row=1, column=2)
# Fastgør 'nettoomsætningsrækken' da det er et krav at den er der for at vi kan kommentere på indtjeningsevnen
self.indtjenings_list[0]['toDelete'][0].configure(command=int) # Dummy function to overwrite the former one
# Create a frame for the separator
sepFrame = Frame(self.indtjeningsFrame.interior, bg=self.grey)
sepFrame.grid(row=3, columnspan=7, pady=5, sticky='ew')
sepFrame.grid_columnconfigure((0,2), weight=1)
# Create separator label TODO indtjeningsevne - blocking 'write more' this has to be fixed
sep = Separator(sepFrame, orient=HORIZONTAL)
lab = Label(sepFrame, text='Omkostninger', bg=self.grey)
sep2 = Separator(sepFrame, orient=HORIZONTAL)
sep.grid( row=0, column=0, sticky='ew')
lab.grid( row=0, column=1, sticky='ew')
sep2.grid(row=0, column=2, sticky='ew')
# Create an "add new line/row" button
add_img = PhotoImage(data=img_data['plus'])
self.but = Button(self.indtjeningsFrame.interior, relief='flat', bg='grey89', image=add_img, command= self.on_indt_addBut)
self.but.grid(row=99, column=0, columnspan=2, padx=(2,0))
self.but.img = add_img
# create an empty disabled entry (for the sake of aesthetic)
self.entry = Entry(self.indtjeningsFrame.interior, width=36, state=DISABLED)
self.entry.grid(row=99, column=2, sticky='w', padx=(0, 4))
def init_gen_entries(self):
"""Initilization - one time use
Makes and places entries in the "advance options" section"""
# WARNING: Do not remove the extra space in 'gennemsnitlig egekapital' - it is intended
self.gen_labels = ['Resultat af primær drift', 'Resultat før finansielle omkost.', 'Gennemsnitlig egenkapital',
'Gennemsnitlige forpligtelser', 'Gennemsnitlige aktiver']
for row, label in enumerate(self.gen_labels):
lab = Label(self.årsagsFrame.interior, text=label, bg='grey89')
lab.grid(row=row + 1, column=0, sticky='w', padx=(0,15))
if label == 'Resultat af primær drift':
# We need to save the label in order to hide/show it later 'on_iv_gen2_RaPD_kendes_ikke()'
self.gen_RaPD_label = lab
elif label == 'Resultat før finansielle omkost.':
self.gen_RfFO_label = lab
col = 1
for i in range(3, 0, -1):
# Create stringvar in order to trace entry
sv = StringVar(self.årsagsFrame.interior)
self.sva[str(label)+str(i)] = sv
# Create entry
entry = Entry(self.årsagsFrame.interior, width=12, relief='groove', textvariable=sv)
entry.insert(END, i * 100)
# Trace entry - (hvornår bliver der skrevet/slettet i entrien)
sv.trace('w', lambda name, *_, sv=sv, Ltext=label: self._check_gen_entry(sv, name, Ltext))
entry.grid(row=row + 1, column=col, padx=(0, 2))
self.entries[str(label) + str(i)] = entry
col += 1
# Gem 'Resultat før finansielle omkostnigner
self.gen_RfFO_label.grid_forget()
for entry in (self.entries['Resultat før finansielle omkost.' + str(i)] for i in range(3, 0, -1)):
entry.grid_forget()
# Overskriv 'gen_labels'
self.gen_labels = [label + str(i) for label in self.gen_labels for i in range(3, 0, -1)]
# pady resultat af primær drift
#temp.config(pady=5)
def init_årEntries(self):
"""Initilization - one time use
Makes and places år entries + stores the objects in a dictionary - self.entriesÅr"""
col = 0
år = 0
for i in range(3, 0, -1):
# Create and store a StringVariable in a dictionary - self.entriesÅr
sv = StringVar(self.right_child)
self.svaÅr['år' + str(i)] = sv
# Create an entry and insert the year
entry = Entry(self.right_child, width=9, textvariable=sv)
entry.insert(END, 2017 - år)
# Make the StringVariable trace the entry
sv.trace('w', lambda name, *_, sv=sv: self._check_årstal(sv, name, 'År: (sidste til første)'))
# Place the entry and store the entry in a dictionary - self.svaÅr
entry.grid(row=0, column=col, sticky='wn', padx=(0, 2), pady=(1, 32))
self.entriesÅr['år' + str(i)] = entry
col += 1
år += 1
def init_entries(self):
"""Initilization - one time use
makes and places entries + stores the objects in a dictionary - self.entries"""
self.labels = ['Afkastningsgrad,%', 'Overskudsgrad,%', 'Aktivernes omsætningshastighed,gange',
'Gældsrente,%', 'Egenkapitalens forrentning,%', 'Gearing,gange',
'Soliditetsgrad,%', 'Likviditetsgrad,%']
self.nøgletal = [label + str(i) for label in self.labels for i in range(3, 0, -1)]
# Create frames in the right_child to organize the entries
self.right_child_mid = Frame(self.right_child, bg=self.ggrey)
self.right_child_mid.grid(row=1, column=0, columnspan=3)
self.right_child_bot = Frame(self.right_child, bg=self.ggrey)
self.right_child_bot.grid(row=2, column=0, columnspan=3, pady=(31,0))
for row, label in enumerate(self.labels):
if (label == 'Soliditetsgrad,%') or (label == 'Likviditetsgrad,%'):
frame = self.right_child_bot
else:
frame = self.right_child_mid
col = 0
for i in range(3, 0, -1):
# Create stringvar in order to trace entry
sv = StringVar(frame)
self.sva[str(label) + str(i)] = sv
# Create entry
entry = Entry(frame, width=9, relief='groove', textvariable=sv)
entry.insert(END, str(i) + '.0')
# Trace entry - (hvornår bliver der skrevet/slettet i entrien)
sv.trace('w', lambda name, *_, sv=sv, label=label: self._check_entry(sv, name, label, update='nøgletal'))
entry.grid(row=row+3, column=col, padx=(0, 2), pady=(0, 4), sticky='n')
self.entries[str(label) + str(i)] = entry
col += 1
def init_DP_entries(self):
self.DP_entries = OrderedDict()
self.DP_standard_labels = ('Nettoomsætning', 'Variable omkostninger', 'Kapacitetsomkostninger',
'Anlægsaktiver', 'Tilgodehavender+varebeholdninger', 'Likvide beholdninger')
# Mandatory entries
row = 0
for label in ('Nettoomsætning', 'Variable omkostninger',
'Kapacitetsomkostninger'):
# Label
lab = Label(self.DP_botFrame, text=label, bg='grey85')
lab.grid(row=row, column=0, sticky='w')
# 3 Entries
år = 3
for col in range(1, 4):
sv = StringVar(self.DP_botFrame)
entry = Entry(self.DP_botFrame, width=17, textvariable=sv)
rnd_int = str(år) #str(randint(1023, 9987))
entry.insert(END, rnd_int[0] + '.0' + rnd_int[1:])
entry.grid(row=row, column=col, padx=(0, 2))
sv.trace('w', lambda name, *_, sv=sv, Ltext=label: self._check_DPentry(sv, name, Ltext))
self.DP_entries[label + str(år)] = entry
år -= 1
row += 1
row = 0
for label in ('Anlægsaktiver', 'Tilgodehavender+varebeholdninger', 'Likvide beholdninger'):
# Label
lab = Label(self.DP_botFrame, text=label, bg='grey85')
lab.grid(row=row, column=4, sticky='w', padx=(8,2))
# 3 Entries
år = 3
for col in range(5, 8):
sv = StringVar(self.DP_botFrame)
entry = Entry(self.DP_botFrame, width=17, textvariable=sv)
rnd_int = str(år) #str(randint(1023, 9987))
entry.insert(END, rnd_int[0] + '.0' + rnd_int[1:])
entry.grid(row=row, column=col, padx=(0, 2))
sv.trace('w', lambda name, *_, sv=sv, Ltext=label: self._check_DPentry(sv, name, Ltext))
self.DP_entries[label + str(år)] = entry
år -= 1
row += 1
# Bind custom 'ctrl+v' to DP entries
for k, entry in self.DP_entries.items():
entry.bind('<<Paste>>', lambda *_, key=k: self.handle_clipboard(key))
# List of all DP_entry names, is needed to "handle_clipboard" properly
self.DP_entries_list = [entry_name for entry_name in self.DP_entries.keys()]
def _is_float(self, x):
"""Tests if the number is a float. (ignoring thousand seperator (all commas))"""
try:
flaot(x.replace(',', ''))
return True
except ValueError:
return False
def _check_årstal(self, sv, name, Ltext):
"""Checks whether the entry is valid and change the color accordingly
(text i entry field) (dynamic variable) (Label to the respective 3 entries)
eg. sv.get() = '2.0', name = PY_VAR6, Ltext = Overskudsgrad,%"""
n = int(name.rsplit('R')[1])
nr = str((n - 2) // 3 * 3 + 3 - (n - 2))
# Check the validity of the entry
if re.match(r'^\d+$', sv.get().strip()):
self.update_analyse_args(Ltext, nr, update='årstal')
root.after(1000, self._display_text)
color = 'black'
bg = 'white'
else:
color = 'red'
bg = 'pink'
self.entriesÅr['år' + nr].config(fg=color, bg=bg)
def _check_gen_entry(self, sv, name, Ltext):
"""Checks whether the entry is valid and changes the color accordingly
(text i entry field) (dynamic variable) (Label to the respective 3 entries)
eg. sv.get() = '2.0', name = PY_VAR6, Ltext = Overskudsgrad,%"""
n = int(name.rsplit('R')[1])
nr = str((n) // 3 * 3 + 3 - (n))
input = sv.get().strip()
# Check the validity of the entry
if re.search('^[+-]?([0-9]{1,3}(,[0-9]{3})+|[0-9]*)\.?[0-9]*$', input):
# Update analyse teksten med det nyt indtastede tal
self.update_analyse_args(Ltext, nr, update='gennemsnitlig')
# display den opdaterede tekst
root.after(3000, self._display_text)
color = 'black'
bg = 'white'
else:
color = 'red'
bg = 'pink'
self.entries[Ltext + nr].config(fg=color, bg=bg)
def _check_entry(self, sv, name, Ltext, update):
"""Checks whether the entry is valid and changes the color accordingly
(text i entry field) (dynamic variable) (Label to the respective 3 entries)
eg. sv.get() = '2.0', name = PY_VAR6, Ltext = Overskudsgrad,%"""
n = int(name.rsplit('R')[1])
nr = str((n - 2) // 3 * 3 + 3 - (n - 2))
input = sv.get().strip() # 5 should return -> 1 ; 6 should return -> 2
if input == '':
self.entries[Ltext + nr].config(fg='red', bg='pink')
return
# Check if digit grouped is used it is then used correctly (only possible to digit group with comma)
# If the number (ignoring commas (digit grouping)) is a float
if re.search('^[+-]?([0-9]{1,3}(,[0-9]{3})+|[0-9]*)\.?[0-9]*$', input):
color = 'black'
bg = 'white'
try:
# Update analyse teksten med det nyt indtastede tal
self.update_analyse_args(Ltext, nr, update=update)
# display den opdaterede tekst
root.after(3000, self._display_text)
except ValueError as e:
print('A number from a year is presumably missing..', e)
pass
else:
color = 'red'
bg = 'pink'
self.entries[Ltext + nr].config(fg=color, bg=bg)
def _check_markedsrente(self, sv, name, Ltext):
"""Checks whether the entry is valid and changes the color accordingly
(text i entry field) (dynamic variable) (Label to the respective 3 entries)
eg. sv.get() = '2.0', name = PY_VAR6, Ltext = Overskudsgrad,%"""
n = int(name.rsplit('R')[1])
nr = str((n - 1) // 3 * 3 + 3 - (n - 1))
# Check the validity of the entry
if re.search(r'^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$', sv.get().strip()):
# Update analyse teksten med det nyt indtastede tal
self.update_analyse_args(Ltext, nr=None, update='markedsrente')
# display den opdaterede tekst
root.after(1000, self._display_text)
color = 'black'
bg = 'white'
else:
color = 'red'
bg = 'pink'
self.entries[Ltext].config(fg=color, bg=bg)
def _check_DPentry(self, sv, name, Ltext):
"""Checks whether the entry is valid and changes the color accordingly
(text i entry field) (dynamic variable) (Label to the respective 3 entries)
eg. sv.get() = '2.0', name = PY_VAR6, Ltext = Overskudsgrad,%"""
n = int(name.rsplit('R')[1])
nr = str((n) // 3 * 3 + 3 - (n))
input = sv.get().strip()
if input == '':
return
# Check validity of the entry
if re.search('^[+-]?([0-9]{1,3}(,[0-9]{3})+|[0-9]*)\.?[0-9]*$', input):
# Update whatever the "optionMenu" is set to; (with this newly entered entry value)
#self.on_DP_dropmenu()
self.update_analyse_args(Ltext, nr, update='DP', sv=input)
color = 'black'
bg = 'white'
else:
color = 'red'
bg = 'pink'
self.DP_entries[Ltext + nr].config(fg=color, bg=bg)
def _check_indtjeningsevne_entries(self, sv, name, Ltext): # TODO - This is not done TODO TODO TODO TODO TODO TODO
"""Checks whether the entry is valid and changes the color accordingly
(text i entry field) (dynamic variable) (Label to the respective 3 entries)
eg. sv.get() = '2.0', name = PY_VAR6, Ltext = """
n = int(name.rsplit('R')[1])
nr = str((n - 1) // 3 * 3 + 3 - (n - 1))
input = sv.get().strip()
# Check validity of the entry
if re.search('^[+-]?([0-9]{1,3}(,[0-9]{3})+|[0-9]*)\.?[0-9]*$', input):
# Update analyse teksten med de nyindtastede indekstal
self.update_analyse_args(Ltext='', update='indtjeningsevne', nr=None)
root.after(1000, self._display_text)
color = 'black'
bg = 'white'
else:
color = 'red'
bg = 'pink'
#self.DP_entries[Ltext + nr].config(fg=color, bg=bg)
def _make_TextWidgets(self):
"""
Initialize text widgets and store them in a dict - self.textWidgets
Initialize buttons and store them in a dict - self.butWidgets
Text widgets are numbered as followed: text1, text3, text5, text7, text9, text11
The respective buttons are numbered as followed: but0, but2, but4, but6, but8, but10
"""
self.textWidgets = {}
self.butWidgets = {}
h = '6'
# Initialize text widgets
for row in range(1, 20, 2):
temp = ExpandoText(self.can_frame, relief='groove', wrap="word", bg='ghost white', font='Arial 9')
temp.grid(row=row, column=0, sticky='new', padx=(5, 5))
self.textWidgets['text' + str(row)] = temp
# Initialize buttons with corrosponding Ltext
bg = 'grey68'
activebg = 'red'
row = 0
# lav også en indtjeningsevne textWidget
self.labels.insert(-2, 'Konklusion')
self.labels.insert(-2, 'Indtjeningsevne')
for Ltext in self.labels:
temp = Button(self.can_frame, text=Ltext, font='verdana 8', activebackground=activebg, bg=bg, height=1,
command=lambda row=row, Ltext=Ltext: self._hide_textWidget(textNR=row, Ltext=Ltext))
# temp.bind("<ButtonPress>", lambda event, button=temp: self.__button_press(event, button))
temp.bind("<Enter>", lambda event, button=temp: button.config(bg='green'))
temp.bind("<Leave>", lambda event, button=temp: button.config(bg=bg))
temp.config()
temp.grid(row=row, column=0, sticky='ew')
self.butWidgets['but' + str(row)] = temp
row += 2
def __collaps_expand_all(self, collapse: bool):
"""Button function that either expands or collapses all text widgets"""
if collapse:
self.co_ex_but.config(text='Expand all')
for textNR in range(0, 19, 2):
self._hide_textWidget(textNR, Ltext=self.butWidgets['but' + str(textNR)].cget("text"))
else:
self.co_ex_but.config(text='Collapse all')
for textNR in range(0, 19, 2):
self._show_textWidget(textNR, Ltext=self.butWidgets['but' + str(textNR)].cget("text"))
def _hide_textWidget(self, textNR, Ltext):
"""Hides the textwidget at row: textNR and disable mousewheel"""
self.butWidgets['but' + str(textNR)].config(text=Ltext, command=lambda: self._show_textWidget(textNR, Ltext))
self.textWidgets['text' + str(textNR+1)].grid_remove()
# wait for root to update grid and scrollbar
self.root.update()
# hvis scrollbar forsvinder så turn mousewheel off
if abs(self.scrollbar.get()[0] - self.scrollbar.get()[1]) == 1:
self.canvas.bind_all("<MouseWheel>", 'break')
def _show_textWidget(self, textNR, Ltext):
"""Makes the text widgets visibel and disable mousewheel"""
self.butWidgets['but' + str(textNR)].config(text=Ltext, command=lambda: self._hide_textWidget(textNR, Ltext))
self.textWidgets['text' + str(textNR+1)].grid()
# wait for root to update grid and scrollbar
self.root.update()
# hvis scrollbar er synlig så sæt mousewheel til igen
if abs(self.scrollbar.get()[0] - self.scrollbar.get()[1]) < 1:
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _float_str(self, string : str) -> str:
"""Hvis string ikke er et komma tal med et ,0 bag på
add'er denne function det."""
try:
return str(round(float(string.replace(',', '.', 1)), 2)).replace('.', ',', 1)
except ValueError:
# Occurs when an input widget is empty
pass
def init_analyse_text(self):
"""Initializes analyse teksterne"""
self.firma = self.firmanavn.get()
# disse 3 er initialize her for at undgå at skrive alt det her ved hver eneste key
år1_afk = float(self.entries['Afkastningsgrad,%1'].get())
år1_gæl = float(self.entries['Gældsrente,%1'].get())
år3_lik = float(self.entries['Likviditetsgrad,%3'].get())
år1 = self.entriesÅr['år1'].get()
år3 = self.entriesÅr['år3'].get()
# self._pp(Ltext, startÅr, slutÅr)
pp_afk = self._pp('Afkastningsgrad,%', 1, 3)
rp_afk = self._rp('Afkastningsgrad,%')
pp_ove = self._pp('Overskudsgrad,%', 1, 3)
rp_ove = self._rp('Overskudsgrad,%')
pp_oms = self._pp('Aktivernes omsætningshastighed,gange', 1, 3)
rp_oms = self._rp('Aktivernes omsætningshastighed,gange')
pp_gæl = self._pp('Gældsrente,%', 1, 3)
rp_gæl = self._rp('Gældsrente,%')
pp_ege = self._pp('Egenkapitalens forrentning,%', 1, 3)
rp_ege = self._rp('Egenkapitalens forrentning,%')
pp_gea = self._pp('Gearing,gange', 1, 3)
rp_gea = self._rp('Gearing,gange')
pp_sol_13 = self._pp('Soliditetsgrad,%', 1, 3)
pp_sol_12 = self._pp('Soliditetsgrad,%', 1, 2)
pp_sol_23 = self._pp('Soliditetsgrad,%', 2, 3)
pp_lik = self._pp('Likviditetsgrad,%', 1, 3)
rp_lik = self._rp('Likviditetsgrad,%')
self.args = {
'Afkastningsgrad': {'firma': self.firma,
'pp': self._replace_dot(pp_afk),
'rp': self._replace_dot(rp_afk),
'år1_afk': self._replace_dot(år1_afk),
'år3_afk': self._float_str(self.entries['Afkastningsgrad,%3'].get()),
'år1': år1,
'år3': år3,
'adj': self.__binary_stigning_verbum(pp_afk, førdatid=True),
'sub': self.__stigning_sub_ubestemt(pp_afk),
'grad': self.__tillægsord_gradbøjet(milestones=(-4, -0.001, 0, 4), værdi=pp_afk),
'tilfreds': self.__binary_tilfredsstillende(self._get('Afkastningsgrad,%3')),
'markedsrente': '4,0', # Init as 4%
'årsag': self._årsag_afkastningsgrad()
},
'Overskudsgrad': {'pp': self._replace_dot(pp_ove),
'rp': self._replace_dot(rp_ove),
'år1_ove': self._float_str(self.entries['Overskudsgrad,%1'].get()),
'år3_ove': self._float_str(self.entries['Overskudsgrad,%3'].get()),
'år1': år1,
'år3': år3,
'sub': self.__stigning_sub_ubestemt(pp_ove),
'grad': self.__tillægsord_gradbøjet(milestones=(-4, -0.001, 0, 4), værdi=pp_ove),
'adj': self.__binary_stigning_verbum(pp_ove, førdatid=True),
'påvirkelse': self.__binary_påvirkelse(pp_ove),
'alleÅr': self.__binary_alleÅr('Overskudsgrad,%'),
'årsag': self._årsag_overskudsgrad()
},
'Aktivernes omsætningshastighed': {'pp': self._replace_dot(pp_oms),
'rp': self._replace_dot(rp_oms),
'år1_oms': self._float_str(
self.entries['Aktivernes omsætningshastighed,gange1'].get()),
'år3_oms': self._float_str(
self.entries['Aktivernes omsætningshastighed,gange3'].get()),
'år1': år1,
'år3': år3,
'ligeledes': self.__binary_ligeledes(),
'adj1': self.__binary_forbedret(pp_oms),
'adj2': self.__binary_stigning_verbum(pp_oms, førdatid=True),
'sub': self.__stigning_sub_ubestemt(pp_oms),
'påvirkelse': self.__binary_påvirkelse(pp_oms),
'alleÅr': self.__binary_alleÅr('Aktivernes omsætningshastighed,gange'),
'årsag': self._årsag_aktivernes_omsætningshastighed()
},
'Gældsrente': {'pp': self._replace_dot(pp_gæl),
'rp': self._replace_dot(rp_gæl),
'år1_gæl': self._replace_dot(år1_gæl),
'år3_gæl': self._float_str(self.entries['Gældsrente,%3'].get()),
'år1': år1,
'år3': år3,
'adj': self.__binary_stigning_verbum(pp_gæl, førdatid=True),
'sub': self.__stigning_sub_ubestemt(pp_gæl),
'grad': self.__tillægsord_gradbøjet(milestones=(-0.6, -0.001, 0, 0.6), værdi=pp_gæl),
'år1_afk': self._replace_dot(år1_afk),
'fortjeneste': self.__binary_fortjeneste(str(år1_afk), str(år1_gæl)),
'årsag': self._årsag_gældsrente()
# Init as none, will be updated later if optional options is provided
},
'Egenkapitalens forrentning': {'pp': self._replace_dot(pp_ege),
'rp': self._replace_dot(rp_ege),
'år1_ege': self._float_str(self.entries['Egenkapitalens forrentning,%1'].get()),
'år3_ege': self._float_str(self.entries['Egenkapitalens forrentning,%3'].get()),
'år1': år1,
'år3': år3,
'steget': self.__binary_stigning_verbum(pp_ege, førdatid=True),
'sub': self.__stigning_sub_ubestemt(pp_ege),
'tilfreds': self.__binary_tilfredsstillende(self._get('Egenkapitalens forrentning,%3')),
'adj1': self.__påvirkelse(pp_afk, 'afk'),
'adj2': self.__påvirkelse(pp_gæl, 'gæl'),
'adj3': self.__påvirkelse(pp_gea, 'gea'),
'gæld': self.__binary_tjent_på_gæld(),
'markedsrente': '4,0', # Init as 4%
'årsag': self._årsag_egenkapitals_forrentning()
# Init as none, will be updated later if optional options is provided
},
'Gearing': {'pp': self._replace_dot(pp_gea),
'rp': self._replace_dot(rp_gea),
'år1_gea': self._float_str(self.entries['Gearing,gange1'].get()),
'år3_gea': self._float_str(self.entries['Gearing,gange3'].get()),
'år1': år1,
'år3': år3,
'adj1': self.__binary_stigning_verbum(pp_gea, førdatid=True),
'sub': self.__stigning_sub_ubestemt(pp_gea),
'grad': self.__tillægsord_gradbøjet(milestones=(-0.6, -0.001, 0, 0.6), værdi=pp_gea),
'årsag': self._årsag_gearing()
# Init as none, will be updated later if optional options is porvided
},
'Konklusion': {'alt_i_alt': self.__konklusion_udvikling(),
},
'Indtjeningsevne': {'firma': self.firma,
'pp_ove': pp_ove,
'rp_ove': rp_ove,
'år1_ove': self._float_str(self.entries['Overskudsgrad,%1'].get()),
'år3_ove': self._float_str(self.entries['Overskudsgrad,%3'].get()),
'adj1': self.__binary_stigning_verbum(pp_ove, førdatid=True),
'sub': self.__stigning_sub_ubestemt(pp_ove),
'grad': self.__tillægsord_gradbøjet(milestones=(-4, -0.001, 0, 4), værdi=pp_ove),
'additional': self.create_indtjeningsevne_text()
},
'Soliditetsgrad': {'firma': self.firma,
'år1': år1,
'år2': self.entriesÅr['år2'].get(),
'år3': år3,
'fremmedkapital': self._replace_dot(round(100 - float(self.entries['Soliditetsgrad,%1'].get()), 2)),
'år1_sol': self._float_str(self.entries['Soliditetsgrad,%1'].get()),
'år2_sol': self._float_str(self.entries['Soliditetsgrad,%2'].get()),
'år3_sol': self._float_str(self.entries['Soliditetsgrad,%3'].get()),
'adj_13': self.__binary_stigning_verbum(pp_sol_13, datid=True),# pp forskel mellem år1_sol og år3_sol
'adj_12': self.__binary_stigning_verbum(pp_sol_12, datid=True),# pp forskel mellem år1_sol og år2_sol
'adj_23': self.__binary_stigning_verbum(pp_sol_23, datid=True),# pp forskel mellem år2_sol og år3_sol
'yderligere': self.__binary_yderligere('Soliditetsgrad,%'),
'soliditetshensyn': self.__soliditetshensyn(),
'niveau': self.__tillægsord_gradbøjet(milestones=(0, 29, 39),
tillægsord=('uacceptabelt lavt', 'relativt lavt',
'passende', 'højt'),
værdi=float(self.entries['Soliditetsgrad,%3'].get())),
},
'Likviditetsgrad': {'firma': self.firma,
'år1_lik': self._float_str(self.entries['Likviditetsgrad,%1'].get()),
'år3_lik': self._replace_dot(år3_lik),
'år1': år1,
'år3': år3,
'adj1': self.__binary_stigning_verbum(pp_lik, førdatid=True),
'adj2': self.__tillægsord_gradbøjet(milestones=(70, 99, 100, 149),
tillægsord=(
'ligger en del under', 'ligger en smule under',
'er lig med',
'ligger en smule over',
'ligger et godt stykke over'), værdi=float(self.entries['Likviditetsgrad,%3'].get().replace(',',''))),
'adj3': self.__tillægsord_gradbøjet(milestones=(70, 99, 100, 130),
tillægsord=(
'væsentlig lavere end', 'lavere end', 'lig med',
'større end',
'del større end'), værdi=float(self.entries['Likviditetsgrad,%3'].get().replace(',',''))),
'passende': self.__tillægsord_gradbøjet(milestones=(135, 149),
tillægsord=(
'upassende', 'en smule upassende', 'passende'),
værdi=float(self.entries['Likviditetsgrad,%3'].get().replace(',',''))),
'tommelfingerregel': self.__likviditets_tommelfingerregel(år3_lik)
}
}
self.string_templates = { # https://virksomheda.systime.dk/?id=c6303
'Afkastningsgrad': "Rentabiliteten i {firma} er {grad} i analyseperioden, eftersom at afkastningsgraden er {adj} "
"fra {år1_afk}% i {år1} til {år3_afk}% i {år3}, dvs. {sub} på {pp} procentpoint ({rp}%). "
"Afkastningsgraden ligger i {år3} på et {tilfreds} niveau sammenlignet med en markedsrente på {markedsrente}% og et risikotillæg på 2%."
" {årsag}",
'Overskudsgrad': "Virksomhedens indtjeningsevne er {grad} idet overskudsgraden er {adj} fra {år1_ove}% i regnskabsår {år1} til {år3_ove}% i regnskabsår {år3},"
" dvs. {sub} på {pp} procentpoint ({rp}%), hvilket {påvirkelse}. {alleÅr}{årsag}",
'Aktivernes omsætningshastighed': "Virksomhedens kapitaltilpasningsevne er {ligeledes}{adj1} i perioden, idet aktivernes omsætningshastighed er {adj2} fra {år1_oms} g i "
"regnskabsår {år1} til {år3_oms} g i regnskabsår {år3}, dvs. {sub} på {pp} g ({rp}%). Dette {påvirkelse}. "
"{alleÅr}{årsag}",
'Gældsrente': "Gældsrenten er {grad} i analyseperioden, idet gældsrenten er {adj} fra {år1_gæl}% i regnskabsår"
" {år1} til {år3_gæl}% i regnskabsår {år3}, dvs. {sub} på {pp} procentpoint ({rp}%). "
"Afkastningsgraden i {år1} viser, at hver investeret krone har givet et afkast på {år1_afk} øre. "
"Imens der i gennemsnit er blevet betalt {år1_gæl} øre i rente for hver lånte krone. {fortjeneste}"
"{årsag}",
'Egenkapitalens forrentning': "Egenkapitalens forrentning er {steget} fra {år1_ege}% i regnskabsår {år1} til {år3_ege}% i regnskabsår {år3}, "
"dvs. {sub} på {pp} procentpoint ({rp}%). Egenkapitalens forrentning befinder sig i {år3} "
"på et {tilfreds} niveau sammenlignet med en markedsrente på {markedsrente}% og et risikotillæg på 2%. "
"\nDet kan konkluderes, at egenkapitalens forrentning fra {år1} til {år3} er blevet påvirket {adj1} af ændringen i afkastningsgraden, "
"{adj2} gældsrenten og {adj3} gearingen, {gæld}{årsag}",
'Gearing': "Gearingen er {grad} i analyseperioden, idet gearingen er {adj1} fra {år1_gea}% i regnskabsår {år1} "
"til {år3_gea}% i regnskabsår {år3}, dvs. {sub} på {pp} procentpoint ({rp}%). {årsag}",
'Konklusion': "{alt_i_alt}",
'Indtjeningsevne': "Indtjeningsevnen i {firma} er {grad}, idet overskudsgraden er {adj1} fra {år1_ove}% "
"til {år3_ove}%, dvs. {sub} på {pp_ove} procentpoint ({rp_ove}%){additional}",
'Soliditetsgrad': "Soliditetsgraden var i {år1} {år1_sol}%, det vil sige at {år1_sol}% af de samlede investeringer (aktiver) i {firma} var finansieret med egenkapital."
" Man kan også udtrykke det på den måde, at {år1_sol} øre af hver investeret krone i virksomheden kom fra virksomhedsejerne."
" I {år2} {adj_12} soliditetsgraden til {år2_sol}%, hvorefter den i {år3} {adj_23} {yderligere}til {år3_sol}%. "
"I {år3} ligger soliditetsgraden på et {niveau} niveau, på baggrund af at soliditetsgraden i en veletableret "
"virksomhed gerne skulle udgøre mindst 30 til 40 procent. "
"{soliditetshensyn}",
'Likviditetsgrad': "Likviditetsgraden i {firma} er {adj1} fra {år1_lik}% i {år1} til {år3_lik}% i {år3}. "
"Den likviditetsmæssige situation i virksomheden ser {passende} ud ved udgangen af {år3}{tommelfingerregel}"
"Da likviditetsgraden {adj2} 100%, kan det konkluderes, at virksomhedens omsætningsaktiver "
"derfor er {adj3} de kortfristede gældsforpligtelser."
}
def update_analyse_args(self, Ltext, nr: str, update, sv=None):
"""Updates self.nøgletal - this method is called whenever an entry is edited and approved in either
_check_entry or _check_årårstal"""
if update == 'årstal':
# Updater standard analyse text
if nr == '2':
# Kun soliditetsgrad bruger år2
self.args['Soliditetsgrad']['år2'] = self.entriesÅr['år2'].get()
else:
# år1 og år3 findes i alle args
for label in self.labels:
self.args[label.rsplit(',')[0]]['år' + nr] = self.entriesÅr['år' + nr].get()
# Gen. årsager er slået til; opdater også dem
if self.iv_gen.get():
self.args['Afkastningsgrad']['årsag'] = self._årsag_afkastningsgrad()
self.args['Overskudsgrad']['årsag'] = self._årsag_overskudsgrad()
self.args['Aktivernes omsætningshastighed']['årsag'] = self._årsag_aktivernes_omsætningshastighed()
self.args['Gældsrente']['årsag'] = self._årsag_gældsrente()
self.args['Egenkapitalens forrentning']['årsag'] = self._årsag_egenkapitals_forrentning()
self.args['Gearing']['årsag'] = self._årsag_gearing()
elif update == 'nøgletal':
if Ltext == 'Afkastningsgrad,%':
self.update_afk(nr)
elif Ltext == 'Overskudsgrad,%':
self.update_ove(nr)
elif Ltext == 'Aktivernes omsætningshastighed,gange':
self.update_oms(nr)
elif Ltext == 'Gældsrente,%':
self.update_gæl(nr)
elif Ltext == 'Egenkapitalens forrentning,%':
self.update_ege(nr)
elif Ltext == 'Gearing,gange':
self.update_gea(nr)
elif Ltext == 'Konklusion':
self.update_kon(nr)
elif Ltext == 'Soliditetsgrad,%':
self.update_sol(nr)
elif Ltext == 'Likviditetsgrad,%':
self.update_lik(nr)
elif update == 'gennemsnitlig':
if Ltext == 'Gennemsnitlig egenkapital':
# Egenkapital bliver brugt i gearing årsag og egenkapitalens forrentning årsag
self.args['Gearing']['årsag'] = self._årsag_gearing()
self.args['Egenkapitalens forrentning']['årsag'] = self._årsag_egenkapitals_forrentning()
elif Ltext == 'Gennemsnitlige forpligtelser':
# Forpligtelser bliver brugt i: gældsrente og gearing
self.args['Gældsrente']['årsag'] = self._årsag_gældsrente()
self.args['Gearing']['årsag'] = self._årsag_gearing()
elif Ltext == 'Gennemsnitlige aktiver':
# Aktiver bliver brugt i afkastningsg. og aktivernes omsætningsh.
self.args['Afkastningsgrad']['årsag'] = self._årsag_afkastningsgrad()
self.args['Aktivernes omsætningshastighed']['årsag'] = self._årsag_aktivernes_omsætningshastighed()
elif Ltext == 'Resultat af primær drift':
# Resultat af primær drift bliver brugt i afkastningsg. og overskudsg.
self.args['Afkastningsgrad']['årsag'] = self._årsag_afkastningsgrad()
self.args['Overskudsgrad']['årsag'] = self._årsag_overskudsgrad()
elif Ltext == 'Resultat før finansielle omkost.':
# Resultat før finansielle omkostninger bliver brugt i afkasntignsg. og overskudsg.
self.args['Afkastningsgrad']['årsag'] = self._årsag_afkastningsgrad()
self.args['Overskudsgrad']['årsag'] = self._årsag_overskudsgrad()
elif update == 'markedsrente':
# Markedsrenten bliver nævnt i afkastnings. og egenkapitalens f.
self.args['Afkastningsgrad']['markedsrente'] = self._float_str(self.entries['markedsrente'].get())
self.args['Egenkapitalens forrentning']['markedsrente'] = self._float_str(self.entries['markedsrente'].get())
elif update == 'DP': # Du pont
item = self.DP_dropVar.get()
forhold = False
if Ltext == 'Everything':
# Update all boxes
item = self.DP_dropVar.get()
print('--->', item)
try:
self.update_everything_DP(forhold=True, årx=item[17], åry=item[-1])
except IndexError:
print('INDEXERROR HERE ---------')
# Item has length of 3, since it's sat to show one year eg. 'år2' as opposed to 'udvikling fra årx to åry'
self.update_everything_DP(forhold=False, årx=item[-1], åry=None)
return
if len(item) == 3:
# Hvis kun et år skal vises (fx.'år2') - det vælges i dropdown menuen
grad = ''
if nr != item[-1]: # årx != nr
# nr = the number of the entry edited
# If the edited entry is not shown on the DP pyramid; don't update anything.
return
else:
num = sv
else:
# Hvis et år skal vises i forhold til et andet (fx år1->år3)
årx = item[17]
åry = item[-1]
if (nr == årx or nr == åry):
forhold = True
årx = self._get(Ltext + årx, DP=True)
åry = self._get(Ltext + åry, DP=True)
grad = self.DP_grad(årx, åry) + self.DP_getSymbol(self.DP_forskel(årx, åry))
num = str(åry - årx)
#print(f"'{num}' {type(num)}")
else:
# The entry being edited is currently not used on the DP pyramid; nothing to update
return
if Ltext == 'Nettoomsætning':
self.DP_colorize_box(Ltext, float(num))
self.DPcanvas.itemconfig(self.DP_textID['net0'], text=self._punctuate_num(num))
self.DPcanvas.itemconfig(self.DP_textID['net1'], text=self._punctuate_num(num))
self.DPcanvas.itemconfig(self.DP_textID['net2'], text=self._punctuate_num(num))
if forhold:
# Hvis der skal vises udvikling i procent; kun muligt ved samligning af år
self.DPcanvas.itemconfig(self.DP_textID_grad['net0'], text=grad)
self.DPcanvas.itemconfig(self.DP_textID_grad['net1'], text=grad)
self.DPcanvas.itemconfig(self.DP_textID_grad['net2'], text=grad)
self.ÅrxÅry['net0'] = (årx, åry)
# Update 'dækningsbidrag' and 'aktivernes omsætningshastighed'
self.DP_update_dækningsbidrag(forhold=forhold)
self.DP_update_oms(forhold=forhold)
elif Ltext == 'Variable omkostninger':
self.DP_colorize_box('var', float(num))
self.DPcanvas.itemconfig(self.DP_textID['var'], text=self._punctuate_num(num))
self.DPcanvas.itemconfig(self.DP_textID_grad['var'], text=grad)
if forhold:
self.ÅrxÅry['var'] = (årx, åry)
# Update dækningsbidrag box; it'll make a call to the box above it, which will make a call to the one above that and so on..
self.DP_update_dækningsbidrag(forhold=forhold)
elif Ltext == 'Tilgodehavender+varebeholdninger':
self.DP_colorize_box('til', float(num))
self.DPcanvas.itemconfig(self.DP_textID['til'], text=self._punctuate_num(num))
self.DPcanvas.itemconfig(self.DP_textID_grad['til'], text=grad)
if forhold:
self.ÅrxÅry['til'] = (årx, åry)
# Update omsætningsaktiver
self.DP_update_omsA(forhold=forhold)
elif Ltext == 'Likvide beholdninger':
self.DP_colorize_box('lik', float(num))
self.DPcanvas.itemconfig(self.DP_textID['lik'], text=self._punctuate_num(num))
self.DPcanvas.itemconfig(self.DP_textID_grad['lik'], text=grad)
if forhold:
self.ÅrxÅry['lik'] = (årx, åry)
# Update omsætningsaktiver
self.DP_update_omsA(forhold=forhold)
elif Ltext == 'Kapacitetsomkostninger':
self.DP_colorize_box('kap', float(num))
self.DPcanvas.itemconfig(self.DP_textID['kap'], text=self._punctuate_num(num))
self.DPcanvas.itemconfig(self.DP_textID_grad['kap'], text=grad)
if forhold:
self.ÅrxÅry['kap'] = (årx, åry)
# Update resultat af primær drift
self.DP_update_RaPD(forhold=forhold)
elif Ltext == 'Anlægsaktiver':
self.DP_colorize_box('anl', float(num))
self.DPcanvas.itemconfig(self.DP_textID['anl'], text=self._punctuate_num(num))