TL;DR: Pokazuje kod taki jakim jest na ten moment, jest chaos, ale program działa i wygląda coraz lepiej. Opisuje co za tym kodem się kryje i co udało mi się do tej pory wprowadzić.

Kod, który się tu znajduje prawdopodobnie nie powinien ujrzeć światła dziennego. Ten blog to jednak zapis historii tworzenia mojego pierwszego programu. To świadectwo jak ja samouk, wprowadzam zmiany po omacku i testuje czy program robi to czego od niego bym oczekiwał. Ku uciesze gawiedzi i mnie samego w przyszłości.

screenshot-from-2017-04-28-15-34-39croped.jpg

# -*- encoding: utf8 -*-
# Panie wężu proszę o program do tacho

from tkinter import *
import re, datetime, Pmw, time

entries = []
#total_info = ""

def add_entry(evt):  # For calculation button
    Entry()
'''
    global total_info, entries
    entry = top_frame_input.get()
    entry = entry.replace("+", "")

    try:
        int(entry)
        # entries_list.insert(0,entry)
        entries.append(int(entry))
        total_info = "Total: " + str(sum(entries))
        status.set(total_info)
        entries_update()
        top_frame_input.delete(0, END)

    except:
        top_frame_input.focus()

'''
def clear_all(evt):  # clear top_frame_input
    top_frame_input.delete(0, END)
    top_frame_input.focus()

def clear_one(evt):  # clear last digit from top_frame_input
    entry = top_frame_input.get()[:-1]
    top_frame_input.delete(0, END)
    top_frame_input.insert(0, entry)

def num_press(num):  # num pad button action
    if top_frame_input.get() == '00:00:00':
        clear_all("C")
    if num == "C":
        clear_all("C")
    elif num == ".":  # doesn't work because counter doesn't allow insert other thinks than numbers and ":"
        top_frame_input.insert(END, ":")
    else:

        top_frame_input.insert(END, num)
        top_frame_input.focus()

def delete_item():

    # delete a selected line from the listbox and from entries
    print('It works')
    try:
        # get selected line index
        index = entries_list.curselection()[0]
        entries_list.delete(index)
        entries.pop(index)

    except IndexError:
        pass

def get_selection():
    print(select_mode.getvalue())

'''
def entries_update():
    entries_list.delete(0, END)
    for sec in entries:
        converted_seconds = "%d:%02d:%02d" % (sec / 3600, sec / 60 % 60, sec % 60)  # convert to HH:MM:SS
        entries_list.insert(0, converted_seconds)
        if sec == 1:
            entries_list.itemconfig(0, {'bg': 'green'})  # it is going to be useful later,
            # for marking different types of entries
            # print(entries_list.get(0, END))
'''

# =======tkinter window===========
win = Tk()
# win.geometry("775x325") # Force window size
win.wm_title("Tacho 0.0.3")
win.resizable(width=FALSE, height=FALSE)

Pmw.initialise(win)

'''
topFrame = Frame(win)
# topFrame.pack(fill=BOTH)
topFrame.grid(row=0, column=0, columnspan=2)
'''
topLeftFrame = Frame(win)
topLeftFrame.grid(row=0, column=0)

topRightFrame = Frame(win)
topRightFrame.grid(row=0, column=1)

rightFrame = Frame(win)
rightFrame.grid(row=1, column=1)

leftFrame = Frame(win)
# middleFrame.pack(fill=BOTH)
leftFrame.grid(row=1, column=0)

bottomFrame = Frame(win)
bottomFrame.grid(row=2, columnspan=2)

# -------keys actions-----
#win.bind("<Return>", add_entry)
win.bind("<KP_Enter>", add_entry)
win.bind("<KP_Add>", add_entry)
win.bind("<KP_Decimal>", num_press)

# ==========input entry===========
'''
top_frame_input = Entry(topFrame, textvariable=StringVar(),
                        font="Helvetica 20 bold", width=18, justify=RIGHT)
top_frame_input.grid(column=8)
# top_frame_input.pack(fill=X, expand=True, side=RIGHT, ipady=10)
top_frame_input.bind('<Button-1>', clear_all)
top_frame_input.focus()
'''
top_frame_input = Pmw.Counter(topRightFrame,
                              #labelpos = 'w',
                              # label_text = 'Time:',
                              entry_font="Helvetica 20 bold",
                              entry_width = 12,
                              autorepeat = True, datatype = 'time',
                              entryfield_validate = {'validator' : 'time'},
                              entryfield_value = '00:00:00',
                              increment = 60)
top_frame_input.grid(column = 0)

top_frame_input.component('entry').focus_set()
top_frame_input.select_range(3,5)
top_frame_input.icursor(5)

# ===========Listbox with scrollbar=================
'''
entries_list = Listbox(leftFrame, exportselection=0, height=15, width=40)
entries_list.pack(side=LEFT, fill=BOTH, expand=True)

entries_list_scrollbar = Scrollbar(leftFrame, orient=VERTICAL)  # scrollbar
entries_list_scrollbar.pack(side=LEFT, fill=BOTH)

entries_list_scrollbar.configure(command=entries_list.yview)
entries_list.configure(yscrollcommand=entries_list_scrollbar.set)

# entries_list.bind('&amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt;ListboxSelect&amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt;', select) #action for selected line
'''
entries_list = Pmw.ScrolledListBox(leftFrame, hscrollmode = 'none', vscrollmode = 'static', listbox_height = 15, listbox_width=40)
'''
entries_list = Pmw.ComboBox(leftFrame, dropdown = 0, scrolledlist_vscrollmode = 'static',
                            scrolledlist_hscrollmode = 'none', scrolledlist_listbox_height = 15,
                            scrolledlist_listbox_width=40,
                            entryfield_validate = {'validator' : 'time'},
                            entryfield_value = '00:00:00',
                            )
'''
#entries_list.pack(side=LEFT, fill=BOTH, expand=True)
entries_list.grid(row = 0, column = 0)

# =======num pad==========
keyboard = []
keys = "789456123C0:"
i = 0
for j in range(1, 5):
    for k in range(3):
        keyboard.append(Button(rightFrame, text=keys[i], font="Helvetica 15 bold", height=1, width=2))
        keyboard[i].grid(row=j, column=k, pady=2, padx=2)
        keyboard[i]["command"] = lambda x=keys[i]: num_press(x)
        i += 1

# --------other buttons---------

add_entry_button = Button(rightFrame, text="+", font="Helvetica 15 bold", height=6, width=7)
add_entry_button.grid(row=1, column=3, rowspan=4, columnspan=2, pady=2, padx=2)
add_entry_button.bind('&amp;amp;amp;amp;lt;Button-1&amp;amp;amp;amp;gt;', add_entry)

clear_one_button = Button(rightFrame, text="←", font="Helvetica 15 bold", height=1, width=2)
clear_one_button.grid(row=0, column=4, pady=2, padx=2)
clear_one_button.bind('&amp;amp;amp;amp;lt;Button-1&amp;amp;amp;amp;gt;', clear_one)
'''
modulo_button = Button(rightFrame, text="M24", font="Helvetica 15 bold", height=1, width=2)
modulo_button.grid(row=5, column=5, pady=2, padx=2)
modulo_button.bind('&amp;amp;amp;amp;lt;Button-1&amp;amp;amp;amp;gt;', add_entry)
'''
select_mode = Pmw.RadioSelect(rightFrame, Button_height=1, Button_width=2, Button_font ="Helvetica 15 bold", pady = 2, padx = 2)
                        # labelpos = 'w',
                        # command = self.callback,
                        # label_text = 'Horizontal',
                        # frame_borderwidth = 2,
                        # frame_relief = 'ridge'

select_mode.grid(row = 0, column = 0, columnspan = 4)

# Add some buttons to the horizontal RadioSelect.
for text in ('D', 'W', 'P', 'R'):
    select_mode.add(text)
select_mode.invoke('R')

# ======top icons==========

top_left_buttons = Pmw.ButtonBox(topLeftFrame, Button_height=1,# Button_width=2,
                                 Button_font ="Helvetica 15 bold", pady = 1, padx = 1)
                        # labelpos = 'w',
                        # command = self.callback,
                        # label_text = 'Horizontal',
                        # frame_borderwidth = 2,
                        # frame_relief = 'ridge'

top_left_buttons.grid(row = 0, column = 0, columnspan =2)

# Add some buttons to the horizontal RadioSelect.
top_left_buttons.add('Delete', command = delete_item)
top_left_buttons.add('Edit')
top_left_buttons.add('Save', command = get_selection)
top_left_buttons.add('Clear')

'''
top_frame_icons = []

for i in range(0, 5):
    top_frame_icons.append(

        Button(topLeftFrame, text='X',

               font="Helvetica 15 bold", height=1, width=2)

    )

    top_frame_icons[i].grid(row=0, column=i, sticky = W,ipadx = 10, ipady=1)
'''

# =====bottom status=============
status = StringVar()
bottom_status_total = Label(bottomFrame, textvariable=status, bd=1, relief=SUNKEN,
                            font="Helvetica 15 bold", width=54)
status.set("")
bottom_status_total.pack(fill=X, expand=True, side=TOP, ipady=10, ipadx=10)

# ============================================

class Entry:

    def __init__(self):
        self.input = top_frame_input.get()
        self.cleaner()
        self.seconds = int(self.input)
        self.add()
        self.update()
        status.set('Total time: '+str(self.converter(self.sum()))+'\n'+str(self.sum())+' seconds')
        top_frame_input.setentry('00:00:00')
        #top_frame_input.delete(0, END)
        print(self.seconds)
        print(self.converter(self.seconds))
        top_frame_input.select_range(3,5)
        top_frame_input.icursor(5)

    def cleaner(self):
        self.input = re.sub("[^0-9:]", "", self.input) # leaves only digits and ":" into input
        try:
            h,m,s = re.split(':',self.input)
            self.input = int(datetime.timedelta(hours=int(h),minutes=int(m),seconds=int(s)).total_seconds())
        except:
            try:
                m,s = re.split(':',self.input)
                self.input = int(datetime.timedelta(minutes=int(m),seconds=int(s)).total_seconds())
            except:
                self.input = re.sub("\D", "", self.input) # clean input from non-digit characters
                self.input = int(datetime.timedelta(minutes=int(self.input)).total_seconds())

        # self.input = self.input.replace("+", "") # simplest method (replace just one character)
            # in case this one above will make a troubles

    def converter(self, sec):
        convertion = "%d:%02d:%02d" % (sec / 3600, sec / 60 % 60, sec % 60)  # convert to HH:MM:SS
        return convertion

    def add(self):
        global entries
        index = 0
        try:
            index = entries_list.curselection()[0]
        except:
            pass
        #entries.append(self.seconds)
        entries.insert(index,self.seconds)
        print("Entries: "+str(entries))
        print('index: '+str(index))

    def sum(self):
        global total_info
        total = sum(entries)
        return total

    def update(self):
        entries_list.delete(0, END)
        for e in entries:
            converted_entry = self.converter(e)
            #entries_list.insert(0, converted_entry)
            entries_list.insert(END, converted_entry)

win.mainloop()

Legenda kodu tego:

Po pierwsze skąd tyle kodu w komentarzach?

Otóż wprowadzając nową funkcjonalność, ulepszając ją lub po prostu podmieniając (np. zwykłego Tkintera na Pmw) nie usuwam starego kodu, ale wrzucam go w komentarz. Pozwala mi to na zorientowanie się co ja tutaj porobiłem oraz szybki powrót do starej opcji w razie jakiegoś wykrzaczenia. Oczywiście to wszystko zniknie z ostatecznej wersji kodu. Zresztą jak tylko uznam, że już nie ma sensu, aby w pogotowiu mieć starą wersję to wyrzucam ją na bieżąco.

Coś się zmieniło na plus?

W tym tygodniu skupiłem się mocno na wyglądzie programu, ale poczyniłem też skromne kroki usprawniające użyteczność.

screenshot-from-2017-04-28-15-34-39croped.jpg

Po pierwsze wprowadziłem Pwm, który w znaczący sposób przyspieszył pracę o czym szerzej już pisałem.

Przede wszystkim zmieniło się pole inputu. Pmw.Counter, który zastąpił pole inputu pozwala na wpisanie godziny tylko w formacie HH:MM:SS oraz posiada wbudowane strzałki do zwiększania i zmniejszania wartości. Bardzo szybko i bez problemu mam wszystko to czego potrzebowałem.

top_frame_input = Pmw.Counter(topRightFrame,
                              entry_font="Helvetica 20 bold",
                              entry_width = 12,
                              autorepeat = True, datatype = 'time',
                              entryfield_validate = {'validator' : 'time'},
                              entryfield_value = '00:00:00',
                              increment = 60)
top_frame_input.component('entry').focus_set()
top_frame_input.select_range(3,5)
top_frame_input.icursor(5)

Oprócz przycisków klawiatury numerycznej pojawiły się przyciski wygenerowane za pomocą Pmw.ButtonBox (Delete, Edit, Save, Clear) oraz Pmw.RadioButton (D, W, P, R).

O ile te pierwsze aż takiego szału nie robią i z grubsza można coś bardzo podobnego osiągnąć pętlą, to radio button zaoszczędziły mi kupę roboty i pewnie frustracji. Za jednym zamachem mam wygenerowane przyciski z których tylko jeden może być wciśnięty.

Jest to coś co mega potrzebuję, aby użytkownik mógł określić czy dodaje czas jazdy (D: driving), pracy (W: working), dyspozycyjności (P: POA – period of availability) czy odpoczynku (R: resting). W przyszłości klawisze te będą oznaczone symbolami, które są powszechnie znane przez kierowców i używane w tachografie.

select_mode = Pmw.RadioSelect(rightFrame, Button_height=1, Button_width=2, Button_font ="Helvetica 15 bold", pady = 2, padx = 2)
select_mode.grid(row = 0, column = 0, columnspan = 4)

# Add some buttons to the horizontal RadioSelect.
for text in ('D', 'W', 'P', 'R'):
    select_mode.add(text)
select_mode.invoke('R')

# ======top icons==========

top_left_buttons = Pmw.ButtonBox(topLeftFrame, Button_height=1,# Button_width=2,
                                 Button_font ="Helvetica 15 bold", pady = 1, padx = 1)
top_left_buttons.grid(row = 0, column = 0, columnspan =2)

# Add some buttons to the horizontal RadioSelect.
top_left_buttons.add('Delete', command = delete_item)
top_left_buttons.add('Edit')
top_left_buttons.add('Save', command = get_selection)
top_left_buttons.add('Clear')

Dodatkowo zmienił się również listbox na Pmw.ScrolledListBox, który ma tą podstawową zaletę, że w przeciwieństwie do tkinterowego listboxa od razu zawiera scrollbars.

entries_list = Pmw.ScrolledListBox(leftFrame,hscrollmode = 'none', vscrollmode = 'static', listbox_height = 15, listbox_width=40)
entries_list.grid()

Nowe funkcjonalności:

Powstała możliwość kasowania wpisu za pomocą przycisku ‚Delete’ choć na razie nie odświeża to statusu.

def delete_item():

    try:
        # get selected line index
        index = entries_list.curselection()[0]
        entries_list.delete(index)
        entries.pop(index)

    except IndexError:
        pass

oraz dodawanie wpisu nad zaznaczonym wpisem na listboxie (

class Entry:
...
    def add(self):
        global entries
        index = 0
        try:
            index = entries_list.curselection()[0]
        except:
            pass
        entries.insert(index,self.seconds)

Chaos panie, chaos

Kod często drukuje jakieś głupoty w konsoli, ale to pomaga mi ogarnąć co tak naprawdę dzieje się w programie. Te rzeczy oczywiście znikną z ostatecznej wersji kodu.

Wbrew pozorom zdaję sobie sprawę z tego jak ten kod jest w tym momencie chaotyczny. Wiem, że klasa ‚Entry’ i jej wywoływanie trochę są bez sensu.

Ogarnianiem tego kodu będę się zajmował w pierwszej kolejności. Powtarzam do znudzenia, że muszę wyczuć co powinno być klasą i jakie funkcje ta klasa powinna mieć. Już teraz widzę, że np. odświeżanie statusu nie powinno być w klasie Entry, bo w tym momencie trzeba odświeżyć status nie tylko kiedy entry jest dodawane, ale również kasowane. Chyba, że kasowanie też powinno się w niej znaleźć?

Nie mam zielonego pojęcia czy to w jakiej formie ten kod jest teraz jest dla kogokolwiek oprócz mnie czytelne. Jeśli jest dla ciebie to zostaw proszę w komentarzu jakąś podpowiedź.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Google

Komentujesz korzystając z konta Google. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s