diff --git a/AGREEmip_app.py b/AGREEmip_app.py new file mode 100644 index 0000000000000000000000000000000000000000..7ea9a51f9dd9e529c98d01dad0289094e7ab44dd --- /dev/null +++ b/AGREEmip_app.py @@ -0,0 +1,1146 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Apr 8 19:07:35 2024 + +@author: W.Wojnowski +""" +# imports: +import tkinter as tk +from tkinter import ttk +from tkinter import messagebox +import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from matplotlib.colors import LinearSegmentedColormap +from tkinter import filedialog +from math import log +from fpdf import FPDF +import os +from datetime import datetime +import numpy as np + +''' +-------------------------------- +''' + +# start the main app +root = tk.Tk() +root.title('AGREEmip - Analytical Greenness Metric for MIPs') +root.geometry('800x490') +root.iconbitmap('AGREEmip_icon.ico') + +# fix the window dimensions: +root.resizable(0, 0) + +# define the color palette. This can be based on https://flatuicolors.com +colors = { + 'foreground': '#ffffff', + 'background': '#dfe4ea', + # 'background': '#dfe4ea', + 'accent': '#70a1ff', + # 'accent': '#9b59b6', + 'text': 'black' +} + +# configure the background: +root.configure(bg=colors['background'], padx=8, pady=8) + +# create and configure a style to be used in tabs and labels +s = ttk.Style() + + +s.theme_create("AGREEmip_theme", parent="alt", settings={ + "TNotebook": { + "configure": { + 'background': colors['background'], + 'borderwidth': 0, + 'tabmargins': [0, 0, 0, 0], + 'padding': [0, 0, 5, 0], + 'relief': 'flat'}}, + "TNotebook.Tab": { + "configure": { + 'padding': [7, 1], + 'background': colors['accent'], + 'foreground': colors['text'], + # change the colour of a dashed frame around the title of the + # selected frame to the foreground colour, so that it is not visible: + 'focuscolor': colors['foreground'], + 'borderwidth': 0, + 'bordercolor': 'red'}, + "map": { + "background": [("selected", colors['foreground'])], + "expand": [("selected", [1, 1, 1, 0])]}}, + 'TLabel': { + 'configure': { + 'background': colors['foreground'], + 'foreground': colors['text'], + 'padding': [5, 5, 5, 5]}}, + 'TFrame': { + 'configure': { + 'background': colors['foreground'], + 'borderwidth': 0}, + "map": { + "background": [("focus", colors['accent']), + ("pressed", colors['accent']), + ("disabled", colors['accent']), + ("readonly", colors['accent']), + ], + "relief": [("focus", 'sunken'), + ("pressed", 'sunken'), + ("disabled", 'sunken'), + ("readonly", 'sunken'), + ]}}, + 'TEntry': { + 'configure': { + 'background': 'red', + 'fieldbackground': colors['background'], + 'foreground': colors['text'], + 'insertcolor': colors['text'], + 'selectbackground': colors['accent'], + }}, + 'TButton': { + 'configure': { + 'background': colors['accent'], + 'focuscolor': colors['accent'], + 'foreground': colors['text'], + 'anchor': 'center'}, + 'map': { + 'relief': [('pressed', 'groove')] + }}, + 'TRadiobutton': { + 'configure': { + 'focuscolor': colors['foreground'], + 'background': colors['foreground'], + 'foreground': colors['text'], + 'indicatorcolor': colors['background']}, + "map": { + "background": [("active", colors['accent']), + ("pressed", colors['accent']), + ("disabled", colors['accent'])], + "foreground": [("active", colors['text'])], + 'indicatorcolor': [('selected', colors['accent'])] + }}, + 'TCheckbutton': { + 'configure': { + 'focuscolor': colors['foreground'], + 'background': colors['background'], + 'foreground': colors['text'], + 'focusthickness': 0, + 'indicatorcolor': colors['background'], + 'padding': 0,}, + "map": { + "background": [("active", colors['accent']), + ("pressed", colors['accent']), + ("disabled", colors['accent']), + ], + 'focuscolor': [("active", colors['accent']), + ("pressed", colors['background']), + ("disabled", colors['background']), + ('selected', colors['accent']), + ('alternate', colors['background']), + ('readonly', colors['background']), + ], + "foreground": [("active", colors['text'])], + "padding": [('active', 0), + ('alternate', 0), + ('pressed', 0), + ('disabled', 0), + ('selected', 0), + ('readonly', 0)], + 'focusthickness': [('pressed', 0)], + 'indicatorcolor': [('selected', colors['accent'])] + }}, + 'TMenubutton': { + 'configure': { + 'background': colors['accent'], + 'foreground': colors['text'], + 'width': '20' + }}, + 'TMenu': { + 'configure': { + 'background': colors['accent'], + 'foreground': colors['text'], + 'width': '20', + 'arrowcolor': colors['text'], + }}, + + 'Vertical.TScrollbar': { + 'configure': { + 'background': colors['accent'], + 'troughcolor': colors['foreground'], + 'foreground': colors['foreground'], + 'arrowcolor': colors['text'], + 'relief': 'flat', + 'borderwidth': 2, + }}, + +}) + + +s.theme_use("AGREEmip_theme") + +# to remove relief from the TEntry widget +# based on https://stackoverflow.com/questions/44383730/ +s.layout('AGREEmip_theme', [ + ('Entry.highlight', { + 'sticky': 'nswe', + 'children': + [('Entry.border', { + 'border': '1', + 'sticky': 'nswe', + 'children': + [('Entry.padding', { + 'sticky': 'nswe', + 'children': + [('Entry.textarea', + {'sticky': 'nswe'})] + })] + })] + })]) + +s.configure('AGREEmip_theme', relief='flat', + background=colors['background'], + foreground=colors['text'], + insertcolor=colors['text'], + selectbackground=colors['accent'] + ) + + +''' +--------------------------------------------------- +Global functions +''' + +def save_image(): + ftypes = [('PNG file', '.png'), ('SVG file', '.svg'), ('All files', '*')] + filename = filedialog.asksaveasfilename(filetypes=ftypes, defaultextension='.png') + # save the plot in the specified path; the 'tight' option removes the whitespace from around the figure: + plt.savefig(filename, bbox_inches='tight', dpi=300) + + +def call_info_popup(): + win = tk.Toplevel() + win_frame = tk.Frame(win, width=500, background=colors['foreground']) + win_frame.pack() + win.iconbitmap('AGREEmip_icon.ico') + win.wm_title('About and citation info') + text = ttk.Label(win_frame, text='If you use AGREEmip, please cite:', wraplength=280, justify='left') + text.grid(column=0, row=0, padx=8, pady=8, sticky='w') + text2 = tk.Text(win_frame, width=50, height=5, wrap='word', bg=colors['background']) + citation = 'Author 1, Author 2 ..., AGREEmip - Analytical Greenness Metric' \ + ' for MIPs, Journal Name (2024), doi.org/10.0000/0000000000' + text2.grid(column=0, row=1, padx=8, pady=8, sticky='w') + text2.insert(tk.END, citation) + text3 = ttk.Label(win_frame, text='Most recent version of the source code can be found at:', wraplength=320, justify='left') + text3.grid(column=0, row=2, padx=8, pady=8, sticky='w') + text4 = tk.Text(win_frame, width=50, height=1, wrap='word', bg=colors['background']) + source_link = 'git.pg.edu.pl/p174235/agreemip' + text4.grid(column=0, row=3, padx=8, pady=8, sticky='w') + text4.insert(tk.END, source_link) + text5 = ttk.Label(win_frame, text='Copyright (c) 2024 Authors, ' + 'available under the MIT License', wraplength=380, + justify='left') + text5.grid(column=0, row=4, padx=8, pady=8, sticky='w') + + +def reset_scores(): + for criterion in criteria: + criterion.textvar.set('1.0') + criterion.optionvar.set('Select') + criterion.valuevar.set('Input') + + criteria[0].radiovar.set(1) + criteria[1].radiovar.set(2) + criteria[2].radiovar.set(1) + criteria[3].radiovar.set(3) + criteria[4].radiovar.set(4) + criteria[5].radiovar.set(3) + criteria[6].radiovar.set(2) + criteria[7].radiovar.set(3) + criteria[8].radiovar.set(1) + criteria[9].radiovar.set(4) + criteria[10].radiovar.set(3) + criteria[11].radiovar.set(3) + + # Reset all checkboxes in all tabs + for tab_items_state in tabs_items_states: + for item_state in tab_items_state: + item_state['var'].set(0) + + create_plot() + +def temp_func(): + pass + + +def generate_report(): + report = Report(tabs, criteria) + report.savePDF() + + +def create_menu(): + menu = tk.Menu() + root.config(menu=menu) + file_menu = tk.Menu(menu) + file_menu.config(background=colors['background'], + activeborderwidth=5, + activeforeground=colors['text'], + activebackground=colors['accent'], + foreground=colors['text'], + tearoff=0) + + file_menu.add_command(label='Save image', command=save_image) + file_menu.add_command(label='Re-set', command=reset_scores) + file_menu.add_command(label='Report', command=generate_report) + about_menu = tk.Menu(menu) + about_menu.config(background=colors['background'], + activeborderwidth=5, + activeforeground=colors['text'], + activebackground=colors['accent'], + foreground=colors['text'], + tearoff=0) + menu.add_cascade(label='File', menu=file_menu) + menu.add_cascade(label='About', menu=about_menu) + about_menu.add_command(label='Info', command=call_info_popup) + + +def calculate_score(): + indv_scores = 0 + weights = 0 + for criterion in criteria: + indv_scores += (float(criterion.textvar.get()) * criterion.radiovar.get()) + weights += criterion.radiovar.get() + score = indv_scores / weights + return score + + +# a function for restarting the right frame (to clear the memory after each change) +# it is then used when there is a need to change the plot +def clear_frame(frame): + frame.destroy() + global right_frame + right_frame = tk.Frame(root, bd=1, padx=2, pady=2, width=500, height=500, bg=colors['foreground']) + right_frame.pack(side='right', anchor='n') + + +# connect a float in range 0.0 : 1.0 to a colour in a spectrum from red to yellow to green +# (256^3 discrete colour values): +def colorMapper(value): + cmap = LinearSegmentedColormap.from_list('rg', ["red", "yellow", "green"], N=256) + mapped_color = int(value * 255) + color = cmap(mapped_color) + return color + + +def change_dropdown(criterion, tab_choices, *args): + if criterion.optionvar.get() == 'Select': + criterion.textvar.set(1.0) + else: + criterion.textvar.set(tab_choices[criterion.optionvar.get()]) + create_plot() + + +def create_plot(event=None): + # event=None is passed so that the entry.bind + # does not return a positional argument; + # close the current plot window (otherwise the generated plots remain open in the memory): + plt.close() + + # re-generate the frame: + clear_frame(right_frame) + + fig = plt.figure(figsize=(8, 8), dpi=150, facecolor=colors['foreground']) + ax = plt.subplot() + ax.set_aspect('equal', adjustable='datalim') + + scores = [] + weights = [] + + for criterion in criteria: + scores.append(float(criterion.textvar.get())) + weights.append(criterion.radiovar.get()) + + # radius of the central circle + R = 5.0 # 4 for 10 circles + + outer_radii = [round(0.28 * w + 0.4, 2) for w in weights] + main_score = calculate_score() + + central_x = 0 + central_y = 0 + angles = np.linspace(0, 2 * np.pi, len(outer_radii), endpoint=False) - np.pi / 2 + + for angle, r, i , score in zip(angles, outer_radii, range(len(outer_radii)), scores): + x = central_x + (R + 2 * r - 2.1) * np.cos(-angle) + y = central_y + (R + 2 * r - 2.1) * np.sin(-angle) + + # Create a clipping annulus + outer_circle_border = plt.Circle((x, y), r + 0.18, facecolor='white', edgecolor='black', + linewidth=1, zorder=2) + central_circle = plt.Circle((central_x, central_y), R, facecolor=colorMapper(main_score), + edgecolor='black', linewidth=1, zorder=1) + + ax.add_patch(central_circle) + outer_circle_border.set_clip_path(central_circle) + + ax.add_patch(outer_circle_border) + + outer_circle_border_noedge = plt.Circle((x, y), r + 0.14, facecolor='white', edgecolor='blue', + linewidth=0, zorder=3) + ax.add_patch(outer_circle_border_noedge) + + + outer_circle = plt.Circle((x, y), r, facecolor=colorMapper(score), edgecolor='black', + linewidth=1, zorder=4) + ax.add_patch(outer_circle) + + ax.annotate(str(i + 1), xy=(x, y), fontsize=8, ha='center', va='center', zorder=5) + + central_circle = plt.Circle((central_x, central_y), R, facecolor=colorMapper(main_score), + edgecolor='black', linewidth=1, zorder=1) + ax.add_patch(central_circle) + + ax.annotate(str(round(main_score, 2)), xy=(0, 0), fontsize=20, ha='center', va='center') + + margin = 7.91 + ax.set(ylim=(-margin, margin), + xlim=(-margin, margin)) + + plt.axis('off') + plt.tight_layout() + + canvas = FigureCanvasTkAgg(figure=fig, master=right_frame) + plot_widget = canvas.get_tk_widget() + plot_widget.pack(side='top') + + +def create_scrollable_list(tab_frame, global_items, tab_index, row, height=130): + # Ensure tab state initialization with a more robust check and initialization + while len(tabs_items_states) <= tab_index: + tabs_items_states.append([]) + + # Proceed with setting up the container, canvas, and scrollbar + container_frame = ttk.Frame(tab_frame) + container_frame.grid(row=row, column=0, sticky='w', pady=0) + tab_frame.grid_rowconfigure(row, weight=0) + tab_frame.grid_columnconfigure(0, weight=1) + + canvas = tk.Canvas(container_frame, height=height, background=colors['background'], border=0) + scrollbar = ttk.Scrollbar(container_frame, orient='vertical', command=canvas.yview) + canvas.configure(yscrollcommand=scrollbar.set, highlightthickness=4, highlightcolor=colors['background']) + canvas.grid(row=0, column=0, sticky='w') + scrollbar.grid(row=0, column=1, sticky='ns') + container_frame.grid_columnconfigure(0, weight=1) + container_frame.grid_rowconfigure(0, weight=0) + + # Setup for scrollable frame + scrollable_frame = ttk.Frame(canvas, style='AGREEmip_theme') + canvas.create_window((0, 0), window=scrollable_frame, anchor='nw', tags="scrollable_frame") + canvas.bind("<Configure>", lambda e: canvas.itemconfig("scrollable_frame", width=e.width)) + scrollable_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) + + # Populate with items + for item in global_items: + var = tk.IntVar() + # Using 'code' as the label text directly, since it includes the description + check = ttk.Checkbutton(scrollable_frame, text=item['code'], variable=var) + check.grid(sticky="w", pady=0) + # Append each new variable and item to the state list for this specific tab + tabs_items_states[tab_index].append({'var': var, 'item': item}) + + + + + +# calculate the sum of selected hazards from the scroll-down list +def calculate_sum(tab_index): + if tab_index < len(tabs_items_states): + tab_items_state = tabs_items_states[tab_index] + total = sum(item_state['item']['value'] for item_state in tab_items_state if item_state['var'].get() == 1) + print("Total value of selected items for tab", tab_index, ":", total) + return total + else: + print("Invalid tab index") + return 0 + + + +''' +-------------------------------------------------- +Script +''' +create_menu() + + +# create the two main frames of the GUI +left_frame = tk.Frame(root, bd=0, padx=2, pady=0, width=300, height=500, bg=colors['background']) +left_frame.pack(side='left', anchor='n') + +right_frame = tk.Frame(root, bd=1, padx=2, pady=2, width=500, height=500, bg=colors['foreground']) +right_frame.pack(side='right', anchor='n') + + +# notebook which will house the 10 tabs (in the left frame) +class Notebook: + + def __init__(self, parent): + self.parent = parent + self.notebook = ttk.Notebook(self.parent, height=500, width=300) + self.notebook.pack() + + def add_tab(self, tab, text): + self.notebook.add(tab, text=text) + + +tab_bar = Notebook(left_frame) + + +class Tab: + + def __init__(self, tab_no, title, text1, text2, criterion): + self.title = title + self.parent = tab_bar + self.frame = tk.Frame(self.parent.notebook, bg=colors['foreground']) + self.parent.add_tab(self.frame, text=tab_no) + self.header = ttk.Label(self.frame, text=title, wraplength=280, font=('TkDefaultFont', 10, 'bold')) + self.header.grid(column=0, row=0, sticky='w') + self.label1 = ttk.Label(self.frame, text=text1, wraplength=280) + self.label1.grid(column=0, row=1, sticky='w') + self.label2 = ttk.Label(self.frame, text=text2, wraplength=280) + self.label2.grid(column=0, row=2, sticky='w') + + self.criterion = criterion + + self.ratio_label = ttk.Label(self.frame, text='Modify the default weight:') + self.ratio_label.place(rely=0.91, relx=0.025, anchor='sw') + + self.radio_1 = ttk.Radiobutton(self.frame, text='1', variable=self.criterion.radiovar, command=create_plot, + value=1) + self.radio_1.place(rely=0.95, relx=0.05, anchor='sw') + self.radio_2 = ttk.Radiobutton(self.frame, text='2', variable=self.criterion.radiovar, command=create_plot, + value=2) + self.radio_2.place(rely=0.95, relx=0.15, anchor='sw') + self.radio_3 = ttk.Radiobutton(self.frame, text='3', variable=self.criterion.radiovar, command=create_plot, + value=3) + self.radio_3.place(rely=0.95, relx=0.25, anchor='sw') + self.radio_4 = ttk.Radiobutton(self.frame, text='4', variable=self.criterion.radiovar, command=create_plot, + value=4) + self.radio_4.place(rely=0.95, relx=0.35, anchor='sw') + + +class Criterion: + + def __init__(self, number): + self.number = number + self.textvar = tk.StringVar() + self.textvar.set('1.0') + self.radiovar = tk.IntVar() + self.optionvar = tk.StringVar() + self.optionvar.set('Select') + self.valuevar = tk.StringVar() + self.valuevar.set('Input') + + +# create the 12 criteria values. They can be later called as +# list elements, e.g. to get the value of criterion 3: criteria[2].textvar.get() +criteria = [] +for i in range(1, 13): + criteria.append(Criterion(i)) + +# assign the default weights (this could be done using itemgetter or itertools.compress, but +# that would require importing additional packages): +criteria[0].radiovar.set(1) +criteria[1].radiovar.set(2) +criteria[2].radiovar.set(1) +criteria[3].radiovar.set(3) +criteria[4].radiovar.set(4) +criteria[5].radiovar.set(3) +criteria[6].radiovar.set(2) +criteria[7].radiovar.set(3) +criteria[8].radiovar.set(1) +criteria[9].radiovar.set(4) +criteria[10].radiovar.set(3) +criteria[11].radiovar.set(3) + +# start the GUI with a default plot +create_plot() + + +class Report: + def __init__(self, tabs, criteria): + self.pdf = FPDF('P', 'mm', 'A4') # 'P' - Portrait + self.pdf.set_font('Helvetica', '', 10) + self.pdf.add_page() + self.pdf.set_margins(left=25, top=20) + self.tabs = tabs + self.criteria = criteria + plt.savefig('temp_figure.png', bbox_inches='tight') + # insert image (image, x, y, width): + self.pdf.image('temp_figure.png', 107, 10, 80) + # delete the temp file from drive: + os.remove('temp_figure.png') + + self.fill_color = (240, 240, 240) + + # insert title (Helvetica, 'B'old, 14 pt): + self.pdf.set_font('Helvetica', 'B', 18) + self.pdf.ln(20) + self.pdf.cell(100, 12, 'AGREEmip') + self.pdf.set_font('Helvetica', '', 12) + self.pdf.ln(10) + self.pdf.multi_cell(55, 4, 'Analytical Greenness Metric for MIPs') + self.pdf.ln(10) + self.pdf.set_font('Helvetica', '', 10) + self.pdf.cell(100, 12, datetime.now().strftime("%d/%m/%Y %H:%M:%S")) + self.pdf.ln(30) + + def fieldColor(score): + x = colorMapper(score)[0] * 255 + y = colorMapper(score)[1] * 255 + z = colorMapper(score)[2] * 255 + self.pdf.set_fill_color(x, y, z) + + def create_header(): + self.pdf.set_font('Helvetica', 'B', 10) + self.pdf.cell(12, 6, '#', align='C') + self.pdf.cell(120, 6, 'Criterion', align='L') + self.pdf.cell(12, 6, 'Score', align='C') + self.pdf.cell(12, 6, 'Weight', align='C') + self.pdf.ln(8) + self.pdf.set_font('Helvetica', '', 10) + + create_header() + + def create_report_field(number, tabs, criterion, text): + self.pdf.cell(12, 12, number, border=1, align='C') + self.pdf.set_fill_color(240, 240, 240) + self.pdf.set_font('Helvetica', 'B', 10) + self.pdf.cell(120, 6, tabs.title, border=1, ln=2, align='L', fill=True) + self.pdf.set_font('Helvetica', '', 8) + self.pdf.cell(120, 6, text, border=1, ln=0, align='L', fill=False) + self.pdf.set_font('Helvetica', '', 10) + + # modify the position of the following cells to ensure proper alignment: + x = self.pdf.get_x() + y = self.pdf.get_y() + self.pdf.set_xy(x, y - 6) + + # set the colour of the score field: + fieldColor(float(criterion.textvar.get())) + self.pdf.cell(12, 12, str(round(float(criterion.textvar.get()), 2)), border=1, fill=True, align='C') + self.pdf.set_fill_color(240, 240, 240) + self.pdf.cell(12, 12, str(criterion.radiovar.get()), border=1, fill=True, align='C') + self.pdf.ln(15) + + create_report_field('1.', self.tabs[0], self.criteria[0], + ('Waste generation: ' + self.criteria[0].optionvar.get())) + + create_report_field('2.', self.tabs[1], self.criteria[1], + ('Total mass of the functional monomer: ' + self.criteria[1].valuevar.get() + ' [g]')) + + create_report_field('3.', self.tabs[2], self.criteria[2], + ('Total mass of the template: ' + self.criteria[2].valuevar.get() + ' [g]')) + + create_report_field('4.', self.tabs[3], self.criteria[3], + ('Total mass of the cross-linking agent: ' + self.criteria[3].valuevar.get() + ' [g]')) + + create_report_field('5.', self.tabs[4], self.criteria[4], + ('Total mass of the porogen/solvent: ' + self.criteria[4].valuevar.get() + ' [g]')) + + create_report_field('6.', self.tabs[5], self.criteria[5], + ('Total mass of other reagents, adjuvants, or carriers: ' + self.criteria[5].valuevar.get() + ' [g]')) + + create_report_field('7.', self.tabs[6], self.criteria[6], + ('Total mass of reagents used in core/particles preparation, surface modification, or both: ' + self.criteria[6].valuevar.get() + ' [g]')) + + create_report_field('8.', self.tabs[7], self.criteria[7], + ('Initiator: ' + self.criteria[7].optionvar.get())) + + create_report_field('9.', self.tabs[8], self.criteria[8], + ('Grains size range: ' + self.criteria[8].optionvar.get())) + + create_report_field('10.', self.tabs[9], self.criteria[9], + ('No. of distinct hazards: ' + str(calc_n_10()))) + + create_report_field('11.', self.tabs[10], self.criteria[10], + ('Elution technique: ' + self.criteria[10].optionvar.get())) + + create_report_field('12.', self.tabs[11], self.criteria[11], + ('Times the end products can be reused: ' + self.criteria[11].optionvar.get())) + + + + def savePDF(self): + ftypes = [('PDF file', '.pdf'), ('All files', '*')] + filename = filedialog.asksaveasfilename(filetypes=ftypes, defaultextension='.pdf') + # save the pdf + self.pdf.output(filename, 'F') + # open the file in the system default viewer: + os.system('start ' + filename) + + + + +global_items = [ + {'code': '[H224] Extremely flammable liquid and vapour', 'value': 9}, + {'code': '[H225] Highly flammable liquid or vapour', 'value': 7}, + {'code': '[H226] Flammable liquid and vapour', 'value': 3}, + {'code': '[H228] Flammable solid', 'value': 3}, + {'code': '[H242] Heating may cause fire', 'value': 3}, + {'code': '[H251] Self-heating; may catch fire', 'value': 3}, + {'code': '[H261] In contact with water releases\nflammable gas', 'value': 2}, + {'code': '[H280] Contains gas under pressure; may\nexplode if heated', 'value': 2}, + {'code': '[H290] May be corrosive to metals', 'value': 2}, + {'code': '[H300] Fatal if swallowed', 'value': 10}, + {'code': '[H301] Toxic if swallowed', 'value': 7}, + {'code': '[H302] Harmful if swallowed', 'value': 5}, + {'code': '[H304] May be fatal if swallowed and\nenters airways', 'value': 8}, + {'code': '[H310] Fatal in contact with skin', 'value': 10}, + {'code': '[H311] Toxic in contact with skin', 'value': 7}, + {'code': '[H312] Harmful in contact with skin', 'value': 5}, + {'code': '[H314] Causes severe skin burns and eye\ndamage', 'value': 5}, + {'code': '[H315] Causes skin irritation', 'value': 2}, + {'code': '[H317] May cause an allergic skin reaction', 'value': 2}, + {'code': '[H318] Causes serious eye damage', 'value': 7}, + {'code': '[H319] Causes serious eye irritation', 'value': 5}, + {'code': '[H330] Fatal if inhaled', 'value': 10}, + {'code': '[H331] Toxic if inhaled', 'value': 7}, + {'code': '[H332] Harmful if inhaled', 'value': 5}, + {'code': '[H334] May cause allergy or asthma symptoms\nor breathing difficulties if inhaled', 'value': 3}, + {'code': '[H335] May cause respiratory irritation', 'value': 2}, + {'code': '[H336] May cause drowsiness or dizziness', 'value': 2}, + {'code': '[H341] Suspected of causing genetic defects', 'value': 7}, + {'code': '[H350] May cause cancer', 'value': 10}, + {'code': '[H351] Suspected of causing cancer', 'value': 7}, + {'code': '[H360] May damage fertility or the unborn child', 'value': 10}, + {'code': '[H361] Suspected of damaging fertility or the\nunborn child', 'value': 7}, + {'code': '[H370] Causes damage to organs', 'value': 10}, + {'code': '[H372] Causes damage to organs through\nprolonged or repeated exposure', 'value': 8}, + {'code': '[H373] May cause damage to organs through\nprolonged or repeated exposure', 'value': 7}, + {'code': '[H400] Very toxic to aquatic life', 'value': 10}, + {'code': '[H410] Very toxic to aquatic life with\nlong-lasting effects', 'value': 10}, + {'code': '[H411] Toxic to aquatic life with\nlong-lasting effects', 'value': 10}, + {'code': '[H412] Harmful to aquatic life with\nlong-lasting effects', 'value': 7}, + {'code': '[H413] May cause long-lasting harmful\neffects on aquatic life', 'value': 5}, + ] + # Add more items as needed + +# Assuming this global structure to hold each tab's selection state +tabs_items_states = [] + + + +''' +Populate the tabs +''' +# --------------------------- TAB1 --------------------------- +tab1 = Tab(tab_no='1 ', title='Preparation of polymerization reaction substrates - removal of polymerization inhibitors', + text1='Assessment of the consequences of removal of polymerization inhibitor from polymerization mixture. This can be done with solid and/or liquid waste generation, or without any resulting waste.', + text2='Waste generation:', criterion=criteria[0]) + +# create a frame to pack the entry into in order to get a border for the entry: +f1 = tk.Frame(tab1.frame, border=2, background=colors['accent']) +f1.grid(column=0, row=3, padx=8, pady=8, sticky='w') + +tab1_choices = {'Lack of waste': 1.0, + 'Generation of solid waste': 0.5, + 'Generation of liquid waste': 0.2, + 'Generation of solid and liquid waste': 0.0} +dropdown1 = ttk.OptionMenu(tab1.frame, criteria[0].optionvar, criteria[0].optionvar.get(), *tab1_choices.keys(), + command=lambda x: change_dropdown(criteria[0], tab1_choices)) +# the ttk style for OptionMenu does not include a style for the tk dropdown menu, +# so this has to be configured separately: +dropdown1['menu'].config(background=colors['background'], + activeborderwidth=5, + activebackground=colors['accent'], + activeforeground=colors['text'], + foreground=colors['text'],) +dropdown1.config(width=40) + +dropdown1.grid(column=0, row=3, padx=8, pady=8, sticky='w') + + +# --------------------------- TAB2 --------------------------- +tab2 = Tab(tab_no='2 ', title='Functional monomer', + text1='Duis semper turpis ipsum, pretium venenatis mauris ultricies sit amet.', + text2='Select the relevant hazard statements:', + criterion=criteria[1]) + + + +create_scrollable_list(tab2.frame, global_items, 1, row=3) + +def calc_crit_2(event=None): + try: + if criteria[1].valuevar.get() == 'Input': + criteria[1].textvar.set(1.0) + else: + criteria[1].textvar.set( + round(abs(2.718281828459 **(float(criteria[1].valuevar.get()) * calculate_sum(1) * -0.004)), 2)) + except ValueError: + messagebox.showerror(title='Value error', + message='The mass has to be a float or an integer, e.g. 0.14 or 21.') + create_plot() + print('criterion 2: ', criteria[1].textvar.get()) + + +label_tab2 = ttk.Label(tab2.frame, text='Input the total mass [g] of the functional monomer:', + wraplength=280) +label_tab2.grid(column=0, row=4, sticky='w') + +# create a frame to pack the entry into in order to get a border for the entry: +f2 = tk.Frame(tab2.frame, border=2, background=colors['accent']) +f2.grid(column=0, row=5, padx=8, pady=8, sticky='w') +# create the entry and pack it in the frame +entry2 = ttk.Entry(f2, textvariable=criteria[1].valuevar, width=10, style='AGREEmip_theme') +entry2.grid(column=0, row=0, sticky='w') + +entry2.bind('<Return>', calc_crit_2) +ttk.Button(tab2.frame, text='Set', command=calc_crit_2, width=8).grid(column=0, row=5, padx=80, pady=8, sticky='w') + +# --------------------------- TAB3 --------------------------- +tab3 = Tab(tab_no='3 ', title='Template', + text1='Duis semper turpis ipsum, pretium venenatis mauris ultricies sit amet.', + text2='Select the relevant hazard statements:', + criterion=criteria[2]) + +create_scrollable_list(tab3.frame, global_items, tab_index=2, row=3) + +def calc_crit_3(event=None): + try: + if criteria[2].valuevar.get() == 'Input': + criteria[2].textvar.set(1.0) + else: + criteria[2].textvar.set( + round(abs(2.718281828459 **(float(criteria[2].valuevar.get()) * calculate_sum(2) * -0.004)), 2)) + except ValueError: + messagebox.showerror(title='Value error', + message='The mass has to be a float or an integer, e.g. 0.14 or 21.') + create_plot() + print('criterion 3: ', criteria[2].textvar.get()) + + +label_tab3 = ttk.Label(tab3.frame, text='Input the total mass [g] of the template:', + wraplength=280) +label_tab3.grid(column=0, row=4, sticky='w') + +# create a frame to pack the entry into in order to get a border for the entry: +f3 = tk.Frame(tab3.frame, border=2, background=colors['accent']) +f3.grid(column=0, row=5, padx=8, pady=8, sticky='w') +# create the entry and pack it in the frame +entry3 = ttk.Entry(f3, textvariable=criteria[2].valuevar, width=10, style='AGREEmip_theme') +entry3.grid(column=0, row=0, sticky='w') + +entry3.bind('<Return>', calc_crit_3) +ttk.Button(tab3.frame, text='Set', command=calc_crit_3, width=8).grid(column=0, row=5, padx=80, pady=8, sticky='w') + + +# --------------------------- TAB4 --------------------------- +tab4 = Tab(tab_no='4 ', title='Cross-linking agent', + text1='Duis semper turpis ipsum, pretium venenatis mauris ultricies sit amet.', + text2='Select the relevant hazard statements:', + criterion=criteria[3]) + +create_scrollable_list(tab4.frame, global_items, tab_index=3, row=3) + +def calc_crit_4(event=None): + try: + if criteria[3].valuevar.get() == 'Input': + criteria[3].textvar.set(1.0) + else: + criteria[3].textvar.set( + round(abs(2.718281828459 **(float(criteria[3].valuevar.get()) * calculate_sum(3) * -0.004)), 2)) + except ValueError: + messagebox.showerror(title='Value error', + message='The mass has to be a float or an integer, e.g. 0.14 or 21.') + create_plot() + +label_tab4 = ttk.Label(tab4.frame, text='Input the total mass [g] of the cross-linking agent:', + wraplength=280) +label_tab4.grid(column=0, row=4, sticky='w') + +# create a frame to pack the entry into in order to get a border for the entry: +f4 = tk.Frame(tab4.frame, border=2, background=colors['accent']) +f4.grid(column=0, row=5, padx=8, pady=8, sticky='w') +# create the entry and pack it in the frame +entry4 = ttk.Entry(f4, textvariable=criteria[3].valuevar, width=10, style='AGREEmip_theme') +entry4.grid(column=0, row=0, sticky='w') + +entry4.bind('<Return>', calc_crit_4) +ttk.Button(tab4.frame, text='Set', command=calc_crit_4, width=8).grid(column=0, row=5, padx=80, pady=8, sticky='w') + + +# --------------------------- TAB5 --------------------------- +tab5 = Tab(tab_no='5 ', title='Porogen / solvent', + text1='Duis semper turpis ipsum, pretium venenatis mauris ultricies sit amet.', + text2='Select the relevant hazard statements:', + criterion=criteria[4]) + +create_scrollable_list(tab5.frame, global_items, tab_index=4, row=3) + +def calc_crit_5(event=None): + try: + if criteria[4].valuevar.get() == 'Input': + criteria[4].textvar.set(1.0) + else: + criteria[4].textvar.set( + round(abs(2.718281828459 **(float(criteria[4].valuevar.get()) * calculate_sum(4) * -0.004)), 2)) + except ValueError: + messagebox.showerror(title='Value error', + message='The mass has to be a float or an integer, e.g. 0.14 or 21.') + create_plot() + +label_tab5 = ttk.Label(tab5.frame, text='Input the total mass [g] of the porogen / solvent:', + wraplength=280) +label_tab5.grid(column=0, row=4, sticky='w') + +# create a frame to pack the entry into in order to get a border for the entry: +f5 = tk.Frame(tab5.frame, border=2, background=colors['accent']) +f5.grid(column=0, row=5, padx=8, pady=8, sticky='w') +# create the entry and pack it in the frame +entry5 = ttk.Entry(f5, textvariable=criteria[4].valuevar, width=10, style='AGREEmip_theme') +entry5.grid(column=0, row=0, sticky='w') + +entry5.bind('<Return>', calc_crit_5) +ttk.Button(tab5.frame, text='Set', command=calc_crit_5, width=8).grid(column=0, row=5, padx=80, pady=8, sticky='w') + + +# --------------------------- TAB6 --------------------------- +tab6 = Tab(tab_no='6 ', title='Other reagents, adjuvants, or carriers', + text1='Duis semper turpis ipsum, pretium venenatis mauris ultricies sit amet.', + text2='Select the relevant hazard statements:', + criterion=criteria[5]) + +create_scrollable_list(tab6.frame, global_items, tab_index=5, row=3) + +def calc_crit_6(event=None): + try: + if criteria[5].valuevar.get() == 'Input': + criteria[5].textvar.set(1.0) + else: + criteria[5].textvar.set( + round(abs(2.718281828459 **(float(criteria[5].valuevar.get()) * calculate_sum(5) * -0.004)), 2)) + except ValueError: + messagebox.showerror(title='Value error', + message='The mass has to be a float or an integer, e.g. 0.14 or 21.') + create_plot() + +label_tab6 = ttk.Label(tab6.frame, text='Input the total mass [g] of other reagents, adjuvants, or carriers:', + wraplength=280) +label_tab6.grid(column=0, row=4, sticky='w') + +# create a frame to pack the entry into in order to get a border for the entry: +f6 = tk.Frame(tab6.frame, border=2, background=colors['accent']) +f6.grid(column=0, row=5, padx=8, pady=8, sticky='w') +# create the entry and pack it in the frame +entry6 = ttk.Entry(f6, textvariable=criteria[5].valuevar, width=10, style='AGREEmip_theme') +entry6.grid(column=0, row=0, sticky='w') + +entry6.bind('<Return>', calc_crit_6) +ttk.Button(tab6.frame, text='Set', command=calc_crit_6, width=8).grid(column=0, row=5, padx=80, pady=8, sticky='w') + + +# --------------------------- TAB7 --------------------------- +tab7 = Tab(tab_no='7 ', title='Core / particles preparation and surface modification', + text1='Duis semper turpis ipsum, pretium venenatis mauris ultricies sit amet.', + text2='Select the relevant hazard statements:', + criterion=criteria[6]) + +create_scrollable_list(tab7.frame, global_items, tab_index=6, row=3) + +def calc_crit_7(event=None): + try: + if criteria[6].valuevar.get() == 'Input': + criteria[6].textvar.set(1.0) + else: + criteria[6].textvar.set( + round(abs(2.718281828459 **(float(criteria[6].valuevar.get()) * calculate_sum(6) * -0.004)), 2)) + except ValueError: + messagebox.showerror(title='Value error', + message='The mass has to be a float or an integer, e.g. 0.14 or 21.') + create_plot() + +label_tab7 = ttk.Label(tab7.frame, text='Input the total mass [g] of reagents used during core / particles preparation, surface modification, or both:', + wraplength=280) +label_tab7.grid(column=0, row=4, sticky='w') + +# create a frame to pack the entry into in order to get a border for the entry: +f7 = tk.Frame(tab7.frame, border=2, background=colors['accent']) +f7.grid(column=0, row=5, padx=8, pady=8, sticky='w') +# create the entry and pack it in the frame +entry7 = ttk.Entry(f7, textvariable=criteria[6].valuevar, width=10, style='AGREEmip_theme') +entry7.grid(column=0, row=0, sticky='w') + +entry7.bind('<Return>', calc_crit_7) +ttk.Button(tab7.frame, text='Set', command=calc_crit_7, width=8).grid(column=0, row=5, padx=80, pady=8, sticky='w') + + +# --------------------------- TAB8 --------------------------- +tab8 = Tab(tab_no='8 ', title='Polymerisation initiation', + text1='Lorem ipsum dolor sit amet.', + text2='Select the initiator:', criterion=criteria[7]) + +# create a frame to pack the entry into in order to get a border for the entry: +f8 = tk.Frame(tab8.frame, border=2, background=colors['accent']) +f8.grid(column=0, row=3, padx=8, pady=8, sticky='w') + +tab8_choices = {'Electropolymerisation': 1.0, + 'Initiating the reaction with UV rays': 0.9, + 'Sonication or microwaves': 0.8, + 'Heating + mixing': 0.5, + 'Chemical reagent + mixing': 0.2, + 'Chemical reagent + heating + mixing': 0.0} + +dropdown8 = ttk.OptionMenu(tab8.frame, criteria[7].optionvar, criteria[7].optionvar.get(), *tab8_choices.keys(), + command=lambda x: change_dropdown(criteria[7], tab8_choices)) +# the ttk style for OptionMenu does not include a style for the tk dropdown menu, +# so this has to be configured separately: +dropdown8['menu'].config(background=colors['background'], + activeborderwidth=5, + activebackground=colors['accent'], + activeforeground=colors['text'], + foreground=colors['text'],) +dropdown8.config(width=40) + +dropdown8.grid(column=0, row=3, padx=8, pady=8, sticky='w') + + +# --------------------------- TAB9 --------------------------- +tab9 = Tab(tab_no='9 ', title='Size of polymer particles', + text1='Lorem ipsum dolor sit amet.', + text2='Select the sorbent grain size:', criterion=criteria[8]) + +# create a frame to pack the entry into in order to get a border for the entry: +f9 = tk.Frame(tab9.frame, border=2, background=colors['accent']) +f9.grid(column=0, row=3, padx=8, pady=8, sticky='w') + +tab9_choices = {'In-situ polymerisation': 1.0, + '> 1000 nm': 0.8, + '100-1000 nm': 0.6, + '10-100 nm': 0.4, + 'Carbon dots': 0.2, + 'Quantum dots': 0.1, + } + +dropdown9 = ttk.OptionMenu(tab9.frame, criteria[8].optionvar, criteria[8].optionvar.get(), *tab9_choices.keys(), + command=lambda x: change_dropdown(criteria[8], tab9_choices)) +# the ttk style for OptionMenu does not include a style for the tk dropdown menu, +# so this has to be configured separately: +dropdown9['menu'].config(background=colors['background'], + activeborderwidth=5, + activebackground=colors['accent'], + activeforeground=colors['text'], + foreground=colors['text'],) +dropdown9.config(width=40) + +dropdown9.grid(column=0, row=3, padx=8, pady=8, sticky='w') + + +# --------------------------- TAB10 --------------------------- +tab10 = Tab(tab_no='10', title='Template elution solvent', + text1='Duis semper turpis ipsum, pretium venenatis mauris ultricies sit amet.', + text2='Select the relevant hazard statements:', + criterion=criteria[9]) + +create_scrollable_list(tab10.frame, global_items, tab_index=9, row=3) + +def calc_crit_10(event=None): + + criteria[9].textvar.set( + round(abs(-0.0256 * calculate_sum(9) + 1), 2)) + + create_plot() + + +# calculate the sum of selected hazards from the scroll-down list +def calc_n_10(): + if 9 < len(tabs_items_states): + tab_items_state = tabs_items_states[9] + number = 0 + for item_state in tab_items_state: + if item_state['var'].get() == 1: + number += 1 + return number + else: + print("Invalid tab index") + return 0 + +ttk.Button(tab10.frame, text='Set', command=calc_crit_10, width=8).grid(column=0, row=5, padx=80, pady=8, sticky='w') + + +# --------------------------- TAB11 --------------------------- +tab11 = Tab(tab_no='11', title='Template elution technique', + text1='Lorem ipsum dolor sit amet.', + text2='Select the relevant elution technique:', criterion=criteria[10]) + +# create a frame to pack the entry into in order to get a border for the entry: +f11 = tk.Frame(tab11.frame, border=2, background=colors['accent']) +f11.grid(column=0, row=3, padx=8, pady=8, sticky='w') + +tab11_choices = {'Ultrasounds': 1.0, + 'Supercritical CO2': 0.8, + 'Regular mixing or shaking': 0.5, + 'Soxhlet technique': 0.0, + } + +dropdown11 = ttk.OptionMenu(tab11.frame, criteria[10].optionvar, criteria[10].optionvar.get(), *tab11_choices.keys(), + command=lambda x: change_dropdown(criteria[10], tab11_choices)) +# the ttk style for OptionMenu does not include a style for the tk dropdown menu, +# so this has to be configured separately: +dropdown11['menu'].config(background=colors['background'], + activeborderwidth=5, + activebackground=colors['accent'], + activeforeground=colors['text'], + foreground=colors['text'],) +dropdown11.config(width=40) + +dropdown11.grid(column=0, row=3, padx=8, pady=8, sticky='w') + + +# --------------------------- TAB12 --------------------------- +tab12 = Tab(tab_no='12', title='Final product reusability', + text1='Lorem ipsum dolor sit amet.', + text2='How many times can the final product be reused?', criterion=criteria[11]) + +# create a frame to pack the entry into in order to get a border for the entry: +f12 = tk.Frame(tab12.frame, border=2, background=colors['accent']) +f12.grid(column=0, row=3, padx=8, pady=8, sticky='w') + +tab12_choices = {'Reused > 10 times': 1.0, + 'Reused 2-10 times': 0.6, + 'Single use': 0.0, + } + +dropdown12 = ttk.OptionMenu(tab12.frame, criteria[11].optionvar, criteria[11].optionvar.get(), *tab11_choices.keys(), + command=lambda x: change_dropdown(criteria[11], tab11_choices)) +# the ttk style for OptionMenu does not include a style for the tk dropdown menu, +# so this has to be configured separately: +dropdown12['menu'].config(background=colors['background'], + activeborderwidth=5, + activebackground=colors['accent'], + activeforeground=colors['text'], + foreground=colors['text'],) +dropdown12.config(width=40) + +dropdown12.grid(column=0, row=3, padx=8, pady=8, sticky='w') + + + +# Create a list of the tabs and generate the report: +tabs = [tab1, tab2, tab3, tab4, tab5, tab6, + tab7, tab8, tab9, tab10, tab11, tab12] + + +# run the app in a loop +def main(): + while True: + root.mainloop() + + +main() + + + + + + +