diff --git a/Clock/clock.py b/Clock/clock.py new file mode 100644 index 0000000..5201965 --- /dev/null +++ b/Clock/clock.py @@ -0,0 +1,423 @@ +import tkinter +import datetime +import math +import time + +COLOR_BACKGROUND = '#000' +COLOR_FONT = '#666' +COLOR_DROPDOWN = '#aaa' +COLOR_DROPDOWN_ACTIVE = '#999' + +MODE_CLOCK = 'clock' +MODE_COUNTDOWN = 'countdown' +MODE_STOPWATCH = 'stopwatch' + +FONT = 'Consolas' +# Used for resizing the clock font to fit the frame. +# For consolas, 1.33333 is a good value. It may differ +# for other fonts +FONT_YX_RATIO = 1.33333 + +MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] +MONTHS_DICT = {'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12} + +class Clock: + def __init__(self): + self.t = tkinter.Tk() + self.t.configure(bg=COLOR_BACKGROUND) + self.mode_methods = { + MODE_CLOCK: self.build_gui_clock, + MODE_COUNTDOWN: self.build_gui_countdown, + MODE_STOPWATCH: self.build_gui_stopwatch, + } + self.dropstring = tkinter.StringVar(self.t) + self.dropstring.trace('w', self.trigger_choose_mode) + modes = list(self.mode_methods.keys()) + modes.sort(key=lambda x: x.lower()) + self.drop = tkinter.OptionMenu(self.t, self.dropstring, *modes) + self.drop.configure(relief='flat', bg=COLOR_DROPDOWN, activebackground=COLOR_DROPDOWN_ACTIVE, direction='below', highlightthickness=0, anchor='w') + self.drop.pack(fill='x', anchor='n') + #self.trigger_choose_mode() + + self.frame_applet = tkinter.Frame(self.t, width=400, height=95, bg=COLOR_BACKGROUND) + self.frame_applet.pack(side='bottom', anchor='s', expand=True, fill='both') + self.frame_applet.pack_propagate(0) + self.frame_applet.grid_propagate(0) + + self.elements = [] + + # Instance is used to keep track of how many times we have + # swapped modes. + # This is to make sure that any `self.t.after` loops are broken + # when we leave that mode. + self.instance = 0 + + self.dropstring.set(MODE_COUNTDOWN) + + self.t.mainloop() + + @property + def mode(self): + return self.drop.cget('text') + + def delete_applet_elements(self): + self.started = 0 + self.frame_applet.unbind('') + for x in range(len(self.elements)): + self.elements.pop().destroy() + self.frame_applet.rowconfigure(0, weight=0) + self.frame_applet.columnconfigure(0, weight=0) + + + '''clock + ###### ### ##### ###### ### ### + ####### ### ####### ####### ### ### + ### ### ### ### ### ##### + ####### ####### ####### ####### ### ### + ###### ####### ##### ###### ### ### + ''' + def build_gui_clock(self): + def tick_clock(): + if this_instance != self.instance: + # used to break the "after" loop + return + #now = datetime.datetime.now() + #now = now.strftime('%H:%M:%S') + now = time.strftime('%H:%M:%S') + label_clock.configure(text=now) + self.t.after(1000, tick_clock) + + this_instance = self.instance + + label_clock = tkinter.Label(self.frame_applet, text='x', bg=COLOR_BACKGROUND, fg=COLOR_FONT) + label_clock.pack(anchor='center', expand=True) + self.elements.append(label_clock) + + tick_clock() + self.frame_applet.bind('', lambda event: self.resize_widget_font(self.frame_applet, label_clock)) + self.resize_widget_font(self.frame_applet, label_clock) + + + '''count + ###### ##### ### ### ##### ####### + ####### ####### ### ### ####### ####### + ### ### ### ### ### ### ### ### + ####### ####### ####### ### ### ### + ###### ##### ##### ### ### ### + ''' + def build_gui_countdown(self): + + def toggle_mode(): + reset_countdown() + + if label_countdown.mode is 'until': + for item in elements_until: + item.grid_forget() + button_reset.grid(row=0, column=6) + spinbox_hour.configure(to=999) + label_countdown.mode = 'in' + else: + spinbox_day.grid(row=0, column=1) + spinbox_month.grid(row=0, column=2) + spinbox_year.grid(row=0, column=3) + spinbox_hour.configure(to=23) + #button_reset.grid_forget() + label_countdown.mode = 'until' + button_countdownmode.configure(text=label_countdown.mode) + + def tick_countdown(): + if this_instance != self.instance: + return + if not label_countdown.is_running: + return + + now = time.time() + if now > label_countdown.destination: + reset_countdown() + return + + until_dest = label_countdown.destination - now + hours, minutes, seconds = self.hms_divmod(until_dest) + display = '%02d:%02d:%04.1f' % (hours, minutes, seconds) + display = display.replace('60.0', '00.0') + previous_size = len(label_countdown.cget('text')) + label_countdown.configure(text=display) + if len(display) != previous_size: + self.resize_widget_font(frame_display, label_countdown) + self.t.after(100, tick_countdown) + + def start_countdown(): + if label_countdown.mode is 'until': + if label_countdown.destination is None: + try: + d = int(spinbox_day.get()) + mo = MONTHS_DICT[spinbox_month.get()] + y = int(spinbox_year.get()) + h = int(spinbox_hour.get()) + m = int(spinbox_minute.get()) + s = int(spinbox_second.get()) + + strp = '%d %s %d %d %d %d' % (d, mo, y, h, m, s) + strp = datetime.datetime.strptime(strp, '%d %m %Y %H %M %S') + label_countdown.destination = strp.timestamp() + except ValueError: + return + else: + now = time.time() + if label_countdown.destination is None: + try: + h = int(spinbox_hour.get()) + m = int(spinbox_minute.get()) + s = int(spinbox_second.get()) + label_countdown.destination = now + (3600*h) + (60*m) + (s) + except ValueError: + return + else: + backlog = now - label_countdown.backlog + #print(backlog) + label_countdown.destination += backlog + label_countdown.is_running = True + button_toggle.configure(text='stop') + tick_countdown() + + def stop_countdown(): + if label_countdown.mode is 'until': + pass + else: + label_countdown.backlog = time.time() + label_countdown.is_running = False + button_toggle.configure(text='start') + + def reset_countdown(): + stop_countdown() + label_countdown.configure(text='00:00:00.0') + label_countdown.destination = None + label_countdown.backlog = 0 + + def toggle_countdown(): + if label_countdown.is_running is True: + stop_countdown() + else: + start_countdown() + + def reset_spinboxes(): + for item in (spinbox_hour, spinbox_minute, spinbox_second, spinbox_day, spinbox_month, spinbox_year): + item.configure(bg=COLOR_BACKGROUND, fg=COLOR_FONT, buttonbackground=COLOR_DROPDOWN, activebackground=COLOR_DROPDOWN_ACTIVE) + item.delete(0, 'end') + + spinbox_hour.insert(0, 0) + spinbox_minute.insert(0, 0) + spinbox_second.insert(0, 0) + spinbox_day.insert(0, time.strftime('%d')) + spinbox_month.insert(0, time.strftime('%b').lower()) + spinbox_year.insert(0, time.strftime('%Y')) + + this_instance = self.instance + + elements_until = [] + + frame_display = tkinter.Frame(self.frame_applet, bg=COLOR_BACKGROUND) + frame_controls = tkinter.Frame(self.frame_applet, bg=COLOR_BACKGROUND) + frame_spinboxes = tkinter.Frame(frame_controls, bg=COLOR_BACKGROUND) + label_countdown = tkinter.Label(frame_display, text='00:00:00.0', bg=COLOR_BACKGROUND, fg=COLOR_FONT) + label_countdown.mode = 'until' + label_countdown.destination = None + label_countdown.backlog = 0 + label_countdown.is_running = False + button_countdownmode = tkinter.Button(frame_controls, text='0', command=toggle_mode, bg=COLOR_DROPDOWN, activebackground=COLOR_DROPDOWN_ACTIVE) + button_countdownmode.configure(width=5) + button_toggle = tkinter.Button(frame_controls, text='start', command=toggle_countdown, bg=COLOR_DROPDOWN, activebackground=COLOR_DROPDOWN_ACTIVE) + button_reset = tkinter.Button(frame_controls, text='reset', command=reset_countdown, bg=COLOR_DROPDOWN, activebackground=COLOR_DROPDOWN_ACTIVE) + + spinbox_hour = tkinter.Spinbox(frame_spinboxes, from_=0, to=999, width=3) + spinbox_minute = tkinter.Spinbox(frame_spinboxes, from_=0, to=59, width=2) + spinbox_second = tkinter.Spinbox(frame_spinboxes, from_=0, to=59, width=2) + + spinbox_day = tkinter.Spinbox(frame_spinboxes, from_=0, to=31, width=2) + spinbox_month = tkinter.Spinbox(frame_spinboxes, values=MONTHS, width=4) + spinbox_year = tkinter.Spinbox(frame_spinboxes, from_=2015, to=9999, width=4) + + reset_spinboxes() + + self.frame_applet.rowconfigure(0, weight=1) + self.frame_applet.columnconfigure(0, weight=1) + frame_controls.columnconfigure(1, weight=1) + + frame_display.grid(row=0, column=0, sticky='news') + label_countdown.pack(anchor='center', expand=True) + + frame_controls.grid(row=1, column=0, sticky='ew') + button_countdownmode.grid(row=0, column=0, sticky='ew') + frame_spinboxes.grid(row=0, column=1, sticky='ew') + spinbox_hour.grid(row=0, column=4) + spinbox_minute.grid(row=0, column=5) + spinbox_second.grid(row=0, column=6) + button_toggle.grid(row=0, column=7) + + self.elements.append(frame_display) + self.elements.append(frame_controls) + self.elements.append(frame_spinboxes) + self.elements.append(label_countdown) + self.elements.append(button_countdownmode) + self.elements.append(spinbox_hour) + self.elements.append(spinbox_minute) + self.elements.append(spinbox_second) + self.elements.append(button_toggle) + self.elements.append(button_reset) + + elements_until.append(spinbox_day) + elements_until.append(spinbox_month) + elements_until.append(spinbox_year) + + toggle_mode() + self.frame_applet.bind('', lambda event: self.resize_widget_font(frame_display, label_countdown)) + self.t.update() + self.resize_widget_font(frame_display, label_countdown) + + + '''stop + ##### ####### ##### ###### + ####### ####### ####### ### ### + ### ### ### ### ###### + ####### ### ####### ### + ##### ### ##### ### + ''' + def build_gui_stopwatch(self): + + def tick_stopwatch(): + if this_instance != self.instance: + return + if not label_stopwatch.is_running: + return + elapsed = time.time() - label_stopwatch.started_at + elapsed += label_stopwatch.backlog + + hours, minutes, seconds = self.hms_divmod(elapsed) + #seconds, centi = divmod(seconds, 100) + display = '%02d:%02d:%06.3f' % (hours, minutes, seconds) + display = display.replace('60.0', '00.0') + previous_size = len(label_stopwatch.cget('text')) + label_stopwatch.configure(text=display) + if len(display) != previous_size: + self.resize_widget_font(frame_display, label_stopwatch) + self.t.after(10, tick_stopwatch) + + def toggle_stopwatch(*event): + if label_stopwatch.is_running: + stop_stopwatch() + else: + start_stopwatch() + + def stop_stopwatch(): + if label_stopwatch.started_at is not None: + elapsed = time.time() - label_stopwatch.started_at + label_stopwatch.backlog += elapsed + label_stopwatch.started_at = None + label_stopwatch.is_running = False + button_toggle.configure(text='start') + + def start_stopwatch(): + label_stopwatch.started_at = time.time() + label_stopwatch.is_running = True + button_toggle.configure(text='stop') + tick_stopwatch() + + def reset_stopwatch(*event): + stop_stopwatch() + label_stopwatch.backlog = 0 + label_stopwatch.configure(text='00:00:00.000') + + this_instance = self.instance + + frame_display = tkinter.Frame(self.frame_applet, bg=COLOR_BACKGROUND) + frame_controls = tkinter.Frame(self.frame_applet, bg=COLOR_BACKGROUND) + label_stopwatch = tkinter.Label(frame_display, text='00:00:00.000', bg=COLOR_BACKGROUND, fg=COLOR_FONT) + label_stopwatch.started_at = None + # Backlog keeps track of how much time was on the watch when we stopped it + # So when we start it again, the counter starts from 0 and we add the backlog + # to get a total. + label_stopwatch.backlog = 0 + label_stopwatch.is_running = False + button_toggle = tkinter.Button(frame_controls, text='start', command=toggle_stopwatch, bg=COLOR_DROPDOWN, activebackground=COLOR_DROPDOWN_ACTIVE) + button_reset = tkinter.Button(frame_controls, text='reset', command=reset_stopwatch, bg=COLOR_DROPDOWN, activebackground=COLOR_DROPDOWN_ACTIVE) + + self.frame_applet.rowconfigure(0, weight=1) + self.frame_applet.columnconfigure(0, weight=1) + frame_display.grid(row=0, column=0, sticky='news') + label_stopwatch.pack(anchor='center', expand=True) + + frame_controls.grid(row=1, column=0, sticky='ew') + frame_controls.columnconfigure(0, weight=1) + frame_controls.columnconfigure(1, weight=1) + button_toggle.grid(row=0, column=1, sticky='ew') + button_reset.grid(row=0, column=0, sticky='ew') + + self.elements.append(frame_display) + self.elements.append(frame_controls) + self.elements.append(label_stopwatch) + self.elements.append(button_toggle) + self.elements.append(button_reset) + + self.frame_applet.bind('', lambda event: self.resize_widget_font(frame_display, label_stopwatch)) + # Update so that the window width and height are correct + # when we call resize_widget_font + self.t.update() + self.resize_widget_font(frame_display, label_stopwatch) + + + '''other + ##### ####### ### ### ####### ###### + ####### ####### ### ### ##### ### ### + ### ### ### ####### ### ###### + ####### ### ### ### ##### ### ### + ##### ### ### ### ####### ### ### + ''' + def resize_widget_font(self, parent, subordinate): + self.t.update() + frame_w = parent.winfo_width() + frame_h = parent.winfo_height() + #print(frame_w, frame_h) + text = subordinate.cget('text') + font = self.font_by_pixels(frame_w, frame_h, text) + + subordinate.configure(font=font) + + def font_by_pixels(self, frame_w, frame_h, text): + lines = text.split('\n') + label_w = max(len(line) for line in lines) + label_h = len(lines) + + # Padding to not look dumb + frame_h -= 20 + + # At 72 ppi, 1 point = 1 pixel height + point_heightbased = int(frame_h / label_h) + # but width requires involving the ratio + point_widthbased = int(frame_w * FONT_YX_RATIO / label_w) + + point_smaller = min(point_widthbased, point_heightbased) + point_smaller = max(point_smaller, 1) + font = (FONT, point_smaller) + + return font + + def hms_divmod(self, amount): + hours, minutes = divmod(amount, 3600) + minutes, seconds = divmod(minutes, 60) + + return hours, minutes, seconds + + def trigger_choose_mode(self, *args): + ''' + This method is fired when the drop-down menu item is selected. + It will choose which build_gui method to use based on the value + of the optionmenu text. + ''' + mode = self.mode + self.t.title(mode) + method = self.mode_methods[mode] + self.instance += 1 + self.delete_applet_elements() + method() + +c = Clock() diff --git a/TextureTile/texturetile.pyw b/TextureTile/texturetile.pyw index 23b8373..be497b9 100644 --- a/TextureTile/texturetile.pyw +++ b/TextureTile/texturetile.pyw @@ -50,6 +50,20 @@ class TextureTile: self.t.mainloop() + def fit_into_bounds(self, iw, ih, fw, fh): + ''' + Given the w+h of the image and the w+h of the frame, + return new w+h that fits the image into the frame + while maintaining the aspect ratio and leaving blank space + everywhere else + ''' + ratio = min(fw/iw, fh/ih) + + w = int(iw * ratio) + h = int(ih * ratio) + + return (w, h) + def file_load_display(self, *event): filename = self.entry_filename.get() # I want to check the spinbox values up @@ -79,12 +93,9 @@ class TextureTile: h = expanded.size[1] fw = self.label_image.winfo_width() fh = self.label_image.winfo_height() - ratio = min(fw/w, fh/h) - - w = int(w * ratio) - h = int(h * ratio) + wh = self.fit_into_bounds(w, h, fw, fh) - expanded = expanded.resize((w, h)) + expanded = expanded.resize(wh) image = ImageTk.PhotoImage(expanded) self.label_image.configure(image=image) self.label_image.dont_garbage_me_bro = image diff --git a/Toddo/README.md b/Toddo/README.md index b52bc10..ddf6b88 100644 --- a/Toddo/README.md +++ b/Toddo/README.md @@ -3,6 +3,9 @@ Toddo Commandline to-do-list manager. + 07 August 2015 + - sqlite connection and cursor will now lazy-load instead of loading + when the object is instantiated.

to-do list manager diff --git a/Toddo/toddo.db b/Toddo/toddo.db index d0b7891..22a8302 100644 Binary files a/Toddo/toddo.db and b/Toddo/toddo.db differ diff --git a/Toddo/toddo.py b/Toddo/toddo.py index 55834d2..9e55410 100644 --- a/Toddo/toddo.py +++ b/Toddo/toddo.py @@ -28,16 +28,35 @@ Use `toddo all` to see if there are entries for other tables.''' HELP_REMOVE = '''Provide an ID number to remove.''' +DISPLAY_INDIVIDUAL = ''' + ID: _id_ + Table: _table_ +Created: _human_ +Message: _message_''' + class ToddoExc(Exception): pass class Toddo(): def __init__(self, dbname='C:/git/else/toddo/toddo.db'): - self.sql = sqlite3.connect(dbname) - self.cur = self.sql.cursor() - self.cur.execute('CREATE TABLE IF NOT EXISTS meta(key TEXT, val TEXT)') - self.cur.execute('CREATE TABLE IF NOT EXISTS todos(id INT, todotable TEXT, created INT, message TEXT)') - self.cur.execute('CREATE INDEX IF NOT EXISTS todoindex on todos(id)') + self.dbname = dbname + self._sql = None + self._cur = None + + @property + def sql(self): + if self._sql is None: + self._sql = sqlite3.connect(self.dbname) + return self._sql + + @property + def cur(self): + if self._cur is None: + self._cur = self.sql.cursor() + self._cur.execute('CREATE TABLE IF NOT EXISTS meta(key TEXT, val TEXT)') + self._cur.execute('CREATE TABLE IF NOT EXISTS todos(id INT, todotable TEXT, created INT, message TEXT)') + self._cur.execute('CREATE INDEX IF NOT EXISTS todoindex on todos(id)') + return self._cur def add_todo(self, message=None): ''' @@ -88,8 +107,14 @@ class Toddo(): width = shutil.get_terminal_size()[0] - (messageleft + 1) message = nicewrap(message, width, messageleft) - return 'ID: %d\nTable: %s\nCreated: %s\nMessage: %s' % ( - todo[SQL_ID], todo[SQL_TODOTABLE], human(todo[SQL_CREATED]), message) + output = DISPLAY_INDIVIDUAL + output = output.replace('_id_', str(todo[SQL_ID])) + output = output.replace('_table_', todo[SQL_TODOTABLE]) + output = output.replace('_human_', human(todo[SQL_CREATED])) + output = output.replace('_message_', message) + + + return output def display_active_todos(self): '''