Skip to content

Instantly share code, notes, and snippets.

@Sebastian-Nielsen
Created April 23, 2018 21:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sebastian-Nielsen/eb1b460f5ea2f8f599010d5beeb80d97 to your computer and use it in GitHub Desktop.
Save Sebastian-Nielsen/eb1b460f5ea2f8f599010d5beeb80d97 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, csv
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 = {'logo': '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rwqPOmCmOoeISDyqH5vXdLpbRESkRVRAF5GYEBjzLPY+9H9id33Gqv+aIHgYmOY6iIiIiIiIiMS9BUAImIVtz/4x9sRmU7eRlfWVeetdhxQRERER8YkK6CISE+or85Z3G5A/GtgfuMh1nlZYAxQApbMqcme6DiMiIiIiIm3M0AHbKlYk0lYC32FbsX8DzAW+I2Ah8H34974BFqpYLiIiIuIZQwB0xL/PCu2x11zE8oHGVlMBXURiRn1l3vxuA/Lvwt7/eR6x8xr2JTAKGFNfmbfYdRgREREREXFiDbbIuXf418Z1IHEuwN6duyH8awM08vOW62s3+b012IL5amAZ8AO2cN50F/A84Mv6sXkbmp1CRERERFxqxM7v5mML1j5seGzHxqt/1rkO40KsFJ9ERACor8yb121A/t3YIvqfsDuzfNWIXcB4sL4yb4TrMCIiIiIi4tRcoBzYjY2FUklsTQuTa9j4NbEBWxxfCawK/7gi/PMV2IL5gvqxeatchxcRERGRiFiLvW7nCeAg7PzPtQ7YeWrTRs2EowK6iMSi2cCj2Hbuqa7DbMVHwP3As66DiIiIiIiIc18Aw7BFU5EmZpOx6a8bNxkN4d9rqB+b1+A6sIiIiIhE1BpgBraI3hF/Ntoa7Dx0hesgLgSuA4iItEa3AfkBkAf8C1tI981/gMeA/9RXqnWeiIiIiIiIiIiIiIhILFABXURiVrcB+TsDVwE3Al1c5wlbBTwDDKmvzKt2HUZERERERERERERERESar73rACIirbWg/vn1XZP++in2DsFjgB0dR5oPVAK311fmfez6+REREREREREREREREZGW0b1bIhLrvgWGA285zvEN8AjwADDPcRYRERERERERERERERFpBbVwF5G40G1AfgowEujl4D//EXA39r7zha6fCxEREREREREREREREWkdFdBFJG50G5D/R+wp8F+04X/2XeDB+sq8510/fhEREREREREREREREdk+ugNdROLGgvrnP+2a9NcVQF+gS5T/c8uBN4Bb6yvzXnf92EVERERERERERERERGT76Q50EYk3r2BbuS+I4n9jNfAUcD0ww/UDFhERERERERERERERkchQC3cRiTvdBuQfCdwMDAQ6RPiPXwMMA/LrK/M+df1YRUREREREREREREREJHLUwl1E4s6C+ud/6Jr014+Aw4BjI/hHzwaGAo/VV+Z94/pxioiIiIiIiIiIiIiISGTpBLqIxK1uA/JPBh4GTtjOP6oBmAQU1Ffmlbt+XCIiIiIiIiIiIiIiIhIdugNdROLZe9gT49tzWnwd8BpwCzDB9QMSERERERERERERERGR6FELdxGJWwvqn6dr0l+/ANYDfYEdWvHHPI0tnlfXV+atd/2YREREREREREREREREJHrUwl1E4l63Afm7An8HrgZ2bub/bBVQCTxSX5n3gevHICIiIiIiIiIiIiIiItGnE+giEvcW1D+/tmvSX2uAPYFuQMdt/E8+Ax4D7qqvzPvadX4RERERERERERERERFpGyqgi0hCWFD//JquSX+dCxwKHA2028y/tgH4FHgAGF5fmbfadW4RERERERERERERERFpO+22/48QEYkZHwAlGD7GwGbG+xhuwTC+vjJvg+uwIiIiIiIiIiIiIiIi0rZ0B7qIJJRu/fM7AenAHcD+m/yjZ4Ci+rF5L7jOKCIiIiIiIiIiIiIiIm6ohbuIJJQF9c83dO3211nAeqAvsA4oAO6rH5s32XU+ERERERERERERERERcUct3EUk4dSPzVtnMOMM5mWDeclghgGzXecSERERERERERERERERt/4P7IuKh4Of/CYAAAAASUVORK5CYII=',
'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, parent, grey):
self.grey = grey
self.parent = parent
def run(self):
print('in licensas dfasdfjælk')
# Check if the window already exists
try:
if self.tw.winfo_exists():
print('it already exist')
self.tw.destroy()
return
except:
# expected to pass on first run, since tw isnt initalized yet
pass
print('creating')
# If the window didn't exist - create it now
self.tw = tk.Toplevel(root)
self.tw.resizable(False, False)
# Set span location
x = self.parent.winfo_rootx()
y = self.parent.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='#e26a84', fg='black', text='Ugyldig kode')
self.status.grid(padx=(0,4), pady=(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 Indstillinger_window:
def __init__(self, root, grey, blue_active, blue_enter, blue_leave):
self.grey = grey
self.blue_active = blue_active
self.blue_enter = blue_enter
self.blue_leave = "#%02x%02x%02x" % (120, 151, 229)
#self.blue_active = "#%02x%02x%02x" % (160, 194, 247)
#self.blue_enter = "#%02x%02x%02x" % (132, 173, 237)
#self.blue_leave = "#%02x%02x%02x" % (100, 151, 229)
def run(self):
# Check if the window already exists
try:
if self.tw.winfo_exists():
self.tw.destroy()
return
except:
# expected to pass on first run, since tw isnt initalized yet
pass
# If the window didn't exist - create it now
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('500x75')
self.init_window()
def init_window(self):
"""Initializes all the widgets in the topLevel window"""
frame = Frame(self.tw, bg=self.grey)
frame.grid(row=0, column=0, sticky='nsew')
# Row and column configure
self.tw.grid_columnconfigure(0, weight=1)
self.tw.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure((0,1), weight=1)
frame.grid_rowconfigure((0,1), weight=1)
# Instance of license class
license = License(self.tw, self.grey)
# Activate License - button
actLic = Button(frame, text='Aktiver license kode', activebackground=self.blue_active,
bg=self.blue_leave, font='arial 10', bd=1, command=lambda: (license.run(), self.tw.destroy()))
actLic.grid(row=0, column=0, sticky='ew')
# Save numbers as csv - button
saveBut = Button(frame, text='Gem nøgletal', activebackground=self.blue_active, bg=self.blue_leave,
font='arial 10', bd=1, command=self.save_numbers_in_csv)
saveBut.grid(row=1, column=0, sticky='ew')
# Load numbers from csv - button
switch = cycle((False, True))
loadBut = Button(frame, text='Load Nøgletal', activebackground=self.blue_active, bg=self.blue_leave,
font='arial 10', bd=1, command=lambda: self.on_load_csv(next(switch)))
loadBut.grid(row=1, column=1, sticky='ew')
# Hidden 'entryPath' for loading csv file
sv = StringVar(self.tw)
entryPath = Entry(frame, textvariable=sv)
entryPath.insert(END, os.getcwd() + '\\VøBot - gemte nøgletal.csv')
entryPath.grid(row=2, column=0, columnspan=2, sticky='ew', pady=(5,10))
# Hover colors for (actLic, saveBut)
actLic.bind("<Enter>", lambda e: actLic.config(bg=self.blue_enter))
actLic.bind("<Leave>", lambda e: actLic.config(bg=self.blue_leave))
saveBut.bind("<Enter>", lambda e: saveBut.config(bg=self.blue_enter))
saveBut.bind("<Leave>", lambda e: saveBut.config(bg=self.blue_leave))
loadBut.bind("<Enter>", lambda e: loadBut.config(bg=self.blue_enter))
loadBut.bind("<Leave>", lambda e: loadBut.config(bg=self.blue_leave))
def save_numbers_in_csv(self):
"""Safes the currently entered numbers in a csv file"""
with open('VøBot - gemte nøgletal.csv', 'w') as csvfile:
writer = csv.writer(csvfile, delimiter=' ', quotechar='|', quoting=csv.QUOTE_MINIMAL)
# Write 'basic nøgletal' and 'gem. nøgletal' til csv
writer.writerow( [n.get() for n in MainApp.entries.values()] )
def load_csv(self):
"""Loads the numbers from a specified csv file"""
def on_load_csv(self, shown):
"""Grids an entry in which the path to the csv file is intended to be written."""
# entryPath is already shown -> hide it
if shown:
os.getcwd()
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)
# init menu bar
#self.init_menu()
# 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_menu(self):
"""Initializes the menu bar (the top widget of the root window)"""
menubar = Menu(self.root, bg='red')
self.root.config(menu=menubar)
fileMenu = Menu(menubar)
fileMenu.add_command(label="Exit")
menubar.add_cascade(label="File", menu=fileMenu)
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')
# Indstillinger
inds_window = Indstillinger_window(root, self.grey, self.blue_active, self.blue_enter, self.blue_leave)
indsBut = Button(tempFrame, text='Indstillinger', activebackground=self.blue_active, width=16,
bg=self.blue_leave, font='arial 10', bd=1, command=lambda: inds_window.run())
indsBut.grid(row=18, column=1)
# indsBut hover colors
indsBut.bind("<Enter>", lambda e: indsBut.config(bg=self.blue_enter))
indsBut.bind("<Leave>", lambda e: indsBut.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'
# Man kunne lige så godt have skrevet: int( (row-1)/3) )
# (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, str(100+(år-1)*10))
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.\n"
"Det fremgår ud fra indekstallene, at omsætningen er {adj2} 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',
adj2= self.__binary_stigning_verbum(oms_3-oms_1, førdatid=True),
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]['år3'].get()) - float(self.indtjenings_list[i]['år1'].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}%. Udviklingen i {label} har derfor påvirket overskudsgraden {påvirket}, idet stigningstakten er {adj2} end "
"omsætningens stigningstakt. ").format(
label=label, rp=abs(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')
)
### Konklusion
result += ('\nKonklusion\nAlt i alt viser udviklingen i indtjeningsevnen et {sub}. '
'{imidlertidligt}').format(
sub=self.__stigning_sub_bestemt(pp=pp_ove, label='overskudsgraden'),
imidlertidligt=('Overskudsgraden er dog imidlertidligt forbedret fra {år2} til {år3}, '
'hvilket lover godt for den fremtidige indtjeningsevne.').format(år2=self.entriesÅr['år2'].get(),
år3=self.entriesÅr['år3'].get())
if self._pp('Overskudsgrad,%', 2, 3) > 1 else ''
)
# 'Årsagen til {sub} er, at {sub2}').format(
# sub=self.__stigning_sub_bestemt(pp=pp_ove, label='overskudsgraden')
# sub2=self.__stigning_sub_bestemt(pp=pp_ove, label='overskudsgraden')
#)
return result
def create_indtjeningsevne_konklusion(self):
### TODO indtjeningsevne konklusion mangler!
...
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 = []
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
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()
if input == '':
self.entries[Ltext + nr].config(fg='red', bg='pink')
return
# 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 == '':
self.DP_entries[Ltext + nr].config(fg='red', bg='pink')
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()
if input == '':
return
# Check validity of the entry
if re.search('^[+-]?([0-9]{1,3}(,[0-9]{3})+|[0-9]*)\.?[0-9]*$', input):
print('updater', 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))
self.DPcanvas.itemconfig(self.DP_textID_grad['anl'], text=grad)
if forhold:
self.ÅrxÅry['anl'] = (årx, åry)
# Update aktiver
self.DP_update_akt(forhold=forhold)
elif update == 'indtjeningsevne':
self.args['Indtjeningsevne']['additional'] = self.create_indtjeningsevne_text()
def update_everything_DP(self, forhold : bool, årx=None, åry=None):
"""Updates everything - This method is used to avoid overheat."""
# Nettoomsætning
if forhold:
# Hvis der skal vises udvikling i procent; kun muligt ved samligning af år
temp_årx = self._get('Nettoomsætning' + årx, DP=True)
temp_åry = self._get('Nettoomsætning' + åry, DP=True)
grad = self.DP_grad(temp_årx, temp_åry) + self.DP_getSymbol(self.DP_forskel(temp_årx, temp_åry))
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'] = (temp_årx, temp_åry)
text = self._punctuate_num(str(self._get('Nettoomsætning'+åry, DP=True) - self._get('Nettoomsætning'+årx, DP=True)))
else:
text = self._punctuate_num(self.DP_entries['Nettoomsætning'+årx].get().strip())
self.DP_colorize_box('Nettoomsætning', self._get('Nettoomsætning1', DP=True))
self.DPcanvas.itemconfig(self.DP_textID['net0'], text=text)
self.DPcanvas.itemconfig(self.DP_textID['net1'], text=text)
self.DPcanvas.itemconfig(self.DP_textID['net2'], text=text)
### Update 'dækningsbidrag' and 'aktivernes omsætningshastighed'
self.DP_update_dækningsbidrag(forhold=forhold)
self.DP_update_oms(forhold=forhold)
### Variable omkostninger
if forhold:
temp_årx = self._get('Variable omkostninger'+årx, DP=True)
temp_åry = self._get('Variable omkostninger'+åry, DP=True)
self.ÅrxÅry['var'] = (temp_årx, temp_åry)
num = str(temp_åry - temp_årx)
else:
num = self.DP_entries['Variable omkostninger' + årx].get().strip()
self.DP_colorize_box('var', float(num))
self.DPcanvas.itemconfig(self.DP_textID['var'], text=self._punctuate_num(num))
# 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)
### Tilgodehavender + varebeholdninger
if forhold:
temp_årx = self._get('Tilgodehavender+varebeholdninger' + årx, DP=True)
temp_åry = self._get('Tilgodehavender+varebeholdninger' + åry, DP=True)
self.ÅrxÅry['til'] = (temp_årx, temp_åry)
num = str(temp_åry - temp_årx)
else:
num = self.DP_entries['Tilgodehavender+varebeholdninger' + årx].get().strip()
self.DP_colorize_box('til', float(num))
self.DPcanvas.itemconfig(self.DP_textID['til'], text=self._punctuate_num(num))
# Update omsætningsaktiver
self.DP_update_omsA(forhold=forhold)
### Likvide beholdninger
if forhold:
temp_årx = self._get('Likvide beholdninger' + årx, DP=True)
temp_åry = self._get('Likvide beholdninger' + åry, DP=True)
self.ÅrxÅry['lik'] = (temp_årx, temp_åry)
num = str(temp_åry - temp_årx)
else:
num = self.DP_entries['Likvide beholdninger' + årx].get().strip()
self.DP_colorize_box('lik', float(num))
self.DPcanvas.itemconfig(self.DP_textID['lik'], text=self._punctuate_num(num))
# Update omsætningsaktiver
self.DP_update_omsA(forhold=forhold)
### Kapacitetsomkostninger
if forhold:
temp_årx = self._get('Kapacitetsomkostninger' + årx, DP=True)
temp_åry = self._get('Kapacitetsomkostninger' + åry, DP=True)
self.ÅrxÅry['Kapacitetsomkostninger'] = (temp_årx, temp_åry)
num = str(temp_åry - temp_årx)
else:
num = self.DP_entries['Kapacitetsomkostninger' + årx].get().strip()
self.DP_colorize_box('kap', float(num))
self.DPcanvas.itemconfig(self.DP_textID['kap'], text=self._punctuate_num(num))
# Update resultat af primær drift
self.DP_update_RaPD(forhold=forhold)
### Anlægsaktiver
if forhold:
temp_årx = self._get('Anlægsaktiver' + årx, DP=True)
temp_åry = self._get('Anlægsaktiver' + åry, DP=True)
self.ÅrxÅry['anl'] = (temp_årx, temp_åry)
num = str(temp_åry - temp_årx)
else:
num = self.DP_entries['Anlægsaktiver' + årx].get().strip()
self.DP_colorize_box('anl', float(num))
self.DPcanvas.itemconfig(self.DP_textID['anl'], text=self._punctuate_num(num))
# Update aktiver
self.DP_update_akt(forhold=forhold)
def update_afk(self, nr: str):
""" Updater afkastningsgraden i self.arg
'nr' er nummeret på den entry som er blevet ændret, nr 1 er entry til år 1"""
Ltext = 'Afkastningsgrad'
# Regn ud nu for at spare plads
pp_afk = self._pp('Afkastningsgrad,%', 1, 3)
rp_afk = self._rp('Afkastningsgrad,%')
år3_afk = self._float_str(self.entries['Afkastningsgrad,%3'].get())
# Updater arguments
self.args[Ltext]['pp'] = self._replace_dot(pp_afk)
self.args[Ltext]['rp'] = self._replace_dot(rp_afk)
self.args[Ltext]['år1_afk'] = self._float_str(self.entries['Afkastningsgrad,%1'].get())
self.args[Ltext]['år3_afk'] = år3_afk
self.args[Ltext]['adj'] = self.__binary_stigning_verbum(pp_afk, førdatid=True)
self.args[Ltext]['sub'] = self.__stigning_sub_ubestemt(pp_afk)
self.args[Ltext]['grad'] = self.__tillægsord_gradbøjet(milestones=(-5, -0.001, 0, 4), værdi=pp_afk)
self.args[Ltext]['tilfreds'] = self.__binary_tilfredsstillende(self._get('Afkastningsgrad,%3'))
# Da afkastningsgraden også bruges i gældsrente teksten, husk også at update den
self.args['Gældsrente']['år1_afk'] = self._float_str(self.entries['Afkastningsgrad,%1'].get())
self.args['Gældsrente']['prLåntKr'] = self.__binary_fortjeneste(str(self.entries['Afkastningsgrad,%1'].get()),
str(self.entries['Gældsrente,%1'].get()))
# Da den skal bruges i Egenkaptialens forrentning
self.args['Egenkapitalens forrentning']['adj1'] = self.__påvirkelse(pp_afk, 'afk')
# Da den bliver brugt i konklusionen
self.args['Konklusion']['alt_i_alt'] = self.__konklusion_udvikling()
def update_ove(self, nr: str):
""" Updater overskudsgraden i self.arg
'nr' er nummeret på den entry som er blevet ændret, nr 1 er entry til år 1"""
Ltext = 'Overskudsgrad'
# Regn ud nu for at spare plads
pp_ove = self._pp('Overskudsgrad,%', 1, 3)
rp_ove = self._rp('Overskudsgrad,%')
# Updater arguments
self.args[Ltext]['pp'] = self._replace_dot(pp_ove)
self.args[Ltext]['rp'] = self._replace_dot(rp_ove)
self.args[Ltext]['år1_ove'] = self._float_str(self.entries['Overskudsgrad,%1'].get())
self.args[Ltext]['år3_ove'] = self._float_str(self.entries['Overskudsgrad,%3'].get())
self.args[Ltext]['sub'] = self.__stigning_sub_ubestemt(pp_ove)
self.args[Ltext]['grad'] = self.__tillægsord_gradbøjet(milestones=(-5, -0.001, 0, 4), værdi=pp_ove)
self.args[Ltext]['adj'] = self.__binary_stigning_verbum(pp_ove, førdatid=True)
self.args[Ltext]['påvirkelse'] = self.__binary_påvirkelse(pp_ove)
self.args[Ltext]['alleÅr'] = self.__binary_alleÅr('Overskudsgrad,%')
# Updater indjeningsevne
self.args['Indtjeningsevne']['pp_ove'] = pp_ove
self.args['Indtjeningsevne']['rp_ove'] = rp_ove
self.args['Indtjeningsevne']['år1_ove'] = self._float_str(self.entries['Overskudsgrad,%1'].get())
self.args['Indtjeningsevne']['år3_ove'] = self._float_str(self.entries['Overskudsgrad,%3'].get())
self.args['Indtjeningsevne']['adj1'] = self.__binary_stigning_verbum(pp_ove, førdatid=True)
self.args['Indtjeningsevne']['grad'] = self.__tillægsord_gradbøjet(milestones=(-4, -0.001, 0, 4), værdi=pp_ove)
self.args['Indtjeningsevne']['sub'] = self.__stigning_sub_ubestemt(pp_ove)
# Da den bliver brugt i konklusionen
self.args['Konklusion']['alt_i_alt'] = self.__konklusion_udvikling()
def update_oms(self, nr: str):
""" Updater aktivernes omsætningshastighed i self.arg
'nr' er nummeret på den entry som er blevet ændret, nr 1 er entry til år 1"""
Ltext = 'Aktivernes omsætningshastighed'
# Regn ud nu for at spare plads
pp_oms = self._pp('Aktivernes omsætningshastighed,gange', 1, 3)
rp_oms = self._rp('Aktivernes omsætningshastighed,gange')
# Updater arguments
self.args[Ltext]['pp'] = self._replace_dot(pp_oms)
self.args[Ltext]['rp'] = self._replace_dot(rp_oms)
self.args[Ltext]['år1_oms'] = self._float_str((self.entries['Aktivernes omsætningshastighed,gange1'].get()))
self.args[Ltext]['år3_oms'] = self._float_str((self.entries['Aktivernes omsætningshastighed,gange3'].get()))
self.args[Ltext]['ligeledes'] = self.__binary_ligeledes()
self.args[Ltext]['adj1'] = self.__binary_forbedret(pp_oms)
self.args[Ltext]['adj2'] = self.__binary_stigning_verbum(pp_oms, førdatid=True)
self.args[Ltext]['sub'] = self.__stigning_sub_ubestemt(pp_oms)
self.args[Ltext]['påvirkelse'] = self.__binary_påvirkelse(pp_oms)
self.args[Ltext]['alleÅr'] = self.__binary_alleÅr('Aktivernes omsætningshastighed,gange')
# Da den bliver brugt i konklusionen
self.args['Konklusion']['alt_i_alt'] = self.__konklusion_udvikling()
def update_gæl(self, nr: str):
Ltext = 'Gældsrente'
# Regn ud nu for at spare plads
pp_gæl = self._pp('Gældsrente,%', 1, 3)
rp_gæl = self._rp('Gældsrente,%')
# Updater arguments
self.args[Ltext]['pp'] = self._replace_dot(pp_gæl)
self.args[Ltext]['rp'] = self._replace_dot(rp_gæl)
self.args[Ltext]['år1_gæl'] = self._float_str(self.entries['Gældsrente,%1'].get())
self.args[Ltext]['år3_gæl'] = self._float_str(self.entries['Gældsrente,%3'].get())
self.args[Ltext]['adj'] = self.__binary_stigning_verbum(pp_gæl, førdatid=True)
self.args[Ltext]['sub'] = self.__stigning_sub_ubestemt(pp_gæl)
self.args[Ltext]['grad'] = self.__tillægsord_gradbøjet(milestones=(-0.6, -0.001, 0, 0.6), værdi=pp_gæl)
self.args[Ltext]['år1_afk'] = self._replace_dot(self.entries['Afkastningsgrad,%1'].get())
self.args[Ltext]['fortjeneste'] = self.__binary_fortjeneste(str(self.entries['Afkastningsgrad,%1'].get()),
str(self.entries['Gældsrente,%1'].get()))
# Da gældsrenten bruges i egenkapitalens f. - opdater den.
self.args['Egenkapitalens forrentning']['adj2'] = self.__påvirkelse(pp_gæl, 'gæl')
def update_ege(self, nr: str):
Ltext = 'Egenkapitalens forrentning'
# Regn ud nu for at spare plads
pp_ege = self._pp('Egenkapitalens forrentning,%', 1, 3)
rp_ege = self._rp('Egenkapitalens forrentning,%')
# Updater arguments
self.args[Ltext]['pp'] = self._replace_dot(pp_ege)
self.args[Ltext]['rp'] = self._replace_dot(rp_ege)
self.args[Ltext]['år1_ege'] = self._float_str(self.entries['Egenkapitalens forrentning,%1'].get())
self.args[Ltext]['år3_ege'] = self._float_str(self.entries['Egenkapitalens forrentning,%3'].get())
self.args[Ltext]['steget'] = self.__binary_stigning_verbum(pp_ege, førdatid=True)
self.args[Ltext]['sub'] = self.__stigning_sub_ubestemt(pp_ege)
self.args[Ltext]['tilfreds'] = self.__binary_tilfredsstillende(self._get('Egenkapitalens forrentning,%3'))
self.args[Ltext]['adj1'] = self.__påvirkelse(self._pp('Afkastningsgrad,%', 1, 3), 'afk')
self.args[Ltext]['adj2'] = self.__påvirkelse(self._pp('Gældsrente,%', 1, 3), 'gæl')
self.args[Ltext]['adj3'] = self.__påvirkelse(self._pp('Gearing,gange', 1, 3), 'gea')
self.args[Ltext]['gæld'] = self.__binary_tjent_på_gæld()
def update_gea(self, nr: str):
Ltext = 'Gearing'
# Regn ud nu for at spare plads
pp_gea = self._pp('Gearing,gange', 1, 3)
rp_gea = self._rp('Gearing,gange')
# Updater arguments
self.args[Ltext]['pp'] = self._replace_dot(pp_gea)
self.args[Ltext]['rp'] = self._replace_dot(rp_gea)
self.args[Ltext]['år1_gea'] = self._float_str(self.entries['Gearing,gange1'].get())
self.args[Ltext]['år3_gea'] = self._float_str(self.entries['Gearing,gange3'].get())
self.args[Ltext]['adj1'] = self.__binary_stigning_verbum(pp_gea, førdatid=True)
self.args[Ltext]['sub'] = self.__stigning_sub_ubestemt(pp_gea)
self.args[Ltext]['grad'] = self.__tillægsord_gradbøjet(milestones=(-0.6, -0.001, 0, 0.6), værdi=pp_gea)
# Da gearingen også bruges i egenkapital - opdater den.
self.args['Egenkapitalens forrentning']['adj3'] = self.__påvirkelse(pp_gea, 'gea')
def update_sol(self, nr: str):
Ltext = 'Soliditetsgrad'
# Regn ud nu for at spare plads
år3_sol = float(self.entries['Soliditetsgrad,%3'].get().replace(',', ''))
pp_sol_13 = self._pp('Soliditetsgrad,%', 1, 3)
pp_sol_12 = self._pp('Soliditetsgrad,%', 1, 2)
pp_sol_23 = self._pp('Soliditetsgrad,%', 2, 3)
# Updater arguments
self.args[Ltext]['fremmedkapital'] = self._replace_dot(round(100 - float(self.entries['Soliditetsgrad,%1'].get().replace(',', '')), 2))
self.args[Ltext]['år1_sol'] = self._float_str(self.entries['Soliditetsgrad,%1'].get())
self.args[Ltext]['år2_sol'] = self._float_str(self.entries['Soliditetsgrad,%2'].get())
self.args[Ltext]['år3_sol'] = self._float_str(self.entries['Soliditetsgrad,%3'].get())
self.args[Ltext]['adj_13'] = self.__binary_stigning_verbum(pp_sol_13, datid=True) # pp forskel mellem år1_sol og år3_sol
self.args[Ltext]['adj_12'] = self.__binary_stigning_verbum(pp_sol_12, datid=True) # pp forskel mellem år1_sol og år2_sol
self.args[Ltext]['adj_23'] = self.__binary_stigning_verbum(pp_sol_23, datid=True) # pp forskel mellem år2_sol og år3_sol
self.args[Ltext]['soliditetshensyn'] = self.__soliditetshensyn()
self.args[Ltext]['yderligere'] = self.__binary_yderligere('Soliditetsgrad,%')
self.args[Ltext]['niveau'] = self.__tillægsord_gradbøjet(milestones=(0, 29, 39), værdi=år3_sol,
tillægsord=(
'uacceptabelt lavt', 'relativt lavt', 'passende',
'højt'))
self.root.update()
def update_lik(self, nr: str):
Ltext = 'Likviditetsgrad'
# Regn ud nu for at spare plads
pp_lik = self._pp('Likviditetsgrad,%', 1, 3)
år3_lik = round(float(self.entries['Likviditetsgrad,%3'].get().replace(',', '')))
# Updater arguments
self.args[Ltext]['år1_lik'] = self._float_str(self.entries['Likviditetsgrad,%1'].get())
self.args[Ltext]['år3_lik'] = self._replace_dot(år3_lik)
self.args[Ltext]['adj1'] = self.__binary_stigning_verbum(pp_lik, førdatid=True)
self.args[Ltext]['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=år3_lik)
self.args[Ltext]['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=år3_lik)
self.args[Ltext]['passende'] = self.__tillægsord_gradbøjet(milestones=(135, 149),
tillægsord=(
'upassende', 'en smule upassende', 'passende'),
værdi=år3_lik)
self.args[Ltext]['tommelfingerregel'] = self.__likviditets_tommelfingerregel(år3_lik)
def update_firmanavn(self):
self.firma = self.firmanavn.get()
self.args['Afkastningsgrad']['firma'] = self.firma
self.args['Soliditetsgrad'] ['firma'] = self.firma
self.args['Soliditetsgrad']['soliditetshensyn'] = self.__soliditetshensyn()
self.args['Likviditetsgrad']['firma'] = self.firma
self.args['Konklusion']['alt_i_alt'] = self.__konklusion_udvikling()
self.args['Indtjeningsevne']['firma'] = self.firma
# display the updates when done updating the values in self.args
root.after(1000, self._display_text)
def _replace_comma(self, string) -> str:
"""Returns s with a dot instead of a comma"""
try:
return string.replace(',', '.', 1)
except AttributeError:
return str(string).replace(',', '.', 1)
def _replace_dot(self, string) -> str:
"""Returns s with a comma instead of a dot"""
try:
return string.replace('.', ',', 1)
except AttributeError:
return str(string).replace('.', ',', 1)
def _punctuate_num(self, string) -> str:
"""Return a large number with punctums"""
return "{:2,}".format(round(float(string.replace(',', '.', 1)), 2)).replace(',', 'X').replace('.', ',').replace('X', '.')
def _rp_between_x_and_y(self, x, y) -> float:
"""Returns procent difference between the number x and y. (x -> y) *growth in procent*
Formula: ((y-x)/abs(x) * 100 - 100) """
try:
return round((y - x) / abs(x) * 100, 2)
except ZeroDivisionError:
return 0.0
def _rp(self, label) -> float:
"""Returns the procent difference between the first and last year of the respective label
Formlen: ((år3-år1)/abs(år1) * 100 - 100) https://www.computerworld.dk/eksperten/spm/726028"""
# Hvis der er et komma erstat det med et punktum
år1 = self.entries[str(label) + '1'].get().replace(',', '')
år3 = self.entries[str(label) + '3'].get().replace(',', '')
try:
return round((float(år3) - float(år1)) / abs(float(år1)) * 100, 2)
except ZeroDivisionError:
return 0.0
def _pp(self, label, x, y) -> float:
"""Returns the procentpoint's difference between the first year (x) and the last year (y)
(første(år3) minus sidste(år1) entry)
eg.
år1 = self.entries[f'{label}1'].get().replace(',', '.', 1)
år3 = self.entries[f'{label}3'].get().replace(',', '.', 1)
return round(float(år3) - float(år1), 2)
"""
# hvis der er et komma erstat det med et punktum
årx = self.entries[str(label) + str(x)].get().strip().replace(',', '')
åry = self.entries[str(label) + str(y)].get().strip().replace(',', '')
return round(float(åry) - float(årx), 2)
def _convert_to_num(self, x : str) -> float: # THIS FUNCTION IS NOT BEING USED
"""Converts whatever num the user might input in the entry
to a useable number that the script can work with"""
x = x.strip()
if re.search('^[0-9]{1,3}(,[0-9]{3})*(\.[0-9]*)?$', x):
return x.replace(',', '')
def _get(self, label, DP=False) -> float:
"""Removes comma and returns the float of the label"""
if DP:
return float(self.DP_entries[label].get().replace(',', ''))
else:
return float(self.entries[label].get().replace(',', ''))
def _DPget_text(self, id : int) -> float:
"""Gets the Du Pont text string giving the id of the text
Removes commas and returns a float of the string"""
return float(self.DPcanvas.itemcget(id, 'text').replace('.', '').replace(',','.'))
def DP_getSymbol(self, n : float) -> str:
"""Returns a symbol depending on the number"""
return ('% ⬆ rp' if n > 0 else
'% ⬇ rp' if n < 0 else '% rp')
def _årsag_afkastningsgrad(self):
"""
Gør brug af optional options entries
Afkastningsg. = (resultat af primær drift + finansielle indtægter) * 100 / gen. aktiver
Finansielle indtægter = (AG * aktiver / 100) - primær drift
AG = Afkastnignsgrad
RaPD = Resutlat af Primær Drift
RfFO = Resultat før Finansielle Omkostninger
fIndt = finansielle indtægter
akt = gennemsnitlige aktiver
"""
# Hvis optioanl options ikke er slået til
if self.iv_gen.get() == False:
return ''
# Udregn resultat af primær drift - skal bruges til udregning af finan. indtægter
RaPD3 = self._get('Resultat af primær drift3')
RaPD1 = self._get('Resultat af primær drift1')
# Udregn finansielle indtægter år1 og år3 - skal bruges til udregning af pp og rp
fIndt3 = (self._get('Afkastningsgrad,%3') * self._get('Gennemsnitlige aktiver3') / 100 - RaPD3)
fIndt1 = (self._get('Afkastningsgrad,%1') * self._get('Gennemsnitlige aktiver1') / 100 - RaPD1)
# Udregn de nødvendige data til at beskrive ændringen i AG
pp_AG = self._pp('Afkastningsgrad,%', 1, 3)
pp_RaPD = self._pp('Resultat af primær drift', 1, 3)
pp_akt = self._pp('Gennemsnitlige aktiver', 1, 3)
pp_fIndt = fIndt3 - fIndt1
pp_RfFO = (fIndt3 + RaPD3) - (fIndt1 + RaPD1)
rp_AG = self._rp('Afkastningsgrad,%')
rp_RaPD = self._rp('Resultat af primær drift')
rp_akt = self._rp('Gennemsnitlige aktiver')
rp_RfFO = self._rp_between_x_and_y(x=RaPD1+fIndt1, y=RaPD3+fIndt3)
rp_fIndt = self._rp_between_x_and_y(x=fIndt1, y=fIndt3)
# Da vi har 3 variabler der skal kommenteres, udregner vi årsagen
# til stigning/faldet nu. For at gøre det mere overskueligt.
if self.iv_gen2.get():
# Vi kender ikke 'resultat af primær drift'
ændring = ''
else:
# Begge er steget/faldet
if (pp_fIndt > 0 and pp_RaPD > 0) or (pp_fIndt < 0 and pp_RaPD < 0):
ændring = ('{sub} i resultatet før finansielle omkostninger skyldes, at både de '
'finansielle indtægter såvel som resultatet af primær drift er {adj1}.').format(
sub='Den positive udvikling' if pp_fIndt > 0 else 'Det negative fald',
adj1=self.__binary_stigning_verbum(pp_fIndt, førdatid=True)
)
# Resultat før finansielle omkostninger (RfFO) er uændret
elif (pp_fIndt == 0 and pp_RaPD == 0):
ændring = 'Årsagen til at resultatet før fiansielle omkostninger er uændret skyldes, {}.'.format(
'at resultat af primær drift samt de fiansielle indtægter ikke har ændret sig perioden' if pp_fIndt == 0
else 'at de fiansielle indtægters stigning har opvejet faldet i resultatet af primær drift' if pp_fIndt > 0
else 'at stigningen i resultatet af primær drift har opvejet faldet i de fiansielle indtægter'# pp_fInd < 0
)
# Den ene er steget/faldet, den anden er neutral
else:
ændring = ('{sub} i resultatet før finansielle omkostninger skyldes, at de finansielle indtægter'
' er {adj1}, imens resultatet af primær drift er {adj2}.').format(
sub='Den positive udvikling' if pp_fIndt > 0 else 'Det negative fald',
adj1=self.__binary_stigning_verbum(pp_fIndt, førdatid=True),
adj2=self.__binary_stigning_verbum(pp_RaPD, førdatid=True)
)
# AG er uændret - år1 til år3 (AG = RfFO * 100 / akt)
if pp_AG == 0:
return '\nÅrsagen til at afkastningsgraden er uændret fra år {år1} til {år3} skyldes overordnet set, at {}. {ændring}'.format(
'de gennemsnitlige aktiver såvel som resultatet før finansielle omkostninger hverken er steget eller faldet' if pp_RfFO == 0
else 'resultatet før finansielle omkostninger samt de gennemsnitlige aktiver er steget med den samme procentvise ændring' if pp_RfFO > 0
else 'resultatet før finansielle omkostninger samt de gennemsnitlige aktiver er faldet med den samme procentvise ændring', # pp_RfFO < 0
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1=self.__binary_stigning_verbum(pp_akt),
adj2=self.__binary_stigning_verbum(pp_RfFO),
ændring=ændring
)
else:
#print('her', int(pp_RfFO), pp_akt)
#print('her', rp_RfFO, rp_akt)
# Både resultat før finansielle omkostninger og akt er steget eller faldet IKKE samme procentvise ændring
if (pp_RfFO > 0 and pp_akt > 0) or (pp_RfFO < 0 and pp_akt < 0):
x = True if pp_RfFO > 0 else False # RfFO og akt er steget - True/False
y = True if abs(rp_RfFO) > abs(rp_akt) else False # abs(RfFO) er steget/faldet procentvis mere end abs(akt) - True/False
print(x, y)
return ('\nDette skyldes at resultatet før finansielle omkostninger er {adj1} procentvis '
'{adj2} i forhold til de gennemsnitlige aktiver, hvilket hermed har resulteret '
'i {sub} i afkastningsgraden fra år {år1} til {år3}. {ændring}').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1='steget' if x else 'faldet',
adj2='mere' if y else 'mindre',
sub='stigningen' if pp_AG > 0 else 'faldet',
ændring=ændring
)
else:
# Udregn adj nu for at spare plads
a = self.__stigning_sub_bestemt(pp_RfFO, 'resultatet før finansielle omkostninger')
b = self.__stigning_sub_bestemt(pp_akt, 'de gennemsnitlige aktiver')
return ('\nÅrsagen til den {grad} i afkastningsgraden fra år {år1} til'
' {år3} skyldes overordnet set {RfFO} og {akt}. {ændring}').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
ændring=ændring,
RfFO=a,
akt='de gennemsnitlige aktiver' if a[0] == b[0] else b,
grad=self.__tillægsord_gradbøjet(milestones=(-5, 0, 4), værdi=pp_AG,
tillægsord=('markante forringelse', 'milde forringelse',
'milde forbedring', 'markante forbedring'))
)
def _årsag_overskudsgrad(self):
"""
Gør brug af optional options entries
Overskudsg. = (resultat af primær drift + finansielle indtægter) * 100 / nettoomsætning
OG = Overskudsgrad
RaPD = resultat af primær drift
RfFO = resultat før finansielle omkostninger
fIndt = fiansielle indtægter
no = NettoOmsætning
"""
# Hvis optioanl options ikke er slået til
if self.iv_gen.get() == False:
return ''
# Start med at udregne nettoomsætningen
no3 = self._get('Aktivernes omsætningshastighed,gange3') * self._get('Gennemsnitlige aktiver3')
no1 = self._get('Aktivernes omsætningshastighed,gange1') * self._get('Gennemsnitlige aktiver1')
print('OMSÆTNING', no1, no3)
# Udregn resultat af primær drift - skal bruges til udregning af finan. indtægter
RaPD3 = self._get('Resultat af primær drift3')
RaPD1 = self._get('Resultat af primær drift1')
# Udregn finansielle indtægter år1 og år3 - skal bruges til udregning af pp og rp
fIndt3 = (self._get('Overskudsgrad,%3') * no3 / 100 - RaPD3)
fIndt1 = (self._get('Overskudsgrad,%1') * no1 / 100 - RaPD1)
# Udregn de nødvendige data til at beskrive ændringen i AG
pp_OG = self._pp('Overskudsgrad,%', 1, 3)
pp_RaPD = self._pp('Resultat af primær drift', 1, 3)
pp_no = no3 - no1
pp_fIndt = fIndt3 - fIndt1
pp_RfFO = (fIndt3 + RaPD3) - (fIndt1 + RaPD1)
rp_OG = self._rp('Overskudsgrad,%')
rp_RaPD = self._rp('Resultat af primær drift')
rp_RfFO = self._rp_between_x_and_y(x=(RaPD1+fIndt1), y=(RaPD3+fIndt3))
rp_no = self._rp_between_x_and_y(x=no1, y=no3)
rp_fIndt = self._rp_between_x_and_y(x=fIndt1, y=fIndt3)
print('finan indtægter', fIndt1, fIndt3)
print('Resultat af primær drift', RaPD1, RaPD3)
print('-testing-> ', rp_RfFO)
# Da vi har 3 variabler der skal kommenteres, udregner vi årsagen
# til stigningen/faldet nu. For at gøre det mere overskueligt.
if self.iv_gen2.get():
# Vi kender ikke 'resultat af primær drift'
ændring = ''
else:
# Begge er steget/faldet
if (pp_fIndt > 0 and pp_RaPD > 0) or (pp_fIndt < 0 and pp_RaPD < 0):
ændring = ('{sub} i resultatet før finansielle omkostninger skyldes, at både de '
'finansielle indtægter samt resultatet af primær drift er {adj1}.').format(
sub='Den positive udvikling' if pp_fIndt > 0 else 'Det negative fald',
adj1=self.__binary_stigning_verbum(pp_RfFO, førdatid=True)
)
# Resultat før finansielle omkostninger (RfFO) er uændret
elif pp_RfFO == 0:
ændring = 'Årsagen til at resutlatet før fiansielle omkostninger er uændret skyldes, {}.'.format(
'at resultat før primær drift samt de fiansielle indtægter ikke har ændret sig perioden' if pp_fIndt == 0
else 'at de fiansielle indtægters stigning har opvejet faldet i resultatet af primær drift' if pp_fIndt > 0
else 'at stigningen i resultatet af primær drift har opvejet faldet i de fiansielle indtægter'
# pp_fInd < 0
)
# Den ene er steget/faldet, den anden er neutral
else:
ændring = ('{sub} i resultatet før finansielle omkostninger skyldes, at de finansielle indtægter'
' er {adj1}, imens resultatet af primær drift er {adj2}.').format(
sub='Den positive udvikling' if pp_fIndt > 0 else 'Det negative fald',
adj1=self.__binary_stigning_verbum(pp_fIndt, førdatid=True),
adj2=self.__binary_stigning_verbum(pp_RaPD, førdatid=True)
)
# OG er uændret - år1 til år3
if pp_OG == 0:
return '\nÅrsagen til at overskudsgraden er uændret fra år {år1} til {år3} skyldes overordnet set, at {}. {ændring}'.format(
'nettoomsætningen såvel som resultatet før finansielle omkostninger er uændret' if pp_RfFO == 0
else 'nettoomsætningen samt resultatet før finansielle omkostninger er steget med den samme procentvise ændring' if pp_RfFO > 0
else 'nettoomsætningen samt resultatet før finansielle omkostninger er faldet mede den samme procentvise ændring',
# pp_RfFO < 0
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1=self.__binary_stigning_verbum(pp_no),
adj2=self.__binary_stigning_verbum(pp_RfFO),
ændring=ændring
)
else:
print('HER __--> ', rp_RfFO, rp_no)
print(RaPD3, fIndt3, RaPD1, fIndt1)
# Både "RfFO" og "no" er steget eller faldet - IKKE samme procentvise ændring
if (pp_RfFO > 0 and pp_no > 0) or (pp_RfFO < 0 and pp_no < 0):
x = True if pp_RfFO > 0 else False # RfFO og no er steget - True/False
y = True if abs(rp_RfFO) > abs(rp_no) else False # abs(RfFO) er steget mere end abs(no) - True/False
return ('\nDette skyldes at resultatet før finansielle omkostninger er {adj1} procentvis '
'{adj2} i forhold til nettoomsætningen, hvilket hermed har resulteret '
'i {sub} i overskudsgraden fra år {år1} til {år3}. {ændring}').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1='steget' if x else 'faldet',
adj2='mere' if y else 'mindre',
sub='stigningen' if pp_OG > 0 else 'faldet',
ændring=ændring
)
else:
# Udregn adj nu for at spare plads
a = self.__stigning_sub_bestemt(pp_RfFO, 'resultatet før finansielle omkostninger')
b = self.__stigning_sub_bestemt(pp_no, 'nettoomsætningen')
return ('\nÅrsagen til den {grad} i overskudsgraden fra år {år1} til'
' {år3} skyldes overordnet set {RfFO} og {no}. {ændring}').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
ændring=ændring,
RfFO=a,
no='nettoomsætningen' if a[0] == b[0] else b,
grad=self.__tillægsord_gradbøjet(milestones=(-5, 0, 4), værdi=pp_OG,
tillægsord=('markante forringelse', 'milde forringelse',
'milde forbedring', 'markante forbedring'))
)
def _årsag_aktivernes_omsætningshastighed(self):
"""
Gør brug af optional options entries
aktivernes omsætningsh. = nettoomsætning / gen. aktiver
AOH = aktivernes omsætningshastighed
no = NettoOmsætning
akt = gennemsnitlige aktiver
"""
# Hvis optional options ikke er slået til
if self.iv_gen.get() == False:
return ''
# Udregn nettoomsætning år1 og år3 - skal bruges til udregning af pp og rp
no3 = (self._get('Aktivernes omsætningshastighed,gange3') * self._get('Gennemsnitlige aktiver3'))
no1 = (self._get('Aktivernes omsætningshastighed,gange1') * self._get('Gennemsnitlige aktiver1'))
# Udregn de nødvendige data til at beskrive ændringen i AOH
pp_AOH = self._pp('Aktivernes omsætningshastighed,gange', 1, 3)
pp_akt = self._pp('Gennemsnitlige aktiver', 1, 3)
pp_no = no3 - no1
rp_AOH = self._rp('Aktivernes omsætningshastighed,gange')
rp_akt = self._rp('Gennemsnitlige aktiver')
rp_no = self._rp_between_x_and_y(x=no1, y=no3)
# Aktivernes omsætningsh. er uændret - år1 til år3
if pp_AOH == 0:
return '\nÅrsagen til at aktivernes omsætningshastighed er uændret fra år {år1} til {år3} skyldes, at {}.'.format(
('både nettoomsætningen og de gennemsnitlige aktiver ikke har ændret sig' if pp_no == 0
else 'nettoomsætningen og de gennemsnitlige aktiver er steget med den samme procentvise ændring' if pp_no > 0
else 'nettoomsætningen og de gennemsnitlige aktiver er faldet med den samme procentvise ændring'), # pp_no < 0
**{k: år.get() for k, år in self.entriesÅr.items()}
)
else:
# Både nettoomsætning og akt er steget eller faldet IKKE samme procentvise ændring
if (pp_no > 0 and pp_akt > 0) or (pp_no < 0 and pp_akt < 0):
x = True if pp_no > 0 else False # no og akt er steget - True/False
y = True if abs(rp_no) > abs(rp_akt) else False # abs(no) er steget mere end abs(akt) - True/False
return ('\nDette skyldes at nettoomsætningen er {adj1} procentvis {adj2} i forhold til'
' de gennemsnitlige aktiver, hvilket hermed har resulteret i {sub} i'
' aktivernes omsætningshastighed fra år {år1} til {år3}.').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1='steget' if x else 'faldet',
adj2='mere' if y else 'mindre',
sub='stigningen' if pp_AOH > 0 else 'faldet'
)
else:
# Udregn adj nu for at spare plads
a = self.__stigning_sub_bestemt(pp_no, 'nettoomsætning')
b = self.__stigning_sub_bestemt(pp_akt, 'de gennemsnitlige aktiver')
return ('\nÅrsagen til den {grad} i aktivernes omsætningshastighed fra år {år1} til'
' {år3} skyldes overordnet set {no} og {akt}.').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
no=a,
akt='de gennemsnitlige aktiver' if a[0] == b[0] else b,
grad=self.__tillægsord_gradbøjet(milestones=(-0.6, 0, 0.6), værdi=pp_AOH,
tillægsord=('markante forringelse', 'milde forringelse',
'milde forbedring', 'markante forbedring'))
)
def _årsag_gældsrente(self):
"""
Gør brug af optional options entries
Gældsrente = finansielle omkostn. * 100 / gen. forpligtelser
"""
# Hvis optional options ikke er slået til
if self.iv_gen.get() == False:
return ''
# Udregn finansielle omkostninger år1 og år3 - skal bruges til udregning af pp og rp
fOmk3 = (self._get('Gældsrente,%3') * self._get('Gennemsnitlige forpligtelser3') / 100)
fOmk1 = (self._get('Gældsrente,%1') * self._get('Gennemsnitlige forpligtelser1') / 100)
# Udregn de nødvendige data til at beskrive årsagen til ændringen i gældsrenten
pp_forpl = self._pp('Gennemsnitlige forpligtelser', 1, 3)
pp_gæl = self._pp('Gældsrente,%', 1, 3)
pp_fOmk = fOmk3 - fOmk1
rp_forpl = self._rp('Gennemsnitlige forpligtelser')
rp_gæl = self._rp('Gældsrente,%')
rp_fOmk = self._rp_between_x_and_y(x=fOmk1, y=fOmk3)
# Til debugging
# print(f'Gældsrente: {pp_gæl} pp\nFiansielle omk: {pp_fOmk} pp\nForpligtelser: {pp_forpl} pp')
# print('gæld:', rp_gæl, '% fOmk:', rp_fOmk, '% forpl:', rp_forpl, '%')
# print(f'fOmk: {rp_fOmk}% rp, forpl: {rp_forpl}% rp')
# print()
# Gældsrenten er uændret - år1 til år3
if pp_gæl == 0:
return '\nÅrsagen til at gældsrenten er uændret fra år {år1} til {år3} skyldes, at {}.'.format(
('både de finansielle omkostninger såvel som de gennemsnitlige forpligtelser ikke har ændret sig' if pp_forpl == 0
else 'de fiansielle omkostninger og de gennemsnitlige forpligtelser er steget med den samme procentvise ændring' if pp_forpl > 0
else 'de fiansielle omkostninger og de gennemsnitlige forpligtelser er faldet med den samme procentvise ændring'), # pp_forpl < 0
**{k: år.get() for k, år in self.entriesÅr.items()},
)
else:
# Både fOmk og forpl er steget eller faldet
if (pp_fOmk > 0 and pp_forpl > 0) or (pp_fOmk < 0 and pp_forpl < 0):
x = True if pp_fOmk > 0 else False # fOmk og forpl er steget - True/False
y = True if abs(rp_fOmk) > abs(
rp_forpl) else False # abs(fOmk) er steget mere end abs(forpl) - True/False
return ('\nDette skyldes at de finansielle omkostninger er {adj1} procentvis {adj2} i forhold til'
' de gennemsnitlige forpligtelser, hvilket hermed har resulteret i {sub} i gældsrenten fra '
'år {år1} til {år3}.').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1='steget' if x else 'faldet',
adj2='mere' if y else 'mindre',
sub='stigningen' if pp_gæl > 0 else 'faldet'
)
else:
# udregn adj nu for at spare plads
a = self.__stigning_sub_bestemt(pp_fOmk, 'de finansielle omkostninger')
b = self.__stigning_sub_bestemt(pp_forpl, 'de gennemsnitlige forpligtelser')
return ('\nÅrsagen til den {grad} i gældsrente fra år {år1} til {år3} skyldes overordnet set {fOmk} '
'og {forp}.').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
fOmk=a,
forp='gennemsnitlige forpligtelser' if a[0] == b[0] else b, # hvis a og b begge stiger eller falder
grad=self.__tillægsord_gradbøjet(milestones=(-0.6, 0, 0.6), værdi=pp_gæl,
tillægsord=('markante forringelse', 'milde forringelse',
'milde forbedring', 'markante forbedring'))
)
def _årsag_egenkapitals_forrentning(self):
"""
Gør brug af optional options entries
egenkapitlens for. = resultat før skat * 100 / gen. egenkapital
egeF = Egenkapitalens forrentning
ege = gennemsnitlig egenkapital
rfs = resultat før skat
"""
# Hvis optional options ikke er slået til
if self.iv_gen.get() == False:
return ''
# Udregn resultat før skat år1 og år3 - skal bruges til udregning af pp og rp
rfs3 = (self._get('Egenkapitalens forrentning,%3') * self._get('Gennemsnitlig egenkapital3') / 100)
rfs1 = (self._get('Egenkapitalens forrentning,%1') * self._get('Gennemsnitlig egenkapital1') / 100)
# Udregn de nødvendige data til at beskrive ændringen i egeF
pp_egeF = self._pp('Egenkapitalens forrentning,%', 1, 3)
pp_ege = self._pp('Gennemsnitlig egenkapital', 1, 3)
pp_rfs = rfs3 - rfs1
rp_egeF = self._rp('Egenkapitalens forrentning,%')
rp_ege = self._rp('Gennemsnitlig egenkapital')
rp_rfs = self._rp_between_x_and_y(x=rfs1, y=rfs3)
# Egenkapitalens for. er uændret - år1 til år3
if pp_egeF == 0:
return '\nÅrsagen til at egenkapitelens forrentning er uændret fra år {år1} til {år3} skyldes, at {}.'.format(
('både resultatet før skat og den gennemsnitlig egenkapital ikke har ændret sig' if pp_rfs == 0
else 'resultatet før skat og den gennemsniltlig egenkapital er steget med den samme procentvise ændring' if pp_rfs > 0
else 'resultatet før skat og den gennemsniltlig egenkapital er faldet med den samme procentivse ændring'), # pp_rfs < 0
**{k: år.get() for k, år in self.entriesÅr.items()}
)
else:
# Både rfs og ege er steget eller faldet
if (pp_rfs > 0 and pp_ege > 0) or (pp_rfs < 0 and pp_ege < 0):
x = True if pp_rfs > 0 else False # rfs og ege er steget - True/False
y = True if abs(rp_rfs) > abs(rp_ege) else False # abs(rfs) er steget mere end abs(ege) - True/False
return ('\nDette skyldes at resultatet før skat er {adj1} procentvis {adj2} i forhold til'
' den gennemsnitlige egenkapital, hvilket hermed har resulteret i {sub} i'
' egenkapitalens forrentning fra år {år1} til {år3}.').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1='steget' if x else 'faldet',
adj2='mere' if y else 'mindre',
sub='stigningen' if pp_egeF > 0 else 'faldet'
)
else:
# Udregn adj nu for at spare plads
a = self.__stigning_sub_bestemt(pp_rfs, 'resultatet før skat')
b = self.__stigning_sub_bestemt(pp_ege, 'den gennemsitlig egenkapital')
return ('\nÅrsagen til den {grad} i egenkapitalens forrentning fra år {år1} til {år3}'
' skyldes overordnet set {rfs} og {ege}.').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
rfs=a,
ege='den gennemsnitlig egenkapital' if a[0] == b[0] else b,
grad=self.__tillægsord_gradbøjet(milestones=(-0.6, 0, 0.6), værdi=pp_egeF),
tillægsord=('mrakante forringelse', 'milde forringelse',
'milde forbedring', 'markante forbedring')
)
def _årsag_gearing(self):
"""
Gør brug af optional options entries
Gearing = gen. forpligtelser / gen. egenkapital
"""
# Hvis optional options ikke er slået til
if self.iv_gen.get() == False:
return ''
# Udregn de nødvendige data til at beskrive ændringen i gearingen
pp_gea = self._pp('Gearing,gange', 1, 3)
pp_forpl = self._pp('Gennemsnitlige forpligtelser', 1, 3)
pp_ege = self._pp('Gennemsnitlig egenkapital', 1, 3)
rp_gea = self._rp('Gearing,gange')
rp_forpl = self._rp('Gennemsnitlige forpligtelser')
rp_ege = self._rp('Gennemsnitlig egenkapital')
# Gearing er uændret - år1 til år3
if pp_gea == 0:
return '\nÅrsagen til den uændrede gearing fra år {år1} til {år3} skyldes, at {}.'.format(
('både den gennemsnitlig egenkapital såvel som de gennemsnitlige forpligtelser ikke har ændret sig' if pp_ege == 0
else 'den gennemsnitlig egenkapital og de gennemsnitlige forpligtelser er steget med den samme procentvise ændring' if pp_ege > 0
else 'den gennemsnitlig egenkapital og de gennemsnitlige forpligtelser er faldet med den samme procentvise ændring'), # pp_forpl < 0
**{k: år.get() for k, år in self.entriesÅr.items()})
else:
# Både ege og forpl er steget eller faldet
if (pp_ege > 0 and pp_forpl > 0) or (pp_ege < 0 and pp_forpl < 0):
x = True if pp_ege > 0 else False # ege og forpl er steget - True/False
y = True if abs(rp_ege) > abs(rp_forpl) else False # abs(ege) er steget mere end abs(forpl) - True/False
return ('\nDette skyldes at den gennemsnitlig egenkapital er {adj1} procentvis {adj2} i forhold '
'til de gennemsnitlige forpligtelser, hvilket hermed har resulteret i {sub} i gearingen fra '
'år {år1} til {år3}.').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
adj1='steget' if x else 'faldet',
adj2='mere' if y else 'mindre',
sub='stigningen' if pp_gea > 0 else 'faldet'
)
else:
# Udregn adj nu for at spare plads
a = self.__stigning_sub_bestemt(pp_ege, 'den gennemsnitlig egenkapital')
b = self.__stigning_sub_bestemt(pp_forpl, 'de gennemsnitlige forpligtelser')
return ('\nÅrsagen til den {grad} i gearing fra år {år1} til {år3} skyldes overordnet set'
' {ege} og {forp}').format(
**{k: år.get() for k, år in self.entriesÅr.items()},
ege=a,
forp=b,
grad=self.__tillægsord_gradbøjet(milestones=(-0.6, 0, 0.6), værdi=pp_gea,
tillægsord=('markante forringelse', 'milde forringelse',
'milde forbedring', 'markante forbedring'))
)
def _firmanavn_ejefald(self, firma):
"""Returns firmanavn i ejefald"""
try:
lastLetter = firma[-1].lowercase()
if (lastLetter in 'abcdefghijklmnopqrtuvwy'):
return firma + 's'
else: #(lastLetter == 's') or (lastLetter == 'x') or (lastLetter == 'z') or (lastLetter not in 'abcdefghijklmnopqrtuvwy')
return firma + '\''
except IndexError:
# len(firma) == 0
return 'virksomhedens'
def __binary_alleÅr(self, Ltext) -> str:
"""Checker om nøgletallet stiger/falder hvert eneste år i forhold til det seneste"""
år1 = float(self.entries[str(Ltext) + '1'].get().replace(',', ''))
år2 = float(self.entries[str(Ltext) + '2'].get().replace(',', ''))
år3 = float(self.entries[str(Ltext) + '3'].get().replace(',', ''))
if år1 > år2 > år3:
return 'Forringelsen er sket over alle analyseårene. '
elif år1 < år2 < år3:
return 'Forbedringen er sket over alle analyseårene. '
else:
return ''
def __binary_stigning_verbum(self, pp, datid=False, førdatid=False) -> str:
"""Vælger et negativ eller positiv tillægsord alt efter differencen ved pp til henholdsvis år 1 og 3"""
if førdatid:
if pp > 0:
return 'steget'
elif pp == 0:
return 'uændret'
else:
return 'faldet'
else: # Datid
if pp > 0:
return 'steg'
elif pp == 0:
return 'fortsat'
else:
return 'faldt'
def __binary_ligeledes(self):
"""Return ligeledes hvis aktivernes omsætningshastighed er steget/faldet ligesom overskudsgraden"""
pp_ove = self._pp('Overskudsgrad,%', 1, 3)
pp_oms = self._pp('Aktivernes omsætningshastighed,gange', 1, 3)
# Begge er forbedret eller forringet
if (pp_ove > 0 and pp_oms > 0) or (pp_ove < 0 and pp_oms < 0):
return 'ligeledes '
else:
return ''
def __soliditetshensyn(self):
"""Return en kommentar med henhold til en soliditetshensyn."""
år3_sol = float(self.entries['Soliditetsgrad,%3'].get().replace(',', ''))
# solidiatetsgraden bør udgøre mindst 30 til 40 %
if år3_sol < 25:
return ("Ud fra et soliditetshensyn er soliditetsgraden derfor utilfredsstillende, da virksomheden kun kan tåle at tabe op til {år3_sol}% af aktivernes værdi før långiverne lider tab. "
"Dette er for {firmanavn} ikke godt, da det medfører at långiverne vil kræve en høj rente, for at opveje den høje risiko långiverne påtager sig ved at låne til virksomheden. ").format(firmanavn=self.firma, år3_sol=år3_sol)
elif 25 <= år3_sol < 30:
return ("Ud fra et soliditetshensyn er soliditetsgraden i {firmanavn} derfor rimelig utilfredsstillende, da virksomheden kun kan tåle at tabe op til {år3_sol}% af aktivernes værdi før långiverne lider tab. "
"Dette er for {firmanavn} ikke godt, da det medfører at långiverne vil kræve en højere rente, for at opveje den forøgede risiko långiverne påtager sig ved at låne til virksomheden. ").format(firmanavn=self.firma, år3_sol=år3_sol)
elif 30 <= år3_sol < 40:
return ("Ud fra et soliditetshensyn er soliditetsgraden derfor tilfredsstillende, da virksomheden kan tåle at tabe op til {år3_sol}% af aktivernes værdi før långiverne lider tab. "
"Det er for {firmanavn} godt at de har en relativ høj soliditetsgrad, da det medfører at långiverne er villige til at yde {firmanavn} lån med lav rente, "
"da en høj soliditetsgrad giver långiverne en høj sikkerhed for at lånet bliver tilbagebetalt. ").format(firmanavn=self.firma, år3_sol=år3_sol)
elif 40 <= år3_sol:
return ("Ud fra et soliditetshensyn er soliditetsgraden derfor yderst tilfredsstillende, da virksomheden kan tåle at tabe op til {år3_sol}% af aktivernes værdi før långiverne lider tab. "
"Det er for {firmanavn} godt at de har en høj soliditetsgrad, da det medfører at långiverne er villige til at yde {firmanavn} lån med lav rente, "
"da den høje soliditetsgrad giver långiverne en høj sikkerhed for at lånet bliver tilbagebetalt. ").format(firmanavn=self.firma, år3_sol=år3_sol)
def __stigning_sub_ubestemt(self, pp) -> str:
if pp > 0:
return 'en stigning'
elif pp == 0:
return 'en ændring'
else:
return 'et fald'
def __stigning_sub_bestemt(self, pp, label) -> str:
""" Important - specialt lavet til 'årsag' formatting """
if pp > 0:
return 'stigningen i {}'.format(label)
elif pp == 0:
return 'at {} ikke har ændret sig'.format(label)
else:
return 'faldet i {}'.format(label)
def __likviditets_tommelfingerregel(self, år3_lik):
"""returns tommelfingerregel hvis likviditeten er under 150% """
if år3_lik < 150:
return ', da men som en tommelfingerregel siger, at likviditetsgraden bør udgøre mindst 150%. '
else:
return '. '
def __binary_forbedret(self, pp) -> str:
if pp > 0:
return 'forbedret'
elif pp == 0:
return 'uændret'
else:
return 'forringet'
def __binary_tilfredsstillende(self, nøgletal) -> str:
"""Returns tilfreds. eller utilfreds. alt efter om nøgletallet (år3) er større end
markedsrenten + et risikotillæg svarende til 2%"""
# grænse = markedsrente + risikotillæg(=2%)
grænse = float(self.mrEntry.get().replace(',','')) + 2
if nøgletal >= grænse:
return 'tilfredsstillende'
else:
return 'utilfredsstillende'
def __binary_påvirkelse(self, pp) -> str:
if pp > 0:
return 'har påvirket afkastningsgraden positivt'
elif pp == 0:
return 'har således ikke har påvirket afkastningsgraden'
else:
return 'har påvirket afkastningsgraden negativt'
def __binary_yderligere(self, Ltext):
"""Returns yderligere hvis alle entries sammenlignet med det eftefølgende år er stigende, eller
hvis alle entries sammenlignet med det efterfølgende år er faldende. """
år1 = float(self.entries[Ltext + '1'].get().replace(',', ''))
år2 = float(self.entries[Ltext + '2'].get().replace(',', ''))
år3 = float(self.entries[Ltext + '3'].get().replace(',', ''))
if år1 > år2 > år3 or år1 < år2 < år3:
return 'yderligere '
else:
return ''
def __binary_fortjeneste(self, år1_afk: str, år1_gæl: str) -> str:
"""Should only be used for 'gældsrente' text widget
## forskel = år1_afk - år1_gæl """
forskel = round((float(år1_gæl.replace(',','')) - float(år1_afk.replace(',','')) * -1), 2)
if forskel > 0:
return 'Det medfører, at der er opnået en fortjeneste på {} øre pr. lånte krone.'.format(forskel)
elif forskel == 0:
return 'Det resulterer i, at fortjenesten pr. lånte krone er 0.'
else:
return 'Det medfører, at der har været et tab på {} øre pr. lånte krone.'.format(forskel)
def __tillægsord_gradbøjet(self, milestones: tuple, værdi: float, tillægsord=('markant forringet', 'mildt forringet', 'uændret', 'mildt forbedret', 'markant forbedret')):
"""Vælger et gradbøjet tillægsord alt efter givne milestones, værdi og tillægsord.
## Eksempel:
## tillægsord = ['markant forringet', 'mildt forringet', 'mildt forbedret', 'markant forbedret']
## milestones = [ -5, 0, 4, ] # værdi er fx pp_difference (år3 - år1)
"""
index = bisect.bisect_left(milestones, værdi)
return tillægsord[index]
def __påvirkelse(self, pp, nøgletal):
""" Tilegnet 'egenkapitalens forrentning' """
if (nøgletal == 'afk') or (nøgletal == 'indtjeningsevne'):
if pp > 0:
return 'positivt'
elif pp == 0:
return 'neutralt'
else:
return 'negativt'
elif nøgletal == 'gæl':
if pp > 0:
return 'påvirket negativt af stigningen i'
elif pp == 0:
return 'upåvirket af'
else:
return 'påvirket positivt af faldet i'
else: # Gearing
if pp > 0:
return 'positivt påvirket af stigningen i'
elif pp == 0:
return 'upåvirket af'
else:
return 'negativt påvirket af faldet i'
def __konklusion_udvikling(self):
"""
kapitaltilpasningsevne (aktivernes omsætningshastighed)
Indtjeningsevne (overskudsgrad)
"""
pp_afk = self._pp('Afkastningsgrad,%', 1, 3)
pp_ove = self._pp('Overskudsgrad,%', 1, 3)
pp_oms = self._pp('Aktivernes omsætningshastighed,gange', 1, 3)
if 4 > pp_afk:
string = 'Alt i alt viser rentabiliteten i {firma} et stort tilbagefald. Tilbagefaldet skyldes '
elif 0 > pp_afk > 4:
string = 'Alt i alt viser rentabiliteten i {firma} et svagt tilbagefald. Tilbagefaldet skyldes '
elif pp_afk == 0:
string = 'Alt i alt er rentabiliten i {firma} uændret. Dette skyldes overordnet set '
elif 4 >= pp_afk > 0:
string = 'Alt i alt viser rentabiliteten i {firma} en svag stigning. Stigningen skyldes '
elif pp_afk > 4:
string = 'Alt i alt viser rentabiliteten i {firma} en stærk stigning. Stigningen skyldes '
string = string.format(firma=self.firmanavn.get())
if (pp_oms > 0 and pp_ove > 0): # begge er steget
string += 'at både kapitaltilpasningsevnen og indtjeningsevnen er forbedret.'
elif (pp_oms < 0 and pp_ove < 0): # begge er faldet
string += 'at både kapitaltilpasningsevnen og indtjeningsevnen er forværret.'
elif (pp_oms > 0 and pp_ove < 0): # Den ene er steget den anden er faldet
if pp_afk > 0:
string += ('den forbedrede kapitaltilpasningsevne, mens indtjeningsevnen isoleret set forringer rentabiliteten. '
'Samlet set er forbedringen i kapitaltilpasningsevnen (aktivernes omsætningshastighed) procentuelt '
'større end den procentuelle forringelse af indtjeningsevnen (overskudsgraden), hvorved '
'rentabiliteten (afkastningsgraden) samlet set bliver forbedret.')
elif pp_afk < 0:
string += ('den forringede indtjeningsevne, mens kapitaltilpasningsevnen isoleret set forbedrer rentabiliteten. '
'Samlet set er forringelsen i indtjeningsevnen (overskudsgraden) procentuelt større end den '
'procentuelle forbedring i kapitaltilpasningsevnen (aktivernes omsætningshastighed), hvorved '
'rentabiliteten (afkastningsgraden) samlet set bliver forværret.')
else: # pp_afk == 0
string += ', at den forbedrede kapitaltilpasningsevne har opvejet den forringede indtjeningsevnen.'
elif (pp_oms == 0 and pp_ove > 0): # Den ene er uændret den anden er steget
string += 'den forbedrede indtjeningsevne, imens kapitaltilpasningsevnen er forblevet den samme.'
elif (pp_oms > 0 and pp_ove == 0):
string += 'den forbedrede kapitaltilpasningsevne, imens indtjeningsevnen er forblevet den samme.'
elif (pp_oms == 0 and pp_ove < 0): # Den ene er uændret den anden er faldet
string += 'den forværrede indtjeningsevne, imens kapitaltilpasningsevnen er forblevet den samme.'
elif (pp_oms < 0 and pp_ove == 0):
string += 'den forværrede kapitaltilpasningsevne, imens indtjeningsevnen er forblevet den samme.'
elif (pp_oms == 0 and pp_ove == 0): # Begge er uændrede
string += 'at både kapitaltilpasnignsevnen såvel som indtjeningsevnen ikke har ændret.'
return string
def __binary_likviditetsgrad(self, pp_lik) -> str:
år3 = self.entriesÅr['år3'].get()
if pp < 0:
return 'På trods af faldet ser den likviditetsmæssige situation ganske fornuftig ud ved udgangen af {}'.format(år3)
else:
return 'Den likviditetsmæssige situation i virksomheden ser derfor ganske fornuftig ud ved udgangen af {}'.format(år3)
def __binary_tjent_på_gæld(self, år3_ege=None, år3_afk=None, år3_gea=None):
år3_ege = float(self.entries['Egenkapitalens forrentning,%3'].get().replace(',', ''))
år3_afk = float(self.entries['Afkastningsgrad,%3'].get().replace(',', ''))
år3_gea = float(self.entries['Gearing,gange3'].get().replace(',', ''))
if år3_ege > år3_afk:
return 'hvilket hermed har resulteret i, at virksomheden har tjent på at arbejde med gæld. '
if år3_ege < år3_afk:
return 'hvilket hermed har resulteret i, at virksomheden har tabt på at arbejde med gæld. '
if år3_ege == år3_afk:
return 'Hvilket hermed har resulteret i, at virksomheden hverken har tjent eller tabt på at arbejde med gæld.'
def _display_text(self):
"""writes 'self.analyse' text to their respective text widgets"""
i = 1
for nøgletal in self.nøgletal_labels:
self.textWidgets['text' + str(i)].delete(1.0, END)
self.textWidgets['text' + str(i)].insert(END, self.string_templates[nøgletal].format(**self.args[nøgletal]))
i += 2
self.__resize_textWidget_height()
def setup_logging():
"""Setup for logging happens here"""
logging.basicConfig(filename='VøBot.log', level=logging.INFO, format='%(asctime)s %(message)s')
logger = logging.getLogger(__name__)
logger.disabled = True
logging.info('-----> New log started <-----')
if __name__ == '__main__':
setup_logging()
root = Tk()
root.title('VøBot')
root.geometry('{}x{}'.format(850, 650))
# Init main window og withdraw (skjul) den og kør sponsorwindow
# efter 'adDuration' forsvinder sponsorwindow og main window viser sig
MainApp = Nøgletal_Analyse(root)
sponserW_instance = SponserWindow(root, adDuration=10)
try:
root.iconbitmap(bitmap=os.getcwd() + '/logo.ico')
except:
pass
root.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment