Skip to content
Snippets Groups Projects
Commit 8bee0a61 authored by Glenn Glazer's avatar Glenn Glazer
Browse files

SL-407: create Tkinter UI

parent c2ef3b4c
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/env python
# $LicenseInfo:firstyear=2016&license=internal$
#
# Copyright (c) 2016, Linden Research, Inc.
#
# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
# this source code is governed by the Linden Lab Source Code Disclosure
# Agreement ("Agreement") previously entered between you and Linden
# Lab. By accessing, using, copying, modifying or distributing this
# software, you acknowledge that you have been informed of your
# obligations under the Agreement and agree to abide by those obligations.
#
# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
# COMPLETENESS OR PERFORMANCE.
# $LicenseInfo:firstyear=2013&license=viewerlgpl$
# Copyright (c) 2013, Linden Research, Inc.
# $/LicenseInfo$
"""
@file InstallerUserMessage.py
@author coyot
@date 2016-05-16
"""
"""
This does everything the old updater/scripts/darwin/messageframe.py script did and some more bits.
Pushed up the manager directory to be multiplatform.
"""
import os
import Queue
import threading
import time
import Tkinter as tk
import ttk
class InstallerUserMessage(tk.Tk):
#Goals for this class:
# Provide a uniform look and feel
# Provide an easy to use convenience class for other scripts
# Provide windows that automatically disappear when done (for differing notions of done)
# Provide a progress bar that isn't a glorified spinner, but based on download progress
#Non-goals:
# No claim to threadsafety is made or warranted. Your mileage may vary.
# Please consult a doctor if you experience thread pain.
def __init__(self, text="", title="", width=500, height=200, fillcolor='#487A7B', *args, **kwargs):
tk.Tk.__init__(self)
self.grid()
self.title(title)
self.choice = tk.BooleanVar()
self.config(background = 'black')
# background="..." doesn't work on MacOS for radiobuttons or progress bars
# http://tinyurl.com/tkmacbuttons
ttk.Style().configure('Linden.TLabel', foreground='#487A7B', background='black')
ttk.Style().configure('Linden.TButton', foreground='#487A7B', background='black')
ttk.Style().configure("black.Horizontal.TProgressbar", foreground='#487A7B', background='black')
# The constants below are to adjust for typical overhead from the
# frame borders.
self.xp = (self.winfo_screenwidth() / 2) - (width / 2) - 8
self.yp = (self.winfo_screenheight() / 2) - (height / 2) - 20
self.geometry('{0}x{1}+{2}+{3}'.format(width, height, self.xp, self.yp))
self.script_dir = os.path.dirname(os.path.realpath(__file__))
self.icon_dir = os.path.abspath(os.path.join(self.script_dir, 'icons'))
#defines what to do when window is closed
self.protocol("WM_DELETE_WINDOW", self._delete_window)
def _delete_window(self):
#capture and discard all destroy events before the choice is set
if not ((self.choice == None) or (self.choice == "")):
try:
self.destroy()
except:
#tk may try to destroy the same object twice
pass
def set_colors(self, widget):
# #487A7B is "Linden Green"
widget.config(foreground = '#487A7B')
widget.config(background='black')
def find_icon(self, icon_path = None, icon_name = None):
#we do this in each message, let's do it just once instead.
if not icon_path:
icon_path = self.icon_dir
icon_path = os.path.join(icon_path, icon_name)
if os.path.exists(icon_path):
icon = tk.PhotoImage(file=icon_path)
self.image_label = tk.Label(image = icon)
self.image_label.image = icon
else:
#default to text if image not available
self.image_label = tk.Label(text = "Second Life")
def auto_resize(self, row_count = 0, column_count = 0, heavy_row = None, heavy_column = None):
#auto resize window to fit all rows and columns
#"heavy" gets extra weight
for x in range(column_count):
if x == heavy_column:
self.columnconfigure(x, weight = 2)
else:
self.columnconfigure(x, weight=1)
for y in range(row_count):
if y == heavy_row:
self.rowconfigure(y, weight = 2)
else:
self.rowconfigure(x, weight=1)
def basic_message(self, message, icon_path = None, icon_name = None):
#message: text to be displayed
#icon_path: directory holding the icon, defaults to icons subdir of script dir
#icon_name: filename of icon to be displayed
self.choice.set(True)
self.find_icon(icon_path, icon_name)
self.text_label = tk.Label(text = message)
self.set_colors(self.text_label)
self.set_colors(self.image_label)
#pad, direction and weight are all experimentally derived by retrying various values
self.image_label.grid(row = 1, column = 1, sticky = 'W')
self.text_label.grid(row = 1, column = 2, sticky = 'W', padx =100)
self.auto_resize(1, 2)
self.mainloop()
def binary_choice_message(self, message, icon_path = None, icon_name = None, one = 'Yes', two = 'No'):
#one: first option, returns True
#two: second option, returns False
#usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice
#usage:
# frame = InstallerUserMessage.InstallerUserMessage( ... )
# frame = frame.binary_choice_message( ... )
# (wait for user to click)
# value = frame.choice.get()
self.find_icon(icon_path, icon_name)
self.text_label = tk.Label(text = message)
#command registers the callback to the method named. We want the frame to go away once clicked.
#button 1 returns True/1, button 2 returns False/0
self.button_one = ttk.Radiobutton(text = one, variable = self.choice, value = True,
command = self._delete_window, style = 'Linden.TButton')
self.button_two = ttk.Radiobutton(text = two, variable = self.choice, value = False,
command = self._delete_window, style = 'Linden.TButton')
self.set_colors(self.text_label)
self.set_colors(self.image_label)
#pad, direction and weight are all experimentally derived by retrying various values
self.image_label.grid(row = 1, column = 1, rowspan = 3, sticky = 'W')
self.text_label.grid(row = 1, column = 2, rowspan = 3)
self.button_one.grid(row = 1, column = 3, sticky = 'W', pady = 40)
self.button_two.grid(row = 2, column = 3, sticky = 'W', pady = 0)
self.auto_resize(row_count = 2, column_count = 3, heavy_column = 3)
#self.button_two.deselect()
self.update()
self.mainloop()
def progress_bar(self, message = None, icon_path = None, icon_name = None, size = 0, interval = 100, pb_queue = None):
#Best effort attempt at a real progress bar
# This is what Tk calls "determinate mode" rather than "indeterminate mode"
#size: denominator of percent complete
#interval: frequency, in ms, of how often to poll the file for progress
#pb_queue: queue object used to send updates to the bar
self.find_icon(icon_path, icon_name)
self.text_label = tk.Label(text = message)
self.set_colors(self.text_label)
self.set_colors(self.image_label)
self.image_label.grid(row = 1, column = 1, sticky = 'NSEW')
self.text_label.grid(row = 2, column = 1, sticky = 'NSEW')
self.progress = ttk.Progressbar(self, style = 'black.Horizontal.TProgressbar', orient="horizontal", length=100, mode="determinate")
self.progress.grid(row = 3, column = 1, sticky = 'NSEW')
self.value = 0
self.progress["maximum"] = size
self.auto_resize(1, 3)
self.queue = pb_queue
self.check_scheduler()
def check_scheduler(self):
if self.value < self.progress["maximum"]:
self.check_queue()
self.after(100, self.check_scheduler)
def check_queue(self):
while self.queue.qsize():
try:
msg = float(self.queue.get(0))
#custom signal, time to tear down
if msg == -1:
self.choice.set(True)
self.destroy()
else:
self.progress.step(msg)
self.value = msg
except Queue.Empty:
pass
class ThreadedClient(threading.Thread):
#for test only, not part of the functional code
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for x in range(1, 90, 10):
time.sleep(1)
print "run " + str(x)
self.queue.put(10)
#tkk progress bars wrap at exactly 100 percent, look full at 99%
print "leftovers"
self.queue.put(9)
time.sleep(5)
# -1 is a custom signal to the progress_bar to quit
self.queue.put(-1)
if __name__ == "__main__":
#When run as a script, just test the InstallUserMessage.
#To proceed with the test, close the first window, select on the second. The third will close by itself.
from contextlib import closing
from multiprocessing import Process
import sys
import tempfile
def set_and_check(frame, value):
print "value: " + str(value)
frame.progress.step(value)
if frame.progress["value"] < frame.progress["maximum"]:
print "In Progress"
else:
print "Over now"
#basic message window test
frame2 = InstallerUserMessage(text = "Something in the way she moves....", title = "Beatles Quotes for 100")
frame2.basic_message(message = "...attracts me like no other.", icon_name="head-sl-logo.gif")
print "Destroyed!"
sys.stdout.flush()
#binary choice test. User destroys window when they select.
frame3 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200")
frame3.binary_choice_message(message = "And all I have to do is think of her.", icon_name="head-sl-logo.gif",
one = "Don't want to leave her now", two = 'You know I believe and how')
print frame3.choice.get()
sys.stdout.flush()
#progress bar
queue = Queue.Queue()
thread = ThreadedClient(queue)
thread.start()
print "thread started"
frame4 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 300")
frame4.progress_bar(message = "You're asking me will my love grow", icon_name="head-sl-logo.gif", size = 100, pb_queue = queue)
print "frame defined"
frame4.mainloop()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment