CSSE7030 Assignment 2

###################################################################
#
#   CSSE7030 - Assignment 2
#
#   Student Number:
#
#   Student Name:
#
###################################################################

####################################################################
#
# Do not change the following code
#
####################################################################

from Tkinter import *
import tkMessageBox
import tkFileDialog

# Formatting for use in the __str__ methods
PART_FORMAT = "{0:10}{1:30}{2:>10}"
COMPOUND_FORMAT = "{0:10}{1:30}{2:>10}  {3}"

# Note: some of the supplied definitions below rely on the classes
# you need to write.

def load_items_from_file(products, filename):
    """Add the items in the supplied file to the products object.

    load_items_from_file(Products, str) -> None

    Precondition: Assumes the supplied is of the correct format
    """
    fid = open(filename, 'U')
    for line in fid:
        item_info = line.split(',')
        if len(item_info) > 2:     # ignores blank lines
            item_id = item_info[0].strip()
            item_name = item_info[1].strip()
            if ':' in item_info[2]:   # compound 
                items = item_info[2:]
                products.add_item(item_id, 
                                  Compound(item_id, item_name, products, 
                                           get_components(items)))
            else:   # part
                item_cost = int(item_info[2].strip())
                products.add_item(item_id, Part(item_id, item_name, item_cost))
    fid.close()

def get_components(items):
    """Return a list of pairs of IDs and numbers in items.

    get_components(list(str)) -> list((str, int))
    """
    components = []
    for item in items:
        item = item.strip()
        itemid, _, itemnumstr = item.partition(':')
        itemid = itemid.strip()
        itemnumstr = itemnumstr.strip()
        components.append((itemid, int(itemnumstr)))
    return components
 


def save_items_to_file(products, filename):
    """Save the items in products to the given file.

    save_items_to_file(Products, str) -> None
    """
    f = open(filename, 'w')
    keys = products.get_keys()
    for key in keys:
        f.write("{0}\n".format(repr(products.get_item(key))))
    f.close()

def items_string(items_list):
    """Convert a list of Id, number pairs into a string representation.

    items_string(list((str, int))) -> str
    """
    result = []
    for itemid, num in items_list:
        result.append("{0}:{1}".format(itemid, num))
    return ','.join(result)
                                  
     

####################################################################
#
# Insert your code below
#
####################################################################

# Item types.
ITEM=0
PART=1
COMPOUND=2

# Command status.
ADD_PART=1
ADD_COMPOUND=2
UPDATE_NAME=3
UPDATE_COST=4
UPDATE_ITEMS=5

# Model part:

class Item(object):
    # Base class for part and compound class.
    
    def __init__(self, _itemID, _name):
        """ The constructor of this class, initialise the ID and name.

            Item(str, str)
        """
        self.itemID=_itemID
        self.name=_name

    def get_name(self):
        """ Get the name of item.

            get_name()->str
        """
        return self.name

    def get_ID(self):
        """ Get the ID of item.

            get_ID()->str
        """
        return self.itemID

    def set_name(self, new_name):
        self.name=new_name

    def get_depend(self):
        """ This method will be overloaded by compound class, the reason why return an empty list is to prevent errors when iterating all the items which contain compound and non-compound ones.

            get_depend()->list
        """
        return []

    def get_type(self):
        """ Get the type of item for identifing it is part, compound or a base object.

            get_type()->int
        """
        if 'Part' in str(type(self)):
            return PART
        elif 'Compound' in str(type(self)):
            return COMPOUND
        else:
            return ITEM

class Part(Item):
    # This class is for storing part item, inherit from Item class.
    
    def __init__(self, _itemID, _name, _cost):
        """ The constructor of this class, initialise all the arguments in base class and its cost.

            Part(str, str, int)
        """
        Item.__init__(self,_itemID, _name)
        self.cost=_cost

    def get_cost(self):
        """ Get the cost of this part.

            get_cost()->int
        """
        return self.cost

    def set_cost(self, new_cost):
        """ Reset the cost.

            set_cost(int)
        """
        self.cost=new_cost

    def __repr__(self):
        """ Overloading the systematic __repr__ method for printing information.

            print(Part)->str
        """        
        return self.itemID+', '+self.name+', '+str(self.cost)

    def __str__(self):
        """Overloading the systematic __str__ method for adding information to the listbox.

            str(Part)->str
        """
        return PART_FORMAT.format(self.itemID, self.name, str(self.cost))

class Compound(Item):
    # This class is for storing compound item, inherit from Item class.    
    def __init__(self, _itemID, _name, _products, _itemlist):
        """ The constructor of this class, initialise all the arguments in base class, the Products object for obtaining the information of items belong to this compound and a list of items.

            Compound(str, str, Products, list((str,int))))
        """
        Item.__init__(self,_itemID, _name)
        self.products=_products
        self.itemlist=_itemlist

    def get_cost(self):
        """ Get the cost of this compound by calculating the sum cost of all the items belong to this compound.

            get_cost()->int
        """
        total_cost=0
        for item in self.itemlist:
            # Using the cost times its quantity. The cost is obtained from its object, the object is obtained from its ID. 
            total_cost+=(self.products.get_item(item[0]).get_cost())*item[1]
        return total_cost

    def get_items_list(self):
        """ Get its list of items.

            get_items_list()->list((str,int)))
        """
        return self.itemlist

    def get_items_str(self):
        """ Get its list of items in string type.

            get_items_str()->str
        """
        return items_string(self.itemlist)
            
    def set_items(self, new_items):
        """ Reset the list of items.

            set_items(str)
        """
        # The raw input will be splited into a list by comma first then transfer into get_components function.
        self.itemlist=get_components(new_items.split(','))

    def get_depend(self):
        """ Get the IDs of items belong to this compound in a list type.

            get_depend()->list(str)
        """
        depend_list=[]
        for item in self.itemlist:
            depend_list.append(item[0])
        return depend_list

    def __repr__(self):
        """ Overloading the systematic __repr__ method for printing information.

            print(Compound)->str
        """
        return self.itemID+', '+self.name+', '+self.get_items_str()

    def __str__(self):
        """Overloading the systematic __str__ method for adding information to the listbox.

            str(Compound)->str
        """
        if self.get_items_str()!=[]:
            items_string=self.get_items_str()
        else:
            # If the item list is empty then show "None" instead.
            items_string='None'
        return COMPOUND_FORMAT.format(self.itemID, self.name, str(self.get_cost()), items_string)

class Products(object):
    # This class contains a dictonary storing all the items and related methods.
    
    def __init__(self):
        """ This constructor initialise a dictonary for storing all the item and an independent list for storing its keys for manage them in a correct order.

            Products()
        """
        self.items={}
        self.keys_list=[]

    def load_items(self, _filename):
        """ Load items from a file.

            load_items(str)
        """
        load_items_from_file(self, _filename)
        
    def save_items(self, _filename):
        """ Save items to a file.

            save_items(str)
        """
        save_items_to_file(self, _filename)

    def get_item(self, _ID):
        """ Return a Item object accrding to its ID.

            get_item(str)
        """
        return self.items[_ID]

    def add_item(self, new_ID, new_item_obj):
        """ Add an item into the dictonary and its key to the list.

            add_item(str, Item)
        """
        self.keys_list.append(new_ID)
        self.items[new_ID]=new_item_obj

    def remove_item(self, _ID):
        """ Delete an item from the dictonary and its key from the list, according to its ID.

            add_item(str)
        """
        self.keys_list.remove(_ID)
        self.items.pop(_ID)

    def delete_all(self):
        """ Delete all the items.

            delete_all()
        """
        self.keys_list=[]
        self.items={}

    def get_keys(self):
        """ Get all the keys from the keys list in a correct order.

            get_keys()->list(str)
        """
        return self.keys_list

    def check_depend(self, _ID):
        """ Check if the given item is depended by a compound.

            check_depend(str)->bool
        """
        all_items_ID=self.get_keys()
        for item_ID in all_items_ID:
            if _ID in self.items[item_ID].get_depend():
                return True
        return False

# View part:

class View(Listbox):
    # This class contains all the data operations driven by the command button, inherits from Tkinter Listbox class.
    def __init__(self, parent):
        """ The constructor of this class, it recieves the root container from the Controller class.

            View(Tk)
        """
        Listbox.__init__(self, parent, width=160, font="Courier 10")
        # Initialise the Products object.
        self.products=Products()

    def refresh_listbox(self):
        """ Clear then rewrite the list box.

            refresh_listbox()
        """
        self.delete(0,END)
        items=self.products.get_keys()
        for item in items:
            self.insert(END, str(self.products.get_item(item)))

    def get_key_from_selection(self):
        """ Get the key of selected item in the list box.

            get_key_from_selection()->str
        """
        selection=self.curselection()
        if selection!=():
            return (self.get(selection))[:10].strip()
        else:
            # If there is no selection, return a null value.
            return None

    def push_open_file(self):
        """ Load a file by a file name from a file dialog box.

            push_open_file()    
        """
        open_file_name=tkFileDialog.askopenfilename(title="Open", filetypes=[('Text', '*.txt'), ('All files', '*')])
        if open_file_name!='':
            # Reset the Products list.
            self.products.delete_all()
            try:
                self.products.load_items(open_file_name)
            except:
                tkMessageBox.showerror("Open File","Bad format.")
                return
            try:
                self.refresh_listbox()
            except:
                tkMessageBox.showerror("Open File","This file could not be loaded correctly becasue some items in compounds are missing.")
                self.delete(0,END)
                self.products.delete_all()

    def push_save_file(self):
        """ Save data to a file by file name from a file dialog box.

            push_save_file()    
        """
        save_file_name=tkFileDialog.asksavefilename(title="Save", filetypes=[('Text', '*.txt'), ('All files', '*')])
        if save_file_name!='':
            try:
                self.products.save_items(save_file_name)
            except:
                tkMessageBox.showerror("Save File","An unexcepted error occured.")

    def add_part(self, new_ID):
        """ Add a part item to the products object.

            add_part(str)
        """
        if new_ID!='':
            if new_ID not in self.products.get_keys():
                self.products.add_item(new_ID, Part(new_ID, "No Name",0))
                self.refresh_listbox()
            else:
                tkMessageBox.showerror("Add Part","The item ID has already existed.")

    def add_compound(self, new_ID):
        """ Add a compound item to the products object.

            add_compound(str)
        """
        if new_ID!='':
            if new_ID not in self.products.get_keys():
                self.products.add_item(new_ID, Compound(new_ID, "No Name", self.products, []))
                self.refresh_listbox()
            else:
                tkMessageBox.showerror("Add Compound","The item ID has already existed.")

    def update_name(self, new_name):
        """ Update the name of selected item.

            update_name(str)
        """
        if new_name!='':
            key=self.get_key_from_selection()
            if key!=None:
                self.products.get_item(key).set_name(new_name)
                self.refresh_listbox()
            else:
                tkMessageBox.showerror("Update Name","No item selected.")
                
    def update_cost(self, new_cost):
        """ Update the cost of selected part item.

            update_cost(int)
        """
        key=self.get_key_from_selection()
        if key!=None:
            item=self.products.get_item(key)
            if item.get_type()==PART:
                if new_cost.isdigit():
                    item.set_cost(int(new_cost))
                    self.refresh_listbox()
                else:
                    tkMessageBox.showerror("Update Cost","Your input should be a number.")
            else:
                tkMessageBox.showerror("Update Cost","The selection should be part item.")
        else:
            tkMessageBox.showerror("Update Cost","No item selected.")

    def update_items(self, new_items_list):
        """ Update the items list which the selected compound item depending on.

            update_items(str)
        """
        key=self.get_key_from_selection()
        if key!=None:
            item=self.products.get_item(key)
            if item.get_type()==COMPOUND:
                # In order to use the internal methods of compound class to check the format and dependence, a temporary compound will recieve new list first before add to the products list..
                temp=Compound('temp','',self.products,[])
                try:
                    temp.set_items(new_items_list)
                except:
                    tkMessageBox.showerror("Update Items","Bad format.")
                    return
                do=True
                # Check all the items in the new list to ensure them not conflict any conditions below, or the add operation will not be done.
                for sub_item in temp.get_depend():
                    if sub_item not in self.products.get_keys():
                        tkMessageBox.showerror("Update Items","There is at least one item not in the products list.")
                        do=False
                        break
                    elif sub_item==item.get_ID():
                        tkMessageBox.showerror("Update Items","The item could not refer to the compound itself.")
                        do=False
                        break
                if do:
                    item.set_items(new_items_list)
                    self.refresh_listbox()
            else:
                tkMessageBox.showerror("Update Items","The selection should be compound item.")
        else:
            tkMessageBox.showerror("Update Items","No item selected.")

    def push_remove_item(self):
        """ Remove the selected item.

            push_remove_item()
        """
        key=self.get_key_from_selection()
        if key!=None:
            if not self.products.check_depend(key):
                self.products.remove_item(key)    
                self.refresh_listbox()
            else:
                tkMessageBox.showerror("Remove Item","There is at least one compound depends on selected item.")   
        else:
            tkMessageBox.showerror("Remove Item","No item selected.")

# Controller part:

class Controller(object):
    # This class organise all the controllers in the window.
    def __init__(self, window):
        """ The constructor of this class, define all the controllers with their layouts, its parameter recieves the root container from the StoreApp class.

            Controller(Tk)
        """
        # Initialise the View object.
        self.view=View(window)
        self.view.pack(side=TOP, ipady=130)
        
        # Initialise the menu. 
        self.menu_bar=Menu(window)
        window['menu']=self.menu_bar
        self.file_menu=Menu(self.menu_bar)
        self.menu_bar.add_cascade(label='File', menu=self.file_menu)
        self.file_menu.add_command(label='Open Products File', command=self.view.push_open_file)
        self.file_menu.add_command(label='Save Products File', command=self.view.push_save_file)
        self.file_menu.add_command(label='Exit', command=exit)
                
        # Initialise the function buttons, using a Frame to container layout them.
        self.control_bar=Frame(window, width=150)
        self.control_bar.pack(side=TOP, pady=20)
        self.add_part_button=Button(self.control_bar, text="Add Part", command=self.push_add_part)
        self.add_part_button.pack(side=LEFT, padx=10, anchor=CENTER)
        self.add_compound_button=Button(self.control_bar, text="Add Compound", command=self.push_add_compound)
        self.add_compound_button.pack(side=LEFT, padx=10, anchor=CENTER)
        self.update_name_button=Button(self.control_bar, text="Update Name", command=self.push_update_name)
        self.update_name_button.pack(side=LEFT, padx=10, anchor=CENTER)
        self.update_cost_button=Button(self.control_bar, text="Update Cost", command=self.push_update_cost)
        self.update_cost_button.pack(side=LEFT, padx=10, anchor=CENTER)
        self.update_items_button=Button(self.control_bar, text="Update Items", command=self.push_update_items)
        self.update_items_button.pack(side=LEFT, padx=10, anchor=CENTER)
        self.remove_item_button=Button(self.control_bar, text="Remove Item", command=self.view.push_remove_item)
        self.remove_item_button.pack(side=LEFT, padx=10, anchor=CENTER)

        # Initialise the entry area in the botttom, using a Frame to container layout them.
        self.entry_area=Frame(window, width=150)
        self.entry_area.pack(side=TOP, pady=20)
        self.status=Label(self.entry_area, width=30)
        self.status.pack(side=LEFT, padx=10, anchor=CENTER)
        self.entry=Entry(self.entry_area, width=50)
        self.entry.pack(side=LEFT, padx=10, anchor=CENTER)
        self.OK_button=Button(self.entry_area, text="OK", command=self.push_OK)
        self.OK_button.pack(side=LEFT, padx=10, anchor=CENTER)

    # The next 5 methods will be activated after their corresponded buttons are pushed, the actions of them are to change the commandID variable and label text.
    # The function of this variable is to set the function statue before typing in the entry box.

    def push_add_part(self):
        self.commandID=ADD_PART
        self.status.config(text="Add Part ID")

    def push_add_compound(self):
        self.commandID=ADD_COMPOUND
        self.status.config(text="Add Compound ID")

    def push_update_name(self):
        self.commandID=UPDATE_NAME
        self.status.config(text="Update Name")

    def push_update_cost(self):
        self.commandID=UPDATE_COST
        self.status.config(text="Update Cost")

    def push_update_items(self):
        self.commandID=UPDATE_ITEMS
        self.status.config(text="Update Compound Items")        

    # The operations setted beofre will only be done after OK button pushed. 
    def push_OK(self):
        if self.commandID==ADD_PART:
            self.view.add_part(self.entry.get())
        elif self.commandID==ADD_COMPOUND:
            self.view.add_compound(self.entry.get())
        elif self.commandID==UPDATE_NAME:
            self.view.update_name(self.entry.get())
        elif self.commandID==UPDATE_COST:
            self.view.update_cost(self.entry.get())
        elif self.commandID==UPDATE_ITEMS:
            self.view.update_items(self.entry.get())
        # Clear the label box.
        self.status.config(text='')
        # Clear the entry box.
        self.entry.delete(0, END)
        # Reset the command statue.
        self.commandID=None
    
####################################################################
#
# WARNING: Leave the following code at the end of your code
#
# DO NOT CHANGE ANYTHING BELOW
#
####################################################################
                
class StoreApp():
    def __init__(self, master=None):
        master.title("Bikes R Us: Products")
        self.controller = Controller(master)

def main():
    root = Tk()
    app = StoreApp(root)
    root.mainloop()
    

if  __name__ == '__main__':
    main()




你可能感兴趣的:(CSSE7030 Assignment 2)