18.1.13

A simple GUI stopper

Hello again
In this short post I will present a simple and useful stopper made with Tk and Python.

Introduction


A friend of mine called me the other day and asked for help. He works in a Call-Center where he and his colleagues answer phone-calls from customers of their company.
He wanted to know exactly how long was every phone call. He wanted a stopper application.

In this post I'll lead you through the requirements we decided on and the solution I gave him.
Hope you enjoy!

Requirements


Well, although my friend needed to measure repetitive measurements, means he didn't want to summarize the time of more than one call each time, I've decided to enable this option with a button called "Continue". When this button is pressed, the stopper doesn't reset after every measurement.
In addition, we would like to give the user time to see the measured time before s/he resets the stopper and after reset we would like to hold until the next count.
Hence, we have 3 states the stopper can be at:
  1. Stop, when launching the application and after every reset. We shall see "000:00:00.00".
  2. Run, after start command, the stopper is counting up.
  3. Freeze, when we pause the counting, the digits are shown but they don't "move".

Having these three states in mind, I came up with the following User-Interface design:
  • The stopper will be activated conveniently with key-presses, either ENTER or SPACE
  • One press will start the measuring (from Stop to Run), the second will freeze it (Run to Freeze) and the third one will reset to zero or continue counting (Freeze to Stop or Freeze to Run), this is for the user to decide.
  • There should be an option to stick the stopper above all other windows so it won't get lost during work.
  • Pressing 'r' will reset the stopper regardless at what state it's in. This is especially important when working in "Continue" mode when Space/Enter key-presses move the stopper from Run to Freeze and back to Run without clearing the previous measurement.

Solution


I chose Python and Tk for the implementation since my "mother tongue" is Python and I'm relatively familiar with Tk after improving the code of the free and open-source GUI package for Python, easygui - A link to this other work

Here is a picture of the stopper.
As you can see, it has the format HHH:MM:SS.hh 
In addition, the button "Stick" on the right side determines whether the stopper stays above all other windows or not.
The button "Continue" on the left side determines whether the stopper resets with every freeze or continue counting from the same point.
Pressing 'r' at any point, resets and initializes the stopper.



Download


- Download Link -
There are 2 files there:
  • stopper.pyw : the script file, run it to get the stopper (you should have Python installed).
  • stopper_icon.ico : the clock icon of the stopper's left-top corner. You can of course use your own icon by overriding this file with your own icon.


Source


Here is the source code of stopper.pyw
#!/usr/bin/python

# This is a very simple GUI stopper
# Run the file
# Press either ENTER or SPACE to start the count
# Press again to freeze
# Now when you press again it depends in which mode you're working in
# The stopper has two modes, reset on every stop or continue counting
# This is depended whether the "Continue" button is pressed on not
# To reset when you're working in Continue mode, press 'r' (you can use in both modes)
# To quit, just press ESC or click at the X button at the upper-right corner
# Enjoy!
#
#                )
#                  (
#      _ ___________ )
#     [_[___________#


from Tkinter import *
import time, sys, os

class Stopper():
 def __init__(self, tkroot):
  self.STOP = 0
  self.RUN = 1
  self.FREEZE = 2
  
  self.state = 0
  self.startTime = None
  self.freezeTime = None
  self.sticked = False
  self.continueGrow = False
  
  # create the continueGrow_widget widget
  self.continueGrow_widget = Label(tkroot)
  self.continueGrow_widget.bind('', self.continue_grow)
  labelfont = ('Comic Sans MS', 10, 'italic')
  self.continueGrow_widget.config(bg='CadetBlue', relief=RAISED, width=7)
  self.continueGrow_widget.config(text = 'Continue', font=labelfont)
  self.continueGrow_widget.pack(expand=YES, fill=BOTH, side=LEFT)
  
  # create the digits_widget widget
  self.digits_widget = Label(tkroot)
  self.digits_widget.bind('',  self.change_state)
  self.digits_widget.bind('',  sys.exit)
  self.digits_widget.bind('',  self.reset_count)
  labelfont = ('Comic Sans MS', 25, 'bold')
  self.digits_widget.config(bg='goldenrod', font=labelfont)
  self.digits_widget.config(text = '000:00:00.00')
  self.digits_widget.pack(expand=YES, fill=BOTH,side=LEFT)
  self.digits_widget.focus()
  
  # create the sticky_widget widget
  self.sticky_widget = Label(tkroot)
  self.sticky_widget.bind('', self.float_above_everything)
  labelfont = ('Comic Sans MS', 10, 'italic')
  self.sticky_widget.config(bg='IndianRed', relief=RAISED, width=7)
  self.sticky_widget.config(text = 'Stick', font=labelfont)
  self.sticky_widget.pack(expand=YES, fill=BOTH, side=RIGHT)
  
  self.iter()
  
 def continue_grow(self, event):
  if self.continueGrow == False:
   self.continueGrow = True
   self.continueGrow_widget.configure(relief=SUNKEN, bg='CornflowerBlue')
  else:
   self.continueGrow = False
   self.continueGrow_widget.configure(relief=RAISED, bg='CadetBlue')
  
 def float_above_everything(self, event):
  ''' toggle sticky_widget window state(keep GUI above all other windows)
   currently supports only Windows! '''
  if self.sticked==False:
   self.sticked = True 
   self.sticky_widget.configure(relief=SUNKEN, bg='HotPink3', text='Stuck')
  else:
   self.sticked = False
   self.sticky_widget.configure(relief=RAISED, bg='IndianRed', text='Stick  ')
  # toggle sticky_widget state
  if 'win' in sys.platform:
   tkroot.wm_attributes("-topmost", 1 if self.sticked else 0) 
  elif 'inux' in sys.platform:
   #to do
   pass    
   
  
 def add_zerros(self, s, min=2):
  '''add zerro chars before a string
   e.i. 7 to  07 when min=2
     7 to 007 when min=3 '''
  if len(s) < min:
   s = '0' * (min - len(s)) + s
  return s

  
 def clock_like_time(self, startTime):
  "convert time in seconds to a stirng like HH:MM:SS.hh "
  t = time.time() - startTime
  #capture the first two digits_widget after the dot for the hundreths
  str_hunds = str(t - int(t)).replace("0.",'')
  if len(str_hunds) > 2: 
   str_hunds = str_hunds[:2]
  elif len(str_hunds) < 2: 
   #add '0' before the digit to keep the form of 0X instead of just X
   str_hunds = self.add_zerros(str_hunds)
  # we skip the case of len(str_hunds)==2 because this is what we want
  
  str_mins_and_secs = time.strftime('%M:%S', time.gmtime(t))   
  # we can't use %H for hours in the above row 
  # b/c for 25 hours we'll get 1 hour and one day
  str_hours = self.add_zerros(str(int(t)/3600), min=3)
  return "%s:%s.%s" % (str_hours, str_mins_and_secs, str_hunds)

  
 def reset_count(self, event=None):
  self.state = self.STOP
  self.digits_widget.configure(text = '000:00:00.00')
  
 def change_state(self, event):
  if self.state == self.STOP:
   self.state = self.RUN
   self.startTime = time.time()
  elif self.state == self.RUN:
   self.state = self.FREEZE
   self.freezeTime = time.time() - self.startTime
  elif self.state == self.FREEZE:
   if self.continueGrow:
    # increasing startTime by the time of freezing
    self.startTime = time.time() - self.freezeTime
    self.state = self.RUN
   else:
    self.reset_count()

   
 def iter(self):
  if self.state == self.RUN:
   # self.startTime=self.startTime-90000 # uncomment for debug a long time test
   self.digits_widget.config(text = self.clock_like_time(self.startTime))
  # else pass, when self.state is FREEZE or STOP
  self.digits_widget.after(100, self.iter)   

 
#====================== MAIN ======================#
if __name__=="__main__":
  
 tkroot = Tk()
 tkroot.title('Stopper')
 # here we load the icon for the GUI window
 # you can use mine or create your own icon file
 if os.path.exists('stopper_icon.ico'): 
  tkroot.iconbitmap(default='stopper_icon.ico')
 
 stopper = Stopper(tkroot)
 mainloop()

No comments:

Post a Comment