def design(self): # lay out the components to project tab self.btn_container1 = Frame(self.tb_projects, bg=background, pady=10) self.btn_container1.pack (side=TOP, fill=X) # BOOKMARK: modal redirections # configure redirections to form modals createProjectModalCmnd = lambda e: (self.windowManager.get_module('CreateProjectModal'))( self, # BOOKMARK_DONE: create project button command lambda formModal: self.create(formModal) ) loadProjectModalCmnd = lambda e: (self.windowManager.get_module('LoadProjectModal'))( self, # BOOKMARK_DONE: create load project button command lambda formModal: self.create(formModal, HomeWindow.PROJECT_LI, True) ) createSessionModalCmnd = lambda e: (self.windowManager.get_module('CreateSessionModal'))( self, # BOOKMARK_DONE: create session button command lambda formModal: self.create(formModal, HomeWindow.SESSION_LI) ) joinSessionModalCmnd = lambda e: (self.windowManager.get_module('JoinModal'))( self, # BOOKMARK_DONE: join session command lambda formModal: self.join_session(formModal) ) joinProjectModalCmnd = lambda e: (self.windowManager.get_module('JoinModal'))( self, # BOOKMARK_DONE: join project command lambda formModal: self.join_project(formModal) ) self.btn_create_project = MainButton(self.btn_container1, translate('Create New Project'), 'new_project.png', createProjectModalCmnd) self.btn_create_project.pack(side=LEFT, padx=(0, 10)) self.btn_open = SecondaryButton(self.btn_container1, translate('Load From Device'), 'upload.png', loadProjectModalCmnd) self.btn_open.pack(side=LEFT, padx=(0, 10)) self.btn_open = SecondaryButton(self.btn_container1, translate('Access Project'), 'login.png', joinProjectModalCmnd) self.btn_open.pack(side=RIGHT) self.lv_project = Scrollable(self.tb_projects, bg=background, pady=15) self.lv_project.set_gridcols(4) self.lv_project.pack(fill=BOTH, expand=1) # lay out the components to session tab self.btn_container2 = Frame(self.tb_sessions, bg=background, pady=10, padx=1) self.btn_container2.pack (side=TOP, fill=X) self.btn_create_session = MainButton(self.btn_container2, translate('Create New Session'), 'new_session.png', createSessionModalCmnd) self.btn_create_session.pack(side=LEFT, padx=(0, 10)) self.btn_join_session = SecondaryButton(self.btn_container2, translate('Join Session'), 'login.png', joinSessionModalCmnd) self.btn_join_session.pack(side=RIGHT) self.lv_session = Scrollable(self.tb_sessions, bg=background, pady=15) self.lv_session.set_gridcols(4) self.lv_session.pack(fill=BOTH, expand=1)
def add_list_item(self, container: Scrollable, **li_args): """ Takes care of instantiating a list item and then append it to the container, and then returns the instanciated item. """ # Shrinking the finding operation f_arg = lambda key: li_args.get(key, None) # Creating an item li = ListItem(container, f_arg('dataObject'), f_arg('bindings'), f_arg('buttons'), f_arg('creationMethod')) # Appending the item container.pack_item(li) # Returning the created item return li
def show_popup(self, x, y, data: list, factory): if hasattr(self, 'frm_popup') == False: self.frm_popup = Scrollable(self, bg=background) self.frm_popup.pack_propagate(0) self.frm_popup.config(highlightthickness=1, highlightbackground=border) self.frm_popup.bind('<Leave>', lambda e: self.hide_component('frm_popup')) self.frm_popup.empty() self.frm_popup.place(x=x, y=y, width=360, height=480) for item in data: li = factory(self.frm_popup.interior, item) li.pack(side=TOP, fill=X) separator = Frame(self.frm_popup.interior, highlightthickness=1, highlightbackground=border) separator.pack(side=TOP, fill=X)
class HomeWindow(TabbedWindow): PROJECT_LI = 0 SESSION_LI = 1 tabSettings = [ { 'icon': 'folder.png', 'text': translate('My Projects'), 'tag': 'tb_projects' }, { 'icon': 'session.png', 'text': translate('My Sessions'), 'tag': 'tb_sessions' } ] def __init__(self, root, **args): TabbedWindow.__init__(self, root, HomeWindow.tabSettings, 'Welcome', **args) # Design elements self.design() # Fill methods self.fill_projects() self.fill_sessions() def design(self): # lay out the components to project tab self.btn_container1 = Frame(self.tb_projects, bg=background, pady=10) self.btn_container1.pack (side=TOP, fill=X) # BOOKMARK: modal redirections # configure redirections to form modals createProjectModalCmnd = lambda e: (self.windowManager.get_module('CreateProjectModal'))( self, # BOOKMARK_DONE: create project button command lambda formModal: self.create(formModal) ) loadProjectModalCmnd = lambda e: (self.windowManager.get_module('LoadProjectModal'))( self, # BOOKMARK_DONE: create load project button command lambda formModal: self.create(formModal, HomeWindow.PROJECT_LI, True) ) createSessionModalCmnd = lambda e: (self.windowManager.get_module('CreateSessionModal'))( self, # BOOKMARK_DONE: create session button command lambda formModal: self.create(formModal, HomeWindow.SESSION_LI) ) joinSessionModalCmnd = lambda e: (self.windowManager.get_module('JoinModal'))( self, # BOOKMARK_DONE: join session command lambda formModal: self.join_session(formModal) ) joinProjectModalCmnd = lambda e: (self.windowManager.get_module('JoinModal'))( self, # BOOKMARK_DONE: join project command lambda formModal: self.join_project(formModal) ) self.btn_create_project = MainButton(self.btn_container1, translate('Create New Project'), 'new_project.png', createProjectModalCmnd) self.btn_create_project.pack(side=LEFT, padx=(0, 10)) self.btn_open = SecondaryButton(self.btn_container1, translate('Load From Device'), 'upload.png', loadProjectModalCmnd) self.btn_open.pack(side=LEFT, padx=(0, 10)) self.btn_open = SecondaryButton(self.btn_container1, translate('Access Project'), 'login.png', joinProjectModalCmnd) self.btn_open.pack(side=RIGHT) self.lv_project = Scrollable(self.tb_projects, bg=background, pady=15) self.lv_project.set_gridcols(4) self.lv_project.pack(fill=BOTH, expand=1) # lay out the components to session tab self.btn_container2 = Frame(self.tb_sessions, bg=background, pady=10, padx=1) self.btn_container2.pack (side=TOP, fill=X) self.btn_create_session = MainButton(self.btn_container2, translate('Create New Session'), 'new_session.png', createSessionModalCmnd) self.btn_create_session.pack(side=LEFT, padx=(0, 10)) self.btn_join_session = SecondaryButton(self.btn_container2, translate('Join Session'), 'login.png', joinSessionModalCmnd) self.btn_join_session.pack(side=RIGHT) self.lv_session = Scrollable(self.tb_sessions, bg=background, pady=15) self.lv_session.set_gridcols(4) self.lv_session.pack(fill=BOTH, expand=1) def set_image(self, li, image): if image != None: photo = getdisplayableimage(image,(li.winfo_width(),205)) li.lbl_image.configure(image=photo) li.lbl_image.image = photo def resize_image(self, event, li, image): self.set_image(li,image) # BOOKMARK_DONE: fill project items def fill_projects(self): # cleaning up old items self.lv_project.empty() # refilling for item in Container.filter(Project, Project.ownerId == HomeWindow.ACTIVE_USER.id): if Container.filter(Session, Session.projectId == item.id).first() == None: li = self.lv_project.grid_item(item, {'title': item.title, 'creationDate': item.creationDate, 'lastEdited': item.lastEdited, 'image': item.image}, None, lambda i: self.create_list_item(i), 15) self.set_image(li, item.image) li.lbl_image.bind('<Configure>', lambda e, l=li, image= item.image: self.resize_image(e, l, image)) # BOOKMARK_DONE: fill session items def fill_sessions(self): # cleaning up old items self.lv_session.empty() # refilling for item in Container.filter(Session): if item.owner == HomeWindow.ACTIVE_USER or Container.filter(Collaboration, Collaboration.userId == HomeWindow.ACTIVE_USER.id, Collaboration.sessionId == item.id).first() != None: li = self.lv_session.grid_item(item, {'title': item.title, 'creationDate': item.creationDate, 'lastEdited': item.project.lastEdited, 'memberCount': str(Container.filter(Collaboration,Collaboration.sessionId == item.id).count()+1), 'image': item.project.image}, None, lambda i: self.create_list_item(i, HomeWindow.SESSION_LI), 15) self.set_image(li, item.project.image) li.lbl_image.bind('<Configure>', lambda e, l=li, image= item.project.image: self.resize_image(e, l, image)) # BOOKMARK: Project List Item & Session List Item Creation Method def create_list_item(self, item: ListItem, liType: int = PROJECT_LI): item.configure (highlightthickness=1, highlightbackground=border, bg=white) comps = [] img_size = 205 img_thumb = Frame(item, height=img_size, bg=white) img_thumb.pack_propagate(0) # adding image label item.lbl_image = Label(img_thumb,bg='white') item.lbl_image.pack(fill=BOTH,expand=1) comps.append(item.lbl_image) border_bottom = Frame(item, bg=border) frm_info = Frame(item, bg=silver, padx=10, pady=10) frm_details = Frame(frm_info, bg=silver) lbl_title = Label(frm_info, text=item.bindings.get('title', '{title}'), bg=silver, fg=black, font='-size 18') lbl_title.pack(side=TOP, fill=X, pady=(0, 10)) comps.append(lbl_title) panel_settings = [ { 'label': translate('Created In:'), 'text': item.bindings.get('creationDate').strftime('%a, %d %b') if 'creationDate' in item.bindings else '{creationDate}' }, { 'label': translate('Edited In:'), 'text': item.bindings.get('lastEdited').strftime('%a, %d %b') if 'lastEdited' in item.bindings else '{lastEdited}' } ] if liType == HomeWindow.SESSION_LI: panel_settings.append({ 'label': translate('Members:'), 'text': item.bindings.get('memberCount', '{memberCount}') }) for i in panel_settings: frm = Frame(frm_details, bg=silver) frm.pack(side=LEFT, fill=X, expand=1) comps.append(frm) lbl_label = Label(frm, text=i.get('label'), fg=teal, bg=silver, font='-size 8') lbl_label.pack(fill=X) comps.append(lbl_label) lbl_text = Label(frm, text=i.get('text'), fg=gray, bg=silver, font='-size 9') lbl_text.pack(fill=X) comps.append(lbl_text) img_thumb.pack(fill=X, side=TOP) comps.append(img_thumb) border_bottom.pack(fill=X, side=TOP) frm_info.pack(fill=X, side=TOP) comps.append(frm_info) frm_details.pack(fill=X, side=TOP) comps.append(frm_details) # BOOKMARK_DONE: project item's menu def options_menu(e): # convert screen mouse position win_coords = self.to_window_coords(e.x_root, e.y_root) menu_buttons = [ { 'text': translate('Open'), 'icon': 'open.png', 'cmnd': lambda e: self.windowManager.run(ProjectWindow(self, item.dataObject)) if liType == HomeWindow.PROJECT_LI else self.windowManager.run(CollaborationWindow(self, item.dataObject)) }, { 'text': translate('Edit'), 'icon': 'edit.png', 'cmnd': lambda e: self.windowManager.run_tag('editor', item.dataObject) }, { 'text': translate('Share'), 'icon': 'share.png', 'cmnd': lambda e: (self.windowManager.get_module('ShareModal'))(self, lambda modal: self.generate_share_link(item.dataObject, modal)) }, { 'text': translate('Delete'), 'icon': 'delete.png', 'fg': danger, 'cmnd': lambda e: self.delete_project(item.dataObject) if liType == self.PROJECT_LI else self.delete_session(item.dataObject) }, { 'text': translate('Leave'), 'icon': 'logout.png', 'fg': danger, 'cmnd': lambda e: self.quit_session(item.dataObject) } ] # pop unwanted buttons if liType == self.SESSION_LI: # remove share button menu_buttons.pop(2) # remove delete or leave menu_buttons.pop(2 if item.dataObject.owner != HomeWindow.ACTIVE_USER else 3) else: # remove leave menu_buttons.pop() # show menu self.show_menu(x=win_coords[0], y=win_coords[1], width=150, height=200, options=menu_buttons) # options icon IconFrame(item, 'resources/icons/ui/menu.png', 10, teal, 32, options_menu, bg=white).place(relx=1-0.03, rely=0.02, anchor=N+E) for c in comps: c.bind('<Double-Button-1>', lambda e: self.windowManager.run(ProjectWindow(self, item.dataObject)) if liType == HomeWindow.PROJECT_LI else self.windowManager.run(CollaborationWindow(self, item.dataObject))) # BOOKMARK: this is how a COMMAND should be def create(self, modal, nature= PROJECT_LI, load= False): title = modal.get_form_data()['txt_title'] date = datetime.now() def create_project(bytesFile= None): project = Project(title= title, creationDate= datetime.now(), lastEdited= datetime.now(), owner= HomeWindow.ACTIVE_USER, file= bytesFile) Container.save(project) self.lv_project.grid_item(project, {'title': project.title, 'creationDate': project.creationDate, 'lastEdited': project.lastEdited}, None, lambda i: self.create_list_item(i, HomeWindow.PROJECT_LI), 15) def create_session(): project = Project(title= title+'Project', creationDate= date, lastEdited= date, owner= HomeWindow.ACTIVE_USER) session = Session(title= title, creationDate= date, owner= HomeWindow.ACTIVE_USER, project= project) message = Message(content=f'welcome to the chat',user=HomeWindow.ACTIVE_USER, session=session, sentDate=session.creationDate) Container.save(project, session, message) seenmessage = SeenMessage(date=session.creationDate, seer=HomeWindow.ACTIVE_USER, messageId=message.id) Container.save(seenmessage) self.lv_session.grid_item(session, {'title': session.title, 'creationDate': project.creationDate, 'lastEdited': project.lastEdited, 'memberCount': str(Container.filter(Collaboration,Collaboration.sessionId == session.id).count()+1)}, None, lambda i: self.create_list_item(i, HomeWindow.SESSION_LI), 15) def load_project(): title = modal.get_form_data()['txt_title'] filename: str= modal.lbl_filename['text'] if filename.endswith('...') != True and filename != '': create_project(filetobytes(filename)) if not re.fullmatch('^[a-zA-Z0-9_]+( [a-zA-Z0-9_]+)*$', title): MessageModal(self,title=f'Title error',message=f'\n1. Must be between 4 - 20 characters \n2. It should not contain any special character',messageType='error') else: create_session() if nature == HomeWindow.SESSION_LI else ( create_project() if load == False else load_project()) modal.destroy() def delete(self, dataObject): Container.deleteObject(dataObject) self.clean_notifications() self.refresh_window() def delete_project(self, dataObject): MessageModal(self,title=f'Confirmation',message=f'Do you want to delete [{dataObject.title}] project?',messageType='prompt',actions={'yes' : lambda e: self.delete(dataObject)}) def delete_session(self, dataObject): MessageModal(self,title=f'Confirmation',message=f'Do you want to delete [{dataObject.title}] session?',messageType='prompt',actions={'yes' : lambda e: self.delete(dataObject.project)}) def quit_session(self, dataObject): MessageModal(self,title=f'Confirmation',message=f'Do you want to quit [{dataObject.title}] session?',messageType='prompt',actions={'yes' : lambda e: self.delete(Container.filter(Collaboration, Collaboration.userId == HomeWindow.ACTIVE_USER.id, Collaboration.sessionId == dataObject.id).first())}) # BOOKMARK: this should redirect to the editor window def join_project(self, modal): link = modal.get_form_data()['txt_link'] slink = Container.filter(ShareLink, ShareLink.link == link).first() if slink == None: MessageModal(self, title= f'Link error' ,message= 'This link doesn\'t exist!', messageType= 'error') elif slink.expirationDate < datetime.now(): MessageModal(self, title= f'Link error' ,message= 'This link has expired!', messageType= 'error') else: if slink.project.owner != HomeWindow.ACTIVE_USER :noti = Notification(notificationTime= datetime.now(), type= NotificationType.JOINED.value, nature= NotificationNature.SHARELINK.value, invitationId= slink.id, actor= HomeWindow.ACTIVE_USER, recipient= slink.project.owner) modal.destroy() self.windowManager.run(EditorWindow(self.master, slink.project)) def join_session(self, modal): link = modal.get_form_data()['txt_link'] date = datetime.now() invlink = Container.filter(InvitationLink, InvitationLink.link == link).first() if invlink == None: MessageModal(self, title= f'Link error' ,message= 'This link doesn\'t exist!', messageType= 'error') elif invlink.expirationDate < datetime.now(): MessageModal(self, title= f'Link error' ,message= 'This link has expired!', messageType= 'error') elif Container.filter(Collaboration, Collaboration.userId == HomeWindow.ACTIVE_USER.id, Collaboration.sessionId == invlink.sessionId).first() != None or invlink.session.owner == HomeWindow.ACTIVE_USER: MessageModal(self, title= f'Error' ,message= f'You are already in {invlink.session.title} session!', messageType= 'error') else: # create relations if they don't exist if Container.filter(Relation,Relation.userOne == HomeWindow.ACTIVE_USER, Relation.userTwo == invlink.sender ).first() == None: Container.save(Relation(userOne= HomeWindow.ACTIVE_USER, userTwo= invlink.sender)) if Container.filter(Relation,Relation.userTwo == HomeWindow.ACTIVE_USER, Relation.userOne == invlink.sender ).first() == None: Container.save(Relation(userTwo= HomeWindow.ACTIVE_USER, userOne= invlink.sender)) # create collaboration Container.save(Collaboration(joiningDate= date, privilege= invlink.privilege, user= HomeWindow.ACTIVE_USER, session= invlink.session)) # add an acceptedInv type notification noti = Notification(notificationTime= date, type= NotificationType.JOINED.value, nature= NotificationNature.INVLINK.value, invitationId= invlink.id, actor= HomeWindow.ACTIVE_USER, recipient= invlink.sender) modal.destroy() self.windowManager.run(CollaborationWindow(self.master, invlink.session)) def generate_share_link(self, dataObject, modal): def set_link(link): modal.form[0]['input'].entry.delete(0,END) modal.form[0]['input'].entry.insert(0,link) def check_privilege(msg, modal, slink): def generate_link(msg2, modal, slink, privilege): msg2.destroy() if slink != None: Container.deleteObject(slink) link= f'bpmntool//{dataObject.title}/{datetime.now()}/' Container.save(ShareLink(link=link, expirationDate=datetime.now()+timedelta(days=1), privilege= privilege, project=dataObject)) self.clean_notifications() set_link(link) if msg != None: msg.destroy() msg2 = MessageModal(self,title=f'Confirmation',message=f'Do you want to grant this link the "edit" privilege?',messageType='prompt',actions={'yes' : lambda e: generate_link(msg2, modal, slink, 'edit'), 'no' : lambda e: generate_link(msg2, modal, slink, 'read')}) def set_old_link(msg,modal): set_link(slink.link) msg.destroy() slink = Container.filter(ShareLink, ShareLink.projectId == dataObject.id).first() if slink != None: msg = check_privilege(None, modal, slink) if slink.expirationDate < datetime.now() else MessageModal(self,title='Link found',message=f'An active link already exists, Do you want to override it?',messageType='prompt',actions={'yes': lambda e: check_privilege(msg, modal, slink) , 'no': lambda e: set_old_link(msg,modal)}) else: check_privilege(None, modal, None) def refresh_window(self, message=None): window = HomeWindow(self.master) self.windowManager.run(window) if message != None: MessageModal(window,title=f'Success',message=message,messageType='info') self.destroy()
def design(self): # Putting the control buttons btn_container = Frame(self.frm_body, bg=background) btn_container.pack(fill=X, side=TOP) for i in self.btnSettings: childCount = len(btn_container.pack_slaves()) method = i.get('type', MainButton) btn = method(btn_container, i.get('text', 'Button'), i.get('icon', 'error.png'), i.get('cmnd', None)) btn.pack(side=i.get('dock', RIGHT), padx=(0 if childCount == 0 else 10, 0)) # Filling the information tab frm_label_container = Frame(self.tb_info, bg=black) frm_label_container.pack(side=TOP, fill=X) for i in CollaborationWindow.lblSettings: frm_lbl_group = Frame(frm_label_container, bg=background) frm_lbl_group.pack(side=LEFT, fill=X, expand=1) lbl_label = Label(frm_lbl_group, bg=background, fg=teal, font='-size 16', text=i.get('label'), anchor='nw') lbl_label.pack(side=TOP, fill=X) lbl_prop = Label(frm_lbl_group, bg=background, fg=black, font='-size 13', text=i.get('prop'), anchor='nw') lbl_prop.pack(side=TOP, fill=X) setattr(self, 'lbl_' + i.get('prop'), lbl_prop) frm_group = Frame(self.tb_info, bg=background) frm_group.pack(expand=1, fill=BOTH, pady=15) self.frm_preview = Frame(frm_group, bg=white, highlightthickness=1, highlightbackground=border) self.frm_preview.pack(side=LEFT, fill=BOTH, expand=1) self.frm_preview.update() def resize_image(event, label): if self.session.project.image != None: photo = getdisplayableimage(self.session.project.image, (self.frm_preview.winfo_width(), self.frm_preview.winfo_height())) label.configure(image=photo) label.image = photo self.lbl_image = Label(self.frm_preview, bg='white') self.lbl_image.pack(fill=BOTH, expand=1) self.lbl_image.bind('<Configure>', lambda e, l=self.lbl_image: resize_image(e, l)) # Filling the history tab self.frm_list_view = Scrollable(self.tb_hist, bg=background) self.frm_list_view.pack(expand=1, fill=BOTH, pady=(0, 15)) # filling member tab self.lv_members = Scrollable(self.tb_member, bg=background) self.lv_members.pack(fill=BOTH, expand=1, pady=(0, 15)) self.fill_collaboration()
class CollaborationWindow(TabbedWindow): tabSettings = [{ 'icon': 'info.png', 'text': translate('General Info'), 'tag': 'tb_info' }, { 'icon': 'history.png', 'text': translate('History'), 'tag': 'tb_hist' }, { 'icon': 'session.png', 'text': translate('Members'), 'tag': 'tb_member' }] lblSettings = [{ 'label': translate('Session\'s Title:'), 'prop': 'title' }, { 'label': translate('Creation Date:'), 'prop': 'creationDate' }, { 'label': translate('Last Edit:'), 'prop': 'lastEdit' }, { 'label': translate('Members:'), 'prop': 'memberCount' }] def __init__(self, root, session=None, **args): TabbedWindow.__init__(self, root, CollaborationWindow.tabSettings, session.title, **args) self.session = session self.collaboratorItems = [] self.historyItems = [] # configure button settings self.btnSettings = [ { 'icon': 'edit.png', 'text': translate('Open Editor'), 'dock': LEFT, 'cmnd': lambda e: self.windowManager.run( EditorWindow(self.master, self.session)) }, { 'icon': 'delete.png', 'text': translate('End Session'), 'type': DangerButton, # BOOKARK: Ending Session Command 'cmnd': lambda e: self.show_prompt( 'Are you sure you want to terminate the session?', lambda e: self.delete_session(), 'Terminating the session') }, { 'icon': 'invite.png', 'text': translate('Invite'), 'type': SecondaryButton, 'cmnd': lambda e: (self.windowManager.get_module('InviteModal'))( self, # BOOKMARK_DONE: Activate Link Command lambda modal: self.generate_inviationlink( modal), # modal.get_form_data()['txt_link'] # BOOKMARK_DONE: Invite User Command lambda modal: self.invite_user(modal.get_form_data()[ 'txt_username'])) }, { 'icon': 'save.png', 'text': translate('Export to XML'), 'dock': LEFT, 'cmnd': lambda e: self.export_project( f'current_{self.session.project.title}', self.session. project.lastEdited, self.session.project.file) } ] if CollaborationWindow.ACTIVE_USER != self.session.owner: self.btnSettings.pop(1) # Design elements self.design() # Fill session members self.fill_members() def generate_inviationlink(self, modal): # modal.form[0]['input'].entry.get() def set_link(link): modal.form[0]['input'].entry.delete(0, END) modal.form[0]['input'].entry.insert(0, link) def check_privilege(msg, modal, inv): def generate_link(msg2, modal, inv, privilege): msg2.destroy() if inv != None: Container.deleteObject(inv) link = f'bpmntool//{self.session.title}/{datetime.datetime.now()}/' Container.save( InvitationLink(link=link, expirationDate=datetime.datetime.now() + datetime.timedelta(days=1), privilege=privilege, sender=CollaborationWindow.ACTIVE_USER, session=self.session)) self.clean_notifications() set_link(link) if msg != None: msg.destroy() msg2 = MessageModal( self, title=f'Confirmation', message=f'Do you want to grant this link the "edit" privilege?', messageType='prompt', actions={ 'yes': lambda e: generate_link(msg2, modal, inv, 'edit'), 'no': lambda e: generate_link(msg2, modal, inv, 'read') }) def set_old_link(msg, modal): set_link(inv.link) msg.destroy() inv = Container.filter( InvitationLink, InvitationLink.senderId == CollaborationWindow.ACTIVE_USER.id, InvitationLink.sessionId == self.session.id).first() if inv != None: msg = check_privilege( None, modal, inv ) if inv.expirationDate < datetime.datetime.now( ) else MessageModal( self, title='link found', message=f'A link already exists, Do you want to override it?', messageType='prompt', actions={ 'yes': lambda e: check_privilege(msg, modal, inv), 'no': lambda e: set_old_link(msg, modal) }) else: check_privilege(None, modal, None) def invite_user(self, username): def send_invite(msg, user, privilege): inv = Invitation(privilege=privilege, invitationTime=datetime.datetime.now(), sender=CollaborationWindow.ACTIVE_USER, recipient=user, session=self.session) Container.save(inv) notif = Notification(type=NotificationType.INVITED.value, notificationTime=datetime.datetime.now(), nature=NotificationNature.INV.value, invitationId=inv.id, actor=inv.sender, recipient=inv.recipient) Container.save(notif) msg.destroy() MessageModal( self, title=f'Success', message=f'Invitation sent to {user.userName} successfully!', messageType='info') user = Container.filter(User, User.userName == username).first() collabs = Container.filter( User, Collaboration.sessionId == self.session.id, or_(User.id == Collaboration.userId, User.id == self.session.ownerId)).all() if user == None: MessageModal( self, title='User error 404', message=f'{username} doesn\'t exist!' if username != '' and not str.isspace(username) else 'Please enter a username!', messageType='error') elif user in collabs: MessageModal(self, title='User already in', message=f'{username} is already in the session!', messageType='error') elif Container.filter(Invitation, Invitation.recipientId == user.id, Invitation.sessionId == self.session.id, Invitation.status == Status.PENDING.value).first() != None: MessageModal(self, title='User already invited', message=f'An invite is already sent to {username}!', messageType='info') else: msg = MessageModal( self, title=f'Confirmation', message= f'An invitation will be sent to [{username}], but do you want to give him the right to make changes?', messageType='prompt', actions={ 'yes': lambda e: send_invite(msg, user, 'edit'), 'no': lambda e: send_invite(msg, user, 'read') }) def delete_session(self): Container.deleteObject(self.session.project) self.clean_notifications() self.windowManager.run_tag('home') self.destroy() def configure_settings(self): # get_label = lambda prop: getattr(self, f'lbl_{prop}') # change label get_label(CollaborationWindow.lblSettings[0] ['prop'])['text'] = self.session.title get_label( CollaborationWindow.lblSettings[1]['prop'] )['text'] = self.session.creationDate.strftime( "%d/%m/%Y" ) if datetime.datetime.now( ).strftime("%x") != self.session.creationDate.strftime( "%x") else 'Today at - ' + self.session.creationDate.strftime("%X") get_label( CollaborationWindow.lblSettings[2]['prop'] )['text'] = self.session.project.lastEdited.strftime( "%d/%m/%Y" ) if datetime.datetime.now( ).strftime("%x") != self.session.project.lastEdited.strftime( "%x" ) else 'Today at - ' + self.session.project.lastEdited.strftime("%X") get_label(CollaborationWindow.lblSettings[3]['prop'])['text'] = str( Container.filter(Collaboration, Collaboration.sessionId == self.session.id).count() + 1) # change image if self.session.project.image != None: photo = getdisplayableimage(self.session.project.image, (self.frm_preview.winfo_width(), self.frm_preview.winfo_height())) self.lbl_image.configure(image=photo) self.lbl_image.image = photo # fill self.fill_collaboration() self.fill_members() def design(self): # Putting the control buttons btn_container = Frame(self.frm_body, bg=background) btn_container.pack(fill=X, side=TOP) for i in self.btnSettings: childCount = len(btn_container.pack_slaves()) method = i.get('type', MainButton) btn = method(btn_container, i.get('text', 'Button'), i.get('icon', 'error.png'), i.get('cmnd', None)) btn.pack(side=i.get('dock', RIGHT), padx=(0 if childCount == 0 else 10, 0)) # Filling the information tab frm_label_container = Frame(self.tb_info, bg=black) frm_label_container.pack(side=TOP, fill=X) for i in CollaborationWindow.lblSettings: frm_lbl_group = Frame(frm_label_container, bg=background) frm_lbl_group.pack(side=LEFT, fill=X, expand=1) lbl_label = Label(frm_lbl_group, bg=background, fg=teal, font='-size 16', text=i.get('label'), anchor='nw') lbl_label.pack(side=TOP, fill=X) lbl_prop = Label(frm_lbl_group, bg=background, fg=black, font='-size 13', text=i.get('prop'), anchor='nw') lbl_prop.pack(side=TOP, fill=X) setattr(self, 'lbl_' + i.get('prop'), lbl_prop) frm_group = Frame(self.tb_info, bg=background) frm_group.pack(expand=1, fill=BOTH, pady=15) self.frm_preview = Frame(frm_group, bg=white, highlightthickness=1, highlightbackground=border) self.frm_preview.pack(side=LEFT, fill=BOTH, expand=1) self.frm_preview.update() def resize_image(event, label): if self.session.project.image != None: photo = getdisplayableimage(self.session.project.image, (self.frm_preview.winfo_width(), self.frm_preview.winfo_height())) label.configure(image=photo) label.image = photo self.lbl_image = Label(self.frm_preview, bg='white') self.lbl_image.pack(fill=BOTH, expand=1) self.lbl_image.bind('<Configure>', lambda e, l=self.lbl_image: resize_image(e, l)) # Filling the history tab self.frm_list_view = Scrollable(self.tb_hist, bg=background) self.frm_list_view.pack(expand=1, fill=BOTH, pady=(0, 15)) # filling member tab self.lv_members = Scrollable(self.tb_member, bg=background) self.lv_members.pack(fill=BOTH, expand=1, pady=(0, 15)) self.fill_collaboration() def fill_collaboration(self): self.frm_list_view.empty() # BOOKMARK_DONE: Fill Collaboration Session Change History for i in Container.filter( History, History.projectId == self.session.projectId ).order_by(History.editDate.desc()).all( ): #, or_(History.editorId == Collaboration.userId, History.editorId == self.session.ownerId) since the project is gonna be unique if i.project.owner == CollaborationWindow.ACTIVE_USER or Container.filter( Collaboration, Collaboration.sessionId == self.session.id, Collaboration.userId == CollaborationWindow.ACTIVE_USER.id).first() != None: li = ListItem( self.frm_list_view.interior, i, { 'username': f'{i.editor.userName} edited on {i.editDate.strftime("%d/%m/%Y at %X")}', 'image': i.editor.image }, self.get_btn_list(i)) li.pack(anchor=N + W, fill=X, pady=(0, 10), padx=5) self.historyItems.append(li) def export_project(self, title, date, fileBytes): if fileBytes == None: MessageModal( self, title='Error', message= 'No changes has been made yet on this session\'s project yet!', messageType='error') else: folderName = filedialog.askdirectory( initialdir="/", title='Please select a directory') if folderName != '': bytestofile(f'{folderName}', f'{title}_{date.strftime("%d-%m-%Y_%H-%M-%S")}', 'xml', fileBytes) MessageModal(self, title=f'Success', message=f'File saved in {folderName}!', messageType='info') def get_btn_list(self, history): def ask_revert_changes(history): def revert_changes(msg, history): history.project.file = history.file history.project.lastEdited = history.editDate history.project.image = history.image Container.save(history.project) msg.destroy() for li in self.historyItems: if li.dataObject.editDate >= history.editDate: Container.deleteObject(li.dataObject) li.destroy() photo = getdisplayableimage(history.image, (self.frm_preview.winfo_width(), self.frm_preview.winfo_height())) self.lbl_image.configure(image=photo) self.lbl_image.image = photo MessageModal( self, title=f'Success', message= f'Changes reverted to the following date:\n{history.editDate.strftime("%x - %X")}!', messageType='info') getattr( self, 'lbl_' + CollaborationWindow.lblSettings[2]['prop'] )['text'] = history.editDate.strftime( "%d/%m/%Y" ) if datetime.datetime.now( ).strftime("%x") != history.editDate.strftime( "%x") else 'Today at - ' + history.editDate.strftime("%X") msg = MessageModal( self, title=f'Confirmation', message=f'Are you sure you want to revert to that change?', messageType='prompt', actions={'yes': lambda e: revert_changes(msg, history)}) btn_list = [{ 'icon': 'save.png', 'text': translate('Export to XML'), 'cmd': lambda e: self.export_project(history.project.title, history. editDate, history.file) }, { 'icon': 'revert_history.png', 'text': translate('Revert'), 'cmd': lambda e: ask_revert_changes(history) }] if CollaborationWindow.ACTIVE_USER != self.session.owner: btn_list.pop(1) return btn_list # BOOKMARK_DONE: Fill Collaboration Session Members def fill_members(self): # Remove all items self.lv_members.empty() # Append items for i in Container.filter( User, Collaboration.sessionId == self.session.id, or_(User.id == Collaboration.userId, User.id == self.session.ownerId)).all(): li = ListItem(self.lv_members.interior, i, { 'username': i.userName, 'image': i.image }, [{ 'icon': 'cancel.png', 'text': translate('Kick'), 'cmd': lambda e, user=i: self.kick_user(user), }] if i != self.session.owner else None) li.pack(anchor=N + W, pady=(0, 10), padx=(10, 10), fill=X) self.collaboratorItems.append(li) def kick_user(self, user): def delete_collaboration(user): Container.deleteObject( Container.filter( Collaboration, Collaboration.userId == user.id, Collaboration.sessionId == self.session.id).first()) msg.destroy() for li in self.collaboratorItems: if li.dataObject == user: li.destroy() getattr( self, 'lbl_' + CollaborationWindow.lblSettings[3]['prop'] )['text'] = str( Container.filter( Collaboration, Collaboration.sessionId == self.session.id).count() + 1) MessageModal( self, title=f'Success', message=f'{user.userName} has been kicked out of the session!', messageType='info') msg = MessageModal( self, title=f'Confirmation', message=f'Are you sure you want to kick {user.userName}?', messageType='prompt', actions={'yes': lambda e: delete_collaboration(user)}) def refresh(self): super().refresh() self.configure_settings()
def design(self): # Setup the tab of profile frm_form = Frame(self.tb_info, bg=background) frm_form.pack(fill=BOTH, expand=1) frm_form.rowconfigure(0, weight=1) frm_form.columnconfigure([0, 1, 2], weight=1) frm_image_column = Frame(frm_form, bg=background) frm_image_column.grid(row=0, column=0, padx=(0, 20)) Label(frm_image_column, pady=10, bg=background, font='-size 10 -weight bold', fg=black, text=translate('Profile Photo:'), anchor=N + W).pack(side=TOP, fill=X) frm_image = Frame(frm_image_column, bg=black, height=150) frm_image.pack(side=TOP, fill=X, pady=(0, 10)) self.lbl_image = Label(frm_image) SecondaryButton(frm_image_column, translate('Upload Image'), 'upload.png', btnCmd=lambda event: self.open_image(event)).pack( side=TOP, fill=X) for column in ProfileWindow.formSettings: # Prepare a form column frm_column = Frame(frm_form, bg=background) frm_column.grid(row=0, column=(1 + ProfileWindow.formSettings.index(column))) # Loop through groups for group in column: # Prepare group frm_group = Frame(frm_column, bg=background) frm_group.pack(side=TOP, pady=10, padx=(0, 20)) # Label and textbox Label(frm_group, bg=background, font='-size 10 -weight bold', fg=black, text=group.get('label'), anchor=N + W).pack(side=TOP, fill=X, pady=(0, 5)) txt = TextBox(frm_group, 'resources/icons/ui/' + group.get('icon')) if group['tag'] in ['password', 'confirmPwd']: txt.entry.config(show='*') txt.pack(side=TOP) # Save the textbox in a list in order to get its value self.textBoxes[group.get('tag')] = txt frm_footer = Frame(self.tb_info, bg=background) frm_footer.pack(side=BOTTOM, fill=X) MainButton( frm_footer, translate('Save Changes'), 'save.png', btnCmd=lambda event: self.save_changes(event)).pack(side=LEFT) # Setup the tab of saved collabs frm_tip = IconButton( self.tb_collabs, 'Saved Collaborators are all the users who have participated in a collaboration session with you.', '-size 12 -weight bold', white, 'resources/icons/ui/info.png', 10, None, black, bg=black, pady=10, padx=5) frm_tip.pack(side=TOP, fill=X) self.lv_collabs = Scrollable(self.tb_collabs, bg=background, pady=5) self.lv_collabs.set_gridcols(2) self.lv_collabs.pack(expand=1, fill=BOTH, pady=(15, 0))
class ProfileWindow(TabbedWindow): tabSettings = [{ 'tag': translate('tb_info'), 'text': 'Profile', 'icon': 'account.png' }, { 'tag': translate('tb_collabs'), 'text': translate('Saved Collaborators'), 'icon': 'session.png' }] formSettings = [[{ 'label': translate('First Name:'), 'icon': 'account.png', 'tag': 'firstName' }, { 'label': translate('Last Name:'), 'icon': 'account.png', 'tag': 'lastName' }, { 'label': translate('Username:'******'icon': 'account.png', 'tag': 'userName' }, { 'label': translate('Email:'), 'icon': 'mail.png', 'tag': 'email' }], [{ 'label': translate('Company:'), 'icon': 'business.png', 'tag': 'company' }, { 'label': translate('Gender:'), 'icon': 'wc.png', 'tag': 'gender' }, { 'label': translate('Password:'******'icon': 'key.png', 'tag': 'password' }, { 'label': translate('Confirm Password:'******'icon': 'key.png', 'tag': 'confirmPwd' }]] def __init__(self, root, **args): TabbedWindow.__init__(self, root, ProfileWindow.tabSettings, 'Profile', **args) self.textBoxes = {} self.collaboratorsItems = [] self.design() self.fill_collaborators() self.fill_profile() def design(self): # Setup the tab of profile frm_form = Frame(self.tb_info, bg=background) frm_form.pack(fill=BOTH, expand=1) frm_form.rowconfigure(0, weight=1) frm_form.columnconfigure([0, 1, 2], weight=1) frm_image_column = Frame(frm_form, bg=background) frm_image_column.grid(row=0, column=0, padx=(0, 20)) Label(frm_image_column, pady=10, bg=background, font='-size 10 -weight bold', fg=black, text=translate('Profile Photo:'), anchor=N + W).pack(side=TOP, fill=X) frm_image = Frame(frm_image_column, bg=black, height=150) frm_image.pack(side=TOP, fill=X, pady=(0, 10)) self.lbl_image = Label(frm_image) SecondaryButton(frm_image_column, translate('Upload Image'), 'upload.png', btnCmd=lambda event: self.open_image(event)).pack( side=TOP, fill=X) for column in ProfileWindow.formSettings: # Prepare a form column frm_column = Frame(frm_form, bg=background) frm_column.grid(row=0, column=(1 + ProfileWindow.formSettings.index(column))) # Loop through groups for group in column: # Prepare group frm_group = Frame(frm_column, bg=background) frm_group.pack(side=TOP, pady=10, padx=(0, 20)) # Label and textbox Label(frm_group, bg=background, font='-size 10 -weight bold', fg=black, text=group.get('label'), anchor=N + W).pack(side=TOP, fill=X, pady=(0, 5)) txt = TextBox(frm_group, 'resources/icons/ui/' + group.get('icon')) if group['tag'] in ['password', 'confirmPwd']: txt.entry.config(show='*') txt.pack(side=TOP) # Save the textbox in a list in order to get its value self.textBoxes[group.get('tag')] = txt frm_footer = Frame(self.tb_info, bg=background) frm_footer.pack(side=BOTTOM, fill=X) MainButton( frm_footer, translate('Save Changes'), 'save.png', btnCmd=lambda event: self.save_changes(event)).pack(side=LEFT) # Setup the tab of saved collabs frm_tip = IconButton( self.tb_collabs, 'Saved Collaborators are all the users who have participated in a collaboration session with you.', '-size 12 -weight bold', white, 'resources/icons/ui/info.png', 10, None, black, bg=black, pady=10, padx=5) frm_tip.pack(side=TOP, fill=X) self.lv_collabs = Scrollable(self.tb_collabs, bg=background, pady=5) self.lv_collabs.set_gridcols(2) self.lv_collabs.pack(expand=1, fill=BOTH, pady=(15, 0)) # select a ne image def open_image(self, event): self.set_image( filetobytes( filedialog.askopenfilename(initialdir="/", title="Select file", filetypes=(("jpeg/jpg files", "*.jpg"), ("png files", "*.png"), ("all files", "*.*"))))) # display current image def set_image(self, img): setattr(self, 'image', img) if img != None: photo = getdisplayableimage(img, (160, 150)) self.lbl_image.configure(image=photo) self.lbl_image.image = photo self.lbl_image.pack() # fill current user's profile def fill_profile(self): self.set_image(ProfileWindow.ACTIVE_USER.image) for key, value in self.textBoxes.items(): if hasattr( ProfileWindow.ACTIVE_USER, key) and getattr(ProfileWindow.ACTIVE_USER, key) != None: value.entry.insert(0, getattr(ProfileWindow.ACTIVE_USER, key)) # BOOKMARK_DONE: this one is responsible for filling the scrollable container def fill_collaborators(self): self.lv_collabs.empty() for i in Container.filter( Relation, Relation.userOneId == ProfileWindow.ACTIVE_USER.id): self.collaboratorsItems.append( self.lv_collabs.grid_item(i, { 'username': i.userTwo.userName, 'image': i.userTwo.image }, [{ 'text': 'Remove', 'icon': 'cancel.png', 'cmd': lambda e, relation=i: self.remove_collaborator(relation) }], None, 15)) # BOOKMARK_DONE: this method takes care of getting the data from the form in a form of dictionary def get_form_data(self): dic = {} # get data from textboxes for key, value in self.textBoxes.items(): dic[key] = value.entry.get() if value.entry.get() != '' else None # get images dic['image'] = getattr(self, 'image') return dic # validate form data def validate_form_data(self, data): try: for key, value in data.items(): if value == None and key not in ['confirmPwd', 'image']: raise Exception(camel_case(key), f'{key} Cannot be null!') elif key in ['firstName', 'lastName'] and not re.fullmatch( '[A-Za-z]{2,15}( [A-Za-z]{2,15})?', value): raise Exception( camel_case(key), f'\n1. Can contain 2 words with 1 space in between\n2.Must be between 2 - 15 alphabets each' ) elif key in [ 'userName', 'password' ] and not re.fullmatch('^[a-zA-Z0-9_.-]+$', value): raise Exception( camel_case(key), f'can only be alphanumeric and contain (. _ -)') elif key == 'email' and not re.fullmatch( '[^@]+@[^@]+\.[^@]+', value): raise Exception( camel_case(key), f'Please enter a valid email!\nExample: [email protected]' ) elif key == 'company' and value != None and not re.fullmatch( '^[a-zA-Z0-9_]+( [a-zA-Z0-9_]+)*$', value): raise Exception( camel_case(key), f'\n1. Must be between 4 - 20 characters\n2. Should not contain any special characters' ) elif key == 'gender' and value != None and value.lower( ) not in ['female', 'male']: raise Exception(camel_case(key), f'Gender must be either male or female!') elif key == 'confirmPwd' and value != data['password']: raise Exception( 'password confirmation', f'Passwords don\'t match, please confirm your password!' ) return True except Exception as ex: MessageModal(self, title=f'{ex.args[0]} Error', message=ex.args[1], messageType='error') return False def refresh_window(self, message=None): window = ProfileWindow(self.master) self.windowManager.run(window) if message != None: MessageModal(window, title=f'Success', message=message, messageType='info') self.destroy() def save_changes(self, event): data = self.get_form_data() # check if data is validated if self.validate_form_data(data) == True: # make changes on current user for key, value in data.items(): if hasattr(ProfileWindow.ACTIVE_USER, key): setattr(ProfileWindow.ACTIVE_USER, key, value) # save changes Container.save(ProfileWindow.ACTIVE_USER) # reload the window self.refresh_window('Changes saved succefully!') def remove_collaborator(self, relation): def delete_relation(relation): # delete relation Container.deleteObject(relation) # destroy message msg.destroy() # remove deleted collaborator's listItem for li in self.collaboratorsItems: if li.dataObject == relation: li.destroy() # self.collaboratorItems.remove(li) self.refresh_window( f'{relation.userTwo.userName} has been removed succefully!') # confirm with the user msg = MessageModal( self, title=f'Confirmation', message= f'Are you sure you want to remove {relation.userTwo.userName} from your collaboration list?', messageType='prompt', actions={'yes': lambda e: delete_relation(relation)})
def design(self): # Session items section self.frm_body.config(padx=0, pady=0) frm_sessions = Frame(self.frm_body, bg=background, width=300) frm_border = Frame(self.frm_body, highlightthickness=1, highlightbackground=border) frm_discussion = Frame(self.frm_body, bg=background) frm_sessions.pack_propagate(0) self.lv_sessions = Scrollable(frm_sessions, bg=silver) self.lv_sessions.pack(fill=BOTH, expand=1) frm_sessions.pack(side=LEFT, fill=Y) frm_border.pack(side=LEFT, fill=Y) frm_discussion.pack(side=LEFT, fill=BOTH, expand=1) # Discussion Section frm_head = Frame(frm_discussion, bg=white, pady=20, padx=20) frm_head.pack(side=TOP, fill=X) frm_border_bottom = Frame(frm_discussion, highlightthickness=1, highlightbackground=border) frm_border_bottom.pack(fill=X, side=TOP) frm_group = Frame(frm_head, bg=white) frm_group.pack(side=LEFT, fill=X) self.lbl_sessionName = Label(frm_group, bg=white, fg=teal, text=translate('Session\'s Title'), font='-size 18 -weight bold') self.lbl_sessionName.pack(anchor=N + W) self.lbl_memberCount = Label(frm_group, bg=white, fg=black, text='X members') self.lbl_memberCount.pack(anchor=N + W) self.btn_open_session = MainButton( frm_head, translate('Open Session'), 'open.png', btnCmd=lambda event: self.open_session(event)) self.btn_open_session.config(pady=5) self.btn_open_session.pack(side=RIGHT) frm_scrollable_container = Frame(frm_discussion, bg=black) frm_scrollable_container.pack_propagate(0) frm_scrollable_container.pack(side=TOP, fill=BOTH, expand=1) self.lv_messages = Scrollable(frm_scrollable_container, bg=background, padx=15, pady=15) self.lv_messages.pack(expand=1, fill=BOTH) frm_footer = Frame(frm_discussion, bg=white, pady=20, padx=20) frm_footer.pack(side=BOTTOM, fill=X) self.is_hint_deleted = False self.txt_message = Entry(frm_footer, highlightthickness=0, relief=FLAT, font='-size 12', fg=black) self.txt_message.insert(0, translate('Type your message here...')) self.txt_message.pack(side=LEFT, fill=BOTH, expand=1) def txt_message_focus(e): self.is_hint_deleted = True self.txt_message.delete(0, len(self.txt_message.get())) self.txt_message.bind('<FocusIn>', txt_message_focus) IconFrame(frm_footer, 'resources/icons/ui/send.png', 25, teal, command=lambda event: self.send_message( event, self.currentItem)).pack(side=RIGHT) frm_border_top = Frame(frm_discussion, highlightthickness=1, highlightbackground=border) frm_border_top.pack(side=BOTTOM, fill=X) self.frm_veil = Frame(frm_discussion, bg=background) self.frm_veil.place(relwidth=1, relheight=1, x=0, y=0)
class DiscussionWindow(SessionWindow): CHAT_ACTIVE = { 'bg': teal, 'lbl_username': background, 'lbl_user': white, 'lbl_content': white, 'lbl_time': white } # Message Item Styles MSG_INCOMING = SessionWindow.CHAT_NORMAL MSG_OUTGOING = CHAT_ACTIVE def __init__(self, root, **args): SessionWindow.__init__(self, root, 'Discussions') self.currentItem = None self.msgItems = [] self.design() self.fill_sessions() if args.get('session', None) != None: for li in self.msgItems: if li.dataObject.session == args['session']: self.Configure_session(None, li) break def design(self): # Session items section self.frm_body.config(padx=0, pady=0) frm_sessions = Frame(self.frm_body, bg=background, width=300) frm_border = Frame(self.frm_body, highlightthickness=1, highlightbackground=border) frm_discussion = Frame(self.frm_body, bg=background) frm_sessions.pack_propagate(0) self.lv_sessions = Scrollable(frm_sessions, bg=silver) self.lv_sessions.pack(fill=BOTH, expand=1) frm_sessions.pack(side=LEFT, fill=Y) frm_border.pack(side=LEFT, fill=Y) frm_discussion.pack(side=LEFT, fill=BOTH, expand=1) # Discussion Section frm_head = Frame(frm_discussion, bg=white, pady=20, padx=20) frm_head.pack(side=TOP, fill=X) frm_border_bottom = Frame(frm_discussion, highlightthickness=1, highlightbackground=border) frm_border_bottom.pack(fill=X, side=TOP) frm_group = Frame(frm_head, bg=white) frm_group.pack(side=LEFT, fill=X) self.lbl_sessionName = Label(frm_group, bg=white, fg=teal, text=translate('Session\'s Title'), font='-size 18 -weight bold') self.lbl_sessionName.pack(anchor=N + W) self.lbl_memberCount = Label(frm_group, bg=white, fg=black, text='X members') self.lbl_memberCount.pack(anchor=N + W) self.btn_open_session = MainButton( frm_head, translate('Open Session'), 'open.png', btnCmd=lambda event: self.open_session(event)) self.btn_open_session.config(pady=5) self.btn_open_session.pack(side=RIGHT) frm_scrollable_container = Frame(frm_discussion, bg=black) frm_scrollable_container.pack_propagate(0) frm_scrollable_container.pack(side=TOP, fill=BOTH, expand=1) self.lv_messages = Scrollable(frm_scrollable_container, bg=background, padx=15, pady=15) self.lv_messages.pack(expand=1, fill=BOTH) frm_footer = Frame(frm_discussion, bg=white, pady=20, padx=20) frm_footer.pack(side=BOTTOM, fill=X) self.is_hint_deleted = False self.txt_message = Entry(frm_footer, highlightthickness=0, relief=FLAT, font='-size 12', fg=black) self.txt_message.insert(0, translate('Type your message here...')) self.txt_message.pack(side=LEFT, fill=BOTH, expand=1) def txt_message_focus(e): self.is_hint_deleted = True self.txt_message.delete(0, len(self.txt_message.get())) self.txt_message.bind('<FocusIn>', txt_message_focus) IconFrame(frm_footer, 'resources/icons/ui/send.png', 25, teal, command=lambda event: self.send_message( event, self.currentItem)).pack(side=RIGHT) frm_border_top = Frame(frm_discussion, highlightthickness=1, highlightbackground=border) frm_border_top.pack(side=BOTTOM, fill=X) self.frm_veil = Frame(frm_discussion, bg=background) self.frm_veil.place(relwidth=1, relheight=1, x=0, y=0) def runnable2(self): # try: while self.time_to_kill != True: time.sleep(2) # Container.session.commit() for li in self.msgItems: lastmsg = Container.filter( Message, Message.sessionId == li.dataObject.session.id).order_by( Message.sentDate.desc()).first() if lastmsg != None and lastmsg != li.dataObject: li.lbl_content['text'] = lastmsg.content if len( lastmsg.content) < 20 else lastmsg.content[:17] + '...' li.lbl_time['text'] = lastmsg.sentDate.strftime("%X") li.dataObject = lastmsg self.change_session_item_style(li, self.CHAT_UNREAD) if self.currentItem != None and self.currentItem == li: self.currentItem = li self.create_message(lastmsg) # except Exception: # # Container.session.rollback() # print('RUNNABLE2 ERROR') def hide(self): # thread killer logic will be here self.time_to_kill = True # continute the original work super().hide() def refresh(self): super().refresh() self.time_to_kill = False # start thread threading.Thread(target=self.runnable2).start() def open_session(self, event): if self.currentItem != None: self.windowManager.run( CollaborationWindow(self, self.currentItem.dataObject.session)) def Configure_session(self, event, listItem): self.frm_veil.place_forget() self.fill_discussion(listItem.dataObject.session) self.lbl_sessionName['text'] = listItem.dataObject.session.title self.lbl_memberCount[ 'text'] = f'{Container.filter(Collaboration,Collaboration.sessionId == listItem.dataObject.session.id).count()+1} ' + translate( 'Members') self.txt_message.bind('<Return>', lambda event, listItem=listItem: self. send_message(event, listItem)) if self.currentItem != None: self.change_session_item_style(self.currentItem, self.CHAT_NORMAL) self.currentItem = listItem self.change_session_item_style(self.currentItem, self.CHAT_ACTIVE) currentSession = self.currentItem.dataObject.session # mark previous messages as seen prevmsgs = Container.filter( Message, Message.sessionId == currentSession.id, Message.userId != DiscussionWindow.ACTIVE_USER.id) # mark them as seen for m in prevmsgs: # check if already marked if len( Container.filter( SeenMessage, SeenMessage.messageId == m.id, SeenMessage.seerId == DiscussionWindow.ACTIVE_USER.id).all()) > 0: continue # mark as read Container.save( SeenMessage(date=datetime.datetime.now(), seer=DiscussionWindow.ACTIVE_USER, message=m)) # Configure sessionlistitem click event def configure_session_click(self): for li in self.msgItems: for control in [ li, li.lbl_username, li.img_photo, li.frm_content, li.lbl_user, li.lbl_content, li.lbl_time ]: control.bind('<Button-1>', lambda event, listItem=li: self.Configure_session( event, listItem)) # control.bind('<Button-1>', lambda e: print ('haha')) def send_message(self, event, listItem): if self.txt_message.get() != '' and not str.isspace( self.txt_message.get( )) and listItem != None and self.is_hint_deleted == True: msg = Message(content=self.txt_message.get(), sentDate=datetime.datetime.now(), user=DiscussionWindow.ACTIVE_USER, session=listItem.dataObject.session) Container.save(msg) listItem.dataObject = msg listItem.lbl_content['text'] = self.txt_message.get() if len( self.txt_message.get()) < 20 else ( self.txt_message.get()[:17] + '...') self.txt_message.delete(0, END) self.fill_discussion(listItem.dataObject.session) # BOOKMARK_DONE: Fill chat sessions def fill_sessions(self): self.msgItems.clear() self.lv_sessions.empty() for i in Container.filter(Session): if i.owner == DiscussionWindow.ACTIVE_USER or Container.filter( Collaboration, Collaboration.userId == DiscussionWindow.ACTIVE_USER.id, Collaboration.sessionId == i.id).first() != None: msg = Container.filter(Message, Message.sessionId == i.id).order_by( Message.sentDate.desc()).first() # if msg == None: msg = Message(content=f'welcome to the chat',user=i.owner, session=i, sentDate=i.creationDate) li = ListItemFactory.DiscussionListItem( self.lv_sessions.interior, msg) self.msgItems.append(li) if msg.user != self.ACTIVE_USER and Container.filter( SeenMessage, SeenMessage.messageId == msg.id, SeenMessage.seerId == DiscussionWindow.ACTIVE_USER.id).first() == None: self.change_session_item_style(li, self.CHAT_UNREAD) # append to a static a list # configure item click self.configure_session_click() # BOOKMARK_DONE: Fill Messages def fill_discussion(self, session): # dispose of old items self.lv_messages.empty() # fill with new items for i in Container.filter(Message, Message.sessionId == session.id).order_by( Message.sentDate.asc()).all(): self.create_message(i) # scroll up self.lv_messages.canvas.yview_moveto(0) # scrolling down thread def scroll_down_thread(): # import sleep method from time import sleep # sleep for a while sleep(0.25) # scroll down self.lv_messages.canvas.yview_moveto(1) # start thread threading.Thread(target=scroll_down_thread).start() def create_message(self, i): createMethod = lambda item: DiscussionWindow.create_message_item( item, DiscussionWindow.MSG_INCOMING if i.user != DiscussionWindow. ACTIVE_USER else DiscussionWindow.MSG_OUTGOING) return ListItem( self.lv_messages.interior, None, { 'username': i.user.userName, 'content': i.content, 'time': i.sentDate.strftime("%d/%m/%Y") if datetime.datetime.now().strftime("%x") != i.sentDate.strftime("%x") else i.sentDate.strftime("%X") }, None, createMethod) # Message item def create_message_item(item, style=MSG_INCOMING): hPivot = W if style == DiscussionWindow.MSG_INCOMING else E bubbleSize = [16, 20] bubbleCoords = { W: [(bubbleSize[0] / 2, bubbleSize[1] / 2), (bubbleSize[0], bubbleSize[1]), (0, bubbleSize[1])], E: [(0, 0), (bubbleSize[0], 0), (bubbleSize[0] / 2, bubbleSize[1] / 2)] } item.pack(side=TOP, anchor=N + hPivot) item.config(bg=background) bubble = Canvas(item, bg=background, width=20, height=20, highlightthickness=0) bubble.pack(side=(TOP if hPivot == W else BOTTOM), anchor=hPivot, padx=5) bubble.create_polygon(bubbleCoords[hPivot], fill=(border if hPivot == W else teal)) frm_message = Frame(item) frm_message.pack(side=TOP) frm_message.config(padx=10, pady=10) frm_group = Frame(frm_message) frm_group.pack(side=LEFT, padx=(0, 30)) item.lbl_username = Label(frm_group, text=item.bindings.get( 'username', '{username}'), font='-size 14') item.lbl_content = Label(frm_group, wraplength=500, justify='left', text=item.bindings.get( 'content', '{content}')) item.lbl_time = Label(frm_message, text=item.bindings.get('time', '{time}'), font='-size 8') item.lbl_username.pack(side=TOP, anchor=N + W) item.lbl_content.pack(side=TOP, anchor=N + W) item.lbl_time.pack(side=RIGHT, anchor=N, pady=5) # Change background for i in [ frm_message, frm_group, item.lbl_username, item.lbl_content, item.lbl_time ]: i.config(bg=style['bg']) # Change foreground for i in style.keys(): if hasattr(item, i): getattr(item, i).config(fg=style[i]) # Apply a border if style == DiscussionWindow.MSG_INCOMING: frm_message.config(highlightthickness=1, highlightbackground=border)
class ProjectWindow(TabbedWindow): tabSettings = [{ 'icon': 'info.png', 'text': translate('General Info'), 'tag': 'tb_info' }, { 'icon': 'history.png', 'text': translate('History'), 'tag': 'tb_hist' }] lblSettings = [{ 'label': translate('Project\'s Title:'), 'prop': 'title' }, { 'label': translate('Creation Date:'), 'prop': 'creationDate' }, { 'label': translate('Last Edit:'), 'prop': 'lastEdit' }] def __init__(self, root, project=None, **args): TabbedWindow.__init__(self, root, ProjectWindow.tabSettings, project.title, **args) self.project = project self.historyItems = [] # Button settings self.btnSettings = [ { 'icon': 'edit.png', 'text': translate('Open Editor'), 'dock': LEFT, 'cmnd': lambda e: self.windowManager.run( EditorWindow(self.master, self.project)) }, { 'icon': 'share.png', 'text': translate('Share Project'), 'type': SecondaryButton, 'cmnd': lambda e: (self.windowManager.get_module('ShareModal'))( self, # BOOKMARK_DONE: Share Project Command lambda modal: self.generate_share_link( self.project, modal)) }, { 'icon': 'save.png', 'text': translate('Export as XML'), 'cmnd': lambda e: self. export_project('current_' + self.project.title, self.project. lastEdited, self.project.file) } ] # Design elements self.design() self.configure_settings() def get_btn_list(self, history): def ask_revert_changes(history): def revert_changes(msg, history): history.project.file = history.file history.project.lastEdited = history.editDate history.project.image = history.image Container.save(history.project) msg.destroy() for li in self.historyItems: if li.dataObject.editDate >= history.editDate: Container.deleteObject(li.dataObject) li.destroy() photo = getdisplayableimage(history.image, (self.frm_preview.winfo_width(), self.frm_preview.winfo_height())) self.lbl_image.configure(image=photo) self.lbl_image.image = photo MessageModal( self, title=f'Success', message= f'Changes reverted to the following date:\n{history.editDate.strftime("%x - %X")}!', messageType='info') getattr( self, 'lbl_' + ProjectWindow.lblSettings[2]['prop'] )['text'] = history.editDate.strftime( "%d/%m/%Y" ) if datetime.datetime.now( ).strftime("%x") != history.editDate.strftime( "%x") else 'Today at - ' + history.editDate.strftime("%X") msg = MessageModal( self, title=f'Confirmation', message=f'Are you sure you want to revert to that change?', messageType='prompt', actions={'yes': lambda e: revert_changes(msg, history)}) btns = [{ 'icon': 'save.png', 'text': translate('Export as XML'), 'cmd': lambda e: self.export_project(history.project.title, history. editDate, history.file) }, { 'icon': 'revert_history.png', 'text': translate('Revert'), 'cmd': lambda e: ask_revert_changes(history) }] return btns def design(self): # Putting the control buttons btn_container = Frame(self.frm_body, bg=background) btn_container.pack(fill=X, side=TOP) for i in self.btnSettings: childCount = len(btn_container.pack_slaves()) method = i.get('type', MainButton) btn = method(btn_container, i.get('text', 'Button'), i.get('icon', 'error.png'), i.get('cmnd', None)) btn.pack(side=i.get('dock', RIGHT), padx=(0 if childCount == 0 else 10, 0)) # Filling the information tab frm_label_container = Frame(self.tb_info, bg=black) frm_label_container.pack(side=TOP, fill=X) for i in ProjectWindow.lblSettings: frm_lbl_group = Frame(frm_label_container, bg=background) frm_lbl_group.pack(side=LEFT, fill=X, expand=1) lbl_label = Label(frm_lbl_group, bg=background, fg=teal, font='-size 16', text=i.get('label'), anchor='nw') lbl_label.pack(side=TOP, fill=X) lbl_prop = Label(frm_lbl_group, bg=background, fg=black, font='-size 13', text=i.get('prop'), anchor='nw') lbl_prop.pack(side=TOP, fill=X) setattr(self, 'lbl_' + i.get('prop'), lbl_prop) self.frm_preview = Frame(self.tb_info, bg=white, highlightthickness=1, highlightbackground=border) self.frm_preview.pack(expand=1, fill=BOTH, pady=15) self.frm_preview.update() def resize_image(event, label): if self.project.image != None: photo = getdisplayableimage(self.project.image, (self.frm_preview.winfo_width(), self.frm_preview.winfo_height())) label.configure(image=photo) label.image = photo self.lbl_image = Label(self.frm_preview, bg='white') self.lbl_image.pack(fill=BOTH, expand=1) self.lbl_image.bind('<Configure>', lambda e, l=self.lbl_image: resize_image(e, l)) # Filling the history tab self.frm_list_view = Scrollable(self.tb_hist, bg=background) self.frm_list_view.pack(expand=1, fill=BOTH, pady=(0, 15)) self.fill_history() def fill_history(self): # BOOKMARK: fill history items self.frm_list_view.empty() for i in Container.filter( History, History.projectId == self.project.id).order_by( History.editDate.desc()): li = ListItem( self.frm_list_view.interior, i, { 'username': f'{i.editor.userName} edited on {i.editDate.strftime("%d/%m/%Y at %X")}', 'image': i.editor.image }, self.get_btn_list(i)) li.pack(anchor=N + W, pady=(0, 10), fill=X, padx=5) self.historyItems.append(li) def configure_settings(self): def get_label(prop): return getattr(self, f'lbl_{prop}') # changing texts get_label( ProjectWindow.lblSettings[0]['prop'])['text'] = self.project.title get_label( ProjectWindow.lblSettings[1]['prop'] )['text'] = self.project.creationDate.strftime( "%d/%m/%Y" ) if datetime.datetime.now( ).strftime("%x") != self.project.creationDate.strftime( "%x") else 'Today at - ' + self.project.creationDate.strftime("%X") get_label( ProjectWindow.lblSettings[2]['prop'] )['text'] = self.project.lastEdited.strftime( "%d/%m/%Y" ) if datetime.datetime.now( ).strftime("%x") != self.project.lastEdited.strftime( "%x") else 'Today at - ' + self.project.lastEdited.strftime("%X") # update image if self.project.image == None: return # proceed photo = getdisplayableimage( self.project.image, (self.frm_preview.winfo_width(), self.frm_preview.winfo_height())) self.lbl_image.configure(image=photo) self.lbl_image.image = photo def export_project(self, title, date, fileBytes): if fileBytes == None: MessageModal(self, title='Error', message='No changes has been made on this project!', messageType='error') else: folderName = filedialog.askdirectory( initialdir="/", title='Please select a directory') if folderName != '': bytestofile(f'{folderName}', f'{title}_{date.strftime("%d-%m-%Y_%H-%M-%S")}', 'xml', fileBytes) MessageModal(self, title=f'Success', message=f'File saved in {folderName}!', messageType='info') def generate_share_link(self, dataObject, modal): def set_link(link): modal.form[0]['input'].entry.delete(0, END) modal.form[0]['input'].entry.insert(0, link) def check_privilege(msg, modal, slink): def generate_link(msg2, modal, slink, privilege): msg2.destroy() if slink != None: Container.deleteObject(slink) link = f'bpmntool//{dataObject.title}/{datetime.datetime.now()}/' Container.save( ShareLink(link=link, expirationDate=datetime.datetime.now() + datetime.timedelta(days=1), privilege=privilege, project=dataObject)) self.clean_notifications() set_link(link) if msg != None: msg.destroy() msg2 = MessageModal( self, title=f'Confirmation', message=f'Do you want to grant this link the "edit" privilege?', messageType='prompt', actions={ 'yes': lambda e: generate_link(msg2, modal, slink, 'edit'), 'no': lambda e: generate_link(msg2, modal, slink, 'read') }) def set_old_link(msg, modal): set_link(slink.link) msg.destroy() slink = Container.filter(ShareLink, ShareLink.projectId == dataObject.id).first() if slink != None: msg = check_privilege( None, modal, slink ) if slink.expirationDate < datetime.datetime.now( ) else MessageModal( self, title='Link found', message=f'A link already exists, Do you want to override it?', messageType='prompt', actions={ 'yes': lambda e: check_privilege(msg, modal, slink), 'no': lambda e: set_old_link(msg, modal) }) else: check_privilege(None, modal, None) def refresh(self): super().refresh() self.configure_settings() self.fill_history()
class Window(Toplevel): """ The base class of all windows in the project, this window shall hold all the essential and general mechanisms of a regular window. """ DEFAULT_WIDTH = 1024 DEFAULT_HEIGHT = 768 IS_MAXIMIZED = False def __init__(self, root, title='Window', width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT, **args): Toplevel.__init__(self, root, **args) # Default configurations self.pack_propagate(0) self.grid_propagate(0) self.title(title) self.config(bg=background, width=width, height=height) self.center() self.attributes('-zoomed', Window.IS_MAXIMIZED) # self.bind('<Configure>', lambda e: self.change_window_state()) def center(self): # Update the idle tasks of the window self.update() # Calculate the offset in order to center the window xOff = int((self.winfo_screenwidth() - self.winfo_width()) / 2) yOff = int((self.winfo_screenheight() - self.winfo_height()) / 2) # Centering the window self.geometry(f'{self.winfo_width()}x{self.winfo_height()}+{xOff}+{yOff}') def add_list_item(self, container: Scrollable, **li_args): """ Takes care of instantiating a list item and then append it to the container, and then returns the instanciated item. """ # Shrinking the finding operation f_arg = lambda key: li_args.get(key, None) # Creating an item li = ListItem(container, f_arg('dataObject'), f_arg('bindings'), f_arg('buttons'), f_arg('creationMethod')) # Appending the item container.pack_item(li) # Returning the created item return li def set_manager(self, manager): self.windowManager = manager def set_opacity(self, opacity): self.attributes('-alpha', opacity) def hide(self): self.withdraw() def show(self): self.deiconify() def fade(self, destination=0, onFinish=None): MoveTransition(lambda v: self.set_opacity(v), lambda: float (self.attributes('-alpha')), destination, 0.01, 0, onFinish) # this method is called when the window is being re-drawn or re-shown def refresh(self): pass # this method is called after constructing the window object def initialize(self): pass # this method is responsible for hiding floating components def hide_component(self, name: str): # check if there's a created component if hasattr(self, name): getattr(self, name).place_forget() # converting the position from screen world to window world def to_window_coords(self, screen_x, screen_y): return [screen_x - self.winfo_rootx(), screen_y - self.winfo_rooty()] # displaying the MENU MODAL component def show_menu(self, **options): if hasattr(self, 'frm_menu') == False: self.frm_menu = Frame(self, bg=options.get('bg', white), width=options.get('width', 0), height=options.get('height', 0), highlightthickness=1, highlightbackground=border, padx=5, pady=5) self.frm_menu.bind('<Leave>', lambda e: self.hide_component('frm_menu')) # reposition menu modal self.frm_menu.place(x=options.get('x'), y=options.get('y'), anchor=N+E) # destroy children for child in self.frm_menu.pack_slaves(): child.destroy() # fill new options for option in options.get('options', []): menu_option = IconButton(self.frm_menu, option.get('text', 'Option Text'), '-size 9 -weight bold', option.get('textfg', option.get('fg', teal)), option.get('folder', 'resources/icons/ui/') + option.get('icon'), 12, None, option.get('fg', teal), 28, option.get('cmnd', None), bg=option.get('bg', white)) menu_option.pack(side=TOP, fill=X, anchor=N+W, pady=(0, (0 if options.get('options').index(option) == len(options.get('options'))-1 else 5))) # displaying the OVERLAY component def show_overlay(self): # if there's no overlay frame, then create it if hasattr(self, 'frm_overlay') == False: # create overlay frame self.frm_overlay = Frame(self, bg=black) # configure auto-close event self.frm_overlay.bind('<Button-1>', lambda e: self.hide_component('frm_overlay')) # show overlay self.frm_overlay.place(x=0, y=0, relwidth=1, relheight=1, anchor=N+W) # message modals sections def show_message(self, msgContent: str, msgTitle='Failure', msgType='danger', actions: dict = None): (self.windowManager.get_module('messagemodal'))(self, msgTitle, msgContent, msgType, actions) def show_info(self, content: str, title='Information'): self.show_message(content, title, 'info') def show_prompt(self, question: str, yesCommand, title='Question'): self.show_message(question, title, 'prompt', {'yes': yesCommand}) # pop-up section def show_popup(self, x, y, data: list, factory): if hasattr(self, 'frm_popup') == False: self.frm_popup = Scrollable(self, bg=background) self.frm_popup.pack_propagate(0) self.frm_popup.config(highlightthickness=1, highlightbackground=border) self.frm_popup.bind('<Leave>', lambda e: self.hide_component('frm_popup')) self.frm_popup.empty() self.frm_popup.place(x=x, y=y, width=360, height=480) for item in data: li = factory(self.frm_popup.interior, item) li.pack(side=TOP, fill=X) separator = Frame(self.frm_popup.interior, highlightthickness=1, highlightbackground=border) separator.pack(side=TOP, fill=X) def change_window_state(self): self.update() ww, sw, wh, sh = int(self.winfo_width()), int(self.winfo_screenwidth()), int(self.winfo_height()), int(self.winfo_screenheight()) Window.IS_MAXIMIZED = ww == sw print('Screen Width:', sw) print('Window Width:', ww) print('Screen Height:', sh) print('Window Height:', wh)