OSDN Git Service

First commit
authorMahmoud Saghaei <mahmood.saghaei@gmail.com>
Mon, 9 Nov 2020 09:21:34 +0000 (12:51 +0330)
committerMahmoud Saghaei <mahmood.saghaei@gmail.com>
Mon, 9 Nov 2020 09:21:34 +0000 (12:51 +0330)
51 files changed:
.gitignore [new file with mode: 0644]
.idea/.gitignore [new file with mode: 0755]
.idea/inspectionProfiles/Project_Default.xml [new file with mode: 0755]
.idea/inspectionProfiles/profiles_settings.xml [new file with mode: 0755]
.idea/minimpy2.iml [new file with mode: 0755]
.idea/misc.xml [new file with mode: 0755]
.idea/modules.xml [new file with mode: 0755]
.idea/vcs.xml [new file with mode: 0644]
about_minimisation.py [new file with mode: 0755]
config.py [new file with mode: 0755]
config_dialog.py [new file with mode: 0755]
config_dialog.ui [new file with mode: 0755]
db.py [new file with mode: 0755]
db/database.sql [new file with mode: 0755]
db/db.sql [new file with mode: 0755]
db/minimizer.db [new file with mode: 0755]
enrol_form.py [new file with mode: 0755]
fa_IR.ts [new file with mode: 0755]
factor_levels.py [new file with mode: 0755]
factor_levels_dialog.py [new file with mode: 0755]
factor_levels_dialog.ui [new file with mode: 0755]
freq_table.py [new file with mode: 0755]
images/about.png [new file with mode: 0755]
images/add.png [new file with mode: 0755]
images/config.png [new file with mode: 0755]
images/delete.png [new file with mode: 0755]
images/error.png [new file with mode: 0755]
images/exit.png [new file with mode: 0755]
images/help.png [new file with mode: 0755]
images/levels.png [new file with mode: 0755]
images/logo.png [new file with mode: 0644]
images/save.png [new file with mode: 0755]
images/tick_mark.png [new file with mode: 0755]
locale_sources.ts [new file with mode: 0755]
locales/READ.ME [new file with mode: 0755]
locales/fa_IR.qm [new file with mode: 0755]
locales/languages.lst [new file with mode: 0755]
main_window.py [new file with mode: 0755]
minim.py [new file with mode: 0755]
minimisation_en_US.txt [new file with mode: 0755]
minimisation_fa_IR.txt [new file with mode: 0755]
minimpy2.pro [new file with mode: 0755]
mnd.py [new file with mode: 0755]
model.py [new file with mode: 0755]
my_random.py [new file with mode: 0755]
run_test.py [new file with mode: 0755]
trial.py [new file with mode: 0755]
ui_about_minimisation.py [new file with mode: 0755]
ui_about_minimisation.ui [new file with mode: 0755]
ui_main_window.py [new file with mode: 0755]
ui_main_window.ui [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..48d8fdb
--- /dev/null
@@ -0,0 +1,3 @@
+__pycache__/
+venv/
+*.pyc
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100755 (executable)
index 0000000..26d3352
--- /dev/null
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100755 (executable)
index 0000000..fb16d7a
--- /dev/null
@@ -0,0 +1,12 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="ignoredPackages">
+        <list>
+          <option value="PyGObject-stubs==0.0.2" />
+        </list>
+      </option>
+    </inspection_tool>
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100755 (executable)
index 0000000..105ce2d
--- /dev/null
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/minimpy2.iml b/.idea/minimpy2.iml
new file mode 100755 (executable)
index 0000000..8e81531
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/venv" />
+    </content>
+    <orderEntry type="jdk" jdkName="Python 3.8 (minimpy2)" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100755 (executable)
index 0000000..be317ac
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (minimpy2)" project-jdk-type="Python SDK" />
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100755 (executable)
index 0000000..13d3595
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/minimpy2.iml" filepath="$PROJECT_DIR$/.idea/minimpy2.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644 (file)
index 0000000..94a25f7
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/about_minimisation.py b/about_minimisation.py
new file mode 100755 (executable)
index 0000000..b33774a
--- /dev/null
@@ -0,0 +1,22 @@
+from PySide2 import QtWidgets as qtw
+from os import path
+
+from ui_about_minimisation import Ui_AboutMinimisationDialog
+
+
+class AboutMinimisationDialog(qtw.QDialog):
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.ui = Ui_AboutMinimisationDialog()
+        self.ui.setupUi(self)
+        lang = parent.settings.value('language', 'en_US', type=str)
+        filename = 'minimisation_{}.txt'.format(lang)
+        if not path.exists(filename):
+            filename = 'minimisation_en_US.txt'
+            if not path.exists(filename):
+                return
+        text = open(filename).read()
+        self.ui.minimTextEdit.setPlainText(text)
+        self.setWindowTitle(parent.tr('What is Minimisation'))
+        self.ui.closePushButton.setText(parent.tr('Close'))
+        self.ui.closePushButton.clicked.connect(self.close)
diff --git a/config.py b/config.py
new file mode 100755 (executable)
index 0000000..c10ba61
--- /dev/null
+++ b/config.py
@@ -0,0 +1,45 @@
+import sys
+
+from PySide2 import QtWidgets as qtw
+
+from config_dialog import Ui_ConfigDialog
+
+
+class Config(qtw.QDialog):
+    def __init__(self, parent):
+        super(Config, self).__init__(parent)
+        self.parent = parent
+        self.ui = Ui_ConfigDialog()
+        self.ui.setupUi(self)
+        lines = open('locales/languages.lst').readlines()
+        self.lang_codes = []
+        cur_lang = self.parent.settings.value('language', 'en_US', type=str)
+        cur_index = 0
+        for index, line in enumerate(lines):
+            if line.count(':') != 1:
+                qtw.QMessageBox.critical(self, parent.tr('Error'), parent.tr('Error in languages.lst file format\nPlease see the READ.ME file in locales folder'))
+                sys.exit(1)
+            parts = line.strip().split(":")
+            self.lang_codes.append(parts[0])
+            self.ui.languageComboBox.addItem(parts[1])
+            if cur_lang == parts[0]:
+                cur_index = index
+        self.ui.languageComboBox.setCurrentIndex(cur_index)
+        if len(lines) < 2:
+            self.ui.languageComboBox.setVisible(False)
+            self.ui.langLabel.setVisible(False)
+        self.ui.randomEnginComboBox.addItem('Random')
+        self.ui.randomEnginComboBox.addItem('Secret')
+        cur_engine = self.parent.settings.value('random_engine', 'random', type=str)
+        if cur_engine == 'random':
+            self.ui.randomEnginComboBox.setCurrentIndex(0)
+        else:
+            self.ui.randomEnginComboBox.setCurrentIndex(1)
+        self.ui.savePushButton.clicked.connect(self.on_save_config)
+
+    def on_save_config(self):
+        lang_index = self.ui.languageComboBox.currentIndex()
+        self.parent.settings.setValue('language', self.lang_codes[lang_index])
+        random_index = self.ui.randomEnginComboBox.currentIndex()
+        self.parent.settings.setValue('random_engine', ('random', 'secret')[random_index])
+        self.close()
\ No newline at end of file
diff --git a/config_dialog.py b/config_dialog.py
new file mode 100755 (executable)
index 0000000..90d17d6
--- /dev/null
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'config_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.14.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
+    QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
+from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
+    QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
+    QPixmap, QRadialGradient)
+from PySide2.QtWidgets import *
+
+
+class Ui_ConfigDialog(object):
+    def setupUi(self, ConfigDialog):
+        if not ConfigDialog.objectName():
+            ConfigDialog.setObjectName(u"ConfigDialog")
+        ConfigDialog.resize(400, 300)
+        self.formLayout_2 = QFormLayout(ConfigDialog)
+        self.formLayout_2.setObjectName(u"formLayout_2")
+        self.langLabel = QLabel(ConfigDialog)
+        self.langLabel.setObjectName(u"langLabel")
+
+        self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.langLabel)
+
+        self.languageComboBox = QComboBox(ConfigDialog)
+        self.languageComboBox.setObjectName(u"languageComboBox")
+
+        self.formLayout_2.setWidget(0, QFormLayout.FieldRole, self.languageComboBox)
+
+        self.randomEnginLabel = QLabel(ConfigDialog)
+        self.randomEnginLabel.setObjectName(u"randomEnginLabel")
+
+        self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.randomEnginLabel)
+
+        self.randomEnginComboBox = QComboBox(ConfigDialog)
+        self.randomEnginComboBox.setObjectName(u"randomEnginComboBox")
+
+        self.formLayout_2.setWidget(1, QFormLayout.FieldRole, self.randomEnginComboBox)
+
+        self.savePushButton = QPushButton(ConfigDialog)
+        self.savePushButton.setObjectName(u"savePushButton")
+
+        self.formLayout_2.setWidget(2, QFormLayout.FieldRole, self.savePushButton)
+
+
+        self.retranslateUi(ConfigDialog)
+
+        QMetaObject.connectSlotsByName(ConfigDialog)
+    # setupUi
+
+    def retranslateUi(self, ConfigDialog):
+        ConfigDialog.setWindowTitle(QCoreApplication.translate("ConfigDialog", u"MinimPy2 Config", None))
+        self.langLabel.setText(QCoreApplication.translate("ConfigDialog", u"Language", None))
+        self.randomEnginLabel.setText(QCoreApplication.translate("ConfigDialog", u"Random engin", None))
+        self.savePushButton.setText(QCoreApplication.translate("ConfigDialog", u"Save", None))
+    # retranslateUi
+
diff --git a/config_dialog.ui b/config_dialog.ui
new file mode 100755 (executable)
index 0000000..e227ac0
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigDialog</class>
+ <widget class="QDialog" name="ConfigDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MinimPy2 Config</string>
+  </property>
+  <layout class="QFormLayout" name="formLayout_2">
+   <item row="0" column="0">
+    <widget class="QLabel" name="langLabel">
+     <property name="text">
+      <string>Language</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QComboBox" name="languageComboBox"/>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="randomEnginLabel">
+     <property name="text">
+      <string>Random engin</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QComboBox" name="randomEnginComboBox"/>
+   </item>
+   <item row="2" column="1">
+    <widget class="QPushButton" name="savePushButton">
+     <property name="text">
+      <string>Save</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/db.py b/db.py
new file mode 100755 (executable)
index 0000000..5d8f86b
--- /dev/null
+++ b/db.py
@@ -0,0 +1,796 @@
+from PySide2 import QtSql as qts
+from PySide2 import QtWidgets as qtw
+from PySide2 import QtCore as qtc
+
+import sys
+import os
+
+
+# noinspection PyTypeChecker
+class Database(qtc.QObject):
+    __instance = None
+
+    @staticmethod
+    def get_instance(mainWindow):
+        if Database.__instance is None:
+            Database(mainWindow)
+        return Database.__instance
+
+    def get_db(self):
+        return self.db
+
+    def create_db(self):
+        if not self.db.open():
+            self.show_error(
+                self.tr('Error'),
+                'open db: ' + self.db.lastError().text()
+            )
+            sys.exit(1)
+        all_sqls = open('db/database.sql').read().split(';')
+        for sql in all_sqls:
+            query = qts.QSqlQuery(self.db)
+            query.prepare(sql)
+            if not query.exec_():
+                self.show_error(
+                    self.tr('Error'),
+                    'creating db: ' + self.db.lastError().text()
+                )
+                sys.exit(1)
+
+    def __init__(self, mainWindow):
+        super().__init__()
+        if Database.__instance is not None:
+            raise Exception("This class is a singleton!")
+        self.mainWindow = mainWindow
+        self.db = qts.QSqlDatabase.addDatabase('QSQLITE')
+        self.db.setDatabaseName('db/minimizer.db')
+        if not os.path.exists('db/minimizer.db'):
+            self.create_db()
+        else:
+            if not self.db.open():
+                self.show_error(
+                    self.tr('DB Connection Error'),
+                    self.tr('Could not open database file {}').format(self.db.lastError().text())
+                )
+                sys.exit(1)
+        required_tables = {'trials', 'discarded_identifiers', 'treatments', 'factors', 'levels', 'subjects',
+                           'subject_levels', 'preloads'}
+        missing_tables = required_tables - set(self.db.tables())
+        if missing_tables:
+            self.show_error(
+                self.tr('DB Integrity Error'),
+                self.tr('Missing tables, please repair DB {}').format(missing_tables)
+            )
+            sys.exit(1)
+        if not self.checkForeignKeys():
+            sys.exit(1)
+        Database.__instance = self
+
+    def checkForeignKeys(self):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('PRAGMA foreign_keys;')
+        if not query.exec_():
+            self.show_query_error(query)
+        res = query.next()
+        if not res:
+            self.show_query_error(query)
+        if query.value(0):
+            return True
+        query.prepare('PRAGMA foreign_keys = ON;')
+        if not query.exec_():
+            self.show_query_error(query)
+        query.prepare('PRAGMA foreign_keys;')
+        query.exec_()
+        if query.next():
+            return query.value(0) > 0
+        else:
+            return False
+
+    def get_treatment_title(self, treatment_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT title FROM treatments WHERE id = :treatment_id')
+        query.bindValue(':treatment_id', treatment_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value('title')
+
+    def update_subject_treatment_id(self, subject_id, treatment_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('''UPDATE subjects set treatment_id = :treatment_id, 
+        modified = CURRENT_TIMESTAMP WHERE id = :subject_id''')
+        query.bindValue(':treatment_id', treatment_id)
+        query.bindValue(':subject_id', subject_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def get_subject_treatment_id(self, subject_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare("SELECT treatment_id FROM subjects WHERE id = :subject_id")
+        query.bindValue(':subject_id', subject_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        if not query.next():
+            return None
+        return query.value('treatment_id')
+
+    def get_subject(self, subject_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare("SELECT * FROM subjects WHERE id = :subject_id")
+        query.bindValue(':subject_id', subject_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        if not query.next():
+            return None
+        return query
+
+    def get_subjects(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('''SELECT id, treatment_id, identifier_value, enrolled, 
+        modified FROM subjects WHERE trial_id = :trial_id''')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        subjects = []
+        while query.next():
+            subject = [
+                query.value('id'),
+                query.value('treatment_id'),
+                query.value('identifier_value'),
+                query.value('enrolled'),
+                query.value('modified')
+            ]
+            subjects.append(subject)
+        return subjects
+
+    def get_subject_level_titles(self, subject_levels):
+        query = qts.QSqlQuery(self.db)
+        level_title = []
+        for factor_id, level_id in subject_levels:
+            query.prepare('SELECT title FROM levels WHERE id = :level_id')
+            query.bindValue(':level_id', level_id)
+            if not query.exec_():
+                self.show_query_error(query)
+            if not query.next():
+                return False
+            level_title.append(query.value('title'))
+        return level_title
+
+    def get_subject_levels(self, subject_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare("SELECT factor_id, level_id FROM subject_levels WHERE subject_id = :subject_id ORDER BY id")
+        query.bindValue(':subject_id', subject_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        subject_levels = []
+        while query.next():
+            subject_level = [query.value('factor_id'), query.value('level_id')]
+            subject_levels.append(subject_level)
+        return subject_levels
+
+    def get_freq(self, trial_id):
+        subjects = self.get_subjects(trial_id)
+        freq = self.get_empty_freq(trial_id)
+        for subject in subjects:
+            subject_levels = self.get_subject_levels(subject[0])
+            for subject_level in subject_levels:
+                key = (subject[1], subject_level[0], subject_level[1])
+                freq[key] += 1
+        return freq
+
+    def get_empty_freq(self, trial_id):
+        freq = {}
+        treatments = self.read_treatments(trial_id)
+        factors = self.read_factors(trial_id)
+        if not treatments or not factors:
+            return
+        for treatment in treatments:
+            for factor in factors:
+                levels = self.factor_levels(factor[0])
+                for level in levels:
+                    key = (treatment[0], factor[0], level[0])
+                    if key in freq:
+                        continue
+                    freq[key] = 0
+        return freq
+
+    def get_preload(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        preload = self.get_empty_freq(trial_id)
+        query.prepare("SELECT treatment_id, factor_id, level_id, count FROM preloads WHERE trial_id = :trial_id")
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        rows = []
+        while query.next():
+            row = [query.value('treatment_id'), query.value('factor_id'), query.value('level_id'), query.value('count')]
+            rows.append(row)
+        for row in rows:
+            key = (row[0], row[1], row[2])
+            preload[key] = row[3]
+        return preload
+
+    def get_preload_with_freq(self, trial_id):
+        freq = self.get_freq(trial_id)
+        preload = self.get_preload(trial_id)
+        if freq is None or preload is None:
+            return None
+        preload_with_freq = {}
+        for key in freq:
+            preload_with_freq[key] = freq[key] + preload[key]
+        return preload_with_freq
+
+    def insert_level(self, trial_id, factor_id, title):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('INSERT INTO levels (trial_id, factor_id, title)  VALUES (:trial_id, :factor_id, :title)')
+        query.bindValue(':trial_id', trial_id)
+        query.bindValue(':factor_id', factor_id)
+        query.bindValue(':title', title)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query.lastInsertId()
+
+    def insert_factor(self, trial_id, title):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('INSERT INTO factors (trial_id, title)  VALUES (:trial_id, :title)')
+        query.bindValue(':title', title)
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query.lastInsertId()
+
+    def insert_treatment(self, trial_id, title):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('INSERT INTO treatments (trial_id, title)  VALUES (:trial_id, :title)')
+        query.bindValue(':title', title)
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query.lastInsertId()
+
+    def insert_trial(self, title):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('INSERT INTO trials (title)  VALUES (:title)')
+        query.bindValue(':title', title)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query.lastInsertId()
+
+    def delete_level(self, level_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM levels WHERE id = :id')
+        query.bindValue(':id', level_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def delete_treatment(self, treatment_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM treatments WHERE id = :id')
+        query.bindValue(':id', treatment_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def delete_factor(self, factor_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM factors WHERE id = :id')
+        query.bindValue(':id', factor_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def delete_trial(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM trials WHERE id = :id')
+        query.bindValue(':id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def get_title(self, row_id, table):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT title FROM {} WHERE id = :row_id'.format(table))
+        query.bindValue(':row_id', row_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value('title')
+
+    def update_level(self, level_id, title):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('UPDATE levels set title = :title WHERE id = :id')
+        query.bindValue(':title', title)
+        query.bindValue(':id', level_id)
+        if not query.exec_():
+            if self.show_query_error(query):
+                return self.get_title(level_id, 'levels')
+
+    def update_factor(self, factor_id, column, value):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('UPDATE factors set {} = :value WHERE id = :id'.format(column))
+        query.bindValue(':value', value)
+        query.bindValue(':id', factor_id)
+        if not query.exec_():
+            if self.show_query_error(query):
+                return self.get_title(factor_id, 'factors')
+
+    def update_treatment(self, treatment_id, column, value):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('UPDATE treatments set {} = :value WHERE id = :id'.format(column))
+        query.bindValue(':value', value)
+        query.bindValue(':id', treatment_id)
+        if not query.exec_():
+            if self.show_query_error(query):
+                return self.get_title(treatment_id, 'treatments')
+
+    def update_trial(self, trial_id, column, value):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('UPDATE trials SET {} = :value, modified = CURRENT_TIMESTAMP WHERE id = :id'.format(column))
+        query.bindValue(':value', value)
+        query.bindValue(':id', trial_id)
+        if not query.exec_():
+            if self.show_query_error(query):
+                return self.get_title(trial_id, 'trials')
+
+    def load_levels(self, factor_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT * FROM levels WHERE factor_id = :factor_id ORDER BY id')
+        query.bindValue(':factor_id', factor_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query
+
+    def load_treatments(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT * FROM treatments WHERE trial_id = :trial_id ORDER BY id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query
+
+    def load_factors(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT * FROM factors WHERE trial_id = :trial_id ORDER BY id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query
+
+    def get_levels(self, factor_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT title FROM levels WHERE factor_id = :factor_id ORDER BY id')
+        query.bindValue(':factor_id', factor_id)
+        if not query.exec_():
+            qtw.QMessageBox.critical(
+                self.parent(),
+                self.tr('DB read Error'),
+                query.lastError().text()
+            )
+            return
+        levels = []
+        while query.next():
+            levels.append(query.value('title'))
+        return ', '.join(levels)
+
+    def get_used_identifiers(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT identifier_value FROM subjects WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        ret = []
+        while query.next():
+            ret.append(query.value('identifier_value'))
+        return ret
+
+    def get_discarded_identifiers(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT identifier FROM discarded_identifiers WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        ret = []
+        while query.next():
+            ret.append(query.value('identifier'))
+        return ret
+
+    def get_count_discarded_identifiers(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM discarded_identifiers WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value(0)
+
+    def get_num_treatments(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM treatments WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value(0)
+
+    def get_factor_level_indices(self, factor_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM levels WHERE factor_id = :factor_id')
+        query.bindValue(':factor_id', factor_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return list(range(query.value(0)))
+
+    def get_factor_weights(self, trial_id):
+        factors = self.read_factors(trial_id)
+        if not factors:
+            return
+        return [factor[2] for factor in factors]
+
+    def get_factors_level_indices(self, trial_id):
+        factors = self.read_factors(trial_id)
+        if not factors:
+            return
+        fli = []
+        for factor in factors:
+            fli.append(self.get_factor_level_indices(factor[0]))
+        return fli
+
+    def get_allocation_ratios(self, trial_id):
+        treatments = self.read_treatments(trial_id)
+        if not treatments:
+            return False
+        return [treatment[2] for treatment in treatments]
+
+    def get_treatments_id_dict(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT id from treatments WHERE trial_id = :trial_id ORDER BY id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        rows = []
+        while query.next():
+            rows.append(query.value('id'))
+        return {row: i for i, row in enumerate(rows)}
+
+    def get_allocations(self, trial_id):
+        treatment_dict = self.get_treatments_id_dict(trial_id)
+        allocations = []
+        subjects = self.get_subjects(trial_id)
+        for subject in subjects:
+            allocation = {'allocation': treatment_dict[subject[1]], 'levels': [], 'UI': subject[2]}
+            levels = self.get_subject_levels(subject[0])
+            for level in levels:
+                level_dict = self.get_factor_level_dict(level[0])
+                allocation['levels'].append(level_dict[level[1]])
+            allocations.append(allocation)
+        return allocations
+
+    def get_min_allocation_ratio_group_index(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT id FROM treatments WHERE trial_id = :trial_id AND ratio = (SELECT min(ratio) FROM treatments);')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        result = query.value('id')
+        treatment_dict = self.get_treatments_id_dict(trial_id)
+        return treatment_dict[result]
+
+    def get_factor_level_dict(self, factor_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT id FROM levels WHERE factor_id = :factor_id ORDER BY id')
+        query.bindValue(':factor_id', factor_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        rows = []
+        while query.next():
+            rows.append(query.value('id'))
+        return {row: i for i, row in enumerate(rows)}
+
+    def update_trial_setting(self, trial_id, ui):
+        title = ui.trialTitleLineEdit.text()
+        if len(title.strip()) == 0:
+            return
+        code = ui.trialCodeLineEdit.text()
+        prob_method = ui.trialProbMethodComboBox.currentIndex()
+        base_prob = ui.trialBaseProbabilitySlider.value() / 100.0
+        dist_method = ui.trialDistanceMethodComboBox.currentIndex()
+        identifier_type = ui.trialIdentifierTypeComboBox.currentIndex()
+        identifier_order = ui.trialIentifierOrderComboBox.currentIndex()
+        identifier_length = ui.trialIdentifierLengthSpinBox.value()
+        recycle_ids = 1 if ui.trialRecycleIdsCheckBox.isChecked() else 0
+        new_subject_random = 1 if ui.trialNewSubjectRandomCheckBox.isChecked() else 0
+        arms_weight = ui.trialArmsWeightDoubleSlider.value()
+        query = qts.QSqlQuery(self.db)
+        query.prepare('UPDATE trials set '
+                      'title = :title, '
+                      'code = :code, '
+                      'modified = CURRENT_TIMESTAMP, '
+                      'prob_method = :prob_method, '
+                      'base_prob = :base_prob, '
+                      'dist_method = :dist_method, '
+                      'identifier_type = :identifier_type, '
+                      'identifier_order = :identifier_order, '
+                      'identifier_length = :identifier_length, '
+                      'recycle_ids = :recycle_ids, '
+                      'new_subject_random = :new_subject_random, '
+                      'arms_weight = :arms_weight '
+                      'WHERE id = :id'
+                      )
+        query.bindValue(':title', title)
+        query.bindValue(':code', code)
+        query.bindValue(':prob_method', prob_method)
+        query.bindValue(':base_prob', base_prob)
+        query.bindValue(':dist_method', dist_method)
+        query.bindValue(':identifier_type', identifier_type)
+        query.bindValue(':identifier_order', identifier_order)
+        query.bindValue(':identifier_length', identifier_length)
+        query.bindValue(':recycle_ids', recycle_ids)
+        query.bindValue(':new_subject_random', new_subject_random)
+        query.bindValue(':arms_weight', arms_weight)
+        query.bindValue(':id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def get_trial_setting(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT * FROM trials WHERE id = :id')
+        query.bindValue(':id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        if not query.next():
+            return False
+        return query
+
+    def clear_trial_setting(self, ui):
+        ui.trialCreatedValueLabel.setText('')
+        ui.trialModifiedValueLabel.setText('')
+        ui.trialTitleLineEdit.setText('')
+        ui.trialCodeLineEdit.setText('')
+        ui.trialProbMethodComboBox.setCurrentIndex(0)
+        ui.trialBaseProbabilitySlider.setValue(0.7 * 100)
+        ui.trialDistanceMethodComboBox.setCurrentIndex(0)
+        ui.trialIdentifierTypeComboBox.setCurrentIndex(0)
+        ui.trialIentifierOrderComboBox.setCurrentIndex(0)
+        ui.trialIdentifierLengthSpinBox.setValue(3)
+        ui.trialRecycleIdsCheckBox.setChecked(False)
+        ui.trialNewSubjectRandomCheckBox.setChecked(False)
+        ui.trialArmsWeightDoubleSlider.setValue(1.0)
+
+    def map_trial_setting(self, query, ui):
+        ui.trialCreatedValueLabel.setText(query.value('created'))
+        ui.trialModifiedValueLabel.setText(query.value('modified'))
+        ui.trialTitleLineEdit.setText(query.value('title'))
+        ui.trialCodeLineEdit.setText(query.value('code'))
+        ui.trialProbMethodComboBox.setCurrentIndex(query.value('prob_method'))
+        ui.trialBaseProbabilitySlider.setValue(query.value('base_prob') * 100)
+        ui.trialDistanceMethodComboBox.setCurrentIndex(query.value('dist_method'))
+        ui.trialIdentifierTypeComboBox.setCurrentIndex(query.value('identifier_type'))
+        ui.trialIentifierOrderComboBox.setCurrentIndex(query.value('identifier_order'))
+        ui.trialIdentifierLengthSpinBox.setValue(query.value('identifier_length'))
+        ui.trialRecycleIdsCheckBox.setChecked(query.value('recycle_ids') != 0)
+        ui.trialNewSubjectRandomCheckBox.setChecked(query.value('new_subject_random') != 0)
+        ui.trialArmsWeightDoubleSlider.setValue(query.value('arms_weight'))
+
+    def load_trial_setting(self, trial_id, ui):
+        self.clear_trial_setting(ui)
+        query = self.get_trial_setting(trial_id)
+        if not query:
+            return
+        self.map_trial_setting(query, ui)
+
+    def read_factors(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT id, title, weight FROM factors WHERE trial_id = :trial_id ORDER BY id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        factors = []
+        while query.next():
+            factor = [query.value('id'), query.value('title'), query.value('weight')]
+            factors.append(factor)
+        return factors
+
+    def read_treatments(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT id, title, ratio FROM treatments WHERE trial_id = :trial_id ORDER BY id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        treatments = []
+        while query.next():
+            treatment = [query.value('id'), query.value('title'), query.value('ratio')]
+            treatments.append(treatment)
+        return treatments
+
+    def get_factor_level(self, trial_id):
+        factors = self.read_factors(trial_id)
+        if not factors:
+            return
+        fl = []
+        for factor in factors:
+            query = qts.QSqlQuery(self.db)
+            query.prepare('SELECT id, title FROM levels WHERE factor_id = :factor_id ORDER BY id')
+            query.bindValue(':factor_id', factor[0])
+            query.exec_()
+            levels = []
+            while query.next():
+                levels.append([query.value('id'), query.value('title')])
+            fl.append(levels)
+        return fl
+
+    def factor_levels(self, factor_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare("SELECT id, title FROM levels WHERE factor_id = :factor_id ORDER BY id")
+        query.bindValue(':factor_id', factor_id)
+        query.exec_()
+        levels = []
+        while query.next():
+            levels.append([query.value('id'), query.value('title')])
+        return levels
+
+    def insert_subject(self, identifier_value, treatment_id, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('''INSERT INTO subjects (trial_id, identifier_value, treatment_id) 
+        VALUES (:trial_id, :identifier_value, :treatment_id)''')
+        query.bindValue(':trial_id', trial_id)
+        query.bindValue(':identifier_value', identifier_value)
+        query.bindValue(':treatment_id', treatment_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        return query.lastInsertId()
+
+    def insert_subject_levels(self, trial_id, subject_id, subject_levels):
+        query = qts.QSqlQuery(self.db)
+        for factor_id, level_id in subject_levels:
+            query.prepare('''INSERT INTO subject_levels (trial_id, subject_id, factor_id, level_id)
+        VALUES (:trial_id, :subject_id, :factor_id, :level_id);''')
+            query.bindValue(':trial_id', trial_id)
+            query.bindValue(':subject_id', subject_id)
+            query.bindValue(':factor_id', factor_id)
+            query.bindValue(':level_id', level_id)
+            if not query.exec_():
+                self.show_query_error(query)
+        return self.get_subject_level_titles(subject_levels)
+
+    def clear_preload(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM preloads WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        query.exec_()
+
+    def delete_subject_levels(self, subject_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM subject_levels WHERE subject_id = :subject_id')
+        query.bindValue(':subject_id', subject_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def delete_subject(self, subject_id, id_value=None, trial_id=None):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM subjects WHERE id = :subject_id;')
+        query.bindValue(':subject_id', subject_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        if id_value is None:
+            return
+        query.prepare('''INSERT INTO discarded_identifiers (trial_id, identifier)
+        VALUES (:trial_id, :identifier)''')
+        query.bindValue(':trial_id', trial_id)
+        query.bindValue(':identifier', id_value)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def delete_subjects(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('DELETE FROM subjects WHERE trial_id = :trial_id;')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.prepare('DELETE FROM discarded_identifiers WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+
+    def save_preload(self, trial_id, preload):
+        query = qts.QSqlQuery(self.db)
+        for key in preload:
+            t, f, lv = key
+            c = preload[key]
+            query.prepare('''INSERT INTO preloads (trial_id, treatment_id, factor_id, level_id, count) 
+                        VALUES (:trial_id, :treatment_id, :factor_id, :level_id, :count)''')
+            query.bindValue(':trial_id', trial_id)
+            query.bindValue(':treatment_id', t)
+            query.bindValue(':factor_id', f)
+            query.bindValue(':level_id', lv)
+            query.bindValue(':count', c)
+            query.exec_()
+
+    def show_error(self, title, text):
+        qtw.QMessageBox.critical(self.mainWindow, title, text)
+
+    def show_query_error(self, query):
+        qtw.QMessageBox.critical(
+            self.mainWindow,
+            self.tr('DB Error'),
+            query.lastError().text()
+        )
+        fatal = 'UNIQUE constraint failed' not in query.lastError().text()
+        if fatal:
+            sys.exit(1)
+            return False
+        else:
+            return True
+
+    def has_preload(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM preloads WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        if not query.next():
+            return False
+        return query.value(0) > 0
+
+    def get_subject_count(self, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM subjects WHERE trial_id = :trial_id')
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        if not query.next():
+            return False
+        return query.value(0)
+
+    def has_subject(self, trial_id):
+        subject_count = self.get_subject_count(trial_id)
+        if not subject_count:
+            return False
+        return subject_count > 0
+
+    def get_first_trial_setting(self):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT min(id) FROM trials')
+        if not query.exec_():
+            self.show_query_error(query)
+        if not query.next():
+            return False
+        trial_id = query.value(0)
+        return self.get_trial_setting(trial_id)
+
+    def trial_title_exists(self, title):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM trials WHERE title = :title')
+        query.bindValue(':title', title)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value(0) > 0
+
+    def treatment_title_exists(self, title, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM treatments WHERE title = :title and trial_id = :trial_id')
+        query.bindValue(':title', title)
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value(0) > 0
+
+    def factor_title_exists(self, title, trial_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM factors WHERE title = :title and trial_id = :trial_id')
+        query.bindValue(':title', title)
+        query.bindValue(':trial_id', trial_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value(0) > 0
+
+    def level_title_exists(self, title, factor_id):
+        query = qts.QSqlQuery(self.db)
+        query.prepare('SELECT COUNT(*) FROM levels WHERE title = :title and factor_id = :factor_id')
+        query.bindValue(':title', title)
+        query.bindValue(':factor_id', factor_id)
+        if not query.exec_():
+            self.show_query_error(query)
+        query.next()
+        return query.value(0) > 0
\ No newline at end of file
diff --git a/db/database.sql b/db/database.sql
new file mode 100755 (executable)
index 0000000..f543b6a
--- /dev/null
@@ -0,0 +1,77 @@
+CREATE TABLE trials (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        title TEXT NOT NULL,
+        code TEXT,
+        created DATETIME DEFAULT CURRENT_TIMESTAMP,
+        modified DATETIME DEFAULT CURRENT_TIMESTAMP,
+        prob_method INTEGER DEFAULT 0,
+        base_prob FLOAT DEFAULT 0.7,
+        dist_method INTEGER DEFAULT 0,
+        identifier_type INTEGER DEFAULT 0,
+        identifier_order INTEGER DEFAULT 0,
+        identifier_length INTEGER DEFAULT 3,
+        recycle_ids INTEGER DEFAULT 0,
+        new_subject_random INTEGER DEFAULT 0,
+        arms_weight FLOAT DEFAULT 0,
+        UNIQUE(title) ON CONFLICT ABORT);
+CREATE TABLE discarded_identifiers (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        identifier INTEGER NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, identifier) ON CONFLICT ABORT);
+CREATE TABLE treatments (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        title TEXT NOT NULL,
+        ratio INTEGER DEFAULT 1,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, title) ON CONFLICT ABORT);
+CREATE TABLE factors (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        title TEXT NOT NULL,
+        weight FLOAT DEFAULT 1.0 NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, title) ON CONFLICT ABORT);
+CREATE TABLE levels (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        factor_id INTEGER NOT NULL,
+        title TEXT NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (factor_id) REFERENCES factors (id) ON DELETE CASCADE,
+        UNIQUE (factor_id, title) ON CONFLICT ABORT);
+CREATE TABLE subjects (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        identifier_value INTEGER NOT NULL,
+        treatment_id INTEGER NOT NULL,
+        enrolled DATETIME DEFAULT CURRENT_TIMESTAMP,
+        modified DATETIME DEFAULT CURRENT_TIMESTAMP,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (treatment_id) REFERENCES treatments (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, identifier_value) ON CONFLICT ABORT);
+CREATE TABLE subject_levels (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        subject_id INTEGER NOT NULL,
+        factor_id INTEGER NOT NULL,
+        level_id INTEGER NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (subject_id) REFERENCES subjects (id) ON DELETE CASCADE,
+        FOREIGN KEY (factor_id) REFERENCES factors (id) ON DELETE CASCADE,
+        FOREIGN KEY (level_id) REFERENCES levels (id) ON DELETE CASCADE,
+        UNIQUE (factor_id, subject_id) ON CONFLICT ABORT);
+CREATE TABLE preloads (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        treatment_id INTEGER NOT NULL,
+        factor_id INTEGER NOT NULL,
+        level_id INTEGER NOT NULL,
+        count INTEGER DEFAULT 0,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (treatment_id) REFERENCES treatments (id) ON DELETE CASCADE,
+        FOREIGN KEY (factor_id) REFERENCES factors (id) ON DELETE CASCADE,
+        FOREIGN KEY (level_id) REFERENCES levels (id) ON DELETE CASCADE,
+        UNIQUE (treatment_id, factor_id, level_id) ON CONFLICT ABORT)
diff --git a/db/db.sql b/db/db.sql
new file mode 100755 (executable)
index 0000000..315489e
--- /dev/null
+++ b/db/db.sql
@@ -0,0 +1,89 @@
+BEGIN TRANSACTION;
+CREATE TABLE trials (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        title TEXT NOT NULL,
+        code TEXT,
+        created DATETIME DEFAULT CURRENT_TIMESTAMP,
+        modified DATETIME DEFAULT CURRENT_TIMESTAMP,
+        prob_method INTEGER DEFAULT 0,
+        base_prob FLOAT DEFAULT 0.7,
+        dist_method INTEGER DEFAULT 0,
+        identifier_type INTEGER DEFAULT 0,
+        identifier_order INTEGER DEFAULT 0,
+        identifier_length INTEGER DEFAULT 3,
+        recycle_ids INTEGER DEFAULT 0,
+        new_subject_random INTEGER DEFAULT 0,
+        arms_weight FLOAT DEFAULT 0);
+CREATE TABLE discarded_identifiers (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        identifier INTEGER NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, identifier) ON CONFLICT ABORT);
+CREATE TABLE treatments (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        title TEXT NOT NULL,
+        ratio INTEGER DEFAULT 1,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, title) ON CONFLICT ABORT);
+CREATE TABLE factors (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        title TEXT NOT NULL,
+        weight FLOAT DEFAULT 1.0 NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, title) ON CONFLICT ABORT);
+CREATE TABLE levels (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        factor_id INTEGER NOT NULL,
+        title TEXT NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (factor_id) REFERENCES factors (id) ON DELETE CASCADE,
+        UNIQUE (factor_id, title) ON CONFLICT ABORT);
+CREATE TABLE subjects (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        identifier TEXT NOT NULL,
+        identifier_value INTEGER NOT NULL,
+        treatment_id INTEGER NOT NULL,
+        enrolled DATETIME DEFAULT CURRENT_TIMESTAMP,
+        modified DATETIME DEFAULT CURRENT_TIMESTAMP,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (treatment_id) REFERENCES treatments (id) ON DELETE CASCADE,
+        UNIQUE (trial_id, identifier) ON CONFLICT ABORT);
+CREATE TABLE subject_levels (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        subject_id INTEGER NOT NULL,
+        factor_id INTEGER NOT NULL,
+        level_id INTEGER NOT NULL,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (subject_id) REFERENCES subjects (id) ON DELETE CASCADE,
+        FOREIGN KEY (factor_id) REFERENCES factors (id) ON DELETE CASCADE,
+        FOREIGN KEY (level_id) REFERENCES levels (id) ON DELETE CASCADE,
+        UNIQUE (factor_id, subject_id) ON CONFLICT ABORT);
+CREATE TABLE preloads (
+        id INTEGER PRIMARY KEY AUTOINCREMENT,
+        trial_id INTEGER NOT NULL,
+        treatment_id INTEGER NOT NULL,
+        factor_id INTEGER NOT NULL,
+        level_id INTEGER NOT NULL,
+        count INTEGER DEFAULT 0,
+        FOREIGN KEY (trial_id) REFERENCES trials (id) ON DELETE CASCADE,
+        FOREIGN KEY (treatment_id) REFERENCES treatments (id) ON DELETE CASCADE,
+        FOREIGN KEY (factor_id) REFERENCES factors (id) ON DELETE CASCADE,
+        FOREIGN KEY (level_id) REFERENCES levels (id) ON DELETE CASCADE,
+        UNIQUE (treatment_id, factor_id, level_id) ON CONFLICT ABORT);
+DELETE FROM sqlite_sequence;
+CREATE VIEW subject_treatment AS SELECT 
+        subjects.id id, subjects.trial_id trial_id, identifier, treatments.title as treatment 
+        FROM subjects INNER JOIN treatments ON treatments.id = treatment_id;
+CREATE VIEW sl_view as SELECT subject_levels.id id, subject_levels.subject_id subject_id, 
+subject_levels.factor_id factor_id, subject_levels.level_id level_id, factors.title factor, 
+levels.title level, subjects.treatment_id from subject_levels LEFT JOIN levels ON 
+subject_levels.level_id = levels.id LEFT JOIN factors ON subject_levels.factor_id = factors.id 
+LEFT JOIN subjects ON subject_levels.subject_id = subjects.id;
+COMMIT;
+
diff --git a/db/minimizer.db b/db/minimizer.db
new file mode 100755 (executable)
index 0000000..56e87ea
Binary files /dev/null and b/db/minimizer.db differ
diff --git a/enrol_form.py b/enrol_form.py
new file mode 100755 (executable)
index 0000000..a8c2d95
--- /dev/null
@@ -0,0 +1,138 @@
+from PySide2 import QtWidgets as qtw
+from PySide2 import QtCore as qtc
+
+
+class EnrolForm(qtw.QDialog):
+    def __init__(self, parent, factors, treatment, edit=False, identifier=None):
+        super().__init__()
+        self.m_treatment = None
+        self.p_treatment = None
+        self.m_index = None
+        self.p_index = None
+        self.selected_probs = None
+        self.parent = parent
+        self.setGeometry(100, 100, 400, -1)
+        self.factors = factors
+        self.treatment = treatment
+        form_layout = qtw.QFormLayout()
+        if edit:
+            self.setWindowTitle(self.tr('Editing subject "{}"').format(identifier))
+            treatment_combo = qtw.QComboBox()
+            treatment_titles = []
+            treatment_ids = []
+            selected_treatment_index = -1
+            for i, t in enumerate(treatment['treatments']):
+                treatment_titles.append(t['title'])
+                treatment_ids.append(t['id'])
+                if treatment['selected_treatment_id'] == t['id']:
+                    selected_treatment_index = i
+            treatment_combo.addItems(treatment_titles)
+            treatment_combo.setCurrentIndex(selected_treatment_index)
+            treatment_combo.currentIndexChanged.connect(self.get_treatment_combo_events(treatment_ids))
+            form_layout.addRow(self.tr('Treatment'), treatment_combo)
+        else:
+            self.setWindowTitle(self.tr('Subject enrol'))
+
+        for f, factor in enumerate(factors):
+            factor_title = factor['title']
+            levels_combo = qtw.QComboBox()
+            level_titles = []
+            level_ids = []
+            selected_index = -1
+            for i, level in enumerate(factor['levels']):
+                level_titles.append(level['title'])
+                level_ids.append(level['id'])
+                if factor['selected_level_id'] == level['id']:
+                    selected_index = i
+            levels_combo.addItems(level_titles)
+            levels_combo.setCurrentIndex(selected_index)
+            levels_combo.currentIndexChanged.connect(self.get_levels_combo_event(f, level_ids))
+            form_layout.addRow(factor_title, levels_combo)
+        self.result_label = qtw.QLabel(self.tr('Select factors, click enrol'))
+        self.enrol_button = qtw.QPushButton(self.tr('Enrol'))
+        close_button = qtw.QPushButton(self.tr('Close'))
+        if edit:
+            self.enrol_button.setText(self.tr('Save'))
+            form_layout.addRow(self.enrol_button)
+            close_button.setText(self.tr('Cancel'))
+            self.enrol_button.clicked.connect(self.save_subject)
+        else:
+            form_layout.addRow(self.enrol_button, self.result_label)
+            self.enrol_button.clicked.connect(self.enrol_subject)
+        clickable(self.result_label).connect(self.on_detail)
+        close_button.clicked.connect(self.close)
+        form_layout.addRow(close_button)
+        self.setLayout(form_layout)
+
+    def on_detail(self):
+        trial_id = self.parent.settings.value('last_trial_id', 0, type=int)
+        if trial_id == 0:
+            return
+        ns = self.parent.database.get_subject_count(trial_id)
+        if ns == 1:
+            qtw.QMessageBox.information(self.parent, self.tr('Minimisation detail'), self.tr('Enrolled by randomisation'))
+            return
+        final_group = self.tr('preferred')
+        prob = self.selected_probs[self.p_index]
+        if self.m_treatment != self.p_treatment:
+            final_group = self.tr('non-preferred')
+            prob = self.selected_probs[self.m_index]
+        msg = self.tr('Preferred treatment = {}\n').format(self.p_treatment)
+        msg += self.tr('Minimised treatment = {}\n').format(self.m_treatment)
+        msg += self.tr('Subject assigned to {} treatment with a probability of {:4.2f}').format(final_group, prob)
+        qtw.QMessageBox.information(self.parent, self.tr('Minimisation detail'), msg)
+
+    def save_subject(self):
+        self.close()
+        self.setResult(qtw.QDialog.Accepted)
+
+    def enrol_subject(self):
+        selected_indices = []
+        selected_ids = []
+        for factor in self.factors:
+            if factor['selected_level_id'] == -1:
+                self.result_label.setText(self.tr('Select {} level').format(factor['title']))
+                return
+            for i, level in enumerate(factor['levels']):
+                if factor['selected_level_id'] == level['id']:
+                    selected_indices.append(i)
+                    selected_ids.append((factor['id'], factor['selected_level_id']))
+                    break
+        mt, mt_index, pt, pt_index, identifier, probs = self.parent.enrol_one(selected_indices, selected_ids)
+        self.m_index = mt_index
+        self.p_index = pt_index
+        self.m_treatment = mt
+        self.p_treatment = pt
+        self.selected_probs = probs
+        style = 'color: blue;'
+        if mt != pt:
+            style = 'color: red;'
+        self.result_label.setStyleSheet(style)
+        self.result_label.setText(self.tr('Subject "{}", Enrolled to {}').format(identifier, mt))
+        self.enrol_button.setEnabled(False)
+
+
+    def get_treatment_combo_events(self, treatment_ids):
+        def on_treatment_combo_index_changed(index):
+            self.treatment['selected_treatment_id'] = treatment_ids[index]
+        return on_treatment_combo_index_changed
+
+    def get_levels_combo_event(self, f, level_ids):
+        def on_levels_combo_index_changed(index):
+            self.factors[f]['selected_level_id'] = level_ids[index]
+        return on_levels_combo_index_changed
+
+def clickable(widget):
+    class Filter(qtc.QObject):
+        clicked = qtc.Signal()
+        def eventFilter(self, obj, event):
+            if obj == widget:
+                if event.type() == qtc.QEvent.MouseButtonRelease:
+                    if obj.rect().contains(event.pos()):
+                        self.clicked.emit()
+                        # The developer can opt for .emit(obj) to get the object within the slot.
+                        return True
+            return False
+    filter = Filter(widget)
+    widget.installEventFilter(filter)
+    return filter.clicked
diff --git a/fa_IR.ts b/fa_IR.ts
new file mode 100755 (executable)
index 0000000..effd5a7
--- /dev/null
+++ b/fa_IR.ts
@@ -0,0 +1,1072 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="fa_IR">
+<context>
+    <name>ConfigDialog</name>
+    <message>
+        <location filename="config_dialog.ui" line="14"/>
+        <source>MinimPy2 Config</source>
+        <translation>تنظیمات مینیم‌پای۲</translation>
+    </message>
+    <message>
+        <location filename="config_dialog.ui" line="20"/>
+        <source>Language</source>
+        <translation>زبان</translation>
+    </message>
+    <message>
+        <location filename="config_dialog.ui" line="30"/>
+        <source>Random engin</source>
+        <translation>موتور تصادفی سازی</translation>
+    </message>
+    <message>
+        <location filename="config_dialog.ui" line="40"/>
+        <source>Save</source>
+        <translation>اندوختن</translation>
+    </message>
+</context>
+<context>
+    <name>Database</name>
+    <message>
+        <location filename="db.py" line="35"/>
+        <source>Error</source>
+        <translation>نادرستی</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="52"/>
+        <source>DB Connection Error</source>
+        <translation>بروز نادرستی هنگام پیوستن به بانک داده</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="53"/>
+        <source>Could not open database file: </source>
+        <translation type="obsolete">پرونده داده را نمی توان باز نمود:</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="61"/>
+        <source>DB Integrity Error</source>
+        <translation>بروز ناردستی در ساختار بانک داده</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="62"/>
+        <source>Missing tables, please repair DB: </source>
+        <translation type="obsolete">نشانه ای از جدول های زیر نیست</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="361"/>
+        <source>DB read Error</source>
+        <translation>بروز نادرستی هنگام خوانش بانک داده</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="702"/>
+        <source>DB Error</source>
+        <translation>بروز نادرستی بانک داده</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="53"/>
+        <source>Could not open database file {}</source>
+        <translation>پرونده بانک دادگان باز نمیشود {}</translation>
+    </message>
+    <message>
+        <location filename="db.py" line="62"/>
+        <source>Missing tables, please repair DB {}</source>
+        <translation>برخی حدول ها در بانک دادگان نییست {}</translation>
+    </message>
+</context>
+<context>
+    <name>EnrolForm</name>
+    <message>
+        <location filename="enrol_form.py" line="19"/>
+        <source>Editing subject &quot;{}&quot;</source>
+        <translation>ویرایش سوژه &quot;{}&quot;</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="32"/>
+        <source>Treatment</source>
+        <translation>درمان</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="51"/>
+        <source>Select factors, click enrol</source>
+        <translation>پس از گزینش فاکتورها بر روی پذیرش کلیک کنید</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="52"/>
+        <source>Enrol</source>
+        <translation>پذیرش</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="53"/>
+        <source>Close</source>
+        <translation>بستن</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="55"/>
+        <source>Save</source>
+        <translation>اندوختن</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="57"/>
+        <source>Cancel</source>
+        <translation>بازگشت</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="90"/>
+        <source>Select {} level</source>
+        <translation>سطح {} را برگزینید</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="107"/>
+        <source>Subject &quot;{}&quot;, Enrolled to {}</source>
+        <translation>سوژه &quot;{}&quot;, برای {} پذیرش شد</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="34"/>
+        <source>Subject enrol</source>
+        <translation>سوژه جدید</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="72"/>
+        <source>preferred</source>
+        <translation>برگزیده</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="75"/>
+        <source>non-preferred</source>
+        <translation>غیربرگزیده</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="76"/>
+        <source>Preferred treatment = {}
+</source>
+        <translation>درمان برگزیده = {}
+</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="77"/>
+        <source>Minimised treatment = {}
+</source>
+        <translation>درمان مینی مایز شده = {}
+</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="78"/>
+        <source>Subject assigned to {} treatment with a probability of {:4.2f}</source>
+        <translation>سوژه به گروه {} با احتمال {:4.2f} افزوده شد</translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="79"/>
+        <source>Minimisation detail</source>
+        <translation>تفصیل مینی میزیشن</translation>
+    </message>
+</context>
+<context>
+    <name>FactorLevels</name>
+    <message>
+        <location filename="factor_levels.py" line="32"/>
+        <source>Level title</source>
+        <translation>عنوان لایه</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="33"/>
+        <source>Title</source>
+        <translation>عنوان</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="39"/>
+        <source>Error!</source>
+        <translation>نادرستی!</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="40"/>
+        <source>Level title &apos;{}&apos; already exist!</source>
+        <translation>لایه ای با نام &apos;{}&apos; هم اکنون در دادگان هست!</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="62"/>
+        <source>Confirm delete</source>
+        <translation>پذیرش پاکسازی</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="63"/>
+        <source>Are you sure you want to delete &quot;%s&quot;?</source>
+        <translation>آیا براستی میخواهدی &quot;%s&quot; پاک کنید؟</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="87"/>
+        <source>ID</source>
+        <translation>شناسه</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="87"/>
+        <source>Level</source>
+        <translation>لایه</translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="18"/>
+        <source>Levels of {}</source>
+        <translation>لایه های {}</translation>
+    </message>
+</context>
+<context>
+    <name>FactorLevelsDialog</name>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="14"/>
+        <source>Dialog</source>
+        <translation>دیالوگ</translation>
+    </message>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="25"/>
+        <source>Close</source>
+        <translation>بستن</translation>
+    </message>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="32"/>
+        <source>Delete</source>
+        <translation>پاک کردن</translation>
+    </message>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="39"/>
+        <source>Add</source>
+        <translation>افزودن</translation>
+    </message>
+</context>
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="ui_main_window.py" line="574"/>
+        <source>Start</source>
+        <translation>شروع</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="532"/>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:20pt; font-weight:600;&quot;&gt;How to do minimisation using MinimPy2?&lt;/span&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;A quick tutorial&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;1 - Go to Trials tab and select a trial as current to work on it. Define a new trial if there is no trial&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;2 - Go to Setting tab to view / edit trial setting. Usually the default setting is good to go&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;3 - Go to Treatments tab to define at lease two treatments&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;4 - Go to Factors tab to define at lease one factor&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;5 - Define at least two levels for each factor&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;6 - Now the trial is ready for subject enrollment&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;7 - Go to subject tab and click on the Add button in the toolbar&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;8 - The enroll window will appear. Select a level for each factor and click enroll&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;9 - Subject will enroll to one of treatment groups by the minimisation algorhythm&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;10 - If you want to define a preload go to Frequencies tab&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:20pt; font-weight:600;&quot;&gt;چگونه مینی مایزیشن انجام دهیم&lt;/span&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;یک آموزش سریع&lt;/span&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۱ - به صفحه کارآزمائی ها بروید و یکی را به عنوان جاری انتخاب کنید. اگر هیچ کارآزائی موجود نبود یکی را تعریف کنید&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۲ - برای دیدن یا ویرایش تنظیمات به صفحه تنظیمات بروید. معمولا تنظیمات اولیه کافی است&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۳ - به صفحه درمان ها بروید و حداقل دو درمان را تعریف کنید&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۴ - به صفحه فاکتورها بروید و حداقل یک فاکتور تعریف کنید&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۵ - برای هر فاکتور حداقل دو لایه تعریف کنید&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۶ - اکنون کارآزمائی آماده پذیرش سوژه است&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۷ - به صفحه سوژه ها بروید و بر روی دکمه افزودن در نوار بالای صفحه کلیک کنید&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۸ - پر پنجره سوژه جدید برای هر فاکتور لایه ای انتخاب کنید و دکمه پذیرش را بزنید&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۹ - سوژه جدید توسز الگوریتم مینی میزیشن به یک از گروه ها افزوده میشود&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p dir=&apos;rtl&apos; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;۱۰ اگر میخواهدی برای کارآزمائی پره لود تعریف کنید به صفحه فراوانی بروید&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1197"/>
+        <source>Current trial exported successfully to "{}"</source>
+        <translation>کارآزمائی جاری با موفقیت در فایل "{}" ذخیره گردید</translation>
+    </message>
+    <message>
+        <location filename="about_minimisation.py" line="16"/>
+        <source>Error in languages.lst file format\nPlease see the READ.ME file in locales folder</source>
+        <translation>خطا در فایل languages.lst\nلطفا فایل READ.ME در پوشه locales  را بدقت بخوانید</translation>
+    </message>
+    <message>
+        <location filename="about_minimisation.py" line="16"/>
+        <source>Close</source>
+        <translation>بستن</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.py" line="478"/>
+        <source>What is Minimisation</source>
+        <translation>درباره مینی‌میزیشن</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="63"/>
+        <source>About</source>
+        <translation>درباره</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="119"/>
+        <source>Factor/Level</source>
+        <translation>فاکتور/لایه</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="119"/>
+        <source>SD</source>
+        <translation>انحراف از معیار</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="469"/>
+        <source>Enrolled</source>
+        <translation>پذیرش شده</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="466"/>
+        <source>Treatment</source>
+        <translation>درمان</translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="25"/>
+        <source>Save not available!</source>
+        <translation>اندوختن هم اکنون شدنی نیست!</translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="107"/>
+        <source>Clear preload</source>
+        <translation>پاکسازی پره لود</translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="108"/>
+        <source>To clear preload, select preload and check edit</source>
+        <translation>برای پاک کردن پره‌لود، پره لود و ویرایش را برگزینید</translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="210"/>
+        <source>Total</source>
+        <translation>جمع کل</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="366"/>
+        <source>Error</source>
+        <translation>نادرستی</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="369"/>
+        <source>Sample size consumed!
+No further subject enrol possible!
+Please convert cases to preload and continue</source>
+        <translation>همه نمونه مصرف شده است. دیگر نمی توان سوژه جدیدی وارد کرد. لطفا سوژه ها را به پره لود تبدیل کنید و ادامه دهید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="483"/>
+        <source>Filter</source>
+        <translation>فیلتر</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="569"/>
+        <source>Double check!</source>
+        <translation>دوبار چک!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="571"/>
+        <source>Need your final confirm</source>
+        <translation>تایید نهائی شما را میخواهد</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="574"/>
+        <source>Deleting preload</source>
+        <translation>پاک کردن پره لود</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="576"/>
+        <source>Are you certainly sure you want to convert all subjects into preload?
+ALL subjects will be deleted</source>
+        <translation>آیا واقعا میخواهید همه سوژه ها را به پره لود تبدیل کنید؟ توجه کنید! همه سوژه ها حذف خواهند شد</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="611"/>
+        <source>Help!</source>
+        <translation>راهنما!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1013"/>
+        <source>minimpy2 [{}]</source>
+        <translation>مینیم‌پای۲ [{}]</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="668"/>
+        <source>Factor title</source>
+        <translation>عنوان فاکتور</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="992"/>
+        <source>Title</source>
+        <translation>عنوان</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="720"/>
+        <source>Error!</source>
+        <translation>خطا!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="676"/>
+        <source>Factor title &apos;{}&apos; already exist!</source>
+        <translation>فاکتور با عنوان &apos;{}&apos; هم اکنون وجود دارد!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="693"/>
+        <source>Treatment title</source>
+        <translation>عنوان درمان</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="701"/>
+        <source>Treatment title &apos;{}&apos; already exist!</source>
+        <translation>درمانی با عنوان &apos;{}&apos; هم اکنون وجود دارد!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="713"/>
+        <source>Trial title</source>
+        <translation>عنوان کارآزمائی</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="721"/>
+        <source>A trial title &apos;{}&apos; already exist!</source>
+        <translation>کارآزمائی بالینی با عنوان &apos;{}&apos; هم اکنون وجود دارد!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="816"/>
+        <source>Confirm delete</source>
+        <translation>تایید عمل حذف</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="749"/>
+        <source>Are you sure you want to delete subject &quot;{}&quot;?</source>
+        <translation>آیا واقعا میخواهید سوژه &apos;{}&apos; را حذف کنید؟</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="769"/>
+        <source>New factor not allowed!</source>
+        <translation>مجاز به افزودن فاکتور جدید نیستید!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="771"/>
+        <source>Trial already has subject or preload. 
+                                    You can not add or delete factors, treatments or levels</source>
+        <translation>این کارآزمائی هم اکنون دارای سوژه و یا پره لود می باشد ولذا شما نمی توانید تغییری در درمانها، فاکتورها و سطوح آنها بدهید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="817"/>
+        <source>Are you sure you want to delete &quot;%s&quot;?</source>
+        <translation>آیا واقعا میخواهید &quot;%s&quot; حذف کنید؟</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="823"/>
+        <source>minimpy2</source>
+        <translation>مینیم‌پای ۲</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>version</source>
+        <translation>نسخه</translation>
+    </message>    
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>2.0</source>
+        <translation>۲/۰</translation>
+    </message>    
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>Copyright</source>
+        <translation>کپی رایت</translation>
+    </message>    
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>2020</source>
+        <translation>۱۳۹۹</translation>
+    </message>    
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>Dr. Mahmoud Saghaei</source>
+        <translation>دکتر محمود سقائی</translation>
+    </message>    
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>MinimPy</source>
+        <translation>مینیم‌پای</translation>
+    </message>    
+    <message>
+        <location filename="main_window.py" line="993"/>
+        <source>ID</source>
+        <translation>شناسه</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="878"/>
+        <source>Ratio</source>
+        <translation>نسبت</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="931"/>
+        <source>Factor</source>
+        <translation>فاکتور</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="931"/>
+        <source>Levels</source>
+        <translation>لایه ها</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="932"/>
+        <source>Weight</source>
+        <translation>وزن</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="994"/>
+        <source>Code</source>
+        <translation>کد</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="995"/>
+        <source>Current</source>
+        <translation>کنونی</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1076"/>
+        <source>Mean</source>
+        <translation>میانگین</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1078"/>
+        <source>Max</source>
+        <translation>بیشینه</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1081"/>
+        <source>Balance (mean MB = {:.4f})</source>
+        <translation>تراز (میانگین ت‌م = {:.4f})</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1084"/>
+        <source>No subject enrolled!</source>
+        <translation>هنوز سوژه ای وارد نشده است!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1122"/>
+        <source>Select mnd file</source>
+        <translation>فایل mnd را انتخاب کنید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1129"/>
+        <source>Warning</source>
+        <translation>هشدار</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1130"/>
+        <source>Do you want to save this trial?</source>
+        <translation>آیا می خواهید این کارآزمائی ذخیره شود؟</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1142"/>
+        <source>Imported</source>
+        <translation>داده ها وارد شد</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1144"/>
+        <source>Data imported successfully!</source>
+        <translation>داده های با موفقیت وارد شد!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1147"/>
+        <source>Import error!</source>
+        <translation>بروز نادرستی هنگام درون‌داد داده!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1149"/>
+        <source>Data are not valid!</source>
+        <translation>داده های بی ارزشند!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1156"/>
+        <source>Export to file</source>
+        <translation>برون داد به پرونده</translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="27"/>
+        <source>Select only preload and edit</source>
+        <translation type="obsolete">تنها پره لود و گزینه ویرایش را برگزینید</translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="110"/>
+        <source>Confirm clear</source>
+        <translation type="obsolete">پذیرش پاکسازی</translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="111"/>
+        <source>Are you sure you want to clear preload</source>
+        <translation type="obsolete">آیا براستی میخواهید پره لود را پاک کنید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="136"/>
+        <source>New trial</source>
+        <translation>کارآزمائی جدید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="212"/>
+        <source>Maximum sample size &gt; {}</source>
+        <translation>بیشترین اندازه نمونه &gt; {}</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="216"/>
+        <source>Are you sure you want to quit?</source>
+        <translation>براستی میخواهید از برنامه بیرون بروید؟</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="231"/>
+        <source>No current trial!</source>
+        <translation>هیچ کارآزمائی گزینش نشده است!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="233"/>
+        <source>Fist you have to define and select a trial as current!</source>
+        <translation>نخست باید که یک کارآزمائی را ساخته و انتخاب کنید!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="312"/>
+        <source>Are you sure you want to edit subject at row &quot;{}&quot; ?
+
+Editing subject may invalidate your research and the result of minimisation</source>
+        <translation>آیا براستی میخواهید سوژه ردیف &quot;{}&quot;  ویرایش نمائید? ویرایش سوژه ها ممکن است پژوهش شما را نادرست نماید و  سبب تورش در مینی مایزیشن گردد</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="14"/>
+        <source>MainWindow</source>
+        <translation>پنجره اصلی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="25"/>
+        <source>Trials</source>
+        <translation>کارآزمائی‌ها</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="35"/>
+        <source>Setting</source>
+        <translation>سازوکار</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="43"/>
+        <source>Created</source>
+        <translation>برپاشده</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="53"/>
+        <source>Modified</source>
+        <translation>ویرایش شده</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="73"/>
+        <source>Trial code</source>
+        <translation>کد کارآزمائی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="83"/>
+        <source>Probability method</source>
+        <translation>روش احتمال</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="91"/>
+        <source>Biased coin</source>
+        <translation>سکه مخدوش</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="96"/>
+        <source>Naive</source>
+        <translation>خام</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="104"/>
+        <source>Base probability</source>
+        <translation>احتمال پایه</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="116"/>
+        <source>0.70</source>
+        <translation>0.70</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="135"/>
+        <source>Distance method</source>
+        <translation>روش بازه</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="143"/>
+        <source>Marginal balance</source>
+        <translation>تراز مرزی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="148"/>
+        <source>Range</source>
+        <translation>دامنه</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="153"/>
+        <source>Standard deviation</source>
+        <translation>انحراف از معیار</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="158"/>
+        <source>Variance</source>
+        <translation>واریانس</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="166"/>
+        <source>Identifier type</source>
+        <translation>گونه شناسه</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="174"/>
+        <source>Numeric</source>
+        <translation>عددی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="179"/>
+        <source>Alpha</source>
+        <translation>حروفی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="184"/>
+        <source>Alphanumeric</source>
+        <translation>حروفی عدد</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="192"/>
+        <source>Identifier order</source>
+        <translation>چیدمان شناسه‌ها</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="200"/>
+        <source>Sequential</source>
+        <translation>پشت سر هم</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="205"/>
+        <source>Random</source>
+        <translation>شانسی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="213"/>
+        <source>Identifier length</source>
+        <translation>درازای شناسه</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="232"/>
+        <source>TextLabel</source>
+        <translation>برچسب متن</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="241"/>
+        <source>Recycle ids</source>
+        <translation>بازیافت شناسه ها</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="251"/>
+        <source>New subject random</source>
+        <translation>سوژه جدید شانسی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="261"/>
+        <source>Arms weight</source>
+        <translation>وزن یازوهای درمانی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="270"/>
+        <source>1.0</source>
+        <translation>۱/۰</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="292"/>
+        <source>Treatments</source>
+        <translation>درمانها</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="302"/>
+        <source>Factors</source>
+        <translation>فاکتورها</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="320"/>
+        <source>Frequencies</source>
+        <translation>فراوانی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="330"/>
+        <source>Preload</source>
+        <translation>پره‌لود</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="340"/>
+        <source>Edit</source>
+        <translation>ویرایش</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="347"/>
+        <source>Convert to preload</source>
+        <translation>تبدیل به پره لود</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="354"/>
+        <source>I know what I&apos;m doing</source>
+        <translation>می دانم چکار میکنم</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="405"/>
+        <source>Subjects</source>
+        <translation>سوژه‌ها</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="419"/>
+        <source>Balance</source>
+        <translation>تراز</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="427"/>
+        <source>Oveall Balance</source>
+        <translation>تراز فراگیر</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="460"/>
+        <source>Randomness</source>
+        <translation>شاخص شانس</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="490"/>
+        <source>File</source>
+        <translation>پرونده</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="504"/>
+        <source>toolBar</source>
+        <translation>نوار ابزار</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="525"/>
+        <source>Exit</source>
+        <translation>بیرون رفتن</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="530"/>
+        <source>Add</source>
+        <translation>افزودن</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="535"/>
+        <source>Delete</source>
+        <translation>پاک کردن</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="543"/>
+        <source>Save</source>
+        <translation>اندوختن</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="551"/>
+        <source>manage factor levels</source>
+        <translation>اداره لایه های فاکتور</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="556"/>
+        <source>Help</source>
+        <translation>راهنما</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="559"/>
+        <source>displays help</source>
+        <translation>نمایش راهنما</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="564"/>
+        <source>Import</source>
+        <translation>درون داد</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="569"/>
+        <source>Export</source>
+        <translation>برون داد</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="574"/>
+        <source>Config</source>
+        <translation>پیکره بندی</translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="577"/>
+        <source>Application config</source>
+        <translation>پیکره بندی برنامه</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="179"/>
+        <source>Restart needed</source>
+        <translation>نیاز به شروع مجدد دارد</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="180"/>
+        <source>Changes will be applied after restart!</source>
+        <translation>تغییرات داده شده تنها پس از شروع مجدد برنامه نشان داده خواهد شد!</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="29"/>
+        <source>Each trial has a uniqe tile and also a code usually in the form of an abreviation of the title.
+Last column show if the trial is the active one. Only one trial can be active at any time. Other pages of the application relate to this active trial. To make a treial active check the box in last column.
+Click and then type over the trial title or code to edit it</source>
+        <translation>هرکارآزمائی یک عنوان منحصربفرد دارد و همچنین کدی دارد که معمولا حروف اول عنوان کارآزمائی می باشد.
+در ستون آخر شما مشخص می کنید که این کارآزمائی به عنوان کارآزمائی فعال یا جاری انتخاب شود. در هر لحظه تنها یک کارآزمائی میتواند کارآزمائی فعال و جاری باشد. دیگر صفحات این برنامه تابع کارآزمائی جاری خواهند بود. برای اینکه یک کارآزمائی را به عنوان جاری انتخاب کنید کافی است که گزینه ستون آخر فعال باشد.
+برای ویرایش عنوان و کد کارآزمائی بالینی بر روی آن کلیک کرده و شروع به تایپ نمائید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="30"/>
+        <source>Creation date of this trial
+</source>
+        <translation>تاریخ آفرینش این کارآزمائی
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="31"/>
+        <source>Modification date of this trial
+</source>
+        <translation>تاریخ تغییر این کارآزمائی
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="32"/>
+        <source>Title of this trial. Title must be unique
+</source>
+        <translation>عنوان کارآزمائی. می بایست عنوانی منحصر به فرد باشد
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="33"/>
+        <source>Code of the trial usually title abbreviation
+</source>
+        <translation>کد کارآزمائی که معمولا حروف اول عنوان کارآزمائی می باشد
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="34"/>
+        <source>The method used for calculating of the assignment probabilities. There are two choices. Biased coin minimization which include the effect of allocation ratios into the computation, and naive method which do not include the effect of allocation ratios. Biased coin is the preffered method. These options only is meaningfull when we have treatments with unequal allocation ratios. Biased Coin method is based on the different probabilities of assignment for subjects to trial groups. The subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Therefore if treatments with higher allocations ratios are assigned more probabilities this will be considered as biased-coin minimization. In this method a base probability is used for the group with the lowest allocation ratio when that group is selected as the preferred treatment. Probabilities for other groups (PHi) when they selected as preferred treatments are calculated as a function of base probability and their allocation ratios. In Naive method the subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Here the probabilities are not affected by allocation ratios and usually the same high probability are used for all treatment groups when they are selected as preferred treatments (base probability). This is denoted by Naive Minimization. In this method, probabilities for non-preferred treatments are distributed equally.
+</source>
+        <translation>روش محاسبه احتمال های تقسیم سوژه ها بین گروه ها کارآزمائی. در اینجا دو گزینه داریم. سکه مخدوش که تفاوت گروه ها از نظر نسبت انتساب را در نظر میگیرد و روش خام که کاری به این نسبت های انتساب ندارد. روش برتر همانا سکه مخدوش می باشد. البته این گزینه تنها هنگامی معنی دارد که گروه های کارآزمائی از نظر نسبت انتساب تفاوت داشته باشند. پایه روش سکه مخدوش بر احتمال های مختلف برای تقسیم بندی سوژه های بین گروه ها می باشد. در این روش برای انتساب سوژه‌ از روش تصادفی سازی استفاده میشود ولی احتمال داده شدن سوژه به گروه برگزیده بیشتر از سایر گروه‌ها می باشد. گروه برگزیده گروهی است که اگر سوژه به آن گروه داده شود تفاوت گروه های کمتر از قبل میشود. گروه هائی که نسبت انتساب بیشتری دارند احتمال انتساب بیشتری برای آنها در نظر گرفته میشود. در واقع در این روش برای گروهی که کمترین میزان نسبت انتساب دارد مقداری مشخص و از پیش تعیین شده ای از احتمال در نظر گرفته میشود که به آن احتمال پایه می گوئیم. هنگامی که گروه دارای کمینه نسبت انتساب به عنوان گروه برگزیده انتخاب میشود با همین پایه احتمال انتساب وی اعمال میشود. مقدار احتمال برای سایر گروه ها هنگامی که گروه برگزیده میشوند بستگی به مقدار احتمال پایه و نسبت انتساب آنها دارد. در روش خام سوژه ها به درمان برگزیده با احتمال بیشتری و به سایر گروه ها با احتمال کمتری انتساب می یابند و این مقادیر احتمال تحت تاثیر نسبت های انتساب قرار نمی گیرند و معمولا با مقدار احتمال بالای یکسانی برای گروه های برگزیده انتساب می یابند. در این روش میزان احتمال بکار رفته برای سایر گروه های غیر برگزیده بصورت یکنواخت بین گروه ها توزیع میشود.
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="35"/>
+        <source>The probability given to the treatment with the lowest allocation ratio in biased-coin minimization. In naive minimization it is the probability used for all treatment groups when they are selected as preferred treatments. Use the spin button to specify the value.
+</source>
+        <translation>احتمال اختصاص یافته به درمانی که کمترین میزان نسبت انتساب می باشد هنگامی که این گروه به عنوان گروه برگزیده انتخاب میشود. برای تغییر مقدار آن از دکمه های جهتی استفاده کنید.
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="36"/>
+        <source>Distance measure is used to calculate the imbalance score. There are four options here. Marginal balance computes the cumulative difference between every possible pairs of level counts. This is the default and the one which is recommended. It tends to minimize more accurately when treatment groups are not equal in size. For each factor level marginal balance is calculated as a function of adjusted number of patients present in a factor level for each treatment group. Range calculates the difference between the maximum and the mimimum values in level counts. Standard deviation calculates the standard deviation of level counts and variance calculate their variances. All of these measures uses adjusted level counts. Adjustment performs relative to the values of allocation ratios.
+</source>
+        <translation>سنجه فاصله برای محاسبه ضریب عدم ترازی بکار میرود. برای این سنجه چهار گزینه موجود است. تراز مرزی که تفاوت انباشته بین لایه های فاکتور های بصورت تمام حالات ترکیبی زوجی از لایه ها می باشد. این روش پیش فرض و توصیه شده است و بویژه هنگامی که اندازه گروه ها یکسان نباشد خیلی دقیق تر عمل میکند. برای هر لایه فاکتور تراز لبه ای به صورت تابعی از تعداد تعدیل شده سوژه ها در آن لایه برای هر شاخه درمانی محاسبه میشود. گزینه دامنه تفاوت بین بیشترین و کمترین مقدار در شمارش سوژه ها در لایه‌های فاکتور می باشد. انحراف از معیار بصورت محاسبه انحراف از معیار شمار سوژه ها در لایه‌های فاکتور و واریانس عبارت از محاسبه واریانس می باشد. تمام این سنجه ها از شمار تعدیل شده سوژه ها استفاده میکنند. تعدیل شمار سوژه ها بر مبنای نسبت انتساب صورت میگیرد.
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="37"/>
+        <source>Type of subject identifier. It can be Numeric, Alpha or Alphanumeric.
+</source>
+        <translation>نوع شناسه نمایش داده شده برای سوژه ها. گزینه های موجود عددی، حرفی و یا حرفی عددی می باشند.\nهنگامی که اولین سوژه وارده مطالعه شد، دیگر نمی توان نوع شناسه را تغییر دارد
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="38"/>
+        <source>The order of identifier sequence. It can be Sequential or Random.
+</source>
+        <translation>ترتیب شناسه سوژه ها. می تواند ترتیبی و یا شانسی باشد.
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="39"/>
+        <source>Length  of subjects identifier in character. The longer the length the higher  will be the possible sample size
+</source>
+        <translation>درازی شناسه سوژه ها. شناسه سوژه ها هر چه بلندتر باشد اندازه نمونه ممکن که توسط شناسه ها قابل پوشش هستند بیشتر خواهد شد
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="40"/>
+        <source>Check to reuse IDs of a deleted subject. The default is to discard
+</source>
+        <translation>برای بازیافت شناسه ها چک باکس را فعال کنید. پیشفرض غیر فعال است
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="41"/>
+        <source>If unchecked (the default), enrol form will show with the level for each factor unselected. If checked, for each factor a random level will be selected. This is only for test purpose to rapidly and randomly enrol a subject
+</source>
+        <translation>اگر چک باکس غیرفعال باشد (حالت پیش فرض)، در فرم سوژه جدید، لایه های هر فاکتور بصورت انتخاب نشده می ماند. در غیر این صورت برای هر فاکتور یک لایه بصورت شانسی انتخاب میگردد. این حالت برای انتساب سریع سوژه های تصادفی برای اهداف پژوهشی می باشد و استفاده دیگری ندارد
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="42"/>
+        <source> This is equalizing factor for arms of trial. The higher the arms weight the higher will be the possibility of having equal group sizes at the end of study
+</source>
+        <translation> این عدد ضریب مساوی سازی اندازه گروه های کارآزمائی بالینی است. هرچه این عدد بیشتر باشد، احتمال مساوی شدن نهائی اندازه نمونه گروه ها بیشتر میشود. به هر حاب حتی با بیشترین مقدار این عدد احتمال نامساوی شدن تعداد سوژه ها در گروه های مختلف موجود دارد
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="43"/>
+        <source>For each treatment a name (unique for the trial) and an integer value for allocation ratio are needed. Allocation ratios are integer numeric values which denote the ratios of the treatment counts. For example if we have three groups a, b and c with their allocation ratios 1, 2 and 3 respectively, this mean that in the final sample nearly 1/6 of subjects will be from treatment a, 1/3 from b and 1/2 from c
+You have to define at lease two groups to use minimisation on them.
+Click on treatment title and type over it to edit the title of the treatment, and use the spin control to change the allocation ratios</source>
+        <translation>برای هر گروه درمانی یک عنوان (منحصر به فرد در کارآزمائی) و یک عدد صحیح نشان دهنده نسبت انتساب تعریف میشود. نسبت انتساب مشخص کننده نسبت نهائی تعداد سوژه ها در هر گروه کارآزمائی می باشد. به عنوان مثال اگر سه گروه درمانی الف و ب و ج داشته باشیم با نسبت های انتساب ۱، ۲ و ۳، این بدان معنی است که در نمونه نهائی تقریبا یک ششم سوژه ها از گروه الف، یک سوم از گروه ب و نیمی از سوژه ها از گروه ج خواهند بود.
+شما باید حداقل دو گروه برای کارآزمائی تان تعریف کنید تا بتوانید بر روی آن دو مینیمایزیشن انجام دهید.
+برای ویرایش عنوان درمان ها بر روی عنوان درمان در جدول درمانها کلیک کنید و شروع به تایپ نمائید. برای تغییر مقدار نسبت انتساب از کنترل ستون آخر استفاده کنید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="44"/>
+        <source>Factors or prognostic factors, are the subject factors which are selected to match subjects based on them. At lease one factor is mandatory for minimisation. Each factor has a name (unique for the trial) and a weight. Weight indicates the relative importance of the factor for minimization. For each factor you must define at least two levels. As an example factor may be gender and its levels may be Male and Female.
+Factor title can be edited by clicking and typing over the title of the factor
+
+Click on the levels button on tool bar or double click on levels in table to manage (add, edit, delete) levels of the selected factor</source>
+        <translation>فاکتورها یا فاکتورهای پروگنوستیک، فاکتور هائی از سوژه ها می باشند که شما برای متعادل ساختن گروه های کارآزمائی از نظر آن فاکتورها انتخاب نموده اید. برای اجرای مینی مایزیشن حداقل می بایست یک فاکتور تعریف کنید و هر فاکتور باید حداقل دو لایه داشته باشد. هر فاکتور دارای عنوانی است که در آن کارآزمائی منحصر به فرد است. همچنین هر فاکتور وزنی نیز دارد که اهمیت نسبی آن فاکتور در مدل مینی مایزیشن را نشان میدهد. هر چه وزن یک فاکتور بیشتر باشد احتما تعادل گروه ها از نظر آن فاکتور بیشتر میگردد.
+مثال برای فاکتور می تواند جنس باشد که شما ممکن است دو لایه مؤنث و مذکر برای آن انتخاب کنید.
+برای ویرایش عنوان فاکتور بر روی عنوان کلیک کرده و شروع به تایپ کنید. در برنامه کنترل هائی برای تعریف فاکتور جدید حذف فاکتورهای موجود و ویرایش لایه های هر فاکتور وجود دارد. برای ویرایش لایه ها دکمه لایه ها رد نوار ابزار بالای صفحه را کلیک کنید و یا بر روی لایه های یک فاکتور در جدول فاکتورها دبل‌کلیک کنید. در صفحه ویرایش لایه ها می توانید لایه جدید تعریف کنید، لایه ها را خذف کنید و یا عنوان آنها را ویرایش کنید</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="45"/>
+        <source>Manage (add, edit, delete) levels of the selected factor</source>
+        <translation>اداره لایه های فاکتور (یعنی افزودن، ویراستن، و حذف کردن لایه‌ها)</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="46"/>
+        <source>This table shows frequencies of subjects enrolled in the trial or entered via preload table depending on the active checkboxes. When you have already enrolled a number of subjects (using other tools or software) and you want to continue with this minimisation program for the remaining cases, you can enter data of already enrolled subject as counts. You enter counts of different treatments accross all levels of each subject into the preload table. Then the counts from this table will be added to counts of enrolled subject by this application and total counts will be used in minimisation model.
+It is as if you have entered the list of subject into your existing list. The effect is exactly the same. When defining preload please note that sum of subjects across different levels of a factor must be equal to thats of other factors, otherwise an error will be generated.
+If you have a greal number of subjects and have no need to keep their list, you may convert them into preload table. Select frequencies check box and unselect preload checkbox, then click &apos;convert to preload&apos; to clear all subjects and add them to preload table.</source>
+        <translation>این جدول فراوانی سوژه ها مستقیما وارد شده در کارآزمائی و یا تعریف شده به عنوان پره لود را نشان میدهد. با انتخاب هرکدام از چک باکس های بالای جدول می توانید فراوانی سوژه های مستقیما وارد شده (فراوانی) و یا تعریف شده به عنوان پره لود و یا مجموع هر دو را مشاهده نمائید. منظور از پره لود چیست؟ وقتی که شما تعدای سوژه را توسط سیستم ها یا برنامه های دیگر وارد کارآزمائی نموده اید و اکنون میخواهید ادامه کار با برنامه مینیم‌پای باشد، چگونه می توانید سوژه های قبلی را به برنامه مینیم‌پای وارد کنید. اگر تعداد سوژه ها خیلی کم باشد (کمتر از ده مورد) وارد کردن آنها بصورت مینی مایز نمودن یک به یک آنها و سپس ویرایش گروه آنها ممکن است خیلی وقت نگیرد ولی اگر تعداد زیادی را از قبل وارد مطالعه کرده اید این روش بسیار وقت گیر و طاقت فرسا و مستعد اشتباه است. در این موارد بهتر است سوژه های را که تا بحال وارد مطالعه نموده اید را بصورت جدولی متشکل از فراوانی سوژه ها در هر لایه فاکتور و برای هر گروه درمانی وارد برنامه کنید. به این جدول که فراوانی سوژه های قبلی را نشان میدهد پره‌لود میگوئیم. پس از این کار، جدول پره لود با سوژه هائی که مستقیما وارد برنامه شده یکجا جمع شده و در مدل مینی مایزیشن بکار خواهد رفت. در واقع از نظر نتیجه مینی مایزیشن هیچ تفاوتی بین جدول پره لود و سوژه هائی که مستقیما وارد شده اند نمی باشد.
+برای وارد کردن جدول پره لود، ابتدا چک باکس فراوانی را غیر فعال و سپس جک باکس پره لود را فعال و سرانجام جک باکس ویرایش را فعال کنید. سپس فراوانی ها را در جدول وارد و دست آخر دکمه &apos;اندوختن&apos; را کلیک کنید. توجه کنید که سرجمع لایه ها برای فاکتور های مختلف الزاما باید برابر باشد در غیر این صورت برنامه از ذخیره کردن خودداری خواهد کرد و پیام خطائی نمایش خواهد داد. همچنین برای پاک کردن جدول پره لود از دکمه پاک کردن در نوار ابراز می توانید استفاده کنید.
+چنانچه تعداد سوژه های شما خیلی زیاد شده است و نیازی به داشتن لیست آنها ندارید می توانید سوژه های موجود را به جدول پره‌لود تبدیل کنید. برای این کار چک باکس فراوانی را فعال کنید، پره لود را غیر فعال کنید و سپس بر روی دکمه تبدیل به پره لود کلیک کنید. برنامه دو بار از شما تایید می خواهد و سپس همه سوژه ها را پاک کرده و آنها را به جدول پره‌لود اضافه میکند.</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="47"/>
+        <source>Subjects enrolled in the trial will be listed in subjects table. Each row belongs to one subject and shows subject ID, treatment, levels of factors, dates of enrollment and modification (if any). Subject can be added, edited or deleted from this table, although editing or deleting subject may compromise the minimisation model and therefore are not recommended.To enter a new subject, click plus sign in the tool bar. To edit double click and to delete click on Delete button on toolbar
+</source>
+        <translation>سوژه های وارد شده در کارآزمائی در جدول سوژه ها نمایش داده میشوند. هر ردیف مربوط به یک سوژه است و شناسه وی، گروه درمانی وی، لایه انتخاب شده هر فاکتور برای وی، تاریخ وارد شدن در مطالعه و در صورت ویرایش های بعدی تاریخ آخرین تغییرات را نشان میدهد. سوژه ها را می توان به کارآزمائی اضافه نمود و یا حذف و ویرایش کرد، اگر چه این دو اقدام آخر توصیه نمی شود۷ چراکه سبب خدشه دار شدن پژوهش و نتایج مینی مایزیشن میگردد. برای وارد کردن (مینی مایز نمودن) یک سوژه جدید، بر روی دکمه &apos;افزودن&apos; در نوار بالای صفحه کلیک کنید. در پنجره باز شده لایه هر فاکتور برای آن بیمار را انتخاب و سپس بر روی دکمه &apos;پذیرش&apos; کلیک کنید تا یک گروه درمانی برای سوژه جدید توسط برنامه تعریف شود (مینی مایزیشن). طی عمل مینی مایزیشن سوژه با احتمال بیشتر به گروه برگزیده افزوده میشود ولی احتمال کمی هم وجود دارد که سوژه به گروه دیگری وارد شود. بنابراین بسته به میزان احتمال پایه انتخاب شده برای کارآزائی، نتیجه نهائی بالانس گروه ها با رعایت اصل تصادفی سازی خواهد بود. اگر سوژه به گروهی برود که سبب افزایش تعادل میشود به اصطلاح می گوئیم سوژه به گروه برگزیده وارد شده است در غیر اینصورت به گروه غیر برگزیده وارد شده است که برنامه با رنگ قرمز نشان میدهد. چنانچه بر روی نتیجه که یا قرمز یا آبی است کلیک کنید، تفصیل پروسه مینی مایزیشن و میزان احتمال مربوطه را می توانید مشاهده کنید. برای ویرایش سوژه ای که قبلا وارد شده است بر روی آن دبل کلیک کنید. برای حذف سوژه بر روی دکمه پاک کردن در نوار بالای صفحه کلیک کنید.\nنواری در بالای جدول سوژه ها میزان پیشرفت کار را بصورت تعداد سوژه های وارد مطالعه شده نسبت به تعداد شناسه های باقیمانده نشان میدهد.
+</translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="50"/>
+        <source>In this table you can see four different scale related to overall trial balance (marginal balance, range, variance, SD) and randomness. Also the balances for each factor or for each level within each factor are displayed. The last two rows of the table show the mean and max values for each scale. The lower the numeric values of these balance, the more balance we have in our trial. Also a scale of randomness for enrolling subjects is calculated based on run test.</source>
+        <translation>در این جدول چهار سنجه مختلف در باره تراز کارآزمائی دیده میشود: تراز مرزی، دامنه، واریانس و انحراف از معیار. همچنین برای هر فاکتور و برای هر لایه از آن فاکتور مقادیر تراز بصورت جداگانه نگاشته شده است. دو ردیف آخر نشان دهنده میانگین و بیشینه هر ستون می باشد. هر چه مقدار تراز عدد کوچکتری باشد، تراز کارآزمائی بیشتر خواهد بود. به عبارت دیگر تفاوت گروه ها از نظر فاکتور های مختلف کمتر خواهد بود. همچنین سنجه ای از کفایت تصادفی بودن تقسیم بندی سوژه ها بین گروه ها نمایش داده شده است که بر اساس آزمون run می باشد.</translation>
+    </message>
+</context>
+</TS>
diff --git a/factor_levels.py b/factor_levels.py
new file mode 100755 (executable)
index 0000000..805c888
--- /dev/null
@@ -0,0 +1,98 @@
+from PySide2 import QtWidgets as qtw
+from PySide2 import QtCore as qtc
+
+from db import Database
+from factor_levels_dialog import *
+
+
+# noinspection PyTypeChecker
+class FactorLevels(qtw.QDialog):
+    def __init__(self, parent, trial_id, factor_id, factor_title):
+        super(FactorLevels, self).__init__(parent)
+        self.parent = parent
+        self.ui = Ui_FactorLevelsDialog()
+        self.ui.setupUi(self)
+        self.database = Database.get_instance(parent)
+        self.trial_id = trial_id
+        self.factor_id = factor_id
+        self.setWindowTitle(self.tr('Levels of {}').format(factor_title))
+        self.ui.addLevelPushButton.clicked.connect(self.add_level)
+        self.ui.deleteLevelPushButton.clicked.connect(self.delete_level)
+        self.ui.closePushButton.clicked.connect(self.close)
+        self.ui.levelsTableWidget.cellChanged.connect(self.level_cell_changed)
+        self.ui.levelsTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
+        self.ui.levelsTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
+        self.load_levels()
+
+    def add_level(self):
+        if self.parent.check_trial_subjects_or_preload():
+            return
+        title, ok = qtw.QInputDialog.getText(
+            self,
+            self.tr('Level title'),
+            self.tr('Title'),
+            qtw.QLineEdit.EchoMode.Normal
+        )
+        if ok and len(title.strip()) != 0:
+            if self.database.treatment_title_exists(title, self.factor_id):
+                qtw.QMessageBox.critical(self,
+                                            self.tr('Error!'),
+                                            self.tr('''Level title '{}' already exist!'''.format(title))
+                                            )
+                return
+            level_id = self.database.insert_level(self.trial_id, self.factor_id, title)
+            state = self.ui.levelsTableWidget.blockSignals(True)
+            r = self.ui.levelsTableWidget.rowCount()
+            self.ui.levelsTableWidget.insertRow(r)
+            id_item = qtw.QTableWidgetItem(str(level_id))
+            id_item.setTextAlignment(qtc.Qt.AlignCenter)
+            self.ui.levelsTableWidget.setCellWidget(r, 0, qtw.QLabel())
+            self.ui.levelsTableWidget.setItem(r, 0, id_item)
+            title_item = qtw.QTableWidgetItem(title)
+            self.ui.levelsTableWidget.setItem(r, 1, title_item)
+            self.ui.levelsTableWidget.blockSignals(state)
+
+    def delete_level(self):
+        if self.parent.check_trial_subjects_or_preload():
+            return
+        selected = self.ui.levelsTableWidget.selectedIndexes()
+        if not selected:
+            return
+        title = self.ui.levelsTableWidget.item(selected[0].row(), 1).text()
+        button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
+                                          self.tr('Are you sure you want to delete "%s"?') % title,
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            return
+        level_id = int(self.ui.levelsTableWidget.item(selected[0].row(), 0).text())
+        self.database.delete_level(level_id)
+        self.ui.levelsTableWidget.blockSignals(True)
+        self.ui.levelsTableWidget.removeRow(selected[0].row())
+        self.ui.levelsTableWidget.blockSignals(False)
+
+    def level_cell_changed(self, row, col):
+        level_id = int(self.ui.levelsTableWidget.item(row, 0).text())
+        title = self.ui.levelsTableWidget.item(row, col).text()
+        oldValue = self.database.update_level(level_id, title)
+        if oldValue is not None:
+            self.parent.abort_table_widget_change(self.ui.levelsTableWidget, row, col, oldValue)
+
+    def load_levels(self):
+        self.ui.levelsTableWidget.blockSignals(True)
+        self.ui.levelsTableWidget.setRowCount(0)
+        self.ui.levelsTableWidget.setColumnCount(2)
+        header = self.ui.levelsTableWidget.horizontalHeader()
+        header.setSectionResizeMode(0, qtw.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(1, qtw.QHeaderView.Stretch)
+        self.ui.levelsTableWidget.setHorizontalHeaderLabels([self.tr('ID'), self.tr('Level')])
+        query = self.database.load_levels(self.factor_id)
+        while query.next():
+            r = self.ui.levelsTableWidget.rowCount()
+            self.ui.levelsTableWidget.insertRow(r)
+            id_item = qtw.QTableWidgetItem(str(query.value('id')))
+            id_item.setTextAlignment(qtc.Qt.AlignCenter)
+            self.ui.levelsTableWidget.setCellWidget(r, 0, qtw.QLabel())
+            self.ui.levelsTableWidget.setItem(r, 0, id_item)
+            title_item = qtw.QTableWidgetItem(query.value('title'))
+            self.ui.levelsTableWidget.setItem(r, 1, title_item)
+        self.ui.levelsTableWidget.blockSignals(False)
diff --git a/factor_levels_dialog.py b/factor_levels_dialog.py
new file mode 100755 (executable)
index 0000000..1981f8e
--- /dev/null
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'factor_levels_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.14.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
+    QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
+from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
+    QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
+    QPixmap, QRadialGradient)
+from PySide2.QtWidgets import *
+
+
+class Ui_FactorLevelsDialog(object):
+    def setupUi(self, FactorLevelsDialog):
+        if not FactorLevelsDialog.objectName():
+            FactorLevelsDialog.setObjectName(u"FactorLevelsDialog")
+        FactorLevelsDialog.resize(358, 300)
+        self.verticalLayout = QVBoxLayout(FactorLevelsDialog)
+        self.verticalLayout.setObjectName(u"verticalLayout")
+        self.gridLayout = QGridLayout()
+        self.gridLayout.setObjectName(u"gridLayout")
+        self.levelsTableWidget = QTableWidget(FactorLevelsDialog)
+        self.levelsTableWidget.setObjectName(u"levelsTableWidget")
+
+        self.gridLayout.addWidget(self.levelsTableWidget, 1, 0, 1, 3)
+
+        self.closePushButton = QPushButton(FactorLevelsDialog)
+        self.closePushButton.setObjectName(u"closePushButton")
+
+        self.gridLayout.addWidget(self.closePushButton, 0, 2, 1, 1)
+
+        self.deleteLevelPushButton = QPushButton(FactorLevelsDialog)
+        self.deleteLevelPushButton.setObjectName(u"deleteLevelPushButton")
+
+        self.gridLayout.addWidget(self.deleteLevelPushButton, 0, 1, 1, 1)
+
+        self.addLevelPushButton = QPushButton(FactorLevelsDialog)
+        self.addLevelPushButton.setObjectName(u"addLevelPushButton")
+
+        self.gridLayout.addWidget(self.addLevelPushButton, 0, 0, 1, 1)
+
+
+        self.verticalLayout.addLayout(self.gridLayout)
+
+
+        self.retranslateUi(FactorLevelsDialog)
+
+        QMetaObject.connectSlotsByName(FactorLevelsDialog)
+    # setupUi
+
+    def retranslateUi(self, FactorLevelsDialog):
+        FactorLevelsDialog.setWindowTitle(QCoreApplication.translate("FactorLevelsDialog", u"Dialog", None))
+        self.closePushButton.setText(QCoreApplication.translate("FactorLevelsDialog", u"Close", None))
+        self.deleteLevelPushButton.setText(QCoreApplication.translate("FactorLevelsDialog", u"Delete", None))
+        self.addLevelPushButton.setText(QCoreApplication.translate("FactorLevelsDialog", u"Add", None))
+    # retranslateUi
+
diff --git a/factor_levels_dialog.ui b/factor_levels_dialog.ui
new file mode 100755 (executable)
index 0000000..534b096
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FactorLevelsDialog</class>
+ <widget class="QDialog" name="FactorLevelsDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>358</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="1" column="0" colspan="3">
+      <widget class="QTableWidget" name="levelsTableWidget"/>
+     </item>
+     <item row="0" column="2">
+      <widget class="QPushButton" name="closePushButton">
+       <property name="text">
+        <string>Close</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QPushButton" name="deleteLevelPushButton">
+       <property name="text">
+        <string>Delete</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0">
+      <widget class="QPushButton" name="addLevelPushButton">
+       <property name="text">
+        <string>Add</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/freq_table.py b/freq_table.py
new file mode 100755 (executable)
index 0000000..ebd20c9
--- /dev/null
@@ -0,0 +1,227 @@
+from db import Database
+from PySide2 import QtWidgets as qtw
+from PySide2 import QtCore as qtc
+from PySide2 import QtGui as qtg
+
+
+class FreqTable:
+    def __init__(self, parent, ui, trial_id):
+        self.parent = parent
+        self.num_treatments = None
+        self.num_levels = None
+        self.ui = ui
+        self.grid = ui.freqGridLayout
+        self.trial_id = trial_id
+        self.database = Database.get_instance(parent)
+
+    def on_save_preload(self):
+        can_save = True
+        if self.ui.frequenciesCheckBox.isChecked():
+            can_save = False
+        if not self.ui.preloadCheckBox.isChecked():
+            can_save = False
+        if not self.ui.editPreloadCheckBox.isChecked():
+            can_save = False
+        if not can_save:
+            qtw.QMessageBox.information(
+                self.parent, self.parent.tr('Save not available!'),
+                self.parent.tr('Select only preload and edit')
+            )
+            return
+        preload = self.extract_counts()
+        if self.valid_counts(preload):
+            pixmap = qtg.QPixmap("images/tick_mark.png")
+            self.database.clear_preload(self.trial_id)
+            self.database.save_preload(self.trial_id, preload)
+        else:
+            pixmap = qtg.QPixmap("images/error.png")
+        self.valid_preload_image.setPixmap(pixmap)
+
+    def add_to_preload(self):
+        preload = self.database.get_preload(self.trial_id)
+        freq = self.extract_counts()
+        for key in freq:
+            freq[key] += preload[key]
+        self.database.clear_preload(self.trial_id)
+        self.database.save_preload(self.trial_id, freq)
+
+    def extract_counts(self):
+        count = {}
+        for r in range(2, self.num_treatments + 2):
+            for c in range(1, self.num_levels + 1):
+                item = self.grid.itemAtPosition(r, c)
+                entry = item.widget()
+                key = self.left_top_key[(r, c)]
+                count[key] = int(entry.text().strip())
+        return count
+
+    def valid_counts(self, count):
+        treatments = self.database.read_treatments(self.trial_id)
+        factors = self.database.read_factors(self.trial_id)
+        tfl = []
+        f_total = []
+        for factor in factors:
+            levels = self.database.factor_levels(factor[0])
+            fl = [[] for i in range(len(levels))]
+            f_total.append(fl)
+        valid = True
+        for treatment in treatments:
+            s = set()
+            row = []
+            for i, factor in enumerate(factors):
+                t, f = treatment[0], factor[0]
+                levels = self.database.factor_levels(f)
+                lst = []
+                for j, level in enumerate(levels):
+                    lv = level[0]
+                    cnt = count[(t, f, lv)]
+                    f_total[i][j].append(cnt)
+                    lst.append(cnt)
+                row.append(lst)
+                s.add(sum(lst))
+                if len(s) > 1:
+                    valid = False
+            tfl.append(row)
+        gt = 0
+        for i in range(len(self.total_treatment_buttons)):
+            sm = sum(tfl[i][0])
+            self.total_treatment_buttons[i].setText(str(sm))
+            gt += sm
+        self.grand_total_button.setText(str(gt))
+        for i, f_t in enumerate(f_total):
+            s = []
+            for j, f_l in enumerate(f_t):
+                sm = sum(f_l)
+                self.total_level_button[i][j].setText(str(sm))
+                s.append(sm)
+            self.total_factorButtons[i].setText(str(sum(s)))
+        return valid
+
+    def on_clear_preload(self):
+        err = False
+        if self.ui.frequenciesCheckBox.isChecked():
+            err = True
+        if not self.ui.preloadCheckBox.isChecked():
+            err = True
+        if not self.ui.editPreloadCheckBox.isChecked():
+            err = True
+        if err:
+            qtw.QMessageBox.warning(self.parent, self.parent.tr('Clear preload'),
+                                    self.parent.tr('To clear preload, select preload and check edit'))
+            return
+        button = qtw.QMessageBox.question(self.parent, self.parent.tr("Confirm clear"),
+                                          self.parent.tr('Are you sure you want to clear preload'),
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            return
+        for r in range(2, self.num_treatments + 2):
+            for c in range(1, self.num_levels + 1):
+                item = self.grid.itemAtPosition(r, c)
+                item.widget().setText('0')
+        self.database.clear_preload(self.trial_id)
+
+    def toggleReadOnly(self):
+        readOnly = not self.ui.editPreloadCheckBox.isChecked()
+        if self.num_treatments is None or self.num_levels is None:
+            return
+        for r in range(2, self.num_treatments + 2):
+            for c in range(1, self.num_levels + 1):
+                item = self.grid.itemAtPosition(r, c)
+                item.widget().setReadOnly(readOnly)
+                style = self.entryCountReadOnly if readOnly else self.entryCount
+                item.widget().setStyleSheet(style)
+
+    def set_counts(self, cur_count):
+        if self.num_treatments is None or self.num_levels is None:
+            return
+        for r in range(2, self.num_treatments + 2):
+            for c in range(1, self.num_levels + 1):
+                item = self.grid.itemAtPosition(r, c)
+                key = self.left_top_key[(r, c)]
+                item.widget().setText(str(cur_count[key]))
+        self.valid_counts(self.extract_counts())
+
+    def build(self):
+        for i in reversed(range(self.grid.count())):
+            self.grid.itemAt(i).widget().setParent(None)
+        factors = self.database.read_factors(self.trial_id)
+        if len(factors) < 1:
+            return
+        treatments = self.database.read_treatments(self.trial_id)
+        self.num_treatments = len(treatments)
+        if len(treatments) < 2:
+            return
+        flc = self.database.get_factor_level(self.trial_id)
+        for lvs in flc:
+            if len(lvs) < 2:
+                return
+        self.left_top_key = {}
+        offset = 1
+        self.total_factorButtons = []
+        self.total_level_button = []
+        fixedStyle = 'background-color: #EEEEEE; color: #000000; border: 1px solid #000000;'
+        self.entryCountReadOnly = 'color: #999999; border: 1px solid #000000;'
+        self.entryCount = 'color: blue; border: 1px solid #000000;'
+        for i in range(len(factors)):
+            factor = factors[i]
+            factor_button = qtw.QLabel(factor[1])
+            factor_button.setStyleSheet(fixedStyle)
+            factor_button.setAlignment(qtc.Qt.AlignCenter)
+            if i > 0:
+                offset += len(flc[i - 1])
+            self.grid.addWidget(factor_button, 0, offset, 1, len(flc[i]))
+            total_factorButton = qtw.QLabel('0')
+            total_factorButton.setAlignment(qtc.Qt.AlignCenter)
+            self.total_factorButtons.append(total_factorButton)
+            total_factorButton.setStyleSheet(fixedStyle)
+            self.grid.addWidget(total_factorButton, len(treatments) + 3, offset, 1, len(flc[i]))
+            level_button_list = []
+            for j in range(len(flc[i])):
+                level = flc[i][j]
+                level_button = qtw.QLabel(level[1])
+                level_button.setAlignment(qtc.Qt.AlignCenter)
+                level_button.setStyleSheet(fixedStyle)
+                self.grid.addWidget(level_button, 1, offset + j, 1, 1)
+                total_level_button = qtw.QLabel('0')
+                total_level_button.setAlignment(qtc.Qt.AlignCenter)
+                level_button_list.append(total_level_button)
+                total_level_button.setStyleSheet(fixedStyle)
+                self.grid.addWidget(total_level_button, len(treatments) + 2, offset + j, 1, 1)
+                for x in range(len(treatments)):
+                    treatment = treatments[x]
+                    cell_entry = qtw.QLineEdit()
+                    cell_entry.setMinimumHeight(50)
+                    cell_entry.setStyleSheet(self.entryCountReadOnly)
+                    cell_entry.setText('0')
+                    cell_entry.setAlignment(qtc.Qt.AlignCenter)
+                    cell_entry.setReadOnly(True)
+                    self.grid.addWidget(cell_entry, x + 2, offset + j, 1, 1)
+                    self.left_top_key[(x + 2, offset + j)] = (treatment[0], factor[0], level[0])
+            self.total_level_button.append(level_button_list)
+        total_button = qtw.QLabel(self.parent.tr('Total'))
+        total_button.setAlignment(qtc.Qt.AlignCenter)
+        total_button.setStyleSheet(fixedStyle)
+        offset += len(flc[-1])
+        self.num_levels = offset - 1
+        self.grid.addWidget(total_button, 0, offset + 2, 2, 1)
+        self.total_treatment_buttons = []
+        for i in range(len(treatments)):
+            treatment = treatments[i]
+            treatment_button = qtw.QLabel(treatment[1])
+            treatment_button.setStyleSheet(fixedStyle)
+            self.grid.addWidget(treatment_button, i + 2, 0, 1, 1)
+            total_treatment_button = qtw.QLabel('0')
+            total_treatment_button.setAlignment(qtc.Qt.AlignCenter)
+            self.total_treatment_buttons.append(total_treatment_button)
+            total_treatment_button.setStyleSheet(fixedStyle)
+            total_treatment_button.setMinimumWidth(100)
+            self.grid.addWidget(total_treatment_button, i + 2, offset + 2, 1, 1)
+        total_button = qtw.QLabel(self.parent.tr('Total'))
+        total_button.setStyleSheet(fixedStyle)
+        self.grid.addWidget(total_button, len(treatments) + 2, 0, 2, 1)
+        self.grand_total_button = qtw.QLabel('0')
+        self.grand_total_button.setAlignment(qtc.Qt.AlignCenter)
+        self.grand_total_button.setStyleSheet(fixedStyle)
+        self.grid.addWidget(self.grand_total_button, len(treatments) + 2, offset + 2, 2, 1)
+        self.valid_preload_image = qtw.QLabel()
+        self.grid.addWidget(self.valid_preload_image, 0, 0, 2, 1)
diff --git a/images/about.png b/images/about.png
new file mode 100755 (executable)
index 0000000..cbadad9
Binary files /dev/null and b/images/about.png differ
diff --git a/images/add.png b/images/add.png
new file mode 100755 (executable)
index 0000000..90f7ed9
Binary files /dev/null and b/images/add.png differ
diff --git a/images/config.png b/images/config.png
new file mode 100755 (executable)
index 0000000..a87e1fe
Binary files /dev/null and b/images/config.png differ
diff --git a/images/delete.png b/images/delete.png
new file mode 100755 (executable)
index 0000000..6153a23
Binary files /dev/null and b/images/delete.png differ
diff --git a/images/error.png b/images/error.png
new file mode 100755 (executable)
index 0000000..5bce5cc
Binary files /dev/null and b/images/error.png differ
diff --git a/images/exit.png b/images/exit.png
new file mode 100755 (executable)
index 0000000..0d0bd88
Binary files /dev/null and b/images/exit.png differ
diff --git a/images/help.png b/images/help.png
new file mode 100755 (executable)
index 0000000..8c53113
Binary files /dev/null and b/images/help.png differ
diff --git a/images/levels.png b/images/levels.png
new file mode 100755 (executable)
index 0000000..e46675c
Binary files /dev/null and b/images/levels.png differ
diff --git a/images/logo.png b/images/logo.png
new file mode 100644 (file)
index 0000000..b7e952c
Binary files /dev/null and b/images/logo.png differ
diff --git a/images/save.png b/images/save.png
new file mode 100755 (executable)
index 0000000..eb24158
Binary files /dev/null and b/images/save.png differ
diff --git a/images/tick_mark.png b/images/tick_mark.png
new file mode 100755 (executable)
index 0000000..b7ac6d8
Binary files /dev/null and b/images/tick_mark.png differ
diff --git a/locale_sources.ts b/locale_sources.ts
new file mode 100755 (executable)
index 0000000..22d3346
--- /dev/null
@@ -0,0 +1,998 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+    <name>ConfigDialog</name>
+    <message>
+        <location filename="config_dialog.ui" line="14"/>
+        <source>MinimPy2 Config</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="config_dialog.ui" line="20"/>
+        <source>Language</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="config_dialog.ui" line="30"/>
+        <source>Random engin</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="config_dialog.ui" line="40"/>
+        <source>Save</source>
+        <translation></translation>
+    </message>
+</context>
+<context>
+    <name>Database</name>
+    <message>
+        <location filename="db.py" line="35"/>
+        <source>Error</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="db.py" line="52"/>
+        <source>DB Connection Error</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="db.py" line="61"/>
+        <source>DB Integrity Error</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="db.py" line="361"/>
+        <source>DB read Error</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="db.py" line="702"/>
+        <source>DB Error</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="db.py" line="53"/>
+        <source>Could not open database file {}</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="db.py" line="62"/>
+        <source>Missing tables, please repair DB {}</source>
+        <translation></translation>
+    </message>
+</context>
+<context>
+    <name>EnrolForm</name>
+    <message>
+        <location filename="enrol_form.py" line="19"/>
+        <source>Editing subject &quot;{}&quot;</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="32"/>
+        <source>Treatment</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="51"/>
+        <source>Select factors, click enrol</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="52"/>
+        <source>Enrol</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="53"/>
+        <source>Close</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="55"/>
+        <source>Save</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="57"/>
+        <source>Cancel</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="90"/>
+        <source>Select {} level</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="107"/>
+        <source>Subject &quot;{}&quot;, Enrolled to {}</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="34"/>
+        <source>Subject enrol</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="72"/>
+        <source>preferred</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="75"/>
+        <source>non-preferred</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="76"/>
+        <source>Preferred treatment = {}
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="77"/>
+        <source>Minimised treatment = {}
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="78"/>
+        <source>Subject assigned to {} treatment with a probability of {:4.2f}</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="enrol_form.py" line="79"/>
+        <source>Minimisation detail</source>
+        <translation></translation>
+    </message>
+</context>
+<context>
+    <name>FactorLevels</name>
+    <message>
+        <location filename="factor_levels.py" line="32"/>
+        <source>Level title</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="33"/>
+        <source>Title</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="39"/>
+        <source>Error!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="40"/>
+        <source>Level title &apos;{}&apos; already exist!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="62"/>
+        <source>Confirm delete</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="63"/>
+        <source>Are you sure you want to delete &quot;%s&quot;?</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="87"/>
+        <source>ID</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="87"/>
+        <source>Level</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels.py" line="18"/>
+        <source>Levels of {}</source>
+        <translation></translation>
+    </message>
+</context>
+<context>
+    <name>FactorLevelsDialog</name>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="14"/>
+        <source>Dialog</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="25"/>
+        <source>Close</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="32"/>
+        <source>Delete</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="factor_levels_dialog.ui" line="39"/>
+        <source>Add</source>
+        <translation></translation>
+    </message>
+</context>
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="ui_main_window.py" line="574"/>
+        <source>Start</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="532"/>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:20pt; font-weight:600;&quot;&gt;How to do minimisation using MinimPy2?&lt;/span&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;A quick tutorial&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;1 - Go to Trials tab and select a trial as current to work on it. Define a new trial if there is no trial&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;2 - Go to Setting tab to view / edit trial setting. Usually the default setting is good to go&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;3 - Go to Treatments tab to define at lease two treatments&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;4 - Go to Factors tab to define at lease one factor&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;5 - Define at least two levels for each factor&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;6 - Now the trial is ready for subject enrollment&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;7 - Go to subject tab and click on the Add button in the toolbar&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;8 - The enroll window will appear. Select a level for each factor and click enroll&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;9 - Subject will enroll to one of treatment groups by the minimisation algorhythm&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;10 - If you want to define a preload go to Frequencies tab&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1197"/>
+        <source>Current trial exported successfully to "{}"</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="about_minimisation.py" line="16"/>
+        <source>Error in languages.lst file format\nPlease see the READ.ME file in locales folder</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="about_minimisation.py" line="16"/>
+        <source>Close</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.py" line="478"/>
+        <source>What is Minimisation</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="63"/>
+        <source>About</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="119"/>
+        <source>Factor/Level</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="119"/>
+        <source>SD</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="469"/>
+        <source>Enrolled</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="466"/>
+        <source>Treatment</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="25"/>
+        <source>Save not available!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="107"/>
+        <source>Clear preload</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="108"/>
+        <source>To clear preload, select preload and check edit</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="freq_table.py" line="210"/>
+        <source>Total</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="366"/>
+        <source>Error</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="369"/>
+        <source>Sample size consumed!
+No further subject enrol possible!
+Please convert cases to preload and continue</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="483"/>
+        <source>Filter</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="569"/>
+        <source>Double check!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="571"/>
+        <source>Need your final confirm</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="574"/>
+        <source>Deleting preload</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="576"/>
+        <source>Are you certainly sure you want to convert all subjects into preload?
+ALL subjects will be deleted</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="611"/>
+        <source>Help!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1013"/>
+        <source>minimpy2 [{}]</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="668"/>
+        <source>Factor title</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="992"/>
+        <source>Title</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="720"/>
+        <source>Error!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="676"/>
+        <source>Factor title &apos;{}&apos; already exist!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="693"/>
+        <source>Treatment title</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="701"/>
+        <source>Treatment title &apos;{}&apos; already exist!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="713"/>
+        <source>Trial title</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="721"/>
+        <source>A trial title &apos;{}&apos; already exist!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="816"/>
+        <source>Confirm delete</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="749"/>
+        <source>Are you sure you want to delete subject &quot;{}&quot;?</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="769"/>
+        <source>New factor not allowed!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="771"/>
+        <source>Trial already has subject or preload. 
+                                    You can not add or delete factors, treatments or levels</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="817"/>
+        <source>Are you sure you want to delete &quot;%s&quot;?</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="823"/>
+        <source>minimpy2</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>version</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>2.0</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>Copyright</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>2020</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>Dr. Mahmoud Saghaei</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="160"/>
+        <source>MinimPy</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="993"/>
+        <source>ID</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="878"/>
+        <source>Ratio</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="931"/>
+        <source>Factor</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="931"/>
+        <source>Levels</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="932"/>
+        <source>Weight</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="994"/>
+        <source>Code</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="995"/>
+        <source>Current</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1076"/>
+        <source>Mean</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1078"/>
+        <source>Max</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1081"/>
+        <source>Balance (mean MB = {:.4f})</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1084"/>
+        <source>No subject enrolled!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1122"/>
+        <source>Select mnd file</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1129"/>
+        <source>Warning</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1130"/>
+        <source>Do you want to save this trial?</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1142"/>
+        <source>Imported</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1144"/>
+        <source>Data imported successfully!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1147"/>
+        <source>Import error!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1149"/>
+        <source>Data are not valid!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="1156"/>
+        <source>Export to file</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="136"/>
+        <source>New trial</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="212"/>
+        <source>Maximum sample size &gt; {}</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="216"/>
+        <source>Are you sure you want to quit?</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="231"/>
+        <source>No current trial!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="233"/>
+        <source>Fist you have to define and select a trial as current!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="312"/>
+        <source>Are you sure you want to edit subject at row &quot;{}&quot; ?
+
+Editing subject may invalidate your research and the result of minimisation</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="14"/>
+        <source>MainWindow</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="25"/>
+        <source>Trials</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="35"/>
+        <source>Setting</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="43"/>
+        <source>Created</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="53"/>
+        <source>Modified</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="73"/>
+        <source>Trial code</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="83"/>
+        <source>Probability method</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="91"/>
+        <source>Biased coin</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="96"/>
+        <source>Naive</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="104"/>
+        <source>Base probability</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="116"/>
+        <source>0.70</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="135"/>
+        <source>Distance method</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="143"/>
+        <source>Marginal balance</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="148"/>
+        <source>Range</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="153"/>
+        <source>Standard deviation</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="158"/>
+        <source>Variance</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="166"/>
+        <source>Identifier type</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="174"/>
+        <source>Numeric</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="179"/>
+        <source>Alpha</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="184"/>
+        <source>Alphanumeric</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="192"/>
+        <source>Identifier order</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="200"/>
+        <source>Sequential</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="205"/>
+        <source>Random</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="213"/>
+        <source>Identifier length</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="232"/>
+        <source>TextLabel</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="241"/>
+        <source>Recycle ids</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="251"/>
+        <source>New subject random</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="261"/>
+        <source>Arms weight</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="270"/>
+        <source>1.0</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="292"/>
+        <source>Treatments</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="302"/>
+        <source>Factors</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="320"/>
+        <source>Frequencies</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="330"/>
+        <source>Preload</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="340"/>
+        <source>Edit</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="347"/>
+        <source>Convert to preload</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="354"/>
+        <source>I know what I&apos;m doing</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="405"/>
+        <source>Subjects</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="419"/>
+        <source>Balance</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="427"/>
+        <source>Oveall Balance</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="460"/>
+        <source>Randomness</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="490"/>
+        <source>File</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="504"/>
+        <source>toolBar</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="525"/>
+        <source>Exit</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="530"/>
+        <source>Add</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="535"/>
+        <source>Delete</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="543"/>
+        <source>Save</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="551"/>
+        <source>manage factor levels</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="556"/>
+        <source>Help</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="559"/>
+        <source>displays help</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="564"/>
+        <source>Import</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="569"/>
+        <source>Export</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="574"/>
+        <source>Config</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="ui_main_window.ui" line="577"/>
+        <source>Application config</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="179"/>
+        <source>Restart needed</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="180"/>
+        <source>Changes will be applied after restart!</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="29"/>
+        <source>Each trial has a uniqe tile and also a code usually in the form of an abreviation of the title.
+Last column show if the trial is the active one. Only one trial can be active at any time. Other pages of the application relate to this active trial. To make a treial active check the box in last column.
+Click and then type over the trial title or code to edit it</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="30"/>
+        <source>Creation date of this trial
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="31"/>
+        <source>Modification date of this trial
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="32"/>
+        <source>Title of this trial. Title must be unique
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="33"/>
+        <source>Code of the trial usually title abbreviation
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="34"/>
+        <source>The method used for calculating of the assignment probabilities. There are two choices. Biased coin minimization which include the effect of allocation ratios into the computation, and naive method which do not include the effect of allocation ratios. Biased coin is the preffered method. These options only is meaningfull when we have treatments with unequal allocation ratios. Biased Coin method is based on the different probabilities of assignment for subjects to trial groups. The subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Therefore if treatments with higher allocations ratios are assigned more probabilities this will be considered as biased-coin minimization. In this method a base probability is used for the group with the lowest allocation ratio when that group is selected as the preferred treatment. Probabilities for other groups (PHi) when they selected as preferred treatments are calculated as a function of base probability and their allocation ratios. In Naive method the subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Here the probabilities are not affected by allocation ratios and usually the same high probability are used for all treatment groups when they are selected as preferred treatments (base probability). This is denoted by Naive Minimization. In this method, probabilities for non-preferred treatments are distributed equally.
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="35"/>
+        <source>The probability given to the treatment with the lowest allocation ratio in biased-coin minimization. In naive minimization it is the probability used for all treatment groups when they are selected as preferred treatments. Use the spin button to specify the value.
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="36"/>
+        <source>Distance measure is used to calculate the imbalance score. There are four options here. Marginal balance computes the cumulative difference between every possible pairs of level counts. This is the default and the one which is recommended. It tends to minimize more accurately when treatment groups are not equal in size. For each factor level marginal balance is calculated as a function of adjusted number of patients present in a factor level for each treatment group. Range calculates the difference between the maximum and the mimimum values in level counts. Standard deviation calculates the standard deviation of level counts and variance calculate their variances. All of these measures uses adjusted level counts. Adjustment performs relative to the values of allocation ratios.
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="37"/>
+        <source>Type of subject identifier. It can be Numeric, Alpha or Alphanumeric.\nOnce a subject is enrolled, you can not change identifier type
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="38"/>
+        <source>The order of identifier sequence. It can be Sequential or Random.
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="39"/>
+        <source>Length  of subjects identifier in character. The longer the length the higher  will be the possible sample size
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="40"/>
+        <source>Check to reuse IDs of a deleted subject. The default is to discard
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="41"/>
+        <source>If unchecked (the default), enrol form will show with the level for each factor unselected. If checked, for each factor a random level will be selected. This is only for test purpose to rapidly and randomly enrol a subject
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="42"/>
+        <source> This is equalizing factor for arms of trial. The higher the arms weight the higher will be the possibility of having equal group sizes at the end of study
+</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="43"/>
+        <source>For each treatment a name (unique for the trial) and an integer value for allocation ratio are needed. Allocation ratios are integer numeric values which denote the ratios of the treatment counts. For example if we have three groups a, b and c with their allocation ratios 1, 2 and 3 respectively, this mean that in the final sample nearly 1/6 of subjects will be from treatment a, 1/3 from b and 1/2 from c
+You have to define at lease two groups to use minimisation on them.
+Click on treatment title and type over it to edit the title of the treatment, and use the spin control to change the allocation ratios</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="44"/>
+        <source>Factors or prognostic factors, are the subject factors which are selected to match subjects based on them. At lease one factor is mandatory for minimisation. Each factor has a name (unique for the trial) and a weight. Weight indicates the relative importance of the factor for minimization. For each factor you must define at least two levels. As an example factor may be gender and its levels may be Male and Female.
+Factor title can be edited by clicking and typing over the title of the factor
+
+Click on the levels button on tool bar or double click on levels in table to manage (add, edit, delete) levels of the selected factor</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="45"/>
+        <source>Manage (add, edit, delete) levels of the selected factor</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="46"/>
+        <source>This table shows frequencies of subjects enrolled in the trial or entered via preload table depending on the active checkboxes. When you have already enrolled a number of subjects (using other tools or software) and you want to continue with this minimisation program for the remaining cases, you can enter data of already enrolled subject as counts. You enter counts of different treatments accross all levels of each subject into the preload table. Then the counts from this table will be added to counts of enrolled subject by this application and total counts will be used in minimisation model.
+It is as if you have entered the list of subject into your existing list. The effect is exactly the same. When defining preload please note that sum of subjects across different levels of a factor must be equal to thats of other factors, otherwise an error will be generated.
+If you have a greal number of subjects and have no need to keep their list, you may convert them into preload table. Select frequencies check box and unselect preload checkbox, then click &apos;convert to preload&apos; to clear all subjects and add them to preload table.</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="47"/>
+        <source>Subjects enrolled in the trial will be listed in subjects table. Each row belongs to one subject and shows subject ID, treatment, levels of factors, dates of enrollment and modification (if any). Subject can be added, edited or deleted from this table, although editing or deleting subject may compromise the minimisation model and therefore are not recommended.To enter a new subject, click plus sign in the tool bar. To edit double click and to delete click on Delete button on toolbar.\n
+A progressbar above the subject table show the enrollment progress as the number of subject enrolled compared to the remaining counts of identifiers</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="main_window.py" line="50"/>
+        <source>In this table you can see four different scale related to overall trial balance (marginal balance, range, variance, SD) and randomness. Also the balances for each factor or for each level within each factor are displayed. The last two rows of the table show the mean and max values for each scale. The lower the numeric values of these balance, the more balance we have in our trial. Also a scale of randomness for enrolling subjects is calculated based on run test.</source>
+        <translation></translation>
+    </message>
+</context>
+</TS>
diff --git a/locales/READ.ME b/locales/READ.ME
new file mode 100755 (executable)
index 0000000..a143f44
--- /dev/null
@@ -0,0 +1,11 @@
+Instruction to translate into another languages
+
+1 - Find your language code. Each language has a code consists of language code and contry code. Example: en_US for US English and fr_CA for Canadian French
+2 - Copy 'locale_sources.ts' file and rename copied file into the language code. Example fr_CA.ts
+3 - Open this file with Qt Linguist application (https://www.qt.io/download-qt-installer)
+4 - In edit menu click 'Translation file settings...' and set the target language settings
+5 - Translate all phrases using the application and save the changes
+6 - From File menu select 'Release As...' option and save the compiled language file into 'locales' folder. The compiled language file will have qm extension.
+7 - Open 'languages.lst' file in 'locales' folder and add and entry for your language at the end of the file. Example: fr_CA:French. Inplace of language name (French in this example) you may use the locale name (Française)
+8 - Copy and translate the file 'minimisation_en_US.txt' and renamee it minimisation_<lang_code>.txt. In place of lang_code please use you language code. For example minimisation_fr_CA.txt
+9 - That's it. Now you can change the MinimPy2 application language by clicking 'Config' button in the toolbar and selecting your language from the list.
diff --git a/locales/fa_IR.qm b/locales/fa_IR.qm
new file mode 100755 (executable)
index 0000000..4a10248
Binary files /dev/null and b/locales/fa_IR.qm differ
diff --git a/locales/languages.lst b/locales/languages.lst
new file mode 100755 (executable)
index 0000000..700e932
--- /dev/null
@@ -0,0 +1,2 @@
+en_US:English
+fa_IR:فارسی
diff --git a/main_window.py b/main_window.py
new file mode 100755 (executable)
index 0000000..1fc852f
--- /dev/null
@@ -0,0 +1,1254 @@
+from PySide2 import QtWidgets as qtw
+from PySide2 import QtCore as qtc
+from PySide2 import QtGui as qtg
+
+from about_minimisation import AboutMinimisationDialog
+from config import Config
+
+from db import Database
+from enrol_form import EnrolForm
+from factor_levels import FactorLevels
+from freq_table import FreqTable
+from mnd import *
+from ui_main_window import Ui_MainWindow
+from minim import Minim
+from model import Model
+from my_random import Random
+from run_test import RunTest
+from trial import *
+import sys
+
+
+# noinspection PyTypeChecker
+class MainWindow(qtw.QMainWindow):
+    def __init__(self, settings):
+        super(MainWindow, self).__init__()
+        self.prev_tab_index = None
+        self.helpdict = {
+                    ('tialsTableWidget',): self.tr("""Each trial has a uniqe tile and also a code usually in the form of an abreviation of the title.\nLast column show if the trial is the active one. Only one trial can be active at any time. Other pages of the application relate to this active trial. To make a treial active check the box in last column.\nClick and then type over the trial title or code to edit it"""),
+                    ('trialSetting',): self.tr('''In this page you can view and edit different settings for the current trial. Click on blue lable for each setting item to view detailed help information for that setting'''),
+                    ('trialCreatedLabel','trialCreatedValueLabel'): self.tr('''Creation date of this trial\n'''),
+                    ('trialModifiedLabel','trialModifiedValueLabel'): self.tr('''Modification date of this trial\n'''),
+                    ('trialTitleLabel','trialTitleLineEdit'): self.tr('''Title of this trial. Title must be unique\n'''),
+                    ('trialCodeLabel','trialCodeLineEdit'): self.tr('''Code of the trial usually title abbreviation\n'''),
+                    ('trialProbmethodLabel','trialProbMethodComboBox'): self.tr('''The method used for calculating of the assignment probabilities. There are two choices. Biased coin minimization which include the effect of allocation ratios into the computation, and naive method which do not include the effect of allocation ratios. Biased coin is the preffered method. These options only is meaningfull when we have treatments with unequal allocation ratios. Biased Coin method is based on the different probabilities of assignment for subjects to trial groups. The subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Therefore if treatments with higher allocations ratios are assigned more probabilities this will be considered as biased-coin minimization. In this method a base probability is used for the group with the lowest allocation ratio when that group is selected as the preferred treatment. Probabilities for other groups (PHi) when they selected as preferred treatments are calculated as a function of base probability and their allocation ratios. In Naive method the subject is allocated to the preferred treatment with a higher probability and to other groups with lower probabilities. Here the probabilities are not affected by allocation ratios and usually the same high probability are used for all treatment groups when they are selected as preferred treatments (base probability). This is denoted by Naive Minimization. In this method, probabilities for non-preferred treatments are distributed equally.\n'''),
+                    ('trialBaseProbabilityLabel','trialBaseProbabilitySlider','baseProbLabel'): self.tr('''The probability given to the treatment with the lowest allocation ratio in biased-coin minimization. In naive minimization it is the probability used for all treatment groups when they are selected as preferred treatments. Use the spin button to specify the value.\n'''),
+                    ('trialDistanceMethodLabel','trialDistanceMethodComboBox'): self.tr('''Distance measure is used to calculate the imbalance score. There are four options here. Marginal balance computes the cumulative difference between every possible pairs of level counts. This is the default and the one which is recommended. It tends to minimize more accurately when treatment groups are not equal in size. For each factor level marginal balance is calculated as a function of adjusted number of patients present in a factor level for each treatment group. Range calculates the difference between the maximum and the mimimum values in level counts. Standard deviation calculates the standard deviation of level counts and variance calculate their variances. All of these measures uses adjusted level counts. Adjustment performs relative to the values of allocation ratios.\n'''),
+                    ('trialIdentifierTypeLabel', 'trialIdentifierTypeComboBox'): self.tr('''Type of subject identifier. It can be Numeric, Alpha or Alphanumeric.\nOnce a subject is enrolled, you can not change identifier type'''),
+                    ('trialIdentifierOrderLabel','trialIentifierOrderComboBox'): self.tr('''The order of identifier sequence. It can be Sequential or Random.\n'''),
+                    ('trialIdentifierLengthLabel','max_sample_size_label'): self.tr('''Length  of subjects identifier in character. The longer the length the higher  will be the possible sample size\n'''),
+                    ('trialRecycleIdsLabel','trialRecycleIdsCheckBox'): self.tr('''Check to reuse IDs of a deleted subject. The default is to discard\n'''),
+                    ('trialNewSubjectRandomLabel','trialNewSubjectRandomCheckBox'): self.tr('''If unchecked (the default), enrol form will show with the level for each factor unselected. If checked, for each factor a random level will be selected. This is only for test purpose to rapidly and randomly enrol a subject\n'''),
+                    ('trialArmsWeightLabel','trialArmsWeightDoubleSlider', 'armWeightLabel'): self.tr(''' This is equalizing factor for arms of trial. The higher the arms weight the higher will be the possibility of having equal group sizes at the end of study\n'''),
+                    ('treatmentTableWidget','Treatments'): self.tr('''For each treatment a name (unique for the trial) and an integer value for allocation ratio are needed. Allocation ratios are integer numeric values which denote the ratios of the treatment counts. For example if we have three groups a, b and c with their allocation ratios 1, 2 and 3 respectively, this mean that in the final sample nearly 1/6 of subjects will be from treatment a, 1/3 from b and 1/2 from c\nYou have to define at lease two groups to use minimisation on them.\nClick on treatment title and type over it to edit the title of the treatment, and use the spin control to change the allocation ratios'''),
+                    ('Factors','factorTableWidget'): self.tr('''Factors or prognostic factors, are the subject factors which are selected to match subjects based on them. At lease one factor is mandatory for minimisation. Each factor has a name (unique for the trial) and a weight. Weight indicates the relative importance of the factor for minimization. For each factor you must define at least two levels. As an example factor may be gender and its levels may be Male and Female.\nFactor title can be edited by clicking and typing over the title of the factor\n\nClick on the levels button on tool bar or double click on levels in table to manage (add, edit, delete) levels of the selected factor'''),
+                    ('factor_levels',): self.tr('''Manage (add, edit, delete) levels of the selected factor'''),
+                    ('Frequencies','freqGridLayout'): self.tr('''This table shows frequencies of subjects enrolled in the trial or entered via preload table depending on the active checkboxes. When you have already enrolled a number of subjects (using other tools or software) and you want to continue with this minimisation program for the remaining cases, you can enter data of already enrolled subject as counts. You enter counts of different treatments accross all levels of each subject into the preload table. Then the counts from this table will be added to counts of enrolled subject by this application and total counts will be used in minimisation model.\nIt is as if you have entered the list of subject into your existing list. The effect is exactly the same. When defining preload please note that sum of subjects across different levels of a factor must be equal to thats of other factors, otherwise an error will be generated.\nIf you have a greal number of subjects and have no need to keep their list, you may convert them into preload table. Select frequencies check box and unselect preload checkbox, then click 'convert to preload' to clear all subjects and add them to preload table.'''),
+                    ('Subjects', 'subjectTableWidget'): self.tr('''Subjects enrolled in the trial will be listed in subjects table. Each row belongs to one subject and shows subject ID, treatment, levels of factors, dates of enrollment and modification (if any). Subject can be added, edited or deleted from this table, although editing or deleting subject may compromise the minimisation model and therefore are not recommended.To enter a new subject, click plus sign in the tool bar. To edit double click and to delete click on Delete button on toolbar\nA progressbar above the subject table show the enrollment progress as the number of subject enrolled compared to the remaining counts of identifiers'''),
+                    ('Balance','balanceTreeWidget'): self.tr('''In this table you can see four different scale related to overall trial balance (marginal balance, range, variance, SD) and randomness. Also the balances for each factor or for each level within each factor are displayed. The last two rows of the table show the mean and max values for each scale. The lower the numeric values of these balance, the more balance we have in our trial. Also a scale of randomness for enrolling subjects is calculated based on run test.'''),
+                    ('start', 'startWidget'): self.tr('''A quick list of steps to start a new minimization.''')
+        }
+
+        self.settings = settings
+
+
+        engine = self.settings.value('random_engine', 'random', type=str)
+        self.random = Random(engine)
+        self.ui = Ui_MainWindow()
+        self.ui.setupUi(self)
+        self.ui.actionNew.setIcon(qtg.QIcon('images/add.png'))
+        self.ui.actionDelete.setIcon(qtg.QIcon('images/delete.png'))
+        self.ui.actionLevels.setIcon(qtg.QIcon('images/levels.png'))
+        self.ui.actionSave.setIcon(qtg.QIcon('images/save.png'))
+        self.ui.actionHelp.setIcon(qtg.QIcon('images/help.png'))
+        self.ui.actionQuit.setIcon(qtg.QIcon('images/exit.png'))
+        self.ui.actionConfig.setIcon(qtg.QIcon('images/config.png'))
+        self.ui.actionAbout_MinimPy2.setIcon(qtg.QIcon('images/about.png'))
+        self.database = Database.get_instance(self)
+        self.trial = None
+
+        self.ui.tabWidget.currentChanged.connect(self.page_changed)
+
+        self.ui.actionNew.triggered.connect(self.action_new)
+        self.ui.actionDelete.triggered.connect(self.action_delete)
+        self.ui.actionSave.triggered.connect(self.action_save)
+        self.ui.actionLevels.triggered.connect(self.factor_levels)
+        self.ui.actionQuit.triggered.connect(self.close)
+        self.ui.actionImport.triggered.connect(self.import_mnd_file)
+        self.ui.actionExport.triggered.connect(self.export_mnd_file)
+        self.ui.actionHelp.triggered.connect(self.action_help)
+        self.ui.actionConfig.triggered.connect(self.on_config)
+        self.ui.actionAbout_Minimisation.triggered.connect(self.on_about_minimisation)
+        self.ui.actionAbout_MinimPy2.triggered.connect(self.on_about_minimpy)
+        self.ui.actionAbout_Qt.triggered.connect(lambda : qtw.QMessageBox.aboutQt(self, ''))
+
+        lst = [self.ui.trialCreatedLabel, self.ui.trialCreatedValueLabel,
+               self.ui.trialModifiedLabel, self.ui.trialModifiedValueLabel,
+               self.ui.trialTitleLabel, self.ui.trialCodeLabel,
+               self.ui.trialProbmethodLabel, self.ui.trialBaseProbabilityLabel,
+               self.ui.baseProbLabel, self.ui.trialDistanceMethodLabel,
+               self.ui.trialIdentifierTypeLabel, self.ui.trialIdentifierOrderLabel,
+               self.ui.trialIdentifierLengthLabel, self.ui.max_sample_size_label,
+               self.ui.trialRecycleIdsLabel, self.ui.trialNewSubjectRandomLabel,
+               self.ui.trialArmsWeightLabel, self.ui.armWeightLabel]
+        for w in lst:
+            self.set_help_handler(w)
+
+        self.ui.tialsTableWidget.cellChanged.connect(self.trial_cell_changed)
+        self.ui.treatmentTableWidget.itemChanged.connect(self.treatment_item_changed)
+        self.ui.factorTableWidget.itemChanged.connect(self.factor_item_changed)
+        self.ui.factorTableWidget.cellClicked.connect(self.set_help_handler(self.ui.factorTableWidget))
+
+        self.ui.tialsTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
+        self.ui.tialsTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
+
+        self.ui.treatmentTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
+        self.ui.treatmentTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
+
+        self.ui.factorTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
+        self.ui.factorTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
+
+        self.ui.subjectTableWidget.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
+        self.ui.subjectTableWidget.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
+
+        self.ui.editPreloadCheckBox.toggled.connect(self.on_edit_preload)
+        self.ui.convertPreloadButton.clicked.connect(self.on_convert_preload)
+        self.ui.factorTableWidget.cellDoubleClicked.connect(self.factor_coloumn_dblclicked)
+        self.ui.subjectTableWidget.cellDoubleClicked.connect(self.subject_coloumn_dblclicked)
+
+        self.ui.trialIdentifierLengthSpinBox.valueChanged.connect(self.on_identifier_length)
+        self.ui.trialBaseProbabilitySlider.valueChanged.connect(self.on_base_prob_change)
+        self.ui.trialArmsWeightDoubleSlider.valueChanged.connect(self.on_arm_weight_change)
+
+        self.freqTable = None
+
+        if self.settings.value('show_tutorial_at_start', True):
+            self.ui.tabWidget.setCurrentIndex(7)
+            self.ui.showAtStartCheckBox.setChecked(False)
+        else:
+            self.ui.tabWidget.setCurrentIndex(0)
+            self.ui.showAtStartCheckBox.setChecked(True)
+        self.ui.balanceTreeWidget.setColumnCount(5)
+        self.ui.balanceTreeWidget.setHeaderLabels([self.tr('Factor/Level'), self.tr('Marginal balance'), self.tr('Range'), self.tr('Variance'), self.tr('SD')])
+
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id != 0:
+            trial_setting = self.database.get_trial_setting(last_trial_id)
+            #  when db file accidentally deleted but the last_trial_id persist
+            if not trial_setting:
+                trial_setting = self.database.get_first_trial_setting()
+                if not trial_setting:
+                    last_trial_id = self.database.insert_trial(self.tr('New trial'))
+                    self.settings.setValue('last_trial_id', last_trial_id)
+                    trial_setting = self.database.get_trial_setting(last_trial_id)
+            else:
+                self.settings.setValue('last_trial_id', trial_setting.value('id'))
+        else:
+            trial_setting = self.database.get_first_trial_setting()
+            if not trial_setting:
+                last_trial_id = self.database.insert_trial(self.tr('New trial'))
+                self.settings.setValue('last_trial_id', last_trial_id)
+                trial_setting = self.database.get_trial_setting(last_trial_id)
+            else:
+                self.settings.setValue('last_trial_id', trial_setting.value('id'))
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id != 0:
+            trial_setting = self.database.get_trial_setting(last_trial_id)
+            if trial_setting:
+                self.trial = Trial(trial_setting)
+                #if self.database.has_preload(self.trial.id) or self.database.has_subject(self.trial.id):
+                self.ui.frequenciesCheckBox.toggled.connect(self.set_cur_count)
+                self.ui.preloadCheckBox.toggled.connect(self.set_cur_count)
+        self.load_trials()
+        self.ui.actionSave.setEnabled(False)
+        self.ui.actionLevels.setEnabled(False)
+        if self.settings.value('show_tutorial_at_start', True, bool):
+            self.ui.tabWidget.setCurrentIndex(7)
+            self.ui.showAtStartCheckBox.setChecked(False)
+        else:
+            self.ui.tabWidget.setCurrentIndex(0)
+            self.ui.showAtStartCheckBox.setChecked(True)
+        self.ui.showAtStartCheckBox.toggled.connect(self.toggle_show_at_start)
+
+    def toggle_show_at_start(self, check):
+        self.settings.setValue('show_tutorial_at_start', not check)
+
+    def on_about_minimpy(self):
+        about = '{}\n{} {} {} ({})\n{} {}\n{}'.format(self.tr('MinimPy'),
+                                                      self.tr('MinimPy'),
+                                                      self.tr('version'),
+                                                      self.tr('2.0'),
+                                                      self.tr('minimpy2'),
+                                                      self.tr('Copyright'),
+                                                      self.tr('2020'),
+                                                      self.tr('Dr. Mahmoud Saghaei'))
+        qtw.QMessageBox.about(self, self.tr('minimpy2'), about)
+
+    def on_about_minimisation(self):
+        ab_min = AboutMinimisationDialog(self)
+        ab_min.show()
+
+    def set_help_handler(self, w):
+        def help_handler():
+            self.setCursor(qtc.Qt.ArrowCursor)
+            name = w.objectName()
+            for key in self.helpdict:
+                if name in key:
+                    qtw.QMessageBox.information(
+                        self, self.tr('Help!'),
+                        self.tr(self.helpdict[key])
+                    )
+            return True
+        clickable(w).connect(help_handler)
+
+    def on_config(self):
+        cur_lang = self.settings.value('language', 'en_US', type=str)
+        cur_random = self.settings.value('random_engine', 'random', type=str)
+        try:
+            config = Config(self)
+            config.exec_()
+        except:
+            qtw.QMessageBox.critical(self, self.tr('Error'), self.tr(
+                'Error in languages.lst file format\nPlease see the READ.ME file in locales folder'))
+            sys.exit(1)
+        new_lang = self.settings.value('language', 'en_US', type=str)
+        new_random = self.settings.value('random_engine', 'random', type=str)
+        if new_lang != cur_lang or new_random != cur_random:
+            qtw.QMessageBox.information(self,
+                                        self.tr('Restart needed'),
+                                        self.tr('Changes will be applied after restart!'))
+
+    def action_help(self):
+        currentIndex = self.ui.tabWidget.currentIndex()
+        pages = ['tialsTableWidget', 'trialSetting', 'treatmentTableWidget',
+                 'factorTableWidget', 'freqGridLayout',
+                 'subjectTableWidget', 'balanceTreeWidget', 'start']
+        page = pages[currentIndex]
+        for key in self.helpdict:
+            if page in key:
+                qtw.QMessageBox.information(
+                    self, self.tr('Help!'),
+                    self.tr(self.helpdict[key])
+                )
+
+    def on_arm_weight_change(self, value):
+        self.ui.armWeightLabel.setText('{}'.format(value))
+
+    def on_base_prob_change(self, value):
+        self.ui.baseProbLabel.setText('{:4.2f}'.format(value / 100.0))
+
+    def on_identifier_length(self, value):
+        self.trial.identifier_length = value
+        self.update_sample_size_label()
+
+    def update_sample_size_label(self):
+        ss, max_sample_size = self.trial.get_max_sample_size()
+        self.ui.max_sample_size_label.setText(self.tr('Maximum sample size > {}').format(max_sample_size))
+
+    def closeEvent(self, event):
+        button = qtw.QMessageBox.question(self, self.tr("Warning"),
+                                          self.tr('Are you sure you want to quit?'),
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            event.ignore()
+        else:
+            event.accept()
+
+    def page_changed(self, currentTabIndex):
+        self.setCursor(qtc.Qt.ArrowCursor)
+        self.ui.actionNew.setEnabled(False)
+        self.ui.actionDelete.setEnabled(False)
+        self.ui.actionSave.setEnabled(False)
+        self.ui.actionLevels.setEnabled(False)
+        if self.trial == None and currentTabIndex != 0:
+            qtw.QMessageBox.information(
+                self, self.tr('No current trial!'),
+                self.tr('Fist you have to define and select a trial as current!')
+            )
+            self.ui.actionNew.setEnabled(True)
+            self.ui.actionDelete.setEnabled(True)
+            self.ui.actionSave.setEnabled(False)
+            self.ui.actionLevels.setEnabled(False)
+            self.ui.tabWidget.setCurrentIndex(0)
+            return
+        if self.prev_tab_index == 1:
+            self.save_trial_setting()
+            last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+            trial_setting = self.database.get_trial_setting(last_trial_id)
+            self.trial = Trial(trial_setting)
+        self.prev_tab_index = currentTabIndex
+        if currentTabIndex == 0:  # trials
+            self.ui.actionNew.setEnabled(True)
+            self.ui.actionDelete.setEnabled(True)
+            self.load_trials()
+        elif currentTabIndex == 1:  # trial  settings
+            self.ui.actionSave.setEnabled(True)
+            self.load_trial_settings()
+        elif currentTabIndex == 2:  # treatments
+            self.ui.actionNew.setEnabled(True)
+            self.ui.actionDelete.setEnabled(True)
+            self.load_treatments()
+        elif currentTabIndex == 3:  # factors
+            self.ui.actionNew.setEnabled(True)
+            self.ui.actionDelete.setEnabled(True)
+            self.ui.actionLevels.setEnabled(True)
+            self.load_factors()
+        elif currentTabIndex == 4:  # Frequencies
+            self.ui.frequenciesCheckBox.setChecked(False)
+            self.ui.preloadCheckBox.setChecked(False)
+            self.ui.editPreloadCheckBox.setChecked(False)
+            self.ui.actionSave.setEnabled(True)
+            self.ui.actionDelete.setEnabled(True)
+            self.ui.convertPreloadButton.setEnabled(False)
+            self.ui.convertWarningCheckBox.setVisible(False)
+            self.load_frequencies()
+        elif currentTabIndex == 5:  # Subjects
+            self.ui.actionNew.setEnabled(True)
+            self.ui.actionDelete.setEnabled(True)
+            self.load_subjects()
+        elif currentTabIndex == 6:  # Balance
+            self.load_balance()
+
+    def get_factor_level_dict(self, trial_id):
+        factor_list = self.database.read_factors(trial_id)
+        factors = []
+        for factor in factor_list:
+            f = {'id': factor[0], 'title': factor[1], 'weight': factor[2], 'levels': []}
+            levels = self.database.factor_levels(factor[0])
+            for level in levels:
+                lv = {'id': level[0], 'title': level[1]}
+                f['levels'].append(lv)
+            if self.trial.new_subject_random:
+                index = self.random.randint(0, len(f['levels']) - 1)
+                f['selected_level_id'] = f['levels'][index]['id']
+            else:
+                f['selected_level_id'] = -1
+            factors.append(f)
+        return factors
+
+    def add_new_subject(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        factors = self.get_factor_level_dict(last_trial_id)
+        treatments = self.database.read_treatments(last_trial_id)
+        treatment = {'selected_treatment_id': -1, 'treatments': []}
+        for t in treatments:
+            treatment['treatments'].append({'id': t[0], 'title': t[1]})
+        enrol_form = EnrolForm(self, factors, treatment, False)
+        enrol_form.exec_()
+        if treatment['selected_treatment_id'] == -1:
+            return
+        self.update_subjects_progress()
+
+    def subject_coloumn_dblclicked(self, row, col):
+        button = qtw.QMessageBox.question(self, self.tr("Warning"),
+                                          self.tr('''Are you sure you want to edit subject at row "{}" ?\n
+Editing subject may invalidate your research and the result of minimisation''').format(row),
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            return
+        subject_id = int(self.ui.subjectTableWidget.item(row, 0).text())
+        identifier = self.ui.subjectTableWidget.item(row, 1).text()
+        treatment_id = self.database.get_subject_treatment_id(subject_id)
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        factors = self.get_factor_level_dict(last_trial_id)
+        subject_levels = self.database.get_subject_levels(subject_id)
+        for factor in factors:
+            for subject_level in subject_levels:
+                if factor['id'] == subject_level[0]:
+                    factor['selected_level_id'] = subject_level[1]
+                    break
+        treatments = self.database.read_treatments(last_trial_id)
+        treatment = {'selected_treatment_id': treatment_id, 'treatments': []}
+        for t in treatments:
+            treatment['treatments'].append({'id': t[0], 'title': t[1]})
+        enrol_form = EnrolForm(self, factors, treatment, True, identifier)
+        enrol_form.exec_()
+        result = enrol_form.result()
+        if result != qtw.QDialog.Accepted:
+            return
+        treatment_id = treatment['selected_treatment_id']
+        for factor in factors:
+            for subject_level in subject_levels:
+                if factor['id'] == subject_level[0]:
+                    subject_level[1] = factor['selected_level_id']
+                    break
+        self.database.delete_subject_levels(subject_id)
+        self.database.insert_subject_levels(last_trial_id, subject_id, subject_levels)
+        self.database.update_subject_treatment_id(subject_id, treatment_id)
+        self.clear_subject_filters()
+        self.ui.subjectTableWidget.selectRow(row)
+        self.ui.subjectTableWidget.setFocus()
+
+    def get_min_free_identifier(self, used_ids):
+        n = 0
+        while n in used_ids:
+            n += 1
+        return n
+
+    def enrol_one(self, selected_indices, selected_ids):
+        trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if trial_id == 0:
+            return
+        max_sample_size, f = self.trial.get_max_sample_size()
+        subject_count = self.database.get_subject_count(trial_id)
+        if subject_count == max_sample_size:
+            qtw.QMessageBox.critical(
+                self,
+                self.tr('Error'),
+                self.tr(
+                    'Sample size consumed!\nNo further subject enrol possible!\nPlease convert cases to preload and continue')
+            )
+            return
+        used_ids = self.database.get_used_identifiers(trial_id)
+        if not self.trial.recycle_ids:
+            used_ids.extend(self.database.get_discarded_identifiers(trial_id))
+        if not used_ids:
+            used_ids = [-1]
+        if self.trial.identifier_order == SEQUENTIAL:
+            identifier_value = self.get_min_free_identifier(used_ids)
+        else:
+            identifier_value = self.get_random_identifier(used_ids, int(max_sample_size))
+        new_case = {'levels': selected_indices, 'allocation': -1, 'UI': identifier_value}
+        # minimised group and preferred group
+        m_treatment, p_treatment, probs = self.get_minimize_case(new_case, trial_id)
+        treatments = self.database.read_treatments(trial_id)
+        treatment_id = treatments[m_treatment][0]
+        subject_id = self.database.insert_subject(identifier_value, treatment_id, trial_id)
+        self.database.insert_subject_levels(trial_id, subject_id, selected_ids)
+        if subject_count == 0:
+            self.load_subjects()
+        else:
+            self.clear_subject_filters()
+        self.select_row(self.ui.subjectTableWidget, subject_id)
+        if p_treatment is None:
+            p_treatment = 0
+        return treatments[m_treatment][1], \
+               m_treatment, \
+               treatments[p_treatment][1], \
+               p_treatment, \
+               self.trial.format_subject_identifier(identifier_value), probs
+
+    def select_row(self, table_widget, row_id):
+        for r in range(table_widget.rowCount()):
+            item = table_widget.item(r, 0)
+            if item is None: continue
+            if row_id == int(item.text()):
+                table_widget.selectRow(r)
+                return
+
+    def get_minimize_case(self, new_case, trial_id):
+        model = Model()
+        num_treatments = self.database.get_num_treatments(trial_id)
+        model.groups = list(range(num_treatments))
+        model.variables = self.database.get_factors_level_indices(trial_id)
+        model.variables_weight = self.database.get_factor_weights(trial_id)
+        model.allocation_ratio = self.database.get_allocation_ratios(trial_id)
+        model.allocations = self.database.get_allocations(trial_id)
+        model.prob_method = self.trial.prob_method
+        model.distance_measure = self.trial.dist_method
+        model.high_prob = self.trial.base_prob
+        model.min_group = self.database.get_min_allocation_ratio_group_index(trial_id)
+        model.arms_weight = self.trial.arms_weight
+        m = Minim(random=self.random, model=model)
+        m.build_probs(model.high_prob, model.min_group)
+        m.build_freq_table()
+        if self.database.has_preload(trial_id):
+            self.add_to_preload(m.freq_table, trial_id)
+        minimised_group = m.enroll(new_case, m.freq_table)
+        preffered_group= m.pref_group
+        probs = m.selected_probs
+        return minimised_group, preffered_group, probs
+
+    def add_to_preload(self, freq_table, trial_id):
+        preload = self.database.get_preload(trial_id)
+        treatments = self.database.read_treatments(trial_id)
+        factors = self.database.read_factors(trial_id)
+        for row, group in enumerate(freq_table):
+            for v, variable in enumerate(group):
+                for l, level in enumerate(variable):
+                    factor = factors[v]
+                    levels = self.database.factor_levels(factor[0])
+                    level = levels[l]
+                    treatment = treatments[row]
+                    key = (treatment[0], factor[0], level[0])
+                    freq_table[row][v][l] += preload[key]
+
+    def get_random_identifier(self, used_ids, max_sample_size):
+        if (max_sample_size / len(used_ids) < 100):
+            pv = list(range(max_sample_size))
+            for used_id in used_ids:
+                if used_id in pv:
+                    pv.remove(used_id)
+            index = self.random.randint(0, len(pv) - 1)
+            return pv[index]
+        else:
+            v = self.random.randint(0, max_sample_size - 1)
+            while v in used_ids:
+                v = self.random.randint(0, max_sample_size - 1)
+            return v
+
+    def update_subjects_progress(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        ss, ss_label = self.trial.get_max_sample_size()
+        ss -= self.database.get_count_discarded_identifiers(last_trial_id)
+        self.ui.subjectProgressBar.setMaximum(ss)
+        cnt = self.database.get_subject_count(last_trial_id)
+        self.ui.subjectProgressBar.setValue(cnt)
+
+    def load_subjects(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        self.ui.subjectTableWidget.setRowCount(0)
+        cnt = self.database.get_subject_count(last_trial_id)
+        self.update_subjects_progress()
+        if cnt == 0:
+            return
+        factors = self.database.read_factors(last_trial_id)
+        self.ui.subjectTableWidget.setColumnCount(len(factors) + 5)
+        headers = ['#', self.tr('ID'), self.tr('Treatment')]
+        for factor in factors:
+            headers.append(factor[1])
+        headers += [self.tr('Enrolled'), self.tr('Modified')]
+        self.ui.subjectTableWidget.setHorizontalHeaderLabels(headers)
+        header = self.ui.subjectTableWidget.horizontalHeader()
+        header.setSectionResizeMode(
+            self.ui.subjectTableWidget.columnCount() - 1,
+            qtw.QHeaderView.ResizeToContents
+        )
+        header.setSectionResizeMode(
+            self.ui.subjectTableWidget.columnCount() - 2,
+            qtw.QHeaderView.ResizeToContents
+        )
+        self.ui.subjectTableWidget.insertRow(0)
+        for i in range(1, len(headers)):
+            lineEdit = qtw.QLineEdit()
+            lineEdit.setPlaceholderText(self.tr('Filter'))
+            lineEdit.editingFinished.connect(self.subject_colum_editing_finished)
+            self.ui.subjectTableWidget.setCellWidget(0, i, lineEdit)
+        self.ui.subjectTableWidget.hideColumn(0)
+        self.load_subject_rows()
+
+    def subject_colum_editing_finished(self):
+        self.load_subject_rows()
+
+    def clear_subject_filters(self):
+        for c in range(1, self.ui.subjectTableWidget.columnCount()):
+            lineEdit = self.ui.subjectTableWidget.cellWidget(0, c)
+            lineEdit.setText('')
+        self.load_subject_rows()
+
+    def load_subject_rows(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        subjects = self.database.get_subjects(last_trial_id)
+        self.ui.subjectTableWidget.setRowCount(1)
+        fields = [None]
+        for c in range(1, self.ui.subjectTableWidget.columnCount()):
+            lineEdit = self.ui.subjectTableWidget.cellWidget(0, c)
+            if lineEdit is None:
+                self.load_subjects()
+                return
+            text = lineEdit.text().strip()
+            fields.append(None if len(text) == 0 else text)
+        # model = self.ui.subjectTableWidget.model()
+        for subject in subjects:
+            subject_id = subject[0]
+            treatment_id = subject[1]
+            identifier_value = subject[2]
+            enrolled = subject[3]
+            modified = subject[4]
+            treatment = self.database.get_treatment_title(treatment_id)
+            row = [subject_id, self.trial.format_subject_identifier(identifier_value), treatment]
+            subject_levels = self.database.get_subject_levels(subject_id)
+            row.extend(self.database.get_subject_level_titles(subject_levels))
+            row.extend([enrolled, modified])
+            for c, row_text in enumerate(row):
+                if fields[c] == None:
+                    continue
+                if not fields[c] in row_text:
+                    break
+            else:
+                r = self.ui.subjectTableWidget.rowCount()
+                self.ui.subjectTableWidget.insertRow(r)
+                # data = model.headerData(r, qtc.Qt.Vertical)
+                for c, row_text in enumerate(row):
+                    item = qtw.QTableWidgetItem(str(row_text))
+                    item.setFlags(item.flags() ^ qtc.Qt.ItemIsEditable)
+                    self.ui.subjectTableWidget.setItem(r, c, item)
+        labels = [str(n) for n in range(1, len(subjects) + 1)]
+        labels.insert(0, '')
+        self.ui.subjectTableWidget.setVerticalHeaderLabels(labels)
+
+    def set_cur_count(self, checked):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        show_freq = self.ui.frequenciesCheckBox.isChecked()
+        show_preload = self.ui.preloadCheckBox.isChecked()
+        if show_preload and not show_freq:
+            self.ui.editPreloadCheckBox.setEnabled(True)
+            self.on_edit_preload(self.ui.editPreloadCheckBox.isChecked())
+        else:
+            self.ui.editPreloadCheckBox.setEnabled(False)
+            self.ui.editPreloadCheckBox.setChecked(False)
+        self.ui.convertPreloadButton.setEnabled(False)
+        if show_preload and show_freq:
+            cur_count = self.database.get_preload_with_freq(last_trial_id)
+        elif show_preload and not show_freq:
+            cur_count = self.database.get_preload(last_trial_id)
+        elif show_freq and not show_preload:
+            cur_count = self.database.get_freq(last_trial_id)
+            if self.database.has_subject(last_trial_id):
+                self.ui.convertPreloadButton.setEnabled(True)
+        else:
+            cur_count = self.database.get_empty_freq(last_trial_id)
+        if cur_count is None:
+            return
+        self.freqTable.set_counts(cur_count)
+
+    def on_convert_preload(self):
+        if not self.ui.convertWarningCheckBox.isChecked():
+            qtw.QMessageBox.information(
+                self, self.tr('Double check!'),
+                self.tr('Need your final confirm')
+            )
+            self.ui.convertWarningCheckBox.setVisible(True)
+            return
+        button = qtw.QMessageBox.question(self, self.tr("Deleting preload"),
+                                          self.tr('''Are you certainly sure you want to convert all subjects into preload?
+ALL subjects will be deleted'''),
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            self.ui.convertWarningCheckBox.setChecked(False)
+            self.ui.convertWarningCheckBox.setVisible(False)
+            return
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        self.ui.convertWarningCheckBox.setChecked(False)
+        self.ui.convertWarningCheckBox.setVisible(False)
+        self.ui.convertPreloadButton.setEnabled(False)
+        self.freqTable.add_to_preload()
+        self.database.delete_subjects(last_trial_id)
+        self.ui.frequenciesCheckBox.setChecked(False)
+        self.ui.preloadCheckBox.setChecked(True)
+
+    def on_edit_preload(self, check):
+        self.freqTable.toggleReadOnly()
+
+    def load_frequencies(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        self.freqTable = FreqTable(self, self.ui, last_trial_id)
+        self.freqTable.build()
+        if self.database.has_subject(self.trial.id):
+            self.ui.frequenciesCheckBox.setChecked(True)
+        elif self.database.has_preload(self.trial.id):
+            self.ui.preloadCheckBox.setChecked(True)
+
+    def factor_levels(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        selected = self.ui.factorTableWidget.selectedIndexes()
+        if not selected:
+            return
+        factor_id = int(self.ui.factorTableWidget.item(selected[0].row(), 0).text())
+        factor_title = self.ui.factorTableWidget.item(selected[0].row(), 1).text()
+        factorLevels = FactorLevels(self, last_trial_id, factor_id, factor_title)
+        factorLevels.exec_()
+        self.load_factors()
+
+    def getCurrentTrialFunc(self, trial):
+        def getToggleFunc(checked):
+            trial_id = int(trial[0])
+            title = trial[1]
+            if len(title) > 100:
+                title = title[:97] + '...'
+            self.setWindowTitle(self.tr('minimpy2 [{}]').format(title))
+            self.settings.setValue('last_trial_id', trial_id)
+            self.trial = Trial(self.database.get_trial_setting(trial_id))
+
+        return getToggleFunc
+
+    def action_new(self):
+        self.setCursor(qtc.Qt.ArrowCursor)
+        currentTabIndex = self.ui.tabWidget.currentIndex()
+        if currentTabIndex == 0:  # trials
+            self.add_new_trial()
+        elif currentTabIndex == 2:  # treatments
+            self.add_new_treatment()
+        elif currentTabIndex == 3:  # factors
+            self.add_new_factor()
+        elif currentTabIndex == 5:  # subjects
+            self.add_new_subject()
+
+    def action_save(self):
+        self.setCursor(qtc.Qt.ArrowCursor)
+        currentTabIndex = self.ui.tabWidget.currentIndex()
+        if currentTabIndex == 1:  # setting
+            self.save_trial_setting()
+        elif currentTabIndex == 4:
+            self.freqTable.on_save_preload()
+
+    def add_new_factor(self):
+        self.setCursor(qtc.Qt.ArrowCursor)
+        if self.check_trial_subjects_or_preload():
+            return
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        title, ok = qtw.QInputDialog.getText(
+            self,
+            self.tr('Factor title'),
+            self.tr('Title'),
+            qtw.QLineEdit.EchoMode.Normal
+        )
+        if ok and len(title.strip()) != 0:
+            if self.database.factor_title_exists(title, last_trial_id):
+                qtw.QMessageBox.critical(self,
+                                         self.tr('Error!'),
+                                         self.tr('''Factor title '{}' already exist!'''.format(title))
+                                         )
+                return
+            factor_id = self.database.insert_factor(last_trial_id, title)
+            state = self.ui.factorTableWidget.blockSignals(True)
+            factor = [str(factor_id), title, '', 1.0]
+            self.add_factor_row(factor)
+            self.ui.factorTableWidget.blockSignals(state)
+
+    def add_new_treatment(self):
+        if self.check_trial_subjects_or_preload():
+            return
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        title, ok = qtw.QInputDialog.getText(
+            self,
+            self.tr('Treatment title'),
+            self.tr('Title'),
+            qtw.QLineEdit.EchoMode.Normal
+        )
+        if ok and len(title.strip()) != 0:
+            if self.database.treatment_title_exists(title, last_trial_id):
+                qtw.QMessageBox.critical(self,
+                                         self.tr('Error!'),
+                                         self.tr('''Treatment title '{}' already exist!'''.format(title))
+                                         )
+                return
+            treatment_id = self.database.insert_treatment(last_trial_id, title)
+            state = self.ui.treatmentTableWidget.blockSignals(True)
+            cols = [str(treatment_id), title, 1]
+            self.add_treatment_row(cols)
+            self.ui.tialsTableWidget.blockSignals(state)
+
+    def add_new_trial(self):
+        title, ok = qtw.QInputDialog.getText(
+            self,
+            self.tr('Trial title'),
+            self.tr('Title'),
+            qtw.QLineEdit.EchoMode.Normal
+        )
+        if ok and len(title.strip()) != 0:
+            if self.database.trial_title_exists(title):
+                qtw.QMessageBox.critical(self,
+                                         self.tr('Error!'),
+                                         self.tr('''A trial title '{}' already exist!'''.format(title))
+                                         )
+                return
+            trial_id = self.database.insert_trial(title)
+            self.settings.setValue('last_trial_id', trial_id)
+            self.trial = Trial(self.database.get_trial_setting(trial_id))
+            self.load_trials()
+
+    def action_delete(self):
+        self.setCursor(qtc.Qt.ArrowCursor)
+        currentTabIndex = self.ui.tabWidget.currentIndex()
+        if currentTabIndex == 0:
+            self.delete_trial()
+        elif currentTabIndex == 2:  # treatments
+            self.delete_treatment()
+        elif currentTabIndex == 3:  # factors
+            self.delete_factor()
+        elif currentTabIndex == 4:  # preload
+            self.freqTable.on_clear_preload()
+        elif currentTabIndex == 5:  # subjects
+            self.delete_subject()
+
+    def delete_subject(self):
+        selected = self.ui.subjectTableWidget.selectedIndexes()
+        if not selected:
+            return
+        identifier = self.ui.subjectTableWidget.item(selected[0].row(), 1).text()
+        button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
+                                          self.tr('Are you sure you want to delete subject "{}"?').format(identifier),
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            return
+        subject_id = int(self.ui.subjectTableWidget.item(selected[0].row(), 0).text())
+        subject = self.database.get_subject(subject_id)
+        id_value = subject.value('identifier_value')
+        trial_id = subject.value('trial_id')
+        if self.trial.recycle_ids:
+            id_value = None
+        self.database.delete_subject(subject_id, id_value, trial_id)
+        self.ui.subjectTableWidget.removeRow(selected[0].row())
+        self.update_subjects_progress()
+
+    def check_trial_subjects_or_preload(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        has_subject = self.database.has_subject(last_trial_id)
+        has_preload = self.database.has_preload(last_trial_id)
+        if has_subject or has_preload:
+            qtw.QMessageBox.warning(self, self.tr('New factor not allowed!'),
+                                    self.tr('''Trial already has subject or preload. 
+                                    You can not add or delete factors, treatments or levels'''))
+            return True
+        return False
+
+    def delete_treatment(self):
+        if self.check_trial_subjects_or_preload():
+            return
+        selected = self.ui.treatmentTableWidget.selectedIndexes()
+        if not selected:
+            return
+        title = self.ui.treatmentTableWidget.item(selected[0].row(), 1).text()
+        button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
+                                          self.tr('Are you sure you want to delete "%s"?') % title,
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            return
+        treatment_id = int(self.ui.treatmentTableWidget.item(selected[0].row(), 0).text())
+        self.database.delete_treatment(treatment_id)
+        self.ui.treatmentTableWidget.blockSignals(True)
+        self.ui.treatmentTableWidget.removeRow(selected[0].row())
+        self.ui.treatmentTableWidget.blockSignals(False)
+
+    def delete_factor(self):
+        if self.check_trial_subjects_or_preload():
+            return
+        selected = self.ui.factorTableWidget.selectedIndexes()
+        if not selected:
+            return
+        title = self.ui.factorTableWidget.item(selected[0].row(), 1).text()
+        button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
+                                          self.tr('Are you sure you want to delete "%s"?') % title,
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            return
+        factor_id = int(self.ui.factorTableWidget.item(selected[0].row(), 0).text())
+        self.database.delete_factor(factor_id)
+        self.ui.factorTableWidget.blockSignals(True)
+        self.ui.factorTableWidget.removeRow(selected[0].row())
+        self.ui.factorTableWidget.blockSignals(False)
+
+    def delete_trial(self):
+        selected = self.ui.tialsTableWidget.selectedIndexes()
+        if not selected:
+            return
+        title = self.ui.tialsTableWidget.item(selected[0].row(), 1).text()
+        button = qtw.QMessageBox.question(self, self.tr("Confirm delete"),
+                                          self.tr('Are you sure you want to delete "%s"?') % title,
+                                          qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+        if button != qtw.QMessageBox.Yes:
+            return
+        trial_id = int(self.ui.tialsTableWidget.item(selected[0].row(), 0).text())
+        self.database.delete_trial(trial_id)
+        self.setWindowTitle(self.tr('minimpy2'))
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == trial_id:
+            self.settings.setValue('last_trial_id', 0)
+            self.trial = None
+        self.ui.tialsTableWidget.removeRow(selected[0].row())
+
+    def factor_item_changed(self, itemWidget):
+        row, col = itemWidget.row(), itemWidget.column()
+        column = 'title'
+        value = self.ui.factorTableWidget.item(row, col).text()
+        if col == 3:
+            column = 'weight'
+            value = float(value)
+        factor_id = int(self.ui.factorTableWidget.item(row, 0).text())
+        oldValue = self.database.update_factor(factor_id, column, value)
+        if oldValue is not None:
+            self.abort_table_widget_change(self.ui.factorTableWidget, row, col, oldValue)
+
+    def abort_table_widget_change(self, table, row, col, oldValue):
+        table.item(row, col).setText(oldValue)
+
+    def treatment_item_changed(self, itemWidget):
+        row, col = itemWidget.row(), itemWidget.column()
+        column = 'title'
+        value = self.ui.treatmentTableWidget.item(row, col).text()
+        if col == 2:
+            column = 'ratio'
+            value = int(value)
+        treatment_id = int(self.ui.treatmentTableWidget.item(row, 0).text())
+        oldValue = self.database.update_treatment(treatment_id, column, value)
+        if oldValue is not None:
+            self.abort_table_widget_change(self.ui.treatmentTableWidget, row, col, oldValue)
+
+    def trial_cell_changed(self, row, col):
+        trial_id = int(self.ui.tialsTableWidget.item(row, 0).text())
+        column = 'title'
+        value = self.ui.tialsTableWidget.item(row, col).text()
+        if col == 2:
+            column = 'code'
+        oldValue = self.database.update_trial(trial_id, column, value)
+        if oldValue is not None:
+            self.abort_table_widget_change(self.ui.tialsTableWidget, row, col, oldValue)
+
+    def load_treatments(self):
+        self.ui.treatmentTableWidget.blockSignals(True)
+        self.ui.treatmentTableWidget.setRowCount(0)
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        query = self.database.load_treatments(last_trial_id)
+        self.ui.treatmentTableWidget.setColumnCount(3)
+        ID = self.tr('ID')
+        Title = self.tr('Title')
+        Ratio = self.tr('Ratio')
+        self.ui.treatmentTableWidget.setHorizontalHeaderLabels([ID, Title, Ratio])
+        header = self.ui.treatmentTableWidget.horizontalHeader()
+        header.setSectionResizeMode(0, qtw.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(1, qtw.QHeaderView.Stretch)
+        header.setSectionResizeMode(2, qtw.QHeaderView.ResizeToContents)
+        while query.next():
+            cols = [str(query.value('id')), query.value('title'), query.value('ratio')]
+            self.add_treatment_row(cols)
+        self.ui.treatmentTableWidget.blockSignals(False)
+
+    def add_treatment_row(self, cols):
+        r = self.ui.treatmentTableWidget.rowCount()
+        self.ui.treatmentTableWidget.insertRow(r)
+        id_item = qtw.QTableWidgetItem(cols[0])
+        id_item.setTextAlignment(qtc.Qt.AlignCenter)
+        self.ui.treatmentTableWidget.setCellWidget(r, 0, qtw.QLabel())
+        self.ui.treatmentTableWidget.setItem(r, 0, id_item)
+        title_item = qtw.QTableWidgetItem(cols[1])
+        self.ui.treatmentTableWidget.setItem(r, 1, title_item)
+        ratio_spin = qtw.QSpinBox()
+        ratio_spin.setMinimum(1)
+        ratio_spin.setMaximum(20)
+        ratio_spin.setValue(cols[2])
+        ratio_item = qtw.QTableWidgetItem(str(cols[2]))
+        self.ui.treatmentTableWidget.setItem(r, 2, ratio_item)
+        ratio_spin.valueChanged.connect(lambda value: ratio_item.setText(str(value)))
+        self.ui.treatmentTableWidget.setCellWidget(r, 2, ratio_spin)
+
+    def add_one_trial_cell(self, r, c, trial):
+        item = trial[c]
+        oneItem = qtw.QTableWidgetItem(item)
+        if c == 0:
+            oneItem.setTextAlignment(qtc.Qt.AlignCenter)
+            self.ui.tialsTableWidget.setCellWidget(r, c, qtw.QLabel())
+        if c == 3:
+            check = qtw.QRadioButton()
+            check.setStyleSheet('text-align: center; margin-left:50%; margin-right:50%;')
+            check.toggled.connect(self.getCurrentTrialFunc(trial))
+            check.setChecked(item)
+            self.ui.tialsTableWidget.setCellWidget(r, c, check)
+        self.ui.tialsTableWidget.setItem(r, c, oneItem)
+
+    def load_factors(self):
+        self.ui.factorTableWidget.blockSignals(True)
+        self.ui.factorTableWidget.setRowCount(0)
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        self.ui.factorTableWidget.setColumnCount(4)
+        header = self.ui.factorTableWidget.horizontalHeader()
+        header.setSectionResizeMode(0, qtw.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(1, qtw.QHeaderView.Stretch)
+        header.setSectionResizeMode(2, qtw.QHeaderView.Stretch)
+        self.ui.factorTableWidget.setHorizontalHeaderLabels([self.tr('ID'), self.tr('Factor'), self.tr('Levels'),
+                                                             self.tr('Weight')])
+        query = self.database.load_factors(last_trial_id)
+        factors = []
+        while query.next():
+            factor = [str(query.value('id')), query.value('title'), query.value('weight')]
+            factors.append(factor)
+        for factor in factors:
+            levels = self.database.get_levels(int(factor[0]))
+            factor.insert(2, levels)
+            self.add_factor_row(factor)
+        self.ui.factorTableWidget.blockSignals(False)
+
+    def factor_coloumn_dblclicked(self, row, col):
+        if col != 2:
+            return
+        self.factor_levels()
+
+    def add_factor_row(self, factor):
+        r = self.ui.factorTableWidget.rowCount()
+        self.ui.factorTableWidget.insertRow(r)
+        id_item = qtw.QTableWidgetItem(factor[0])
+        id_item.setTextAlignment(qtc.Qt.AlignCenter)
+        self.ui.factorTableWidget.setCellWidget(r, 0, qtw.QLabel())
+        self.ui.factorTableWidget.setItem(r, 0, id_item)
+        title_item = qtw.QTableWidgetItem(factor[1])
+        self.ui.factorTableWidget.setItem(r, 1, title_item)
+        levels_item = qtw.QTableWidgetItem(factor[2])
+        levels_item.setTextAlignment(qtc.Qt.AlignCenter)
+        self.ui.factorTableWidget.setCellWidget(r, 2, qtw.QLabel())
+        self.ui.factorTableWidget.setItem(r, 2, levels_item)
+        weight_spin = qtw.QDoubleSpinBox()
+        weight_spin.setMinimum(1.0)
+        weight_spin.setMaximum(10.0)
+        weight_spin.setSingleStep(0.10)
+        weight_spin.setValue(factor[3])
+        weight_item = qtw.QTableWidgetItem(str(factor[3]))
+        self.ui.factorTableWidget.setItem(r, 3, weight_item)
+        weight_spin.valueChanged.connect(lambda value: weight_item.setText(str(value)))
+        self.ui.factorTableWidget.setCellWidget(r, 3, weight_spin)
+
+    def load_trials(self):
+        self.ui.tialsTableWidget.blockSignals(True)
+        self.ui.tialsTableWidget.setRowCount(0)
+        query = self.database.get_db().exec_('SELECT id, title, code FROM trials')
+        trial_list = []
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        while query.next():
+            row = [
+                str(query.value('id')),
+                query.value('title'),
+                query.value('code'),
+                query.value('id') == last_trial_id
+            ]
+
+            trial_list.append(row)
+        if len(trial_list) == 0:
+            return
+        self.ui.tialsTableWidget.setRowCount(len(trial_list))
+        self.ui.tialsTableWidget.setColumnCount(len(trial_list[0]))
+        title = self.tr('Title')
+        trial_id = self.tr('ID')
+        code = self.tr('Code')
+        current = self.tr('Current')
+        header_lst = [trial_id, title, code, current]
+        self.ui.tialsTableWidget.setHorizontalHeaderLabels(header_lst)
+        header = self.ui.tialsTableWidget.horizontalHeader()
+        header.setSectionResizeMode(0, qtw.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(1, qtw.QHeaderView.Stretch)
+        header.setSectionResizeMode(2, qtw.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(3, qtw.QHeaderView.ResizeToContents)
+        for r in range(len(trial_list)):
+            for c in range(len(trial_list[r])):
+                self.add_one_trial_cell(r, c, trial_list[r])
+        self.ui.tialsTableWidget.setColumnWidth(1, 400)
+        self.ui.tialsTableWidget.blockSignals(False)
+
+    def save_trial_setting(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        self.database.update_trial_setting(last_trial_id, self.ui)
+        self.setWindowTitle(self.tr('minimpy2 [{}]').format(self.ui.trialTitleLineEdit.text()))
+        self.load_trial_settings()
+
+    def load_trial_settings(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        if self.database.has_subject(last_trial_id):
+            self.ui.trialIdentifierTypeComboBox.setEnabled(False)
+        else:
+            self.ui.trialIdentifierTypeComboBox.setEnabled(True)
+        self.database.load_trial_setting(last_trial_id, self.ui)
+        self.on_identifier_length(self.trial.identifier_length)
+        self.on_base_prob_change(self.trial.base_prob * 100)
+
+    def load_balance(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        if not self.database.has_preload(last_trial_id) and not self.database.has_subject(last_trial_id):
+            return
+        table = []
+        freq = self.database.get_preload_with_freq(last_trial_id)
+        treatments = self.database.read_treatments(last_trial_id)
+        factors = self.database.read_factors(last_trial_id)
+        for treatment in treatments:
+            row = []
+            for factor in factors:
+                t = treatment[0]
+                f = factor[0]
+                levels = self.database.factor_levels(f)
+                for level in levels:
+                    l = level[0]
+                    k = (t, f, l)
+                    row.append(freq[k])
+            table.append(row)
+        balance = self.get_trial_balance(table, last_trial_id)
+        variables = []
+        for factor in factors:
+            f = factor[0]
+            levels = self.database.factor_levels(f)
+            variables.append(list(range(len(levels))))
+        row = 0
+        self.ui.balanceTreeWidget.clear()
+        for idx, variable in enumerate(variables):
+            wt = factors[idx][2]
+            var_total = [0] * 4
+            level_rows = []
+            for level in variable:
+                for i in range(4):
+                    balance[row][i] *= float(wt)
+                    var_total[i] += balance[row][i]
+                level_rows.append(balance[row])
+                row += 1
+            var_total = [factors[idx][1]] + [var_total[j] / len(variable) for j in range(4)]
+            var_total = list(map(str, var_total))
+            variable_node = qtw.QTreeWidgetItem(self.ui.balanceTreeWidget, var_total)
+            levels = self.database.factor_levels(factors[idx][0])
+            level_names = [level[1] for level in levels]
+            for level, level_row in enumerate(level_rows):
+                level_str_list = list(map(str, [level_names[level]] + level_row))
+                qtw.QTreeWidgetItem(variable_node, level_str_list)
+        last_row = row
+        rows = len(balance) - 2
+        for col in range(4):
+            balance[rows].append(1.0 * sum([balance[row][col] for row in range(rows)]) / rows)
+            balance[rows + 1].append(max([balance[row][col] for row in range(rows)]))
+        means = list(map(str, [self.tr('Mean')] + balance[last_row]))
+        qtw.QTreeWidgetItem(self.ui.balanceTreeWidget, means)
+        maxes = list(map(str, [self.tr('Max')] + balance[last_row + 1]))
+        qtw.QTreeWidgetItem(self.ui.balanceTreeWidget, maxes)
+        self.ui.balanceProgressBar.setValue(100 - 100 * balance[last_row][0])
+        self.ui.mean_balance_label.setText(self.tr('Balance (mean MB = {:.4f})').format(balance[last_row][0]))
+        subjects = self.database.get_subjects(last_trial_id)
+        if len(subjects) == 0:
+            self.ui.randomness_label.setText(self.tr('No subject enrolled!'))
+            self.ui.randomnessProgressBar.setValue(0)
+            return
+        else:
+            self.set_randomness(subjects, last_trial_id)
+
+    def set_randomness(self, subjects, trial_id):
+        id_dict = self.database.get_treatments_id_dict(trial_id)
+        seq = [id_dict[subject[1]] for subject in subjects]
+        mean = sum(seq) / len(seq)
+        seq = list(map(lambda x: 1 if x > mean else 0, seq))
+        rt = RunTest(seq, 0, 1)
+        z, p = rt.get_p()
+        if p == None:
+            return
+        self.ui.randomnessProgressBar.setValue(p * 100)
+        self.ui.randomness_label.setText('Randomness (P = {:.4f})'.format(p))
+
+    def get_trial_balance(self, table, trial_id):
+        model = Model()
+        m = Minim(random=self.random, model=model)
+        levels = [[] for col in table[0]]
+        balances = [[] for col in table[0]] + [[], []]
+        for row in table:
+            for col, value in enumerate(row):
+                levels[col].append(value)
+        treatments = self.database.read_treatments(trial_id)
+        allocation_ratio = [treatment[2] for treatment in treatments]
+        for row, level_count in enumerate(levels):
+            adj_count = [(1.0 * level_count[i]) / allocation_ratio[i] for i in range(len(level_count))]
+            balances[row].append(m.get_marginal_balance(adj_count))
+            balances[row].append(max(adj_count) - min(adj_count))
+            balances[row].append(m.get_variance(adj_count))
+            balances[row].append(m.get_standard_deviation(adj_count))
+        return balances
+
+    def import_mnd_file(self):
+        filename = qtw.QFileDialog.getOpenFileName(self,
+                                                   self.tr('Select mnd file'),
+                                                   qtc.QDir.currentPath(),
+                                                   'mnd files (*.mnd)')
+        if filename is None:
+            return
+        mnd = Mnd(filename[0])
+        if mnd.data_file_valid():
+            button = qtw.QMessageBox.question(self, self.tr("Warning"),
+                                              mnd.get_detail() + '\n' + self.tr('Do you want to save this trial?'),
+                                              qtw.QMessageBox.Yes | qtw.QMessageBox.No)
+            if button != qtw.QMessageBox.Yes:
+                return
+            trial_id = mnd.import_data(self.database)
+            self.settings.setValue('last_trial_id', trial_id)
+            self.trial = Trial(self.database.get_trial_setting(trial_id))
+            if self.ui.tabWidget.currentIndex() != 0:
+                self.ui.tabWidget.setCurrentIndex(0)
+            else:
+                self.load_trials()
+            qtw.QMessageBox.information(self,
+                                        self.tr('Imported'),
+                                        self.tr('''Data imported successfully!''')
+                                        )
+        else:
+            qtw.QMessageBox.information(
+                self, self.tr('Import error!'),
+                self.tr('Data are not valid!')
+            )
+
+    def export_mnd_file(self):
+        last_trial_id = self.settings.value('last_trial_id', 0, type=int)
+        if last_trial_id == 0:
+            return
+        filename = qtw.QFileDialog.getSaveFileName(self,
+                                                   self.tr('Export to file'),
+                                                   qtc.QDir.currentPath(),
+                                                   'mnd files (*.mnd)')
+        if filename is None:
+            return
+        filename = filename[0]
+        if not filename.endswith('.mnd'):
+            filename += '.mnd'
+        mnd = Mnd(filename)
+        if mnd.export_data(last_trial_id, self.database):
+            qtw.QMessageBox.information(self, self.tr('Export'), self.tr('Current trial exported successfully to "{}"').format(filename))
+
+
+def clickable(widget):
+    class Filter(qtc.QObject):
+        clicked = qtc.Signal()
+        def eventFilter(self, obj, event):
+            if obj == widget:
+                if event.type() == qtc.QEvent.MouseButtonRelease:
+                    if obj.rect().contains(event.pos()):
+                        self.clicked.emit()
+                        # The developer can opt for .emit(obj) to get the object within the slot.
+                        return True
+            return False
+    filter = Filter(widget)
+    widget.installEventFilter(filter)
+    return filter.clicked
+
+
+def start_app():
+    try:
+        app = qtw.QApplication(sys.argv)
+    except RuntimeError:
+        app = qtc.QCoreApplication.instance()
+    app.setWindowIcon(qtg.QIcon('images/logo.png'))
+    settings = qtc.QSettings('net.saghaei', 'minimpy2')
+    lang = settings.value('language', 'en_US', type=str)
+    if lang != 'en_US':
+        translator = qtc.QTranslator()
+        translator.load('locales/{}'.format(lang))
+        app.installTranslator(translator)
+    mw = MainWindow(settings)
+    app.mainWindow = mw
+    mw.show()
+    exit_code = app.exec_()
+    return exit_code
+
+
+if __name__ == '__main__':
+    start_app()
diff --git a/minim.py b/minim.py
new file mode 100755 (executable)
index 0000000..98b7c65
--- /dev/null
+++ b/minim.py
@@ -0,0 +1,181 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+import math
+from model import Model
+
+class Minim(object):
+    """
+Main Minimization Class
+-------------------------------------------------------------------------------
+Instanciation: Minim(allocations=None, groups=None, new_cae=None)
+
+    'allocations' is a list of cases.
+        Each 'case' is a dictionairy of 'levels' and 'allocation' and optianlly other info.
+        Levels element is a list, with ith element is the level of ith var
+        allocation is the allocation of the case: 0 to n-1, -1 = unalocated
+
+    new_case is one case, with allocation = -1
+    """
+
+    def __init__(self, random, model=None, new_cae=None):
+        self.pref_group = None
+        self.selected_probs = None
+        self.random = random
+        self._allocations = model.allocations
+        self._prob_method = model.prob_method
+        self._distance_measure = model.distance_measure
+        self._groups = model.groups
+        self._variables = model.variables
+        self._variables_weight = model.variables_weight
+        self._allocation_ratio = model.allocation_ratio
+        self._high_prob = model.high_prob
+        self._min_group = model.min_group
+        self._arms_weight = model.arms_weight
+
+    @property
+    def allocations(self):
+        return self._allocations
+
+    @allocations.setter
+    def allocations(self, allocations):
+        self._allocations = allocations
+
+    @property
+    def groups(self):
+        return self._groups
+
+    @groups.setter
+    def groups(self, groups):
+        self._groups = groups
+
+    def build_probs(self, min_high_prob=None, min_group=0):
+        """
+This is only for bcm method of probability assignment
+    min_high_prob: The high probability value for the group with
+        lowest allocation ratio
+    min_group: index of the group with the lowest probability ratio
+for Naive method group rows are similar
+        """
+        if min_high_prob == None:
+            min_high_prob = self._high_prob
+        self._probs = [[0] * len(self._groups) for g in range(len(self._groups))]
+        self._probs[min_group][min_group] = min_high_prob
+        for r in self._groups:
+            if r == min_group:
+                continue
+            numerator = sum([self._allocation_ratio[row] for row in range(len(self._groups)) if row != r])
+            denominator = sum([self._allocation_ratio[row] for row in range(len(self._groups)) if row != min_group])
+            if self._prob_method == Model.BCM:
+                self._probs[r][r] = 1.0 - (1.0 * numerator / denominator) * (1.0 - min_high_prob)
+            elif self._prob_method == Model.NM:
+                self._probs[r][r] = min_high_prob
+        for r in self._groups:
+            for c in self._groups:
+                if r == c:
+                    continue
+                H = self._probs[r][r]
+                numerator = self._allocation_ratio[c]
+                denominator = sum([self._allocation_ratio[col] for col in range(len(self._groups)) if col != r])
+                if self._prob_method == Model.BCM:
+                    self._probs[r][c] = (1.0 * numerator / denominator) * (1.0 - H)
+                elif self._prob_method == Model.NM:
+                    self._probs[r][c] = 1.0 * (1.0 - min_high_prob) / (len(self._groups) - 1.0)
+
+    def enroll(self, case, freq_table=None):
+        if freq_table:
+            self.freq_table = freq_table
+        else:
+            self.build_freq_table()
+        new_levels = case['levels']
+        level_count = [[] for v in self._variables]
+        # add the freq_table to level count
+        for row in self.freq_table:
+            for variable, level in enumerate(new_levels):
+                level_count[variable].append(row[variable][level])
+        scores = []
+        adj_count = [sum(self.freq_table[i][0]) / self._allocation_ratio[i] for i in self._groups]
+        max_list = []
+        mx = max(adj_count)
+        for i, cnt in enumerate(adj_count):
+            if cnt == mx:
+                max_list.append(i)
+        for g in self._groups:
+            w_arms = self._arms_weight
+            func = min
+            if g in max_list:
+                func = max
+            scores.append(sum([1.0 * func(self._variables_weight[v], w_arms) * self.get_imbalance_score(level_count[v], g) for v in range(len(self._variables))]))
+        # indices of minimum score values
+        min_indices = self.get_min_ties_index(scores)
+        if len(min_indices) == len(self._groups):
+            # all treatment have same score
+            # so build a probs based on allocation ratio
+            probs = self._allocation_ratio
+            self.pref_group = None
+        else:
+            # indices of prefered treatment(s)
+            # randomly selecting an index
+            pt = self.random.choice(min_indices)
+            self.pref_group = pt
+            probs = self._probs[pt]
+        self.selected_probs = probs
+        case['allocation'] = self.get_rand_biased(probs)
+        self.build_freq_table()
+        return case['allocation']
+
+    def get_rand_biased(self, probs):
+        p = self.random.uniform(0, sum(probs))
+        for i in range(len(probs)):
+            if p < sum(probs[:i+1]):
+                return i
+
+    def get_min_ties_index(self, lst):
+        """get indices of min values of the input list"""
+        L, ret = min(lst), []
+        for idx, item in enumerate(lst):
+            # this pair is equal
+            if abs(item - L) < sys.float_info.epsilon:
+                # so take it
+                ret.append(idx)
+        return ret
+
+    def get_marginal_balance(self, count):
+        numerator = sum([abs(count[i] - count[j]) for i in range(len(count)-1) for j in range(i+1, len(count))])
+        denominator = (len(count)-1) * sum(count)
+        if denominator == 0: return 0.0
+        return (1.0 * numerator) / denominator
+
+    def get_imbalance_score(self, count, group, enroll=True):
+        if enroll: count[group] += 1
+        adj_count = [(1.0 * count[i]) / self._allocation_ratio[i] for i in range(len(count))]
+        if self._distance_measure == Model.MB:
+            ret = self.get_marginal_balance(adj_count)
+        elif self._distance_measure == Model.rng:
+            ret = max(adj_count) - min(adj_count)
+        elif self._distance_measure == Model.var:
+            ret = self.get_variance(adj_count)
+        elif self._distance_measure == Model.SD:
+            ret = self.get_standard_deviation(adj_count)
+        if enroll: count[group] -= 1
+        return ret
+
+    def get_standard_deviation(self, count):
+        return math.sqrt(self.get_variance(count))
+
+    def get_variance(self, count):
+        mean = 1.0 * sum(count) / len(count)
+        sq_terms = sum([(i - mean)**2 for i in count])
+        return 1.0 * sq_terms / (len(count) - 1.0)
+
+    def build_freq_table(self):
+        table = [[[0 for l in v] for v in self._variables] for g in self._groups]
+        self.freq_table = table
+        if not self._allocations: return
+        for case in self._allocations:
+            if not 'allocation' in case: return
+            group = case['allocation']
+            for variable, level in enumerate(case['levels']):
+                table[group][variable][level] += 1
+        self.freq_table = table
diff --git a/minimisation_en_US.txt b/minimisation_en_US.txt
new file mode 100755 (executable)
index 0000000..04da1e3
--- /dev/null
@@ -0,0 +1,4 @@
+In clinical trials, best result will be obtained with controlled designs using randomised enrollment. By randomised we mean the process of subject allocation determines only by pure chance, and there is no selection bias in play. Furthermore by randomisation we aim to have balance over background variables such as demographic and other important prognostic factors, among branches of clinical trial. The rational behind having balanced groups is that if there is a significant deffernces among groups with respect to one or more baseline factors, then the results of clinical trial in unreliable which needs complication data analysis with overal loss of study power and validity. For this reason published articles present at the begininng of result sectiob the overal distribution of baseline factors among trial groups to demonstrate the state of balance among them and valifity of results and conclusions. Every study chooses different subsets of baseline factors to for the purpose of balancing thme among groups, though general factors such as gender, age and weight are included in most trials as the baseline factors. It must be emphasized absence of significant difference among groups with respect to baseline factors does not nessecerily equvalent to balanced groups.
+Unfortunately is spite of random allocation of subject among groups, sometimes significant differences occure among trial groups with respect to baseline factors which bring considerable diturbances in analysis and interpretation of the results. The probability of this phenomenon is not low, though depends on the number of baseline factors, sample and effect sizes. The probability is high in small samples with large effect sizes and many baseline factors.
+A method for preventing this phenomenon is minimisation which largely unknow among clinical researchers. In this method, allocation is not based solely and purely on chance but also the allocation is in the favor of minimising the difference among groups with respect of baseline factors. Therefore the subject in enrolled into a favored group with a more probability that non-favored groups. This process bring increasing balance with continuing enrollment of subjects into arms of trial.
+Unfortunately the whole process of minimisation is calculation intensive and requires a lot of data keeping and maintenance which make manual application of minimisation practically impossible. There are several desktop or online application for implementation of minimisation. Some of these include Minim, MinimPy and QMinim.
diff --git a/minimisation_fa_IR.txt b/minimisation_fa_IR.txt
new file mode 100755 (executable)
index 0000000..a3a1859
--- /dev/null
@@ -0,0 +1,4 @@
+در کارآزمائی های بالینی، بهترین نتیجه از مداخلات کنترل شده و تصادفی بدست می‌آید. منظور از تصادفی سازی یعنی تقسیم سوژه ها بین گروه‌های مختلف مداخله در کارآزمائی بالینی فقط براساس شانس می‌باشد و تورش انتخاب در این پدیده نقشی نداشته ندارد. به علاوه با تصادفی سازی تقسیم سوژه ها بین گروه ها سعی بر این داریم که عوامل مختلف زمینه ای بصورت یک نواخت بین گروه های توزیع گردند و گروه یا گروه هائی از نظر برخی عوامل زمینه ای مانند سن و جنس و غیره با بقیه تفاوت چشمگیری نداشته باشد. چرا که در صورت وقوع تفاوت بارز بین گروه ها از نظر عوامل زمینه ای، آنالیز نتایح حاصله غیر ممکن یا دشوار میگردد، قدرت مطالعه کم میشود و بطور کلی اعتبار کارآزمائی کاهش می یابد. اگر گروه ها از نظر عوامل زمینه مشابه باشند و تنها از نظر نوع مداخله با هم فرق کنند در این صورت هرگونه تفاوت در نتایج کارآزمائی قابل انتساب به نوع مداخله صورت گرفته می باشد. برای همین است که مقالات حاصل از کارآزمائی های بالینی قبل از پرداختن به نتایج اصلی ابتدا شرح در مورد مقایسه عوامل زمینه ای بین گروه های بالینی ارائه می نمایند تا خواننده از مشابهت گروه ها اطمینان حاصل نماید. اینکه این عوامل زمینه ای گزارش شده چه عواملی باید باشند بسته به نوع مداخله ممکن است بسیار متفاوت باشد اگر چه برخی عوامل مانند سن، وزن، قد و جنس معمولا در اکثر مطالعات گزارش میگردند. البته لازم به ذکر است که عدم وجود تفاوت معنی دار بین گروه‌ها از نظر این عوامل زمینه ای لزوما به معنای مشابه بودن آنها نمی باشد.
+متاسفانه علی رغم تقسیم بندی تصادفی سوژه ها بین گروه ها گاهی بصورت کاملا شانسی اختلافات بارزی بین برخی گروه ها از نظر پاره‌ای از عوامل زمینه ای بروز میکند. هر چه تعداد این عوامل زمینه ای مورد مقایسه بیشتر باشد احتمال بروز این رویداد ناخواسته و ناجور بیشتر میشود و سبب اختلال کلی در نتایج کارآزمائی و تفسی آنها میگردد. احتمال بروز این پدیده بستگی تام به اندازه نمونه دارد و در نمونه های کوچک این احتمال بالا است.
+یکی از روشهای جلوگیری از این پدیده استفاده از مینی میزیشن است که متاسفانه هنوز بخوبی در بین محققان علوم بالینی بخوبی شناخته شده نیست. در این روش تقسیم بندی سوژه ها بین گروه ها فقط بر مبنای شانس خالص نیست، بلکه قدری نیز در جهت متعادل سازی گروه ها از نظر عوامل زمینه ای مهم می باشد، بطوری که با پیشرفت کار کارآزمائی و افزوده شدن هرچه بیشتر سوژه ها اختلاف بین عوامل پایه مشخص شده بتدریج کمتر و کمتر شده و گروه ها از نظر عوامل زمینه ای کاملا قابل مقایسه می گردند. در این روش پژوهشگر از ابتدا مشخص میکند که کدام عوامل زمینه ای از نظر یکسان سازی بین گروه ها مهم می باشند. سپس با استفاده از فرایندی بنام مینی میزیشن تقسیم بندی سوژه ها بین گروه ها بیشتر به گروه‌هائی است که سبب بالانس رو به رشد تعادل بیم آنها گردد. البته در این عمل تقسیم بندی قدری نیز شانس دخالت داده میشود. بدین معنی که سوژه ها با احتمال بیشتری به گروهی داده میشوند که تعادل بیشتری ایجاد میکنند.
+متاسفانه فرایند مینی میزیشن پیچیدگی محاسباتی خاصی دارد که مشمول ذخیره داده های قبلی بصورت فراوانی سوژه ها در گروه ها بر حسب لایه های مختلف عوامل زمینه ای می باشد. همچنین پس از ورود هر سوژه به کارآزمائی این داده ها دوباره باید بروزرسانی گشته و برای سوژه بعدی آماده باشند. انجام تمامی این اقدامات بدون استفاده از برنامه های کامپیوتری بسیار مشکل و مستعد خطا می باشد و به همین خاطر برنامه های سرویس های آنلاین گوناگونی تا بحال برای انجام مینی میزیشن ساخته شده است. برخی از این برنامه ها عبارتند از Minim, MinimPy, QMinim و غیره.
diff --git a/minimpy2.pro b/minimpy2.pro
new file mode 100755 (executable)
index 0000000..5e63802
--- /dev/null
@@ -0,0 +1,3 @@
+SOURCES = db.py  enrol_form.py  factor_levels_dialog.py  factor_levels.py  freq_table.py  main_window.py  ui_main_window.py  minim.py  mnd.py  model.py  my_random.py  run_test.py  trial.py config.py config_dialog.py
+TRANSLATIONS = fa_IR.ts
+FORMS = factor_levels_dialog.ui  ui_main_window.ui config_dialog.ui
diff --git a/mnd.py b/mnd.py
new file mode 100755 (executable)
index 0000000..0c20821
--- /dev/null
+++ b/mnd.py
@@ -0,0 +1,162 @@
+import pickle
+
+from trial import Trial
+
+
+class Mnd:
+    def __init__(self, filename):
+        self.filename = filename
+        self.data = None
+
+    def export_data(self, trial_id, db):
+        trial_setting = db.get_trial_setting(trial_id)
+        trial = Trial(trial_setting)
+        subjects = db.get_subjects(trial_id)
+        ss = 30
+        if len(subjects) > 30:
+            ss = len(subjects) + 10
+        ui_pool = list(range(ss))
+        for subject in subjects:
+            if subject[2] in ui_pool:
+                ui_pool.remove(subject[2])
+        while len(ui_pool) > (ss - len(subjects)):
+            ui_pool.pop()
+        treatments = db.read_treatments(trial_id)
+        groups = []
+        for treatment in treatments:
+            group = {'name': treatment[1], 'allocation_ratio': treatment[2]}
+            groups.append(group)
+        factors = db.read_factors(trial_id)
+        variables = []
+        for factor in factors:
+            factor_levels = db.factor_levels(factor[0])
+            titles = [factor_level[1] for factor_level in factor_levels]
+            factor.append([factor_level[0] for factor_level in factor_levels])
+            levels = ','.join(titles)
+            variable = {'name': factor[1], 'weight': factor[2], 'levels': levels}
+            variables.append(variable)
+        allocations = []
+        for subject in subjects:
+            allocation = {'UI': subject[2]}
+            for treatment_index, treatment in enumerate(treatments):
+                if treatment[0] == subject[1]:
+                    allocation['allocation'] = treatment_index
+                    break
+            subject_levels = db.get_subject_levels(subject[0])
+            levels = []
+            for subject_level in subject_levels:
+                    levels.append(self.get_factor_level_index(factors, subject_level))
+            allocation['levels'] = levels
+            allocations.append(allocation)
+        if not db.has_preload(trial_id):
+            initial_freq_table = 0
+        else:
+            preload = db.get_preload(trial_id)
+            initial_freq_table = [[[preload[(t[0], factor[0], level_id)] for level_id in factor[-1]] for factor in factors] for t in treatments]
+        self.data = {'trial_title': trial.title,
+                     'trial_description': trial.title,
+                     'trial_properties': [],
+                     'high_prob': trial.base_prob,
+                     'prob_method': trial.prob_method,
+                     'distance_measure': trial.dist_method,
+                     'ui_pool': ui_pool,
+                     'sample_size': ss,
+                     'groups': groups,
+                     'variables': variables,
+                     'allocations': allocations,
+                     'initial_freq_table': initial_freq_table}
+        fp = open(self.filename, 'wb')
+        pickle.dump(self.data, fp, protocol=2)
+        fp.flush()
+        fp.close()
+        return True
+
+    def get_factor_level_index(self, factors, subject_level):
+        for factor_index, factor in enumerate(factors):
+            if factor[0] == subject_level[0]:
+                for factor_level_index, factor_level in enumerate(factor[-1]):
+                    if factor_level == subject_level[1]:
+                        return factor_level_index
+
+    def data_file_valid(self):
+        try:
+            fp = open(self.filename, 'rb')
+            self.data = pickle.load(fp)
+            self.trial_title = self.data['trial_title']
+            self.allocations = self.data['allocations']
+            self.high_prob = self.data['high_prob']
+            self.initial_freq_table = self.data['initial_freq_table']
+            self.prob_method = self.data['prob_method']
+            self.distance_measure = self.data['distance_measure']
+            self.groups = self.data['groups']
+            self.variables = self.data['variables']
+            return True
+        except:
+            return False
+
+    def get_detail(self):
+        det = []
+        det.append('trial_title: {}'.format(self.trial_title))
+        treatments = []
+        for group in self.groups:
+            treatments.append(group['name'])
+        det.append('Treatments: ({})'.format(', '.join(treatments)))
+        factors = []
+        for variable in self.variables:
+            factors.append('{}({})'.format(variable['name'], variable['levels']))
+        det.append('Factors: [{}]'.format(', '.join(factors)))
+        det.append('{} Subjects'.format(len(self.allocations)))
+        if self.initial_freq_table:
+            det.append('Trial has preload')
+        return '\n'.join(det)
+
+    def import_data(self, db):
+        trial_id = db.insert_trial(self.trial_title)
+        treatments = []
+        for group in self.groups:
+            treatment_id = db.insert_treatment(trial_id, group['name'])
+            treatments.append(treatment_id)
+        factors = []
+        for variable in self.variables:
+            factor_id = db.insert_factor(trial_id, variable['name'])
+            factor = [factor_id]
+            level_ids = []
+            levels = variable['levels'].split(',')
+            for level in levels:
+                level_id = db.insert_level(trial_id, factor_id, level)
+                level_ids.append(level_id)
+            factor.append(level_ids)
+            factors.append(factor)
+        all_numeric = True
+        for allocation in self.allocations:
+            if str(allocation['UI']).isnumeric():
+                continue
+            all_numeric = False
+            break
+        id_value = 0
+        for allocation in self.allocations:
+            treatment_index = allocation['allocation']
+            treatment_id = treatments[treatment_index]
+            identifier_value = allocation['UI'] if all_numeric else id_value
+            if not all_numeric:
+                id_value += 1
+            subject_id = db.insert_subject(identifier_value, treatment_id, trial_id)
+            levels = allocation['levels']
+            subject_levels = []
+            for factor_index, level_index in enumerate(levels):
+                factor_id = factors[factor_index][0]
+                level_id = factors[factor_index][1][level_index]
+                subject_levels.append((factor_id, level_id))
+            db.insert_subject_levels(trial_id, subject_id, subject_levels)
+        if not self.initial_freq_table:
+            return trial_id
+        preload = {}
+        for treatment_index, row in enumerate(self.initial_freq_table):
+            t = treatments[treatment_index]
+            for factor_index, factor in enumerate(row):
+                f = factors[factor_index][0]
+                for level_index, count in enumerate(factor):
+                    lv = factors[factor_index][1][level_index]
+                    preload[(t, f, lv)] = count
+        db.save_preload(trial_id, preload)
+        return trial_id
\ No newline at end of file
diff --git a/model.py b/model.py
new file mode 100755 (executable)
index 0000000..922087c
--- /dev/null
+++ b/model.py
@@ -0,0 +1,24 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+class Model(object):
+    BCM = 0
+    NM = 1
+
+    MB = 0
+    rng = 1
+    SD = 2
+    var = 3
+
+    def __init__(self):
+        self.allocations = None
+        self.prob_method = self.BCM
+        self.distance_measure = self.MB
+        self.high_prob = 0.7
+        self.min_group = 0
+        self.groups = [0, 1, 2]
+        self.variables = [[0, 1], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
+        self.variables_weight = [1, 1]
+        self.allocation_ratio = [1, 2, 3]
+        self.arms_weight = 1.0
+
diff --git a/my_random.py b/my_random.py
new file mode 100755 (executable)
index 0000000..794d3c3
--- /dev/null
@@ -0,0 +1,46 @@
+import random
+import secrets
+
+SECRETS = 'secrets'
+RANDOM  = 'random'
+
+
+class Random:
+    __instance = None
+
+    @staticmethod
+    def get_instance(name):
+        if Random.__instance is None:
+            Random(name)
+        return Random.__instance
+
+    def __init__(self, name):
+        if Random.__instance is not None:
+            raise Exception("This class is a singleton!")
+        else:
+            if name == RANDOM:
+                self.engine = random
+            else:
+                self.engine = secrets
+            self.name = name
+            Random.__instance = self
+
+    def seed(self, seed):
+        if self.name == SECRETS:
+            return
+        self.engine.seed(seed)
+
+    def randint(self, a, b):
+        a, b = int(a), int(b)
+        if self.name == RANDOM:
+            return self.engine.randint(a, b)
+        return a + self.engine.randbelow(b - a + 1)
+
+    def choice(self, seq):
+        return self.engine.choice(seq)
+
+    def uniform(self, a, b):
+        a, b = int(a), int(b)
+        if self.name == RANDOM:
+            return self.engine.uniform(a, b)
+        return self.engine.SystemRandom().uniform(a, b)
\ No newline at end of file
diff --git a/run_test.py b/run_test.py
new file mode 100755 (executable)
index 0000000..0487797
--- /dev/null
@@ -0,0 +1,41 @@
+from statistics import NormalDist
+import sys
+
+
+class RunTest:
+    def __init__(self, sequence, v1, v2):
+        self.sequence = sequence
+        self.v1, self.v2 = v1, v2
+
+    def get_p(self):
+        n1 = self.sequence.count(self.v1)
+        n2 = self.sequence.count(self.v2)
+        if n1 == 0 or n2 == 0:
+            return None, None
+        r_bar = 1.0 + (2.0 * n1 * n2) / (n1 + n2)
+        sd = (2 * n1 * n2 * (2 * n1 * n2 - n1 -n2)) / (((n1 + n2) ** 2) * (n1 + n2 - 1))
+        r = self.get_runs()
+        z = (r - r_bar) / sd
+        p = 2 * (1 - NormalDist().cdf(z))
+        if p > 1.0:
+            p = 1.0
+        return z, p
+
+    def get_runs(self):
+        prev_value = None
+        i = 0
+        runs = 0
+        while i < len(self.sequence):
+            cur_vale = self.sequence[i]
+            if prev_value != cur_vale:
+                runs += 1
+            i += 1
+            prev_value = cur_vale
+        return runs
+
+
+def get_random_sequence(min, max, random):
+    mn = 10**min
+    mx = 10**max
+    n = random.randint(mn, mx)
+    return bin(n)[2:]
diff --git a/trial.py b/trial.py
new file mode 100755 (executable)
index 0000000..215fafb
--- /dev/null
+++ b/trial.py
@@ -0,0 +1,76 @@
+import math
+
+BIASED_COIN = 0
+NAIVE = 1
+MARGINAL_BALANCE = 0
+RANGE = 1
+SD = 2
+VARIANCE = 3
+NUMERIC = 0
+ALPHA = 1
+MIX = 2
+SEQUENTIAL = 0
+RANDOM = 1
+LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+NUMBER_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+
+class Trial:
+    def __init__(self, setting):
+        self.id = setting.value('id')
+        self.title = setting.value('title')
+        self.code = setting.value('code')
+        self.created = setting.value('created')
+        self.modified = setting.value('modified')
+        self.prob_method = setting.value('prob_method')
+        self.base_prob = setting.value('base_prob')
+        self.dist_method = setting.value('dist_method')
+        self.identifier_type = setting.value('identifier_type')
+        self.identifier_order = setting.value('identifier_order')
+        self.identifier_length = setting.value('identifier_length')
+        self.recycle_ids = setting.value('recycle_ids')
+        self.new_subject_random = setting.value('new_subject_random')
+        self.arms_weight = setting.value('arms_weight')
+
+    def get_max_sample_size(self):
+        base = 10
+        if self.identifier_type == ALPHA:
+            base = 26
+        elif self.identifier_type == MIX:
+            base = 36
+        ss = math.pow(base, self.identifier_length)
+        return ss, self.format_unit(ss)
+
+    def format_unit(self, size):
+        power = 1000
+        n = 0
+        power_labels = {0: '', 1: ' K', 2: ' M', 3: ' G', 4: ' T', 5: ' P'}
+        while size > power:
+            size //= power
+            n += 1
+        size = int(size)
+        return '{}{}'.format(size, power_labels[n])
+
+    def format_subject_identifier(self, value):
+        length = int(self.identifier_length)
+        if self.identifier_type == NUMERIC:
+            return str(value).zfill(length)
+        base = [10, 26, 36][self.identifier_type]
+        z = ['0', 'A', '0'][self.identifier_type]
+        lst = number_to_base(value, base)
+        w = ['', LETTERS, NUMBER_LETTERS][self.identifier_type]
+        lst = ''.join(list(map(lambda x: w[x], lst)))
+        lst = lst.zfill(length)
+        lst = lst.replace('0', z)
+        return lst
+
+
+def number_to_base(n, b):
+    if n == 0:
+        return [0]
+    digits = []
+    while n:
+        digits.append(int(n % b))
+        n //= b
+    return digits[::-1]
+
+
diff --git a/ui_about_minimisation.py b/ui_about_minimisation.py
new file mode 100755 (executable)
index 0000000..e56fdba
--- /dev/null
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_about_minimisation.ui'
+##
+## Created by: Qt User Interface Compiler version 5.14.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
+    QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
+from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
+    QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
+    QPixmap, QRadialGradient)
+from PySide2.QtWidgets import *
+
+
+class Ui_AboutMinimisationDialog(object):
+    def setupUi(self, AboutMinimisationDialog):
+        if not AboutMinimisationDialog.objectName():
+            AboutMinimisationDialog.setObjectName(u"AboutMinimisationDialog")
+        AboutMinimisationDialog.resize(400, 300)
+        self.verticalLayout = QVBoxLayout(AboutMinimisationDialog)
+        self.verticalLayout.setObjectName(u"verticalLayout")
+        self.minimTextEdit = QPlainTextEdit(AboutMinimisationDialog)
+        self.minimTextEdit.setObjectName(u"minimTextEdit")
+        self.minimTextEdit.setReadOnly(True)
+
+        self.verticalLayout.addWidget(self.minimTextEdit)
+
+        self.closePushButton = QPushButton(AboutMinimisationDialog)
+        self.closePushButton.setObjectName(u"closePushButton")
+
+        self.verticalLayout.addWidget(self.closePushButton)
+
+
+        self.retranslateUi(AboutMinimisationDialog)
+
+        QMetaObject.connectSlotsByName(AboutMinimisationDialog)
+    # setupUi
+
+    def retranslateUi(self, AboutMinimisationDialog):
+        self.minimTextEdit.setPlainText("")
+        self.closePushButton.setText(QCoreApplication.translate("AboutMinimisationDialog", u"Close", None))
+        pass
+    # retranslateUi
+
diff --git a/ui_about_minimisation.ui b/ui_about_minimisation.ui
new file mode 100755 (executable)
index 0000000..e6dbee5
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AboutMinimisationDialog</class>
+ <widget class="QDialog" name="AboutMinimisationDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QPlainTextEdit" name="minimTextEdit">
+     <property name="readOnly">
+      <bool>true</bool>
+     </property>
+     <property name="plainText">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QPushButton" name="closePushButton">
+     <property name="text">
+      <string>Close</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui_main_window.py b/ui_main_window.py
new file mode 100755 (executable)
index 0000000..36e5899
--- /dev/null
@@ -0,0 +1,579 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_main_window.ui'
+##
+## Created by: Qt User Interface Compiler version 5.14.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
+    QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
+from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
+    QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
+    QPixmap, QRadialGradient)
+from PySide2.QtWidgets import *
+
+
+class Ui_MainWindow(object):
+    def setupUi(self, MainWindow):
+        if not MainWindow.objectName():
+            MainWindow.setObjectName(u"MainWindow")
+        MainWindow.resize(802, 600)
+        self.actionQuit = QAction(MainWindow)
+        self.actionQuit.setObjectName(u"actionQuit")
+        self.actionNew = QAction(MainWindow)
+        self.actionNew.setObjectName(u"actionNew")
+        self.actionDelete = QAction(MainWindow)
+        self.actionDelete.setObjectName(u"actionDelete")
+        self.actionSave = QAction(MainWindow)
+        self.actionSave.setObjectName(u"actionSave")
+        self.actionLevels = QAction(MainWindow)
+        self.actionLevels.setObjectName(u"actionLevels")
+        self.actionHelp = QAction(MainWindow)
+        self.actionHelp.setObjectName(u"actionHelp")
+        self.actionImport = QAction(MainWindow)
+        self.actionImport.setObjectName(u"actionImport")
+        self.actionExport = QAction(MainWindow)
+        self.actionExport.setObjectName(u"actionExport")
+        self.actionConfig = QAction(MainWindow)
+        self.actionConfig.setObjectName(u"actionConfig")
+        self.actionAbout_Minimisation = QAction(MainWindow)
+        self.actionAbout_Minimisation.setObjectName(u"actionAbout_Minimisation")
+        self.actionAbout_MinimPy2 = QAction(MainWindow)
+        self.actionAbout_MinimPy2.setObjectName(u"actionAbout_MinimPy2")
+        self.actionAbout_Qt = QAction(MainWindow)
+        self.actionAbout_Qt.setObjectName(u"actionAbout_Qt")
+        self.centralwidget = QWidget(MainWindow)
+        self.centralwidget.setObjectName(u"centralwidget")
+        self.verticalLayout_2 = QVBoxLayout(self.centralwidget)
+        self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+        self.tabWidget = QTabWidget(self.centralwidget)
+        self.tabWidget.setObjectName(u"tabWidget")
+        self.tab = QWidget()
+        self.tab.setObjectName(u"tab")
+        self.verticalLayout = QVBoxLayout(self.tab)
+        self.verticalLayout.setObjectName(u"verticalLayout")
+        self.tialsTableWidget = QTableWidget(self.tab)
+        self.tialsTableWidget.setObjectName(u"tialsTableWidget")
+
+        self.verticalLayout.addWidget(self.tialsTableWidget)
+
+        self.tabWidget.addTab(self.tab, "")
+        self.tab_2 = QWidget()
+        self.tab_2.setObjectName(u"tab_2")
+        self.verticalLayout_3 = QVBoxLayout(self.tab_2)
+        self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+        self.trialFormLayout = QFormLayout()
+        self.trialFormLayout.setObjectName(u"trialFormLayout")
+        self.trialCreatedLabel = QLabel(self.tab_2)
+        self.trialCreatedLabel.setObjectName(u"trialCreatedLabel")
+        self.trialCreatedLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(0, QFormLayout.LabelRole, self.trialCreatedLabel)
+
+        self.trialCreatedValueLabel = QLabel(self.tab_2)
+        self.trialCreatedValueLabel.setObjectName(u"trialCreatedValueLabel")
+
+        self.trialFormLayout.setWidget(0, QFormLayout.FieldRole, self.trialCreatedValueLabel)
+
+        self.trialModifiedLabel = QLabel(self.tab_2)
+        self.trialModifiedLabel.setObjectName(u"trialModifiedLabel")
+        self.trialModifiedLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(1, QFormLayout.LabelRole, self.trialModifiedLabel)
+
+        self.trialModifiedValueLabel = QLabel(self.tab_2)
+        self.trialModifiedValueLabel.setObjectName(u"trialModifiedValueLabel")
+
+        self.trialFormLayout.setWidget(1, QFormLayout.FieldRole, self.trialModifiedValueLabel)
+
+        self.trialTitleLabel = QLabel(self.tab_2)
+        self.trialTitleLabel.setObjectName(u"trialTitleLabel")
+        self.trialTitleLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(2, QFormLayout.LabelRole, self.trialTitleLabel)
+
+        self.trialTitleLineEdit = QLineEdit(self.tab_2)
+        self.trialTitleLineEdit.setObjectName(u"trialTitleLineEdit")
+
+        self.trialFormLayout.setWidget(2, QFormLayout.FieldRole, self.trialTitleLineEdit)
+
+        self.trialCodeLabel = QLabel(self.tab_2)
+        self.trialCodeLabel.setObjectName(u"trialCodeLabel")
+        self.trialCodeLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(3, QFormLayout.LabelRole, self.trialCodeLabel)
+
+        self.trialCodeLineEdit = QLineEdit(self.tab_2)
+        self.trialCodeLineEdit.setObjectName(u"trialCodeLineEdit")
+
+        self.trialFormLayout.setWidget(3, QFormLayout.FieldRole, self.trialCodeLineEdit)
+
+        self.trialProbmethodLabel = QLabel(self.tab_2)
+        self.trialProbmethodLabel.setObjectName(u"trialProbmethodLabel")
+        self.trialProbmethodLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(4, QFormLayout.LabelRole, self.trialProbmethodLabel)
+
+        self.trialProbMethodComboBox = QComboBox(self.tab_2)
+        self.trialProbMethodComboBox.addItem("")
+        self.trialProbMethodComboBox.addItem("")
+        self.trialProbMethodComboBox.setObjectName(u"trialProbMethodComboBox")
+
+        self.trialFormLayout.setWidget(4, QFormLayout.FieldRole, self.trialProbMethodComboBox)
+
+        self.trialBaseProbabilityLabel = QLabel(self.tab_2)
+        self.trialBaseProbabilityLabel.setObjectName(u"trialBaseProbabilityLabel")
+        self.trialBaseProbabilityLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(5, QFormLayout.LabelRole, self.trialBaseProbabilityLabel)
+
+        self.horizontalLayout_6 = QHBoxLayout()
+        self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
+        self.baseProbLabel = QLabel(self.tab_2)
+        self.baseProbLabel.setObjectName(u"baseProbLabel")
+        self.baseProbLabel.setLineWidth(1)
+
+        self.horizontalLayout_6.addWidget(self.baseProbLabel)
+
+        self.trialBaseProbabilitySlider = QSlider(self.tab_2)
+        self.trialBaseProbabilitySlider.setObjectName(u"trialBaseProbabilitySlider")
+        self.trialBaseProbabilitySlider.setMinimum(10)
+        self.trialBaseProbabilitySlider.setOrientation(Qt.Horizontal)
+
+        self.horizontalLayout_6.addWidget(self.trialBaseProbabilitySlider)
+
+
+        self.trialFormLayout.setLayout(5, QFormLayout.FieldRole, self.horizontalLayout_6)
+
+        self.trialDistanceMethodLabel = QLabel(self.tab_2)
+        self.trialDistanceMethodLabel.setObjectName(u"trialDistanceMethodLabel")
+        self.trialDistanceMethodLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(6, QFormLayout.LabelRole, self.trialDistanceMethodLabel)
+
+        self.trialDistanceMethodComboBox = QComboBox(self.tab_2)
+        self.trialDistanceMethodComboBox.addItem("")
+        self.trialDistanceMethodComboBox.addItem("")
+        self.trialDistanceMethodComboBox.addItem("")
+        self.trialDistanceMethodComboBox.addItem("")
+        self.trialDistanceMethodComboBox.setObjectName(u"trialDistanceMethodComboBox")
+
+        self.trialFormLayout.setWidget(6, QFormLayout.FieldRole, self.trialDistanceMethodComboBox)
+
+        self.trialIdentifierTypeLabel = QLabel(self.tab_2)
+        self.trialIdentifierTypeLabel.setObjectName(u"trialIdentifierTypeLabel")
+        self.trialIdentifierTypeLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(7, QFormLayout.LabelRole, self.trialIdentifierTypeLabel)
+
+        self.trialIdentifierTypeComboBox = QComboBox(self.tab_2)
+        self.trialIdentifierTypeComboBox.addItem("")
+        self.trialIdentifierTypeComboBox.addItem("")
+        self.trialIdentifierTypeComboBox.addItem("")
+        self.trialIdentifierTypeComboBox.setObjectName(u"trialIdentifierTypeComboBox")
+
+        self.trialFormLayout.setWidget(7, QFormLayout.FieldRole, self.trialIdentifierTypeComboBox)
+
+        self.trialIdentifierOrderLabel = QLabel(self.tab_2)
+        self.trialIdentifierOrderLabel.setObjectName(u"trialIdentifierOrderLabel")
+        self.trialIdentifierOrderLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(8, QFormLayout.LabelRole, self.trialIdentifierOrderLabel)
+
+        self.trialIentifierOrderComboBox = QComboBox(self.tab_2)
+        self.trialIentifierOrderComboBox.addItem("")
+        self.trialIentifierOrderComboBox.addItem("")
+        self.trialIentifierOrderComboBox.setObjectName(u"trialIentifierOrderComboBox")
+
+        self.trialFormLayout.setWidget(8, QFormLayout.FieldRole, self.trialIentifierOrderComboBox)
+
+        self.trialIdentifierLengthLabel = QLabel(self.tab_2)
+        self.trialIdentifierLengthLabel.setObjectName(u"trialIdentifierLengthLabel")
+        self.trialIdentifierLengthLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(9, QFormLayout.LabelRole, self.trialIdentifierLengthLabel)
+
+        self.horizontalLayout_5 = QHBoxLayout()
+        self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+        self.trialIdentifierLengthSpinBox = QSpinBox(self.tab_2)
+        self.trialIdentifierLengthSpinBox.setObjectName(u"trialIdentifierLengthSpinBox")
+        self.trialIdentifierLengthSpinBox.setMinimum(3)
+        self.trialIdentifierLengthSpinBox.setMaximum(10)
+
+        self.horizontalLayout_5.addWidget(self.trialIdentifierLengthSpinBox)
+
+        self.max_sample_size_label = QLabel(self.tab_2)
+        self.max_sample_size_label.setObjectName(u"max_sample_size_label")
+
+        self.horizontalLayout_5.addWidget(self.max_sample_size_label)
+
+
+        self.trialFormLayout.setLayout(9, QFormLayout.FieldRole, self.horizontalLayout_5)
+
+        self.trialRecycleIdsLabel = QLabel(self.tab_2)
+        self.trialRecycleIdsLabel.setObjectName(u"trialRecycleIdsLabel")
+        self.trialRecycleIdsLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(10, QFormLayout.LabelRole, self.trialRecycleIdsLabel)
+
+        self.trialRecycleIdsCheckBox = QCheckBox(self.tab_2)
+        self.trialRecycleIdsCheckBox.setObjectName(u"trialRecycleIdsCheckBox")
+
+        self.trialFormLayout.setWidget(10, QFormLayout.FieldRole, self.trialRecycleIdsCheckBox)
+
+        self.trialNewSubjectRandomLabel = QLabel(self.tab_2)
+        self.trialNewSubjectRandomLabel.setObjectName(u"trialNewSubjectRandomLabel")
+        self.trialNewSubjectRandomLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(11, QFormLayout.LabelRole, self.trialNewSubjectRandomLabel)
+
+        self.trialNewSubjectRandomCheckBox = QCheckBox(self.tab_2)
+        self.trialNewSubjectRandomCheckBox.setObjectName(u"trialNewSubjectRandomCheckBox")
+
+        self.trialFormLayout.setWidget(11, QFormLayout.FieldRole, self.trialNewSubjectRandomCheckBox)
+
+        self.trialArmsWeightLabel = QLabel(self.tab_2)
+        self.trialArmsWeightLabel.setObjectName(u"trialArmsWeightLabel")
+        self.trialArmsWeightLabel.setStyleSheet(u"color: blue;")
+
+        self.trialFormLayout.setWidget(12, QFormLayout.LabelRole, self.trialArmsWeightLabel)
+
+        self.horizontalLayout_9 = QHBoxLayout()
+        self.horizontalLayout_9.setObjectName(u"horizontalLayout_9")
+        self.armWeightLabel = QLabel(self.tab_2)
+        self.armWeightLabel.setObjectName(u"armWeightLabel")
+
+        self.horizontalLayout_9.addWidget(self.armWeightLabel)
+
+        self.trialArmsWeightDoubleSlider = QSlider(self.tab_2)
+        self.trialArmsWeightDoubleSlider.setObjectName(u"trialArmsWeightDoubleSlider")
+        self.trialArmsWeightDoubleSlider.setMinimum(1)
+        self.trialArmsWeightDoubleSlider.setOrientation(Qt.Horizontal)
+
+        self.horizontalLayout_9.addWidget(self.trialArmsWeightDoubleSlider)
+
+
+        self.trialFormLayout.setLayout(12, QFormLayout.FieldRole, self.horizontalLayout_9)
+
+
+        self.verticalLayout_3.addLayout(self.trialFormLayout)
+
+        self.tabWidget.addTab(self.tab_2, "")
+        self.tab_3 = QWidget()
+        self.tab_3.setObjectName(u"tab_3")
+        self.verticalLayout_4 = QVBoxLayout(self.tab_3)
+        self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+        self.treatmentTableWidget = QTableWidget(self.tab_3)
+        self.treatmentTableWidget.setObjectName(u"treatmentTableWidget")
+
+        self.verticalLayout_4.addWidget(self.treatmentTableWidget)
+
+        self.tabWidget.addTab(self.tab_3, "")
+        self.tab_4 = QWidget()
+        self.tab_4.setObjectName(u"tab_4")
+        self.verticalLayout_5 = QVBoxLayout(self.tab_4)
+        self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+        self.factorTableWidget = QTableWidget(self.tab_4)
+        self.factorTableWidget.setObjectName(u"factorTableWidget")
+
+        self.verticalLayout_5.addWidget(self.factorTableWidget)
+
+        self.tabWidget.addTab(self.tab_4, "")
+        self.tab_5 = QWidget()
+        self.tab_5.setObjectName(u"tab_5")
+        self.verticalLayout_6 = QVBoxLayout(self.tab_5)
+        self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+        self.horizontalLayout = QHBoxLayout()
+        self.horizontalLayout.setObjectName(u"horizontalLayout")
+        self.frequenciesCheckBox = QCheckBox(self.tab_5)
+        self.frequenciesCheckBox.setObjectName(u"frequenciesCheckBox")
+        self.frequenciesCheckBox.setChecked(False)
+
+        self.horizontalLayout.addWidget(self.frequenciesCheckBox)
+
+        self.preloadCheckBox = QCheckBox(self.tab_5)
+        self.preloadCheckBox.setObjectName(u"preloadCheckBox")
+
+        self.horizontalLayout.addWidget(self.preloadCheckBox)
+
+        self.editPreloadCheckBox = QCheckBox(self.tab_5)
+        self.editPreloadCheckBox.setObjectName(u"editPreloadCheckBox")
+        self.editPreloadCheckBox.setEnabled(False)
+
+        self.horizontalLayout.addWidget(self.editPreloadCheckBox)
+
+        self.convertPreloadButton = QPushButton(self.tab_5)
+        self.convertPreloadButton.setObjectName(u"convertPreloadButton")
+
+        self.horizontalLayout.addWidget(self.convertPreloadButton)
+
+        self.convertWarningCheckBox = QCheckBox(self.tab_5)
+        self.convertWarningCheckBox.setObjectName(u"convertWarningCheckBox")
+
+        self.horizontalLayout.addWidget(self.convertWarningCheckBox)
+
+
+        self.verticalLayout_6.addLayout(self.horizontalLayout)
+
+        self.scrollArea = QScrollArea(self.tab_5)
+        self.scrollArea.setObjectName(u"scrollArea")
+        self.scrollArea.setWidgetResizable(True)
+        self.scrollAreaWidgetContents = QWidget()
+        self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
+        self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 760, 422))
+        self.horizontalLayout_2 = QHBoxLayout(self.scrollAreaWidgetContents)
+        self.horizontalLayout_2.setSpacing(0)
+        self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+        self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
+        self.freqGridLayout = QGridLayout()
+        self.freqGridLayout.setSpacing(1)
+        self.freqGridLayout.setObjectName(u"freqGridLayout")
+
+        self.horizontalLayout_2.addLayout(self.freqGridLayout)
+
+        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
+
+        self.verticalLayout_6.addWidget(self.scrollArea)
+
+        self.tabWidget.addTab(self.tab_5, "")
+        self.tab_6 = QWidget()
+        self.tab_6.setObjectName(u"tab_6")
+        self.verticalLayout_7 = QVBoxLayout(self.tab_6)
+        self.verticalLayout_7.setObjectName(u"verticalLayout_7")
+        self.subjectProgressBar = QProgressBar(self.tab_6)
+        self.subjectProgressBar.setObjectName(u"subjectProgressBar")
+        self.subjectProgressBar.setValue(0)
+
+        self.verticalLayout_7.addWidget(self.subjectProgressBar)
+
+        self.subjectTableWidget = QTableWidget(self.tab_6)
+        self.subjectTableWidget.setObjectName(u"subjectTableWidget")
+        self.subjectTableWidget.setSortingEnabled(True)
+
+        self.verticalLayout_7.addWidget(self.subjectTableWidget)
+
+        self.tabWidget.addTab(self.tab_6, "")
+        self.tab_7 = QWidget()
+        self.tab_7.setObjectName(u"tab_7")
+        self.verticalLayout_8 = QVBoxLayout(self.tab_7)
+        self.verticalLayout_8.setObjectName(u"verticalLayout_8")
+        self.horizontalLayout_3 = QHBoxLayout()
+        self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+        self.mean_balance_label = QLabel(self.tab_7)
+        self.mean_balance_label.setObjectName(u"mean_balance_label")
+
+        self.horizontalLayout_3.addWidget(self.mean_balance_label)
+
+        self.balanceProgressBar = QProgressBar(self.tab_7)
+        self.balanceProgressBar.setObjectName(u"balanceProgressBar")
+        self.balanceProgressBar.setMaximum(100)
+        self.balanceProgressBar.setValue(0)
+        self.balanceProgressBar.setTextVisible(True)
+
+        self.horizontalLayout_3.addWidget(self.balanceProgressBar)
+
+
+        self.verticalLayout_8.addLayout(self.horizontalLayout_3)
+
+        self.balanceTreeWidget = QTreeWidget(self.tab_7)
+        __qtreewidgetitem = QTreeWidgetItem()
+        __qtreewidgetitem.setText(0, u"1");
+        self.balanceTreeWidget.setHeaderItem(__qtreewidgetitem)
+        self.balanceTreeWidget.setObjectName(u"balanceTreeWidget")
+
+        self.verticalLayout_8.addWidget(self.balanceTreeWidget)
+
+        self.horizontalLayout_4 = QHBoxLayout()
+        self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+        self.randomness_label = QLabel(self.tab_7)
+        self.randomness_label.setObjectName(u"randomness_label")
+
+        self.horizontalLayout_4.addWidget(self.randomness_label)
+
+        self.randomnessProgressBar = QProgressBar(self.tab_7)
+        self.randomnessProgressBar.setObjectName(u"randomnessProgressBar")
+        self.randomnessProgressBar.setValue(0)
+
+        self.horizontalLayout_4.addWidget(self.randomnessProgressBar)
+
+
+        self.verticalLayout_8.addLayout(self.horizontalLayout_4)
+
+        self.tabWidget.addTab(self.tab_7, "")
+        self.tab_8 = QWidget()
+        self.tab_8.setObjectName(u"tab_8")
+        self.verticalLayout_9 = QVBoxLayout(self.tab_8)
+        self.verticalLayout_9.setObjectName(u"verticalLayout_9")
+        self.textEdit = QTextEdit(self.tab_8)
+        self.textEdit.setObjectName(u"textEdit")
+
+        self.verticalLayout_9.addWidget(self.textEdit)
+
+        self.showAtStartCheckBox = QCheckBox(self.tab_8)
+        self.showAtStartCheckBox.setObjectName(u"showAtStartCheckBox")
+
+        self.verticalLayout_9.addWidget(self.showAtStartCheckBox)
+
+        self.tabWidget.addTab(self.tab_8, "")
+
+        self.verticalLayout_2.addWidget(self.tabWidget)
+
+        MainWindow.setCentralWidget(self.centralwidget)
+        self.menubar = QMenuBar(MainWindow)
+        self.menubar.setObjectName(u"menubar")
+        self.menubar.setGeometry(QRect(0, 0, 802, 22))
+        self.menuFile = QMenu(self.menubar)
+        self.menuFile.setObjectName(u"menuFile")
+        self.menuHelp = QMenu(self.menubar)
+        self.menuHelp.setObjectName(u"menuHelp")
+        MainWindow.setMenuBar(self.menubar)
+        self.statusbar = QStatusBar(MainWindow)
+        self.statusbar.setObjectName(u"statusbar")
+        MainWindow.setStatusBar(self.statusbar)
+        self.toolBar = QToolBar(MainWindow)
+        self.toolBar.setObjectName(u"toolBar")
+        self.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
+        MainWindow.addToolBar(Qt.TopToolBarArea, self.toolBar)
+
+        self.menubar.addAction(self.menuFile.menuAction())
+        self.menubar.addAction(self.menuHelp.menuAction())
+        self.menuFile.addSeparator()
+        self.menuFile.addAction(self.actionImport)
+        self.menuFile.addAction(self.actionExport)
+        self.menuFile.addSeparator()
+        self.menuFile.addAction(self.actionConfig)
+        self.menuFile.addAction(self.actionQuit)
+        self.menuHelp.addAction(self.actionHelp)
+        self.menuHelp.addSeparator()
+        self.menuHelp.addAction(self.actionAbout_Minimisation)
+        self.menuHelp.addAction(self.actionAbout_MinimPy2)
+        self.menuHelp.addAction(self.actionAbout_Qt)
+        self.toolBar.addAction(self.actionNew)
+        self.toolBar.addAction(self.actionDelete)
+        self.toolBar.addAction(self.actionLevels)
+        self.toolBar.addAction(self.actionSave)
+        self.toolBar.addAction(self.actionConfig)
+        self.toolBar.addAction(self.actionHelp)
+        self.toolBar.addAction(self.actionAbout_MinimPy2)
+        self.toolBar.addAction(self.actionQuit)
+
+        self.retranslateUi(MainWindow)
+
+        self.tabWidget.setCurrentIndex(7)
+
+
+        QMetaObject.connectSlotsByName(MainWindow)
+    # setupUi
+
+    def retranslateUi(self, MainWindow):
+        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
+        self.actionQuit.setText(QCoreApplication.translate("MainWindow", u"Exit", None))
+        self.actionNew.setText(QCoreApplication.translate("MainWindow", u"Add", None))
+        self.actionDelete.setText(QCoreApplication.translate("MainWindow", u"Delete", None))
+        self.actionSave.setText(QCoreApplication.translate("MainWindow", u"Save", None))
+#if QT_CONFIG(tooltip)
+        self.actionSave.setToolTip(QCoreApplication.translate("MainWindow", u"Save", None))
+#endif // QT_CONFIG(tooltip)
+        self.actionLevels.setText(QCoreApplication.translate("MainWindow", u"Levels", None))
+#if QT_CONFIG(tooltip)
+        self.actionLevels.setToolTip(QCoreApplication.translate("MainWindow", u"manage factor levels", None))
+#endif // QT_CONFIG(tooltip)
+        self.actionHelp.setText(QCoreApplication.translate("MainWindow", u"Help", None))
+#if QT_CONFIG(tooltip)
+        self.actionHelp.setToolTip(QCoreApplication.translate("MainWindow", u"displays help", None))
+#endif // QT_CONFIG(tooltip)
+        self.actionImport.setText(QCoreApplication.translate("MainWindow", u"Import", None))
+        self.actionExport.setText(QCoreApplication.translate("MainWindow", u"Export", None))
+        self.actionConfig.setText(QCoreApplication.translate("MainWindow", u"Config", None))
+#if QT_CONFIG(tooltip)
+        self.actionConfig.setToolTip(QCoreApplication.translate("MainWindow", u"Application config", None))
+#endif // QT_CONFIG(tooltip)
+        self.actionAbout_Minimisation.setText(QCoreApplication.translate("MainWindow", u"What is Minimisation", None))
+        self.actionAbout_MinimPy2.setText(QCoreApplication.translate("MainWindow", u"About", None))
+        self.actionAbout_Qt.setText(QCoreApplication.translate("MainWindow", u"About Qt", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QCoreApplication.translate("MainWindow", u"Trials", None))
+        self.trialCreatedLabel.setText(QCoreApplication.translate("MainWindow", u"Created", None))
+        self.trialModifiedLabel.setText(QCoreApplication.translate("MainWindow", u"Modified", None))
+        self.trialTitleLabel.setText(QCoreApplication.translate("MainWindow", u"Trial title", None))
+        self.trialCodeLabel.setText(QCoreApplication.translate("MainWindow", u"Trial code", None))
+        self.trialProbmethodLabel.setText(QCoreApplication.translate("MainWindow", u"Probability method", None))
+        self.trialProbMethodComboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Biased coin", None))
+        self.trialProbMethodComboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Naive", None))
+
+        self.trialBaseProbabilityLabel.setText(QCoreApplication.translate("MainWindow", u"Base probability", None))
+        self.baseProbLabel.setText(QCoreApplication.translate("MainWindow", u"0.70", None))
+        self.trialDistanceMethodLabel.setText(QCoreApplication.translate("MainWindow", u"Distance method", None))
+        self.trialDistanceMethodComboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Marginal balance", None))
+        self.trialDistanceMethodComboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Range", None))
+        self.trialDistanceMethodComboBox.setItemText(2, QCoreApplication.translate("MainWindow", u"Standard deviation", None))
+        self.trialDistanceMethodComboBox.setItemText(3, QCoreApplication.translate("MainWindow", u"Variance", None))
+
+        self.trialIdentifierTypeLabel.setText(QCoreApplication.translate("MainWindow", u"Identifier type", None))
+        self.trialIdentifierTypeComboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Numeric", None))
+        self.trialIdentifierTypeComboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Alpha", None))
+        self.trialIdentifierTypeComboBox.setItemText(2, QCoreApplication.translate("MainWindow", u"Alphanumeric", None))
+
+        self.trialIdentifierOrderLabel.setText(QCoreApplication.translate("MainWindow", u"Identifier order", None))
+        self.trialIentifierOrderComboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Sequential", None))
+        self.trialIentifierOrderComboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Random", None))
+
+        self.trialIdentifierLengthLabel.setText(QCoreApplication.translate("MainWindow", u"Identifier length", None))
+        self.max_sample_size_label.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
+        self.trialRecycleIdsLabel.setText(QCoreApplication.translate("MainWindow", u"Recycle ids", None))
+        self.trialNewSubjectRandomLabel.setText(QCoreApplication.translate("MainWindow", u"New subject random", None))
+        self.trialArmsWeightLabel.setText(QCoreApplication.translate("MainWindow", u"Arms weight", None))
+        self.armWeightLabel.setText(QCoreApplication.translate("MainWindow", u"1.0", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"Setting", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), QCoreApplication.translate("MainWindow", u"Treatments", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), QCoreApplication.translate("MainWindow", u"Factors", None))
+        self.frequenciesCheckBox.setText(QCoreApplication.translate("MainWindow", u"Frequencies", None))
+        self.preloadCheckBox.setText(QCoreApplication.translate("MainWindow", u"Preload", None))
+        self.editPreloadCheckBox.setText(QCoreApplication.translate("MainWindow", u"Edit", None))
+        self.convertPreloadButton.setText(QCoreApplication.translate("MainWindow", u"Convert to preload", None))
+        self.convertWarningCheckBox.setText(QCoreApplication.translate("MainWindow", u"I know what I'm doing", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), QCoreApplication.translate("MainWindow", u"Frequencies", None))
+        self.subjectProgressBar.setFormat(QCoreApplication.translate("MainWindow", u"%v/%m", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_6), QCoreApplication.translate("MainWindow", u"Subjects", None))
+        self.mean_balance_label.setText(QCoreApplication.translate("MainWindow", u"Oveall Balance", None))
+        self.randomness_label.setText(QCoreApplication.translate("MainWindow", u"Randomness", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_7), QCoreApplication.translate("MainWindow", u"Balance", None))
+        self.textEdit.setHtml(QCoreApplication.translate("MainWindow", u"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
+"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
+"p, li { white-space: pre-wrap; }\n"
+"</style></head><body style=\" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;\">\n"
+"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:20pt; font-weight:600;\">How to do minimisation using MinimPy2?</span></p>\n"
+"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:14pt; font-weight:600;\">A quick tutorial</span></p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; marg"
+                        "in-right:0px; -qt-block-indent:0; text-indent:0px;\">1 - Go to Trials tab and select a trial as current to work on it. Define a new trial if there is no trial</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">2 - Go to Setting tab to view / edit trial setting. Usually the default setting is good to go</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">3 - Go to Treatments tab to define at lease two treatments</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0"
+                        "; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">4 - Go to Factors tab to define at lease one factor</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">5 - Define at least two levels for each factor</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">6 - Now the trial is ready for subject enrollment</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-"
+                        "indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">7 - Go to subject tab and click on the Add button in the toolbar</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">8 - The enroll window will appear. Select a level for each factor and click enroll</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">9 - Subject will enroll to one of treatment groups by the minimisation algorhythm</p>\n"
+"<p style=\"-qt-paragraph-type:empty; margin-top:0px; marg"
+                        "in-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
+"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">10 - If you want to define a preload go to Frequencies tab</p></body></html>", None))
+        self.showAtStartCheckBox.setText(QCoreApplication.translate("MainWindow", u"Don't show at startup", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_8), QCoreApplication.translate("MainWindow", u"Start", None))
+        self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
+        self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
+        self.toolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None))
+    # retranslateUi
+
diff --git a/ui_main_window.ui b/ui_main_window.ui
new file mode 100755 (executable)
index 0000000..0b2a2f1
--- /dev/null
@@ -0,0 +1,705 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>802</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout_2">
+    <item>
+     <widget class="QTabWidget" name="tabWidget">
+      <property name="currentIndex">
+       <number>7</number>
+      </property>
+      <widget class="QWidget" name="tab">
+       <attribute name="title">
+        <string>Trials</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout">
+        <item>
+         <widget class="QTableWidget" name="tialsTableWidget"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_2">
+       <attribute name="title">
+        <string>Setting</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <item>
+         <layout class="QFormLayout" name="trialFormLayout">
+          <item row="0" column="0">
+           <widget class="QLabel" name="trialCreatedLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Created</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QLabel" name="trialCreatedValueLabel"/>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="trialModifiedLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Modified</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QLabel" name="trialModifiedValueLabel"/>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="trialTitleLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Trial title</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QLineEdit" name="trialTitleLineEdit"/>
+          </item>
+          <item row="3" column="0">
+           <widget class="QLabel" name="trialCodeLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Trial code</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="1">
+           <widget class="QLineEdit" name="trialCodeLineEdit"/>
+          </item>
+          <item row="4" column="0">
+           <widget class="QLabel" name="trialProbmethodLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Probability method</string>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="1">
+           <widget class="QComboBox" name="trialProbMethodComboBox">
+            <item>
+             <property name="text">
+              <string>Biased coin</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Naive</string>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="5" column="0">
+           <widget class="QLabel" name="trialBaseProbabilityLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Base probability</string>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="1">
+           <layout class="QHBoxLayout" name="horizontalLayout_6">
+            <item>
+             <widget class="QLabel" name="baseProbLabel">
+              <property name="lineWidth">
+               <number>1</number>
+              </property>
+              <property name="text">
+               <string>0.70</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSlider" name="trialBaseProbabilitySlider">
+              <property name="minimum">
+               <number>10</number>
+              </property>
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="6" column="0">
+           <widget class="QLabel" name="trialDistanceMethodLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Distance method</string>
+            </property>
+           </widget>
+          </item>
+          <item row="6" column="1">
+           <widget class="QComboBox" name="trialDistanceMethodComboBox">
+            <item>
+             <property name="text">
+              <string>Marginal balance</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Range</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Standard deviation</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Variance</string>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="7" column="0">
+           <widget class="QLabel" name="trialIdentifierTypeLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Identifier type</string>
+            </property>
+           </widget>
+          </item>
+          <item row="7" column="1">
+           <widget class="QComboBox" name="trialIdentifierTypeComboBox">
+            <item>
+             <property name="text">
+              <string>Numeric</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Alpha</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Alphanumeric</string>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="8" column="0">
+           <widget class="QLabel" name="trialIdentifierOrderLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Identifier order</string>
+            </property>
+           </widget>
+          </item>
+          <item row="8" column="1">
+           <widget class="QComboBox" name="trialIentifierOrderComboBox">
+            <item>
+             <property name="text">
+              <string>Sequential</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Random</string>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="9" column="0">
+           <widget class="QLabel" name="trialIdentifierLengthLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Identifier length</string>
+            </property>
+           </widget>
+          </item>
+          <item row="9" column="1">
+           <layout class="QHBoxLayout" name="horizontalLayout_5">
+            <item>
+             <widget class="QSpinBox" name="trialIdentifierLengthSpinBox">
+              <property name="minimum">
+               <number>3</number>
+              </property>
+              <property name="maximum">
+               <number>10</number>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="max_sample_size_label">
+              <property name="text">
+               <string>TextLabel</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="10" column="0">
+           <widget class="QLabel" name="trialRecycleIdsLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Recycle ids</string>
+            </property>
+           </widget>
+          </item>
+          <item row="10" column="1">
+           <widget class="QCheckBox" name="trialRecycleIdsCheckBox"/>
+          </item>
+          <item row="11" column="0">
+           <widget class="QLabel" name="trialNewSubjectRandomLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>New subject random</string>
+            </property>
+           </widget>
+          </item>
+          <item row="11" column="1">
+           <widget class="QCheckBox" name="trialNewSubjectRandomCheckBox"/>
+          </item>
+          <item row="12" column="0">
+           <widget class="QLabel" name="trialArmsWeightLabel">
+            <property name="styleSheet">
+             <string notr="true">color: blue;</string>
+            </property>
+            <property name="text">
+             <string>Arms weight</string>
+            </property>
+           </widget>
+          </item>
+          <item row="12" column="1">
+           <layout class="QHBoxLayout" name="horizontalLayout_9">
+            <item>
+             <widget class="QLabel" name="armWeightLabel">
+              <property name="text">
+               <string>1.0</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSlider" name="trialArmsWeightDoubleSlider">
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_3">
+       <attribute name="title">
+        <string>Treatments</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout_4">
+        <item>
+         <widget class="QTableWidget" name="treatmentTableWidget"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_4">
+       <attribute name="title">
+        <string>Factors</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout_5">
+        <item>
+         <widget class="QTableWidget" name="factorTableWidget"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_5">
+       <attribute name="title">
+        <string>Frequencies</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout_6">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout">
+          <item>
+           <widget class="QCheckBox" name="frequenciesCheckBox">
+            <property name="text">
+             <string>Frequencies</string>
+            </property>
+            <property name="checked">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QCheckBox" name="preloadCheckBox">
+            <property name="text">
+             <string>Preload</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QCheckBox" name="editPreloadCheckBox">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="text">
+             <string>Edit</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="convertPreloadButton">
+            <property name="text">
+             <string>Convert to preload</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QCheckBox" name="convertWarningCheckBox">
+            <property name="text">
+             <string>I know what I'm doing</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <widget class="QScrollArea" name="scrollArea">
+          <property name="widgetResizable">
+           <bool>true</bool>
+          </property>
+          <widget class="QWidget" name="scrollAreaWidgetContents">
+           <property name="geometry">
+            <rect>
+             <x>0</x>
+             <y>0</y>
+             <width>760</width>
+             <height>422</height>
+            </rect>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0">
+            <property name="spacing">
+             <number>0</number>
+            </property>
+            <property name="leftMargin">
+             <number>0</number>
+            </property>
+            <property name="topMargin">
+             <number>0</number>
+            </property>
+            <property name="rightMargin">
+             <number>0</number>
+            </property>
+            <property name="bottomMargin">
+             <number>0</number>
+            </property>
+            <item>
+             <layout class="QGridLayout" name="freqGridLayout" rowstretch="0">
+              <property name="spacing">
+               <number>1</number>
+              </property>
+             </layout>
+            </item>
+           </layout>
+          </widget>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_6">
+       <attribute name="title">
+        <string>Subjects</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout_7">
+        <item>
+         <widget class="QProgressBar" name="subjectProgressBar">
+          <property name="value">
+           <number>0</number>
+          </property>
+          <property name="format">
+           <string>%v/%m</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QTableWidget" name="subjectTableWidget">
+          <property name="sortingEnabled">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_7">
+       <attribute name="title">
+        <string>Balance</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout_8">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_3">
+          <item>
+           <widget class="QLabel" name="mean_balance_label">
+            <property name="text">
+             <string>Oveall Balance</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QProgressBar" name="balanceProgressBar">
+            <property name="maximum">
+             <number>100</number>
+            </property>
+            <property name="value">
+             <number>0</number>
+            </property>
+            <property name="textVisible">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <widget class="QTreeWidget" name="balanceTreeWidget">
+          <column>
+           <property name="text">
+            <string notr="true">1</string>
+           </property>
+          </column>
+         </widget>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_4">
+          <item>
+           <widget class="QLabel" name="randomness_label">
+            <property name="text">
+             <string>Randomness</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QProgressBar" name="randomnessProgressBar">
+            <property name="value">
+             <number>0</number>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_8">
+       <attribute name="title">
+        <string>Start</string>
+       </attribute>
+       <layout class="QVBoxLayout" name="verticalLayout_9">
+        <item>
+         <widget class="QTextEdit" name="textEdit">
+          <property name="html">
+           <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:20pt; font-weight:600;&quot;&gt;How to do minimisation using MinimPy2?&lt;/span&gt;&lt;/p&gt;
+&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;A quick tutorial&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;1 - Go to Trials tab and select a trial as current to work on it. Define a new trial if there is no trial&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;2 - Go to Setting tab to view / edit trial setting. Usually the default setting is good to go&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;3 - Go to Treatments tab to define at lease two treatments&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;4 - Go to Factors tab to define at lease one factor&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;5 - Define at least two levels for each factor&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;6 - Now the trial is ready for subject enrollment&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;7 - Go to subject tab and click on the Add button in the toolbar&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;8 - The enroll window will appear. Select a level for each factor and click enroll&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;9 - Subject will enroll to one of treatment groups by the minimisation algorhythm&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;10 - If you want to define a preload go to Frequencies tab&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QCheckBox" name="showAtStartCheckBox">
+          <property name="text">
+           <string>Don't show at startup</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>802</width>
+     <height>22</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>File</string>
+    </property>
+    <addaction name="separator"/>
+    <addaction name="actionImport"/>
+    <addaction name="actionExport"/>
+    <addaction name="separator"/>
+    <addaction name="actionConfig"/>
+    <addaction name="actionQuit"/>
+   </widget>
+   <widget class="QMenu" name="menuHelp">
+    <property name="title">
+     <string>Help</string>
+    </property>
+    <addaction name="actionHelp"/>
+    <addaction name="separator"/>
+    <addaction name="actionAbout_Minimisation"/>
+    <addaction name="actionAbout_MinimPy2"/>
+    <addaction name="actionAbout_Qt"/>
+   </widget>
+   <addaction name="menuFile"/>
+   <addaction name="menuHelp"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <property name="toolButtonStyle">
+    <enum>Qt::ToolButtonTextUnderIcon</enum>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="actionNew"/>
+   <addaction name="actionDelete"/>
+   <addaction name="actionLevels"/>
+   <addaction name="actionSave"/>
+   <addaction name="actionConfig"/>
+   <addaction name="actionHelp"/>
+   <addaction name="actionAbout_MinimPy2"/>
+   <addaction name="actionQuit"/>
+  </widget>
+  <action name="actionQuit">
+   <property name="text">
+    <string>Exit</string>
+   </property>
+  </action>
+  <action name="actionNew">
+   <property name="text">
+    <string>Add</string>
+   </property>
+  </action>
+  <action name="actionDelete">
+   <property name="text">
+    <string>Delete</string>
+   </property>
+  </action>
+  <action name="actionSave">
+   <property name="text">
+    <string>Save</string>
+   </property>
+   <property name="toolTip">
+    <string>Save</string>
+   </property>
+  </action>
+  <action name="actionLevels">
+   <property name="text">
+    <string>Levels</string>
+   </property>
+   <property name="toolTip">
+    <string>manage factor levels</string>
+   </property>
+  </action>
+  <action name="actionHelp">
+   <property name="text">
+    <string>Help</string>
+   </property>
+   <property name="toolTip">
+    <string>displays help</string>
+   </property>
+  </action>
+  <action name="actionImport">
+   <property name="text">
+    <string>Import</string>
+   </property>
+  </action>
+  <action name="actionExport">
+   <property name="text">
+    <string>Export</string>
+   </property>
+  </action>
+  <action name="actionConfig">
+   <property name="text">
+    <string>Config</string>
+   </property>
+   <property name="toolTip">
+    <string>Application config</string>
+   </property>
+  </action>
+  <action name="actionAbout_Minimisation">
+   <property name="text">
+    <string>What is Minimisation</string>
+   </property>
+  </action>
+  <action name="actionAbout_MinimPy2">
+   <property name="text">
+    <string>About</string>
+   </property>
+  </action>
+  <action name="actionAbout_Qt">
+   <property name="text">
+    <string>About Qt</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>