summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Glazer <coyot@lindenlab.com>2016-06-16 09:04:19 -0700
committerGlenn Glazer <coyot@lindenlab.com>2016-06-16 09:04:19 -0700
commit8bee0a61a5bd70183ace8ae6207f626d17875d51 (patch)
tree86f55eeacefdd7636fe1f175c43e633c634b1c83
parentc2ef3b4c7186dbbd95b16520f281b7d58364fb52 (diff)
SL-407: create Tkinter UI
-rw-r--r--indra/viewer_components/manager/InstallerUserMessage.py258
1 files changed, 258 insertions, 0 deletions
diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py
new file mode 100644
index 0000000000..bef2bf28a6
--- /dev/null
+++ b/indra/viewer_components/manager/InstallerUserMessage.py
@@ -0,0 +1,258 @@
+#!/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()
+
+
+
+