├── Schedule.pdf ├── README.md ├── s.txt ├── scheduleGenerator.py └── gui.py /Schedule.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uderbashi/TimeTable-Generator/HEAD/Schedule.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Timetable Generator 2 | If you are bored of adding your time tiles manually, having a bad visibility of courses, or having an issue with displaying conflicting courses, this is the tool for you. 3 | 4 | It generates a PDF file with colorful tiles to make everything easy to see. `Schedule.pdf` is a demonstration generated from `s.txt` which has the detailed instructions to set your schedule. 5 | 6 | # How to use 7 | The script is written with Python 3 and requires matplotlib library installed. 8 | After setting your input file(s) as explained in `s.txt`, you can run the script by typing 9 | 10 | python3 scheduleGenerator.py file1.txt file2.txt file3.txt 11 | where `file1.txt file2.txt file3.txt` are the list of the files you have. 12 | If no files were given as an argument the script will default to process `s.txt`. 13 | The script will end up generating `Schedule.pdf`containing the schedule(s) indicated by the files. 14 | 15 | To override Monday-Friday 8:30-17:30, or the name of the output file there are command line arguments to help with that, use 16 | 17 | python3 scheduleGenerator.py -h 18 | to get more information about the arguments. 19 | 20 | # Special Thanks 21 | Special thanks to Jacob from StudyGizmo on his hints about overlap resolution. 22 | -------------------------------------------------------------------------------- /s.txt: -------------------------------------------------------------------------------- 1 | # This is a demonstration input file for schedule generator 2 | # In this file the input format will be explined with 3 | # There are four types of lines in this format, comment, label, course, and meeting 4 | 5 | # Comment is what you have been seeing until now, they are the lines starting with # and they are to be ignored by the program, used for documentation purposes 6 | 7 | # Label is a line starting with * and it will be the label on top of the timetable and the table ex. 8 | * Test File 9 | 10 | # Course is the definition of a course, thus its entry in the database will be opened and it will be able to recive meetings 11 | # Its format is CODE, NAME, INSTRUCTOR where 12 | #### CODE is the code of the course that will be used to identify it, 13 | #### NAME is the full name of the course used only in the table, 14 | #### and INSTRUCTOR is the name of the instructor who is going to be teaching that course, also only appearng in the table, 15 | # the feilds must be comma separated ex. 16 | CSE101, Intoduction to Computer Science and Engineering, Alan Turing 17 | 18 | # Meeting is the definition of a meeting of a PREVIOUSLY DEFINED course 19 | # Its format is CODE, DAY, START, END, LOC where 20 | #### CODE is the previously defined code, 21 | #### DAY is a three letter abbreiation of a day with only the first letter being capital ex. Mon , 22 | #### START and END are the stating and the ending times of the course respectively in the format of HHMM in 24-h system ex. 1330 (which is 1:30 PM), 23 | #### and LOC is an optional feild setting the location of the meeting 24 | # the feilds must be comma separated ex. 25 | CSE101, Tue, 1330, 1530, Hall 1 26 | CSE101, Thu, 1230, 1430 27 | 28 | # Here is an example of some schedule 29 | MATH101, Calculus I, Issac Newton 30 | PHYS211, Modern Physics, Albert Einstein 31 | PAS155, Associate Football, Zinedine Zidane 32 | FLC101, Russian Literature I, Lev Tolstoy 33 | CSE111, C Programming Language, Dennis Ritchie 34 | 35 | MATH101, Mon, 930, 1230, Hall 2 36 | MATH101, Wed, 930, 1230, Hall 3 37 | PHYS211, Tue, 830, 1030, Hall 1 38 | PHYS211, Thu, 830, 1030, Hall 1 39 | FLC101, Tue, 1430, 1630, Hall 2 40 | PAS155, Fri, 1430, 1730, Pitch 1 41 | CSE111, Tue, 1530, 1730, Hall 1 42 | CSE111, Thu, 1530, 1730, Hall 1 -------------------------------------------------------------------------------- /scheduleGenerator.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from math import ceil 3 | import matplotlib.pyplot as plt 4 | import matplotlib.transforms as trns 5 | from matplotlib.backends.backend_pdf import PdfPages 6 | from os.path import isfile 7 | import sys 8 | 9 | # constants that will be used to create the schedule. 10 | _DAYS_LIST = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 11 | DAYS_LIST = [] 12 | COLOURS = ["limegreen", "royalblue", "mediumorchid", "gold", "pink", "chocolate", "aqua", "lightcoral", "slateblue", "springgreen", "deepskyblue", "plum", "orange", "firebrick", "olive", "darkcyan"] 13 | START_TIME = 830 14 | END_TIME = 1730 15 | 16 | # Classes to be used in the program 17 | class Course: 18 | def __init__(self, code, name, instructor, colour): 19 | self.code = code 20 | self.name = name 21 | self.instructor = instructor 22 | self.colour = colour 23 | self.times = [] 24 | 25 | class Scheduled: 26 | def __init__(self, course, day, start, end, place): 27 | self.course = course 28 | self.day = day 29 | self.start = start 30 | self.end = end 31 | self.place = place 32 | 33 | self.overlapCols = 1 #how many total columns are there 34 | self.overlapCol = 0 #in which column is the current one standing 35 | self.overlapSpan = 1 #how many columns does the entry span horizontally 36 | 37 | def comparator(self): 38 | return self.day * 10000 + self.start 39 | 40 | def __repr__(self): 41 | return "The course {c} is occuring on {d} from {b} to {e}".format(c=self.course.code, d=DAYS_LIST[self.day], b=self.start, e=self.end) 42 | 43 | def parse(filename): 44 | courses = [] 45 | lineCount = 0 46 | newid = 0 47 | label = "" 48 | for line in open(filename, "r"): 49 | lineCount += 1 50 | 51 | line = str.lstrip(line) 52 | if len(line) == 0 or line[0] == "#": 53 | continue 54 | 55 | if line[0] == "*": 56 | label = line[1:] 57 | label = label.strip() 58 | continue 59 | 60 | data = line.split(",") 61 | data = map(str.strip, data) 62 | data = list(data) 63 | 64 | for current in courses: 65 | if current.code == data[0]: 66 | 67 | if data[1] not in DAYS_LIST: 68 | msg = "Error on line {lc}: \"{d}\" is not a valid day. Make sure you use a correct 3 letter day format ex \"Mon\" with only the first letter capitalised.".format(lc= lineCount, d = data[1]) 69 | sys.exit(msg) 70 | 71 | if int(data[2]) > END_TIME or int(data[2]) < START_TIME: 72 | msg = "Error on line {lc}: \"{d}\" is not a valid statring time. Make sure the time is formated with 24hour style HHMM ex \"0930\" and is within the courses time.".format(lc= lineCount, d = data[2]) 73 | sys.exit(msg) 74 | 75 | if int(data[3]) > END_TIME or int(data[3]) < START_TIME: 76 | msg = "Error on line {lc}: \"{d}\" is not a valid ending time. Make sure the time is formated with 24hour style HHMM ex \"0930\" and is within the courses time.".format(lc= lineCount, d = data[3]) 77 | sys.exit(msg) 78 | 79 | if int(data[3]) <= int(data[2]): 80 | msg = "Error on line {lc}: \"{d},{dd}\" the ending time should be later than the starting time.".format(lc= lineCount, d = data[2], dd = data[3]) 81 | sys.exit(msg) 82 | 83 | if int(data[2]) % 100 >= 60: 84 | msg = "Error on line {lc}: \"{d}\" is not a valid time due to the minutes being larger than 59.".format(lc= lineCount, d = data[2]) 85 | sys.exit(msg) 86 | 87 | if int(data[3]) % 100 >= 60: 88 | msg = "Error on line {lc}: \"{d}\" is not a valid time due to the minutes being larger than 59.".format(lc= lineCount, d = data[3]) 89 | sys.exit(msg) 90 | 91 | current.times.append(Scheduled(current, DAYS_LIST.index(data[1]),int(data[2]),int(data[3]), None if len(data) == 4 else data[4])) 92 | break 93 | else: 94 | courses.append(Course(data[0], data[1], data[2], COLOURS[newid])) 95 | newid += 1 96 | 97 | for current in courses: 98 | current.times.sort(key=Scheduled.comparator) 99 | 100 | return courses, label 101 | 102 | def fillDays(courses): 103 | days = [[] for _ in range(len(DAYS_LIST))] 104 | 105 | for current in courses: 106 | for session in current.times: 107 | days[session.day].append(session) 108 | 109 | for current in days: 110 | current.sort(key=Scheduled.comparator) 111 | 112 | return days 113 | 114 | def detectOverlap(days): 115 | overlapping = [] 116 | 117 | for day in days: 118 | overlapping.clear() 119 | overlapLast = 0 120 | for current in day: 121 | if current.start >= overlapLast: 122 | if len(overlapping) > 1: 123 | resolveOverlap(overlapping) 124 | overlapping.clear() 125 | overlapLast = 0 126 | overlapping.append(current) 127 | if current.end > overlapLast: 128 | overlapLast = current.end 129 | if len(overlapping) > 1: 130 | resolveOverlap(overlapping) 131 | 132 | def resolveOverlap(overlapping): 133 | colCount = len(overlapping) 134 | columns = [[] for _ in range(colCount)] 135 | for i in range(colCount): 136 | columns[i].append(overlapping[i]) 137 | 138 | shiftFlag = True # To enter the loop 139 | while shiftFlag is True: 140 | shiftFlag = False 141 | i = 0 142 | for current in columns[:colCount]: 143 | i += 1 144 | for check in columns[i:]: 145 | if current[-1].end <= check[0].start: 146 | colCount -= 1 147 | current.extend(check) 148 | columns.remove(check) 149 | shiftFlag = True 150 | 151 | i = -1 152 | for col in columns: 153 | i += 1 154 | for current in col: 155 | current.overlapCols = colCount 156 | current.overlapCol = i 157 | 158 | def drawTimetable(days, title): 159 | fig = plt.figure(figsize=(11.7,8.3), dpi=300) 160 | for day in days: 161 | for current in day: 162 | x1 = current.day + (current.overlapCol / current.overlapCols) 163 | x2 = x1 + (current.overlapSpan / current.overlapCols) 164 | ystart = convertTime(current.start) 165 | yend = convertTime(current.end) 166 | plt.fill_between([x1, x2], [ystart, ystart], [yend, yend], color=current.course.colour, edgecolor="k", linewidth=1) 167 | plt.text(x1+0.02, ystart+5, "{0}:{1:02d}".format(current.start//100, current.start%100), va="top", fontsize=7) 168 | plt.text(x1+0.02, yend-5, "{0}:{1:02d}".format(current.end//100, current.end%100), va="bottom", fontsize=7) 169 | plt.text(x1+(x2-x1)*0.5, (ystart+yend)*0.5, current.course.code, ha="center", va="center", fontsize=9) 170 | 171 | plotystart = convertTime(modTime(START_TIME, -30)) 172 | plotyend = convertTime(modTime(END_TIME, 30)) 173 | 174 | ax = fig.gca() 175 | ax.yaxis.grid() 176 | ax.set_ylim(plotyend, plotystart) 177 | ax.set_yticks(range(plotystart, plotyend, 100)) 178 | ax.set_yticklabels(genYLabels()) 179 | ax.xaxis.grid() 180 | ax.set_xlim(0,len(days)) 181 | ax.set_xticks(range(1, len(days)+1)) 182 | ax.set_xticklabels(DAYS_LIST) 183 | for label in ax.xaxis.get_majorticklabels(): 184 | label.set_transform(label.get_transform() + trns.ScaledTranslation(-.9, 0, fig.dpi_scale_trans)) 185 | 186 | ax2 = ax.twinx().twiny() 187 | ax2.set_ylim(ax.get_ylim()) 188 | ax2.set_yticks(ax.get_yticks()) 189 | ax2.set_yticklabels(ax.get_yticklabels()) 190 | ax2.set_xlim(ax.get_xlim()) 191 | ax2.set_xticks(ax.get_xticks()) 192 | ax2.set_xticklabels(ax.get_xticklabels()) 193 | for label in ax2.xaxis.get_majorticklabels(): 194 | label.set_transform(label.get_transform() + trns.ScaledTranslation(-.9, 0, fig.dpi_scale_trans)) 195 | 196 | ax.set_title(title, fontweight ="bold") 197 | 198 | return fig 199 | 200 | def genYLabels(): 201 | labels = [] 202 | 203 | labelQty = ceil((convertTime(END_TIME) - convertTime(START_TIME)) / 100) + 1 204 | 205 | start = modTime(START_TIME, -30) 206 | 207 | for x in range(labelQty): 208 | labels.append("{h}:{m:02d}".format(h=int(start/100), m=start%100)) 209 | start += 100 210 | 211 | return labels 212 | 213 | def modTime(time, mod): 214 | hours = time // 100 215 | minutes = time % 100 216 | 217 | hmod = mod // 60 218 | mmod = mod % 60 219 | 220 | hours += hmod 221 | minutes += mmod 222 | 223 | hours = abs(hours % 24) 224 | 225 | if minutes < 0 : 226 | hours -= 1 227 | minutes = minutes + 60 228 | elif minutes >= 60: 229 | hours += 1 230 | minutes = minutes % 60 231 | 232 | return hours * 100 + minutes 233 | 234 | def convertTime(time): 235 | hours = time // 100 236 | minutes = time % 100 237 | 238 | return hours * 100 + int((minutes / 60) * 100) 239 | 240 | def drawTable(courses, title): 241 | lines = 0 242 | for current in courses: 243 | lines += len(current.times) 244 | 245 | cols = ["Course", "Instructor", "Time", "Place"] 246 | rows = ["" for i in range(lines)] 247 | vals = [["" for i in range(4)] for j in range(lines)] 248 | ccolours = [["" for i in range(4)] for j in range(lines)] 249 | rcolours = ["" for i in range(lines)] 250 | 251 | i = 0 252 | for current in courses: 253 | rows[i] = current.code 254 | vals[i][0] = current.name 255 | vals[i][1] = current.instructor 256 | for time in current.times: 257 | rcolours[i] = current.colour 258 | ccolours[i] = [current.colour for i in range(4)] 259 | vals[i][2] = "{d} {sh:02d}:{sm:02d}-{eh:02d}:{em:02d}".format(d=DAYS_LIST[time.day], sh=time.start//100, sm=time.start%100, eh=time.end//100, em=time.end%100) 260 | vals[i][3] = "" if time.place == None else time.place 261 | i += 1 262 | 263 | fig = plt.figure(figsize=(11.7,8.3), dpi=300) 264 | ax = fig.subplots() 265 | ax.set_axis_off() 266 | table = ax.table( 267 | cellText = vals, 268 | rowLabels = rows, 269 | colLabels = cols, 270 | cellColours = ccolours, 271 | rowColours = rcolours, 272 | cellLoc ="center", 273 | colLoc ="center", 274 | rowLoc ="center", 275 | loc ="upper left") 276 | 277 | ax.set_title(title, fontweight ="bold") 278 | return fig 279 | 280 | def main(argv): 281 | ## Parse Arguments 282 | parser = argparse.ArgumentParser(description="Schedule Generator generates a beautiful PDF schedule for schools taking a text file as an input.\nThe input file should follow the format shopwn in s.txt file.") 283 | parser.add_argument('-f', '--firstDay', metavar='', type=str, default='Mon', choices=['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], help="The first day in the week, has to be one of {'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'}") 284 | parser.add_argument('-d', '--days', metavar='', type=int, default=5, help="How many days are desired in the schedule of the week") 285 | parser.add_argument('-s', '--startTime', metavar='', type=int, default=830, help="The stariting time of the schedule in the format HHMM") 286 | parser.add_argument('-e', '--endTime', metavar='', type=int, default=1730, help="The ending time of the schedule in the format HHMM") 287 | parser.add_argument('-o', '--output', metavar='', type=str, default='Schedule', help="The name of the output pdf file (.pdf will be added automatically)") 288 | parser.add_argument('files', nargs='*', type=str, default=['s.txt'], help="The input text files containing the details of the schedule") 289 | args = parser.parse_args(args=argv) 290 | 291 | ## Check arguments 292 | if args.startTime >= args.endTime: 293 | msg = "Error: Start time cannot be greater than ending time!" 294 | sys.exit(msg) 295 | if args.startTime < 0: 296 | msg = "Error: Start time cannot be less than 0!" 297 | sys.exit(msg) 298 | if args.endTime > 2400: 299 | msg = "Error: Ending time cannot be greater than than 24:00!" 300 | sys.exit(msg) 301 | if args.startTime % 100 > 59 or args.endTime % 100 > 59: 302 | msg = "Error: Time cannot contain more than 59 minutes (XX59)!" 303 | sys.exit(msg) 304 | if args.days < 1 or args.days > 7: 305 | msg = "Error: Days should be between 1-7!" 306 | sys.exit(msg) 307 | for file in args.files: 308 | if not isfile(file): 309 | msg = "Error: the file {} does not exist!".format(file) 310 | sys.exit(msg) 311 | 312 | #Use arguments 313 | global _DAYS_LIST 314 | global DAYS_LIST 315 | global START_TIME 316 | global END_TIME 317 | START_TIME = args.startTime 318 | END_TIME = args.endTime 319 | DAYS_LIST = [] 320 | i = _DAYS_LIST.index(args.firstDay) 321 | for x in range(args.days): 322 | DAYS_LIST.append(_DAYS_LIST[i]) 323 | i = (i+1)%7 324 | 325 | pp = PdfPages(args.output+".pdf") 326 | for file in args.files: 327 | courses, title = parse(file) 328 | days = fillDays(courses) 329 | detectOverlap(days) 330 | ttbl = drawTimetable(days, title) 331 | tbl = drawTable(courses, title) 332 | pp.savefig(ttbl) 333 | pp.savefig(tbl) 334 | pp.close() 335 | 336 | if __name__ == "__main__": 337 | main(sys.argv[1:]) 338 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | from os.path import isfile 2 | import PySimpleGUI as sg 3 | from re import match 4 | import scheduleGenerator 5 | 6 | 7 | HOURS = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23] 8 | MINUTES = [0,15,30,45,"--",1,2,3,4,5,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,33,34,35,36,37,38,39,40,41,42,43,44,46,47,48,49,50,51,52,53,54,55,56,57,58,59] 9 | DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 10 | 11 | def make_main(): 12 | main_layout = [ 13 | [sg.Text("Schedule Generator GUI", justification='center', font=("Helvetica", 25))], 14 | [sg.Frame(layout=[ 15 | [sg.Column([ 16 | [sg.Text("Starting Time:")], 17 | [sg.Text("Ending Time:")] 18 | ]), 19 | sg.Column([ 20 | [sg.Combo(HOURS, size=(3, 1), readonly=True, default_value=8, key='shours')], 21 | [sg.Combo(HOURS, size=(3, 1), readonly=True, default_value=18, key='ehours')] 22 | ]), 23 | sg.Column([ 24 | [sg.Combo(MINUTES, size=(3, 1), readonly=True, default_value=0, key='smins')], 25 | [sg.Combo(MINUTES, size=(3, 1), readonly=True, default_value=0, key='emins')] 26 | ]), 27 | sg.Column([ 28 | [sg.Text("Starting Day:")], 29 | [sg.Text("Ending Day:")] 30 | ]), 31 | sg.Column([ 32 | [sg.Combo(DAYS, size=(4, 1), readonly=True, default_value='Mon', key='sday')], 33 | [sg.Combo(DAYS, size=(4, 1), readonly=True, default_value='Fri', key='eday')] 34 | ])] 35 | ], title='Options', relief=sg.RELIEF_SUNKEN)], 36 | [sg.Frame(layout=[ 37 | [ 38 | sg.Listbox([], size=(60, 6), key='fileList'), 39 | sg.Column([ 40 | [sg.Input(key='mainBrowse', visible=False, enable_events=True), sg.FileBrowse(size=(8,1), file_types=(('Text Files (.txt)', '*.txt'),))], 41 | [sg.Button("Create", size=(8,1), key='fileCreate')], 42 | [sg.Button("Remove", size=(8,1), key='fileRemove')] 43 | ]) 44 | ] 45 | ], title='Input Files', relief=sg.RELIEF_SUNKEN)], 46 | [sg.Frame(layout=[ 47 | [sg.Text('Path', size=(55, 1), key='pdfPath'), sg.Input(key='pdfBrowse', visible=False, enable_events=True), sg.FolderBrowse(size=(8,1))], 48 | [ 49 | sg.InputText('Schedule', key='pdfName'), 50 | sg.Text('.pdf') 51 | ] 52 | ], title='Output File', relief=sg.RELIEF_SUNKEN)], 53 | [ 54 | sg.Button("Generate", key='gen', size=(8,1)), 55 | sg.Exit(size=(8,1)) 56 | ] 57 | ] 58 | return sg.Window('Schedule Generator GUI', main_layout, finalize=True) 59 | 60 | def make_schedule(): 61 | schedule_layout = [ 62 | [sg.Text("Schedule Creator GUI", justification='center', font=("Helvetica", 25))], 63 | [sg.InputText("Schedule Name", key='sname')], 64 | [sg.Frame(layout=[ 65 | [ 66 | sg.Listbox([], size=(60, 5), key='courseList'), 67 | sg.Column([ 68 | [sg.Button("Create", size=(8,1), key='courseCreate')], 69 | [sg.Button("Remove", size=(8,1), key='courseRemove')] 70 | ]) 71 | ] 72 | ], title='Courses', relief=sg.RELIEF_SUNKEN)], 73 | [sg.Frame(layout=[ 74 | [ 75 | sg.Listbox([], size=(60, 5), key='timeList'), 76 | sg.Column([ 77 | [sg.Button("Create", size=(8,1), key='timeCreate')], 78 | [sg.Button("Remove", size=(8,1), key='timeRemove')] 79 | ]) 80 | ] 81 | ], title='Course Times', relief=sg.RELIEF_SUNKEN)], 82 | [sg.Frame(layout=[ 83 | [sg.Text('Path', size=(55, 1), key='txtPath'), sg.Input(key='pdfBrowse', visible=False, enable_events=True), sg.FolderBrowse(size=(8,1))], 84 | [ 85 | sg.InputText('Schedule', key='txtName'), 86 | sg.Text('.txt') 87 | ] 88 | ], title='Output File', relief=sg.RELIEF_SUNKEN)], 89 | [ 90 | sg.Button("Generate", size=(8,1), key='txtGen'), 91 | sg.Cancel(size=(8,1)) 92 | ] 93 | ] 94 | return sg.Window('Schedule Creator GUI', schedule_layout, finalize=True) 95 | 96 | def make_add_course(): 97 | add_course_layout = [ 98 | [sg.Text("New Course")], 99 | [ 100 | sg.Column([ 101 | [sg.Text("Code:")], 102 | [sg.Text("Name:")], 103 | [sg.Text("Lecturer:")] 104 | ]), 105 | sg.Column([ 106 | [sg.InputText('Code', size=(32, 1), key='courseCode', tooltip="Must be unique")], 107 | [sg.InputText('Name', size=(32, 1), key='courseName')], 108 | [sg.InputText('Lecturer', size=(32, 1), key='courseLec')] 109 | ]) 110 | ], 111 | [sg.Submit(key='courseSubmit', size=(8,1)), sg.Cancel(size=(8,1))] 112 | ] 113 | return sg.Window('New Course', add_course_layout, finalize=True) 114 | 115 | def make_add_time(): 116 | add_time_layout = [ 117 | [sg.Text("New Time")], 118 | [ 119 | sg.Column([ 120 | [sg.Text("Code:")], 121 | [sg.Text("Day:")], 122 | [sg.Text("Start Time:")], 123 | [sg.Text("End Time:")], 124 | [sg.Text("Location:")] 125 | ]), 126 | sg.Column([ 127 | [sg.Combo([], size=(10, 1), readonly=True, key='timeCode', tooltip="If a course was addded after this window is opened\nplease reopen the window to refresh the window.")], 128 | [sg.Combo(DAYS, size=(4, 1), readonly=True, default_value='Mon', key='timeDay')], 129 | [sg.Combo(HOURS, size=(3, 1), readonly=True, default_value=8, key='timeSHours'), sg.Combo(MINUTES, size=(3, 1), readonly=True, default_value=0, key='timeSMins')], 130 | [sg.Combo(HOURS, size=(3, 1), readonly=True, default_value=9, key='timeEHours'), sg.Combo(MINUTES, size=(3, 1), readonly=True, default_value=0, key='timeEMins')], 131 | [sg.InputText('', size=(16, 1), key='timeLoc'), sg.Text("(optional)")] 132 | ]) 133 | ], 134 | [sg.Text("Make sure the details above are within\nthe boundries set in the main window.", text_color='white',background_color='black')], 135 | [sg.Submit(key='timeSubmit', size=(8,1)), sg.Cancel(size=(8,1))] 136 | ] 137 | return sg.Window('New Time', add_time_layout, finalize=True) 138 | 139 | def main(): 140 | sg.theme('Dark Blue 3') 141 | main_window = make_main() 142 | schedule_window = None 143 | add_course_window = None 144 | add_time_window = None 145 | 146 | while True: 147 | window, event, values = sg.read_all_windows() 148 | 149 | if event == sg.WIN_CLOSED or event == 'Exit' or event == 'Cancel': 150 | popup = sg.popup_yes_no('Are you sure you want to close this window?') 151 | if popup == 'Yes': 152 | window.close() 153 | if window == main_window: 154 | break 155 | if window == schedule_window: 156 | if add_course_window != None: 157 | add_course_window.close() 158 | if add_time_window != None: 159 | add_time_window.close() 160 | 161 | # Main Events 162 | if event == 'mainBrowse': 163 | tempList = window['fileList'].get_list_values() 164 | tempFile = values['mainBrowse'] 165 | if tempFile not in tempList: 166 | tempList.append(tempFile) 167 | window['fileList'].update(values=tempList) 168 | 169 | if event == 'fileCreate': 170 | if schedule_window != None: 171 | if schedule_window.was_closed(): 172 | schedule_window = None 173 | else: 174 | sg.popup_error('This window is opened') 175 | continue 176 | 177 | schedule_window = make_schedule() 178 | 179 | if event == 'fileRemove': 180 | tempList = window['fileList'].get_list_values() 181 | tempFile = values['fileList'] 182 | for current in tempFile: 183 | tempList.remove(current) 184 | window['fileList'].update(values=tempList) 185 | 186 | if event == 'pdfBrowse': 187 | window['pdfPath'].update(value=values['pdfBrowse'] + '/') 188 | 189 | if event == 'gen': 190 | # check empty feild 191 | if window['pdfName'].get() == '' or window['fileList'].get_list_values() == []: 192 | sg.popup_error('Filename and file list cannot be empty') 193 | continue 194 | if values['smins'] == '--' or values['emins'] == '--': 195 | sg.popup_error("Please specify the minutes feild (don't leave it at '--')") 196 | continue 197 | if not match("^[A-Za-z0-9_-]*$" ,window['pdfName'].get()): 198 | sg.popup_error("Filename can only contain letters, numbers, _, and -") 199 | continue 200 | 201 | daysArg = DAYS.index(values['eday']) - DAYS.index(values['sday']) + 1 202 | if daysArg < 0: 203 | daysArg += 7 204 | 205 | outArg = values['pdfName'] 206 | if window['pdfPath'].get() != 'Path': 207 | outArg = window['pdfPath'].get() + outArg 208 | #check bad input 209 | if not match("^[A-Za-z0-9_-]*$", values['pdfName']): 210 | sg.popup_error("Filename can only contain letters, numbers, _, and -") 211 | continue 212 | #check duplicated file 213 | if isfile(outArg+'.pdf'): 214 | popup = sg.popup_yes_no('A file with this name already exists. Do you want to override it?') 215 | if popup == 'No': 216 | continue 217 | 218 | args = ['-s', str(values['shours']*100+values['smins']), '-e', str(values['ehours']*100+values['emins']), '-f', values['sday'], '-d', str(daysArg), '-o', outArg] 219 | tempList = window['fileList'].get_list_values() 220 | for current in tempList: 221 | args.append(current) 222 | 223 | try: 224 | scheduleGenerator.main(args) 225 | except SystemExit as e: 226 | sg.popup_error(e) 227 | 228 | sg.popup("Generated successfully!") 229 | 230 | # Schedule Events 231 | if event == 'courseCreate': 232 | if add_course_window != None: 233 | if add_course_window.was_closed(): 234 | add_course_window = None 235 | else: 236 | sg.popup_error('This window is opened') 237 | continue 238 | 239 | add_course_window = make_add_course() 240 | 241 | if event == 'courseRemove': 242 | tempList = window['courseList'].get_list_values() 243 | tempCourse = values['courseList'] 244 | for current in tempCourse: 245 | tempList.remove(current) 246 | window['courseList'].update(values=tempList) 247 | 248 | if event == 'timeCreate': 249 | if add_time_window != None: 250 | if add_time_window.was_closed(): 251 | add_time_window = None 252 | else: 253 | sg.popup_error('This window is opened') 254 | continue 255 | 256 | add_time_window = make_add_time() 257 | tempList = schedule_window['courseList'].get_list_values() 258 | tempList = [current.partition(',')[0] for current in tempList] 259 | add_time_window['timeCode'].update(values=tempList, set_to_index=0) 260 | 261 | if event == 'timeRemove': 262 | tempList = window['timeList'].get_list_values() 263 | tempTime = values['timeList'] 264 | for current in tempTime: 265 | tempList.remove(current) 266 | window['timeList'].update(values=tempList) 267 | 268 | if event == 'txtBrowse': 269 | window['txtPath'].update(value=values['txtBrowse'] + '/') 270 | 271 | if event == 'txtGen': 272 | #check empty inputs 273 | if values['sname'] == '' or values['txtName'] == '': 274 | sg.popup_error('Filename and Schedule name cannot be empty') 275 | continue 276 | if window['courseList'].get_list_values() == [] or window['timeList'].get_list_values() == []: 277 | sg.popup_error('Course list and file list cannot be empty') 278 | continue 279 | 280 | outArg = values['txtName'] + ".txt" 281 | if window['txtPath'].get() != 'Path': 282 | outArg = window['txtPath'].get() + outArg 283 | #check bad input 284 | if not match("^[A-Za-z0-9_-]*$", values['txtName']): 285 | sg.popup_error("Filename can only contain letters, numbers, _, and -") 286 | continue 287 | #check duplicated file 288 | if isfile(outArg): 289 | popup = sg.popup_yes_no('A file with this name already exists. Do you want to override it?') 290 | if popup == 'No': 291 | continue 292 | 293 | with open(outArg, 'w') as file: 294 | file.write("* " + values['sname'] + '\n') 295 | tempList = schedule_window['courseList'].get_list_values() 296 | file.writelines("%s\n" % current for current in tempList) 297 | tempList = schedule_window['timeList'].get_list_values() 298 | file.writelines("%s\n" % current for current in tempList) 299 | 300 | sg.popup("Generated successfully!") 301 | 302 | if event == 'courseSubmit': 303 | #check for empty feilds 304 | if values['courseCode'] == '' or values['courseName'] == '' or values['courseLec'] == '': 305 | sg.popup_error('Feilds cannot be empty') 306 | continue 307 | 308 | # check commas 309 | if ',' in values['courseCode']+values['courseName']+values['courseLec']: 310 | sg.popup_error('Feilds cannot contain a comma (,)') 311 | continue 312 | 313 | #check for repeating course codes 314 | tempList = schedule_window['courseList'].get_list_values() 315 | tempList = [current.partition(',')[0] for current in tempList] 316 | if values['courseCode'] in tempList: 317 | sg.popup_error('Course code must be unique') 318 | continue 319 | 320 | tempCourse = '' 321 | tempCourse += values['courseCode']+',' 322 | tempCourse += values['courseName']+',' 323 | tempCourse += values['courseLec'] 324 | 325 | tempList = schedule_window['courseList'].get_list_values() 326 | tempList.append(tempCourse) 327 | schedule_window['courseList'].update(values=tempList) 328 | window.close() 329 | 330 | if event == 'timeSubmit': 331 | #check for empty feilds 332 | if values['timeCode'] == '': 333 | sg.popup_error('The code is empty, please add a new course before adding time.') 334 | window.close() 335 | if values['timeSMins'] == '--' or values['timeEMins'] == '--': 336 | sg.popup_error("Please specify the minutes feild (don't leave it at '--')") 337 | continue 338 | 339 | #check beginning time and end time 340 | if values['timeSHours']*100+values['timeSMins'] >= values['timeEHours']*100+values['timeEMins']: 341 | sg.popup_error("Ending time can't be earlier than starting time") 342 | continue 343 | 344 | tempTime = values['timeCode'] + ',' + values['timeDay'] + ',' + str(values['timeSHours']*100+values['timeSMins']) + ',' + str(values['timeEHours']*100+values['timeEMins']) 345 | if values['timeLoc'] != '': 346 | tempTime += ',' + values['timeLoc'] 347 | 348 | tempList = schedule_window['timeList'].get_list_values() 349 | tempList.append(tempTime) 350 | schedule_window['timeList'].update(values=tempList) 351 | window.close() 352 | 353 | if __name__ == '__main__': 354 | main() 355 | --------------------------------------------------------------------------------