--- a
+++ b/minimpy.py
@@ -0,0 +1,1047 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+import pickle
+import random
+import tempfile
+import functions
+from model import Model
+from minimclass import Minim
+
+try:
+    import pygtk
+    pygtk.require("2.0")
+    import gobject
+except:
+    print("pygtk Not Availible")
+    sys.exit(1)
+try:
+    import gtk
+except:
+    print("GTK Not Availible")
+    sys.exit(1)
+
+class ModelInteface(object):
+    def close_window(self, widget, data=None):
+        if self.trial_file_name:
+            self.save_trial(self.trial_file_name)        
+        self.window.destroy()
+        if __name__ == "__main__":
+            sys.exit(0)
+
+    def delete_event(self, widget, event):
+        if self.trial_file_name:
+            self.save_trial(self.trial_file_name)        
+        self.window.destroy()
+        if __name__ == "__main__":
+            sys.exit(0)
+
+    def group_cell_edited(self, cell, row, new_text, col):
+        if col == 0:
+            # names must not be repeated
+            for r in range(len(self.group_liststore)):
+                if r != row and new_text == self.group_liststore[r][0]:
+                    self.statusbar.pop(self.sb_context_id)
+                    self.statusbar.push(self.sb_context_id, "Error: Group name already in use! Choose another name!")
+                    return False
+        if len(new_text) == 0:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: Group name can not be empty!")
+            return False
+        if col == 1:
+            if not new_text.isdigit():
+                self.statusbar.pop(self.sb_context_id)
+                self.statusbar.push(self.sb_context_id, "Error: Allocation ratio must be an integer!")
+                return False
+            new_text = int(new_text)
+            if not new_text:
+                self.statusbar.pop(self.sb_context_id)
+                self.statusbar.push(self.sb_context_id, "Error: Allocation ratio must be zero!")
+                return False
+        self.group_liststore[row][col] = new_text
+        self.update_allocations()
+
+    def group_add_button(self, widget, data=None):
+        self.group_liststore.append(('Group {0}'.format(len(self.group_liststore)+1), 1))
+        self.update_allocations()
+
+    def group_delete_button(self, widget, data=None):
+        if self.group_treeview.get_cursor()[0]:
+            row = self.group_treeview.get_cursor()[0][0]
+            self.group_liststore.remove(self.group_liststore.get_iter(row))
+            for idx, record in enumerate(self.allocations):
+                if record['allocation'] == row:
+                    self.ui_pool.append(self.allocations[idx]['UI'])
+                    self.allocations[idx] = False
+                elif record['allocation'] > row:
+                    record['allocation'] -= 1
+            self.allocations = filter(None, self.allocations)
+            self.update_allocations()
+        else:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: Please select a group first!")
+            return False
+
+    def variable_cell_edited(self, cell, row, new_text, col):
+        if col == 0:
+            for r in range(len(self.variable_liststore)):
+                if r != row and new_text == self.variable_liststore[r][0]:
+                    self.statusbar.pop(self.sb_context_id)
+                    self.statusbar.push(self.sb_context_id, "Error: This variable name already in use! Choose another name!")
+                    return False
+        if len(new_text) == 0:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: Variable names can not be empty!")
+            return False
+        if col == 1:
+            new_text = float(new_text)
+            if not new_text:
+                self.statusbar.pop(self.sb_context_id)
+                self.statusbar.push(self.sb_context_id, "Error: Variable weight can not be zero!")
+                return False
+        if col == 2:
+            # check for deleting a level
+            old_levels = self.variable_liststore[row][col].split(',')
+            new_levels = new_text.split(',')
+            if len(new_levels) < 2:
+                self.statusbar.pop(self.sb_context_id)
+                self.statusbar.push(self.sb_context_id, "Error: At least two variable levels should be entered! Separate levels with commas")
+                return False
+            if len(old_levels) > len(new_levels):
+                # which levels has been deleted?
+                del_levels = list(set(old_levels) - set(new_levels))
+                # convert items to indices
+                del_levels = [old_levels.index(level) for level in del_levels]
+                del_levels.sort()
+                for idx in range(len(self.allocations)):
+                    for i, level in enumerate(self.allocations[idx]['levels']):
+                        if level in del_levels:
+                            self.ui_pool.append(self.allocations[idx]['UI'])
+                            self.allocations[idx] = False
+                # deleting false items
+                self.allocations = filter(None, self.allocations)
+                for idx in range(len(self.allocations)):
+                    for i, level in enumerate(self.allocations[idx]['levels']):
+                        d = 0
+                        for del_level in del_levels:
+                            # for each del_level less than level the level index number should decrement one unit
+                            if level > del_level:
+                                d += 1
+                        self.allocations[idx]['levels'][i] -= d
+        self.variable_liststore[row][col] = new_text
+        self.update_allocations()
+
+    def variable_add_button(self, widget, data=None):
+        self.variable_liststore.append(('Variable {0}'.format(len(self.variable_liststore)+1), 1.0, 'Level 0, Level 1'))
+        self.update_allocations()
+
+    def variable_delete_button(self, widget, data=None):
+        if self.variable_treeview.get_cursor()[0]:
+            row = self.variable_treeview.get_cursor()[0][0]
+            self.variable_liststore.remove(self.variable_liststore.get_iter(row))
+            for idx in range(len(self.allocations)):
+                self.allocations[idx]['levels'].pop(row)
+            self.update_allocations()
+
+    def update_allocations(self):
+        n = len(self.allocations_treeview.get_columns())
+        while n:
+            n = self.allocations_treeview.remove_column(self.allocations_treeview.get_column(n-1))
+        widgets = self.combo_vbox.get_children()
+        for widget in widgets:
+            widget.destroy()
+        table = gtk.Table(3, len(self.variable_liststore)+1)
+        min_group_old_index = self.min_group_combo.get_active()
+        if min_group_old_index < 0:
+            min_group_old_index = 0
+        if (min_group_old_index + 1) > len(self.group_liststore):
+            min_group_old_index = 0
+        self.min_group_combo.get_model().clear()
+        for group in self.group_liststore:
+            self.min_group_combo.append_text(group[0])
+        if len(self.group_liststore):
+            self.min_group_combo.set_active(min_group_old_index)
+        self.variable_combos = []
+        n = 0
+        for variable in self.variable_liststore:
+            lbl = gtk.Label(variable[0])
+            table.attach(lbl, 0, 1, n, n+1)
+            cbo = gtk.combo_box_new_text()
+            for level in map(str.strip, variable[2].split(',')):
+                cbo.append_text(level)
+            self.variable_combos.append(cbo)
+            table.attach(cbo, 1, 2, n, n+1)
+            n += 1
+        self.combo_vbox.pack_start(table, False, False)
+        types = ((str,) * (len(self.variable_liststore) + 2))
+        liststore = gtk.ListStore(*types)
+
+        tvcolumn = gtk.TreeViewColumn('UI')
+        self.allocations_treeview.append_column(tvcolumn)
+        cell = gtk.CellRendererText()
+        tvcolumn.pack_start(cell, True)        
+        tvcolumn.add_attribute(cell, 'text', 0)
+        tvcolumn.set_resizable(True)
+        tvcolumn.set_expand(True)
+
+        tvcolumn = gtk.TreeViewColumn('Group')
+        self.allocations_treeview.append_column(tvcolumn)
+        cell = gtk.CellRendererText()
+        tvcolumn.pack_start(cell, True)        
+        tvcolumn.add_attribute(cell, 'text', 1)
+        tvcolumn.set_resizable(True)
+        tvcolumn.set_expand(True)
+
+        n = 1
+        for variable in self.variable_liststore:
+            n += 1
+            tvcolumn = gtk.TreeViewColumn(variable[0])
+            self.allocations_treeview.append_column(tvcolumn)
+            cell = gtk.CellRendererText()
+            tvcolumn.pack_start(cell, True)        
+            tvcolumn.add_attribute(cell, 'text', n)
+            tvcolumn.set_resizable(True)
+            tvcolumn.set_expand(True)
+        self.allocations_treeview.set_model(liststore)
+        variable_models = [cbo.get_model() for cbo in self.variable_combos]
+        for row, record in enumerate(self.allocations):
+            liststore.append()
+            liststore[row][0] = record['UI']
+            liststore[row][1] = self.group_liststore[record['allocation']][0]
+            for col, variable_model in enumerate(variable_models):
+                liststore[row][col+2] = variable_model[record['levels'][col]][0]
+        self.window.show_all()
+
+    def get_minimize_case(self, new_case):
+        model = Model()
+        model.groups = range(len(self.group_liststore))
+        model.variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
+        model.variables_weight = [self.variable_liststore[row][1] for row in range(len(self.variable_liststore))]
+        model.allocation_ratio = [self.group_liststore[row][1] for row in range(len(self.group_liststore))]
+        model.allocations = self.allocations
+        model.prob_method = self.get_prob_method()
+        model.distance_measure = self.get_distance_measure()
+        model.high_prob = self.high_prob_spin.get_value()
+        model.min_group = self.min_group_combo.get_active()
+        m = Minim(model)
+        m.build_probs(model.high_prob, model.min_group)
+        m.build_freq_table()
+        if self.initial_freq_table:
+            self.add_to_initial(m.freq_table)
+        return m.enroll(new_case, m.freq_table)
+
+    def add_to_initial(self, freq_table):
+        for row, group in enumerate(freq_table):
+            for v, variable in enumerate(group):
+                for l, level in enumerate(variable):
+                    freq_table[row][v][l] += self.initial_freq_table[row][v][l]
+
+    def allocations_undo_button(self, widget, data=None):
+        model = self.allocations_treeview.get_model()
+        if model == None or len(model) == 0:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: No subject allocated!")
+            if self.trial_file_name:
+                self.want_trial_unlock()
+            return False
+        msg = "Deallocation may introduce errors in randomization!\nAre you sure you want to undo the last allocation?"
+        dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
+        dialog.set_title("Undo Allocation!")
+        if dialog.run() == gtk.RESPONSE_YES:
+            row = len(model)-1
+            self.ui_pool.append(model[row][0])
+            path = (row,)
+            iter = model.get_iter(path)
+            model.remove(iter)
+            self.allocations.pop()
+            self.save_trial(self.trial_file_name)
+        dialog.destroy()
+
+    def allocations_add_button(self, widget, data=None):
+        model = self.allocations_treeview.get_model()
+        if not model:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: Groups and prognostic factors must be defined first!")
+            return False
+        for cbo in self.variable_combos:
+            if cbo.get_active() == -1:
+                self.statusbar.pop(self.sb_context_id)
+                self.statusbar.push(self.sb_context_id, 'Error:  Some factor levels have not been selected for this case!')
+                return False
+        if not self.ui_pool:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, 'Error: Either this trial is finished or has not been saved!')
+            return False
+        model.append()
+        ui = self.ui_pool.pop()
+        new_case = {'levels': [], 'allocation': -1, 'UI': ui}
+        row = len(model)-1
+        model[row][0] = ui
+        for col, cbo in enumerate(self.variable_combos):
+            model[row][col+2] = cbo.get_active_text()
+            new_case['levels'].append(cbo.get_active())
+        m_group = self.get_minimize_case(new_case)
+        new_case['allocation'] = m_group
+        model[row][1] = self.group_liststore[m_group][0]
+        self.allocations.append(new_case)
+        self.save_trial(self.trial_file_name)
+        self.report_allocation(model)
+        if len(self.ui_pool) == 0:
+           self.end_trial()
+
+    def report_allocation(self, model):
+        column = self.allocations_treeview.get_columns()
+        row = len(model)-1
+        ret = []
+        for col, column in enumerate(column):
+            ret.append(column.get_title() + ': ' + model[row][col])
+
+        msg = "New case allocated\n" + '\n'.join(ret)
+        dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format = msg)
+        dialog.set_title("New Allocation!")
+        dialog.run()
+        dialog.destroy()
+
+    def end_trial(self):
+        msg = "{0} cases have been minimized!\nDo you like to extend the sample size".format(len(self.allocations))
+        dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
+        dialog.set_title("Trial finished!")
+        if dialog.run() == gtk.RESPONSE_YES:
+            extra_sample = ""
+            while not extra_sample.isdigit():
+                extra_sample = functions.simple_dialog("Extra Sample", self.window)
+            ss = len(self.allocations)+int(extra_sample)
+            self.build_ui_pool(range(len(self.allocations), ss))
+            model = self.allocations_treeview.get_model()
+            for row in range(len(model)):
+                model[row][0] = model[row][0].zfill(len(str(ss)))
+        else:
+            self.ui_pool = None
+        dialog.destroy()
+
+    def refresh_freq_table(self):
+        model = Model()
+        model.groups = range(len(self.group_liststore))
+        model.variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
+        model.variables_weight = [self.variable_liststore[row][1] for row in range(len(self.variable_liststore))]
+        model.allocation_ratio = [self.group_liststore[row][1] for row in range(len(self.group_liststore))]
+        model.allocations = self.allocations
+        m = Minim(model)
+        m.build_freq_table()
+        if self.initial_freq_table:
+            self.add_to_initial(m.freq_table)
+        if not len(m.freq_table):
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: No allocation!")
+            return False
+        n = len(self.freq_table_treeview.get_columns())
+        while n:
+            n = self.freq_table_treeview.remove_column(self.freq_table_treeview.get_column(n-1))
+        self.initial_table_vbox.hide()
+        # following for test only
+        # m.freq_table = [[[18, 25], [6, 3, 3, 4, 3, 6, 4, 5, 5, 4]], [[39, 45], [12, 8, 7, 7, 7, 9, 6, 12, 11, 5]], [[55, 68], [17, 12, 10, 14, 10, 13, 8, 16, 15, 8]]]
+        cols = 0
+        column_names = []
+        for variable in self.variable_liststore:
+            column_names.extend([level for level in map(str.strip, variable[2].split(','))])
+        column_names = ['Group'] + column_names + ['Total']
+        for var in m.freq_table[0]:
+            cols += len(var)
+        col_types = [str] + [int] * (cols + 1)
+        liststore = gtk.ListStore(*col_types)        
+        for n in range(len(column_names)):
+            tvcolumn = gtk.TreeViewColumn(column_names[n])
+            self.freq_table_treeview.append_column(tvcolumn)
+            cell = gtk.CellRendererText()
+            #cell.set_property('editable', True)
+            #cell.connect("edited", edit_handler, n)
+            tvcolumn.pack_start(cell, True)
+            tvcolumn.add_attribute(cell, 'text', n)
+            tvcolumn.set_resizable(True)
+            tvcolumn.set_expand(True)
+        self.freq_table_treeview.set_model(liststore)
+        for row, group in enumerate(m.freq_table):
+            liststore.append()
+            liststore[row][0] = self.group_liststore[row][0]
+            col = 1
+            for variable in group:
+                for level in variable:
+                    liststore[row][col] = level
+                    col += 1
+            liststore[row][col] = sum(variable)
+        liststore.append()
+        liststore[len(m.freq_table)][0] = "Total"
+        total = 0
+        for c in range(1, col):
+            col_total = sum([liststore[row][c] for row in range(len(m.freq_table))])
+            total += col_total
+            liststore[len(m.freq_table)][c] = col_total
+        liststore[len(m.freq_table)][c+1] = total / len(self.variable_liststore)
+        if self.freq_table_enable_edit_button.get_label() == "Save as Initial Table":
+            self.freq_table_enable_edit()
+        self.window.show_all()
+
+    def notebook_switch_page(self, notebook, page, page_num, data=None):
+        self.statusbar.pop(self.sb_context_id)
+        self.statusbar.push(self.sb_context_id, self.notbook_tabs[page_num])
+        if page_num < 4: return
+        self.refresh_freq_table()
+        self.balance_table_refresh()
+
+    def license_button_clicked(self, widget, data=None):
+        self.display_about_dialog()
+
+    def display_about_dialog(self):
+        dialog = gtk.AboutDialog()
+        dialog.set_name("MinimPy Program")
+        dialog.set_version("0.1")
+        dialog.set_copyright("Copyright (c) 2010-2011 Mahmoud Saghaei")
+        dialog.set_license("Distributed under the GNU GPL v3.\nFor full terms refer to http://www.gnu.org/copyleft/gpl.html.")
+        dialog.set_website("http://minimpy.sourceforge.net")
+        dialog.set_authors(["Mahmoud Saghaei"])
+        dialog.set_logo(self.pixbuf)
+        dialog.run()
+        dialog.destroy()
+        return False
+        
+        dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = 
+gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format = "XKnight game\nCopyright (c) 2008-2009 Mahmoud Saghaei\nDistributed under the GNU GPL v3.\nFor full terms refer to http://www.gnu.org/copyleft/gpl.html.")
+        dialog.set_title("About XKnight Game!")
+        dialog.run()
+        dialog.destroy()
+
+    def trial_properties_cell_edited(self, cell, row, new_text, col):
+        self.trial_properties_liststore[row][col] = new_text
+
+    def trial_properties_add_button(self, widget, data=None):
+        self.trial_properties_liststore.append(('Property Name', 'Property Value'))
+
+    def trial_properties_delete_button(self, widget, data=None):
+        if self.trial_properties_treeview.get_cursor()[0]:
+            row = self.trial_properties_treeview.get_cursor()[0][0]
+            self.trial_properties_liststore.remove(self.trial_properties_liststore.get_iter(row))
+
+    def __init__(self):
+        self.trial_file_name = None
+        self.notbook_tabs = []
+        self.ui_pool = None
+        self.initial_freq_table = False
+        self.allocations = []
+        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+        self.window.set_title("Minimization Program")
+        self.window.connect("delete_event", self.delete_event)
+        self.window.set_border_width(2)
+        self.window.set_size_request(750, 400)
+        self.notebook = gtk.Notebook()
+        self.notebook.set_tab_pos(gtk.POS_LEFT)
+        self.notebook.popup_enable()
+        self.notebook.connect("switch-page", self.notebook_switch_page)
+        img = gtk.Image()
+        img.set_from_file('logo.png')
+        img.show()
+        self.pixbuf = img.get_pixbuf()        
+
+        win_vbox = gtk.VBox(False)
+        win_vbox.pack_start(self.notebook, True, True)
+
+        self.statusbar = gtk.Statusbar()
+        win_vbox.pack_start(self.statusbar, False, False, 0)
+        self.sb_context_id = self.statusbar.get_context_id("Statusbar")
+
+        vbx = gtk.VBox(False)
+        hbx = gtk.HBox(False)
+        label = gtk.Label("Trial Title")
+        hbx.pack_start(label, False, False)
+        self.trial_title_entry = gtk.Entry()
+        hbx.pack_start(self.trial_title_entry, True, True)
+
+        label = gtk.Label("Sample Size")
+        hbx.pack_start(label, False, False)
+        adj = gtk.Adjustment(value=30, lower=5, upper=1000000, step_incr=1, page_incr=10)
+        self.trial_sample_size_spin = gtk.SpinButton(adj)
+        hbx.pack_start(self.trial_sample_size_spin, False, False)
+        vbx.pack_start(hbx, False, False)
+
+        hbx = gtk.HBox(False)
+        label = gtk.Label("Description")
+        hbx.pack_start(label, False, False)
+        vbx.pack_start(hbx, False, False)
+        sep = gtk.HSeparator()
+        vbx.pack_start(sep, False, False)
+        hbx = gtk.HBox(False)
+        sep = gtk.VSeparator()
+        hbx.pack_start(sep, False, False)
+        self.trial_description_text = gtk.TextView()
+        hbx.pack_start(self.trial_description_text, True, True)
+        sep = gtk.VSeparator()
+        hbx.pack_start(sep, False, False)
+        vbx.pack_start(hbx, True, True)
+        sep = gtk.HSeparator()
+        vbx.pack_start(sep, False, False)
+
+        hbox = gtk.HBox(False)
+        vbx.pack_start(hbox, True, True)
+        label = gtk.Label("Probability Method")
+        vbox = gtk.VBox(False)
+        vbox.pack_start(label, False, False)
+        radio = gtk.RadioButton(None, label="Biased Coin Minimization")
+        vbox.pack_start(radio, False, False)
+        radio = gtk.RadioButton(radio, label="Naive Minimization")
+        vbox.pack_start(radio, False, False)
+        # this method returns the radios in revese order
+        self.prob_method_radios = radio.get_group()
+        # so we need to invert the returned list
+        self.prob_method_radios.reverse()
+
+        hb = gtk.HBox(False)
+        lbl = gtk.Label("Min Group")
+        hb.pack_start(lbl, False, False)
+        self.min_group_combo = gtk.combo_box_new_text()
+        hb.pack_start(self.min_group_combo, False, False)
+        vbox.pack_start(hb, False, False)
+
+        hb = gtk.HBox(False)
+        lbl = gtk.Label("High Probability")
+        hb.pack_start(lbl, False, False)
+        adj = gtk.Adjustment(value=0.7, lower=0.1, upper=1.0, step_incr=0.01, page_incr=0.1)
+        self.high_prob_spin = gtk.SpinButton(adj, digits=2)
+        hb.pack_start(self.high_prob_spin, False, False)
+        vbox.pack_start(hb, False, False)
+        hbox.pack_start(vbox, False, False)
+        sep = gtk.VSeparator()
+        hbox.pack_start(sep, False, False)
+
+        label = gtk.Label("Distance Measure")
+        vbox = gtk.VBox(False)
+        vbox.pack_start(label, False, False)
+        radio = gtk.RadioButton(None, label="Marginal Balance")
+        vbox.pack_start(radio, False, False)
+        radio = gtk.RadioButton(radio, label="Range")
+        vbox.pack_start(radio, False, False)
+        radio = gtk.RadioButton(radio, label="Standard Deviation")
+        vbox.pack_start(radio, False, False)
+        radio = gtk.RadioButton(radio, label="Variance")
+        vbox.pack_start(radio, False, False)
+        hbox.pack_start(vbox, False, False)
+        self.distance_measure_radios = radio.get_group()
+        self.distance_measure_radios.reverse()
+        sep = gtk.VSeparator()
+        hbox.pack_start(sep, False, False)
+
+        vbox = gtk.VBox(False)
+        lbl = gtk.Label("Trial Properties")
+        vbox.pack_start(lbl, False, False)
+        hb = gtk.HBox(False)
+        self.trial_properties_liststore, self.trial_properties_treeview = functions.make_treeview(
+        (str, str), ['Name', 'Value'],
+        editable=True, edit_handler=self.trial_properties_cell_edited)
+        hb.pack_start(self.trial_properties_treeview, True, True)
+        vbuttonbox = gtk.VButtonBox()
+        vbuttonbox.set_layout(gtk.BUTTONBOX_START)
+        button = gtk.Button(None, gtk.STOCK_ADD)
+        button.connect("clicked", self.trial_properties_add_button)
+        vbuttonbox.pack_start(button, False, False)
+        button = gtk.Button(None, gtk.STOCK_DELETE)
+        button.connect("clicked", self.trial_properties_delete_button)
+        vbuttonbox.pack_start(button, False, False)
+        hb.pack_start(vbuttonbox, False, False)
+        vbox.pack_start(hb, False, False)
+
+        hbox.pack_start(vbox, True, True)
+        sep = gtk.VSeparator()
+        hbox.pack_start(sep, False, False)
+
+        sep = gtk.HSeparator()
+        vbx.pack_start(sep, False, False)
+
+        hbuttonbox = gtk.HButtonBox()
+        hbuttonbox.set_layout(gtk.BUTTONBOX_START)
+
+        self.trial_load_button = gtk.Button(None, gtk.STOCK_OPEN)
+        self.trial_load_button.connect("clicked", self.load_trial_clicked)
+        hbuttonbox.pack_start(self.trial_load_button, False, False)
+
+        self.trial_save_button = gtk.Button(None, gtk.STOCK_SAVE)
+        self.trial_save_button.connect("clicked", self.save_trial_clicked)
+        hbuttonbox.pack_start(self.trial_save_button, False, False)
+
+        vbx.pack_start(hbuttonbox, False, False)
+
+        lbl = gtk.Label("Settings")
+        self.notbook_tabs.append("Settings: Define and save a new trial or load a previous trial")
+        self.notebook.append_page(vbx, lbl)
+
+        hbox = gtk.HBox(False)
+        self.group_liststore, self.group_treeview = functions.make_treeview(
+        (str, int), ['Group Name', 'Allocation Ratio'],
+        editable=True, edit_handler=self.group_cell_edited)
+        hbox.pack_start(self.group_treeview, True, True)
+        vbuttonbox = gtk.VButtonBox()
+        vbuttonbox.set_layout(gtk.BUTTONBOX_START)
+        button = gtk.Button(None, gtk.STOCK_ADD)
+        button.connect("clicked", self.group_add_button)
+        vbuttonbox.pack_start(button, False, False)
+        button = gtk.Button(None, gtk.STOCK_DELETE)
+        button.connect("clicked", self.group_delete_button)
+        vbuttonbox.pack_start(button, False, False)
+        hbox.pack_start(vbuttonbox, False, False)
+        lbl = gtk.Label('Groups')
+        self.notbook_tabs.append("Groups: Define trial groups and their allocation ratios")
+        self.group_hbox = hbox
+        self.notebook.append_page(hbox, lbl)
+
+        hbox = gtk.HBox(False)
+        self.variable_liststore, self.variable_treeview = functions.make_treeview(
+        (str, float, str), ['Variable Name', 'Weight', 'Levels'],
+        editable=True, edit_handler=self.variable_cell_edited)
+        hbox.pack_start(self.variable_treeview, True, True)
+        vbuttonbox = gtk.VButtonBox()
+        vbuttonbox.set_layout(gtk.BUTTONBOX_START)
+        button = gtk.Button(None, gtk.STOCK_ADD)
+        button.connect("clicked", self.variable_add_button)
+        vbuttonbox.pack_start(button, False, False)
+        button = gtk.Button(None, gtk.STOCK_DELETE)
+        button.connect("clicked", self.variable_delete_button)
+        vbuttonbox.pack_start(button, False, False)
+        hbox.pack_start(vbuttonbox, False, False)
+        lbl = gtk.Label('Variables')
+        self.notbook_tabs.append("Variables: Define trial prognostic factors, their weights and levels")
+        self.variable_hbox = hbox
+        self.notebook.append_page(hbox, lbl)
+
+        hbox = gtk.HBox(False)
+        vbox = gtk.VBox(False)
+
+        self.allocations_treeview = gtk.TreeView()
+        hbox.pack_start(self.allocations_treeview, True, True)
+
+        vbuttonbox = gtk.VButtonBox()
+        vbuttonbox.set_layout(gtk.BUTTONBOX_START)
+        button = gtk.Button(None, gtk.STOCK_ADD)
+        button.connect("clicked", self.allocations_add_button)
+        vbuttonbox.pack_start(button, False, False)
+        button = gtk.Button(None, gtk.STOCK_UNDO)
+        button.connect("clicked", self.allocations_undo_button)
+        vbuttonbox.pack_start(button, False, False)
+        vbox.pack_start(vbuttonbox, False, False)
+        self.combo_vbox = gtk.VBox(False)
+        vbox.pack_start(self.combo_vbox, False, False)
+        hbox.pack_start(vbox, False, False)
+
+        lbl = gtk.Label('Allocations')
+        self.notbook_tabs.append('Allocations: Allocate subjects to treatments. To use minimization select "Minimize" as group name')
+        self.notebook.append_page(hbox, lbl)
+
+        vbox = gtk.VBox(False)
+        self.freq_table_treeview = gtk.TreeView()
+        vbox.pack_start(self.freq_table_treeview, False, False)
+        self.initial_table_vbox = gtk.VBox(False)
+        self.freq_table_enable_edit_button = gtk.Button("Start Edit")
+        self.freq_table_enable_edit_button.connect("clicked", self.freq_table_start_edit_clicked)
+        self.initial_table_vbox.pack_start(self.freq_table_enable_edit_button, False, False)
+        button = gtk.Button("Delete Initial Table")
+        button.connect("clicked", self.freq_table_delete_initial_clicked)
+        self.initial_table_vbox.pack_start(button, False, False)
+        vbox.pack_start(self.initial_table_vbox, False, False)
+        lbl = gtk.Label('Table')
+        self.notbook_tabs.append("Table: Displays frequency table for different levels of prognostic factors")
+        self.notebook.append_page(vbox, lbl)
+
+        hbox = gtk.HBox(True)
+        treestore = gtk.TreeStore(str, float, float, float, float)
+        self.balance_table_treeview = gtk.TreeView(treestore)
+        col_names = ['Variable/Level', 'Marginal Balance', 'Range', 'Variance', 'SD']
+        for i in range(5):
+            cell = gtk.CellRendererText()
+            column = gtk.TreeViewColumn(col_names[i], cell, text=i)
+            self.balance_table_treeview.append_column(column)
+        hbox.pack_start(self.balance_table_treeview, True, True)
+        lbl = gtk.Label('Balance')
+        self.notbook_tabs.append("Balance: Displays current trial balance as four different balance measures")
+        self.notebook.append_page(hbox, lbl)
+
+        hbox = gtk.HBox(True)
+        self.about_text = gtk.TextView()
+        self.about_text.set_wrap_mode(gtk.WRAP_WORD)
+        self.about_text.set_editable(False)
+        self.about_text.set_cursor_visible(False)
+        self.about_text.set_left_margin(20)
+        self.about_text.set_right_margin(20)
+        about_text = """
+
+
+*************************** MinimPy 0.1 ***********************************
+Copyright (c) 2010 Mahmoud Saghaei
+http://www.saghaei.com
+MinimPy is a desktop minimization program for sequential randomization of subject to treatment groups in a clinical trial.
+With this program you can define a new minimization model specifying the probability method used for assigning subject to treatment groups, distance measure which is the imbalance score at a the time of allocation considering the levels of prognostic factor for the a new subject which going to be allocated. Main features of MinimPy include total control over choice of probability method, distance measure, defining an initial minimization preload, adding a new method of probability assignment as biased coin minimization and another as marginal balance which has been shown to be superior to other distance measures. 
+        """
+        functions.set_text_buffer(self.about_text, about_text)
+        hbox.pack_start(self.about_text)
+        button = gtk.Button(None, gtk.STOCK_INFO)
+        button.connect("clicked", self.license_button_clicked)
+        functions.textview_add_widget(self.about_text, 0, button)
+        self.notbook_tabs.append("ABout: Displays information about MinimPy and the developers")
+        lbl = gtk.Label('ABout')
+        self.notebook.append_page(hbox, lbl)
+        self.notebook.set_show_tabs(True)
+
+        self.window.add(win_vbox)
+        self.window.show_all()
+        self.initial_table_vbox.hide()
+
+    def balance_table_refresh(self):
+        liststore = self.freq_table_treeview.get_model()
+        if not liststore:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: No allocation!")
+            return False
+        table = []
+        for row in range(len(liststore)-1):
+            table.append([])
+            for col in range(1, len(liststore[0])-1):
+                table[row].append(liststore[row][col])
+        balance = self.get_trial_balances(table)
+        treestore = self.balance_table_treeview.get_model()
+        treestore.clear()
+        variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
+        row = 0
+        for idx, variable in enumerate(variables):
+            var_total = [0] * 4
+            level_rows = []
+            for level in variable:
+                for i in range(4):
+                    var_total[i] += balance[row][i]
+                level_rows.append(balance[row])
+                row += 1
+            var_total = [self.variable_liststore[idx][0]] + [var_total[i] / len(variable) for i in range(4)]
+            variable_node = treestore.append(None, var_total)
+            level_names = map(str.strip, self.variable_liststore[idx][2].split(','))
+            for level, level_row in enumerate(level_rows):
+                treestore.append(variable_node, [level_names[level]] + level_row)
+        treestore.append(None, ['Mean'] + balance[row])
+        treestore.append(None, ['Max'] + balance[row+1])
+        self.window.show_all()
+
+    def get_trial_balances(self, table):
+        model = Model()
+        m = Minim(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)
+        for row, level_count in enumerate(levels):
+            allocation_ratio = [self.group_liststore[r][1] for r in range(len(self.group_liststore))]
+            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))
+        for col in range(4):
+             balances[len(levels)].append(1.0 * sum([balances[row][col] for row in range(len(levels))]) / len(levels))
+             balances[len(levels)+1].append(max([balances[row][col] for row in range(len(levels))]))
+        return balances
+
+    def freq_table_disable_edit(self):
+        cols = self.freq_table_treeview.get_columns()
+        for n, col in enumerate(cols):
+            cells = col.get_cell_renderers()
+            for cell in cells:
+                cell.set_property('editable', False)
+
+    def freq_table_enable_edit(self):
+        cols = self.freq_table_treeview.get_columns()
+        for n, col in enumerate(cols):
+            cells = col.get_cell_renderers()
+            for cell in cells:
+                cell.set_property('editable', True)
+                cell.connect("edited", self.freq_table_treeview_edit_handler, n, len(cols))
+
+    def freq_table_start_edit_clicked(self, button, data=False):
+        if button.get_label() == "Start Edit":
+            self.freq_table_enable_edit()
+            button.set_label("Save as Initial Table")
+        else:
+            groups = range(len(self.group_liststore))
+            variables = [range(len(self.variable_liststore[row][2].split(','))) for row in range(len(self.variable_liststore))]
+            initial_freq_model = self.freq_table_treeview.get_model()
+            table = [[[0 for l in v] for v in variables] for g in groups]
+            for row, group in enumerate(table):
+                col = 1
+                for v, variable in enumerate(group):
+                    for l, level in enumerate(variable):
+                        table[row][v][l] = initial_freq_model[row][col]
+                        col += 1
+            self.initial_freq_table = table
+            button.set_label("Start Edit")
+
+    def freq_table_treeview_edit_handler(self, cell, row, new_text, col, cols):
+        model = self.freq_table_treeview.get_model()
+        if col == 0:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: This cell can not be changed!")
+            return False
+        if not new_text.isdigit():
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: Only interger values allowed!")
+            return False
+        model[row][col] = int(new_text)
+
+    def freq_table_delete_initial_clicked(self, button, data=None):
+        self.initial_freq_table = False
+        self.freq_table_disable_edit()
+        self.freq_table_enable_edit_button.set_label("Start Edit")
+        
+    def load_trial_clicked(self, button, data=None):
+        file_name = self.select_file("Select file", gtk.FILE_CHOOSER_ACTION_OPEN)
+        if not file_name:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: Trial load canceled!")
+            return False
+        self.trial_file_name = self.load_trial(file_name)
+
+    def save_trial_clicked(self, button, data=None):
+        if not self.trial_is_valid():
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: This trial is not ready to save! Please check the requirements")
+            return False
+        file_name = self.select_file("Enter name of the file", gtk.FILE_CHOOSER_ACTION_SAVE)
+        if not file_name:
+            self.statusbar.pop(self.sb_context_id)
+            self.statusbar.push(self.sb_context_id, "Error: Trial save canceled!")
+            return False
+        self.build_ui_pool()
+        self.trial_file_name = self.save_trial(file_name)
+
+    def load_trial(self, file_name, lock=True):
+        trial_state = self.trial_sample_size_spin.get_property("sensitive")
+        temp_file = self.save_trial()
+        try:
+            fp = open(file_name, 'rb')
+            data = pickle.load(fp)
+            fp.close()
+            self.ui_pool = data['ui_pool']
+            self.trial_title_entry.set_text(data['trial_title'])
+            self.trial_sample_size_spin.set_value(data['sample_size'])
+            functions.set_text_buffer(self.trial_description_text, data['trial_description'])
+            self.set_trial_properties(data['trial_properties'])
+            self.high_prob_spin.set_value(data['high_prob'])
+            self.initial_freq_table = data['initial_freq_table']
+            self.prob_method_radios[data['prob_method']].set_active(True)
+            self.distance_measure_radios[data['distance_measure']].set_active(True)
+            self.allocations = data['allocations']
+            self.set_groups_data(data['groups'])
+            self.set_variables_data(data['variables'])
+            self.update_allocations()
+            if len(self.group_liststore):
+                self.min_group_combo.set_active(data['min_group'])
+            if lock:
+                self.lock_trial()
+                return file_name
+        except Exception as ex:
+            self.lock_trial(trial_state)
+            self.load_trial(file_name=temp_file, lock=False)
+            functions.error_dialog(self.window, 'An error occured while loading the trial!\nThe selected file may not be valid\n' + repr(ex) + '\nTrial restored to its previous state', 'Trial load failed!')
+            return False
+
+    def set_groups_data(self, groups):
+        self.group_liststore.clear()
+        for group in groups:
+            self.group_liststore.append((group['name'], group['allocation_ratio']))
+
+    def set_variables_data(self, variables):
+        self.variable_liststore.clear()
+        for variable in variables:
+            self.variable_liststore.append((variable['name'], variable['weight'], variable['levels']))
+
+    def build_ui_pool(self, lst=None):
+        if not lst:
+            ss = self.trial_sample_size_spin.get_value_as_int()
+            lst = range(ss)
+        random.shuffle(lst)
+        self.ui_pool = map(lambda x: x.zfill(len(str(max(lst)))), map(str, lst))
+
+    def get_trial_properties(self):
+        table = [['', ''] for row in range(len(self.trial_properties_liststore))]
+        for row in range(len(table)):
+            for col in range(len(table[0])):
+                table[row][col] = self.trial_properties_liststore[row][col]
+        return table
+
+    def set_trial_properties(self, table):
+        self.trial_properties_liststore.clear()
+        for row in range(len(table)):
+            self.trial_properties_liststore.append(table[row])
+
+    def trial_is_valid(self):
+        if not self.trial_title_entry.get_text():
+            functions.error_dialog(self.window, "Please specify the trial title first", "Trial title")
+            self.notebook.set_current_page(0)
+            self.window.set_focus(self.trial_title_entry)
+            return False
+        if not functions.get_text_buffer(self.trial_description_text):
+            functions.error_dialog(self.window, "Please enter a description for this trial first", "Trial description")
+            self.notebook.set_current_page(0)
+            self.window.set_focus(self.trial_description_text)
+            return False
+        if len(self.group_liststore) < 2:
+            functions.error_dialog(self.window, "At least two groups must be defined for the trial", "Trial groups")
+            self.notebook.set_current_page(1)
+            self.window.set_focus(self.group_treeview)
+            return False
+        if len(self.variable_liststore) < 1:
+            functions.error_dialog(self.window, "At least one prognostic factor (variable) must be defined for the trial", "Trial variables")
+            self.notebook.set_current_page(2)
+            self.window.set_focus(self.variable_treeview)
+            return False
+        msg = "Are you sure you want to save the trial?\nOnce you saved a trial no change in trial settings are allowed!\nTrial details:\n" + self.get_trial_info()
+        dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
+        dialog.set_title("Trial Save!")
+        if dialog.run() == gtk.RESPONSE_YES:
+            dialog.destroy()
+            return True
+        dialog.destroy()
+        return False
+
+    def want_trial_unlock(self):
+        msg = "Do you want to unlock the trial!\nUnlocking the trial make changes of trial setting possible!\nTrial file will be released.\nYou have to save the trial again!"
+        dialog = gtk.MessageDialog(self.window, flags = gtk.DIALOG_MODAL, type = gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format = msg)
+        dialog.set_title("Unlock Trial!")
+        if dialog.run() == gtk.RESPONSE_YES:
+            self.lock_trial(True)
+            self.ui_pool = None
+            self.trial_file_name = None
+        dialog.destroy()
+
+    def lock_trial(self, state=False):
+        self.trial_load_button.set_property("sensitive", state)
+        self.trial_save_button.set_property("sensitive", state)
+        self.trial_sample_size_spin.set_property("sensitive", state)
+        for radio in self.prob_method_radios:
+            radio.set_property("sensitive", state)
+        self.min_group_combo.set_property("sensitive", state)
+        self.high_prob_spin.set_property("sensitive", state)
+        for radio in self.distance_measure_radios:
+            radio.set_property("sensitive", state)
+        for child in self.group_hbox.get_children():
+            child.set_property("sensitive", state)
+        for child in self.variable_hbox.get_children():
+            child.set_property("sensitive", state)
+        for child in self.initial_table_vbox.get_children():
+            child.set_property("sensitive", state)
+        self.statusbar.pop(self.sb_context_id)
+        self.statusbar.push(self.sb_context_id, ("Trial lock!", "Trial unlocked!")[state])
+
+    def get_trial_info(self):
+        ret = []
+        ret.append("Title: " + self.trial_title_entry.get_text())
+        ret.append("Sample Size: " + str(self.trial_sample_size_spin.get_value_as_int()))
+        ret.append("Description: " + functions.get_text_buffer(self.trial_description_text))
+        ret.append("Probability Method: " + self.get_prob_method_name())
+        ret.append("Distance Measure: " + self.get_distance_measure_name())
+        ret.append("High Probability: " + str(self.high_prob_spin.get_value()))
+        ret.append("Groups: " + str(self.get_groups_data()))
+        ret.append("Variables: " + str(self.get_variables_data()))
+        return '\n'.join(ret)
+
+    def save_trial(self, file_name=None):
+        if file_name:
+            # do not lock if this is a temporary save
+            self.lock_trial()
+        else:
+            file_name = tempfile.mkstemp()[1]
+        try:
+            data = {}
+            data['ui_pool'] = self.ui_pool
+            data['trial_title'] = self.trial_title_entry.get_text()
+            data['sample_size'] = self.trial_sample_size_spin.get_value_as_int()
+            data['trial_description'] = functions.get_text_buffer(self.trial_description_text)
+            data['trial_properties'] = self.get_trial_properties()
+            data['high_prob'] = self.high_prob_spin.get_value()
+            data['min_group'] = self.min_group_combo.get_active()
+            data['initial_freq_table'] = self.initial_freq_table
+            data['prob_method'] = self.get_prob_method()
+            data['distance_measure'] = self.get_distance_measure()
+            data['allocations'] = self.allocations
+            data['groups'] = self.get_groups_data()
+            data['variables'] = self.get_variables_data()
+            fp = open(file_name, 'wb')
+            pickle.dump(data, fp)
+            fp.close()
+            # useful if temp save
+            return file_name
+        except Exception as ex:
+            functions.error_dialog(self.window, "An unknown error occured during trial save!\n" + repr(ex), "Trial save error!")
+            return False
+
+    def get_distance_measure_name(self):
+        for distance_measure_radio in self.distance_measure_radios:
+            if distance_measure_radio.get_active():
+                return distance_measure_radio.get_label()
+        return "Unknown"
+
+    def get_distance_measure(self):
+        for idx, distance_measure_radio in enumerate(self.distance_measure_radios):
+            if distance_measure_radio.get_active():
+                return idx
+        return 0
+
+    def get_prob_method_name(self):
+        for prob_method_radio in self.prob_method_radios:
+            if prob_method_radio.get_active():
+                return prob_method_radio.get_label()
+        return "Unknown"
+
+    def get_prob_method(self):
+        for idx, prob_method_radio in enumerate(self.prob_method_radios):
+            if prob_method_radio.get_active():
+                return idx
+        return 0
+
+    def get_variables_data(self):
+        variables = []
+        for row in self.variable_liststore:
+            variable = {}
+            variable['name'] = row[0]
+            variable['weight'] = row[1]
+            variable['levels'] = row[2]
+            variables.append(variable)
+        return variables
+
+    def get_groups_data(self):
+        groups = []
+        for row in self.group_liststore:
+            group = {}
+            group['name'] = row[0]
+            group['allocation_ratio'] = row[1]
+            groups.append(group)
+        return groups
+
+    def select_file(self, title, action):
+        """
+        A generic method returning file name
+        """
+        stock = {gtk.FILE_CHOOSER_ACTION_OPEN: gtk.STOCK_OPEN, gtk.FILE_CHOOSER_ACTION_SAVE: gtk.STOCK_SAVE}
+        buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, stock[action], gtk.RESPONSE_OK)
+        fdialog = gtk.FileChooserDialog(title, self.window, action, buttons)
+        if action == gtk.FILE_CHOOSER_ACTION_SAVE:
+            fdialog.set_do_overwrite_confirmation(True)
+        fdialog.set_default_response(gtk.RESPONSE_OK)
+        response = fdialog.run()
+        if response == gtk.RESPONSE_OK:
+            file_name = fdialog.get_filename()
+        else:
+            file_name = None
+        fdialog.destroy()
+        return file_name
+
+def main():
+    gtk.main()
+    return 0
+
+if __name__ == "__main__":
+    ModelInteface()
+    main()
+