################################################################### # # 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()