From 8bee0a61a5bd70183ace8ae6207f626d17875d51 Mon Sep 17 00:00:00 2001
From: Glenn Glazer <coyot@lindenlab.com>
Date: Thu, 16 Jun 2016 09:04:19 -0700
Subject: SL-407: create Tkinter UI

---
 .../manager/InstallerUserMessage.py                | 258 +++++++++++++++++++++
 1 file changed, 258 insertions(+)
 create mode 100644 indra/viewer_components/manager/InstallerUserMessage.py

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()
+
+
+
+
-- 
cgit v1.2.3


From dd18fb215f76b36c96da00a6516ada096b11262c Mon Sep 17 00:00:00 2001
From: Glenn Glazer <coyot@lindenlab.com>
Date: Fri, 17 Jun 2016 08:49:26 -0700
Subject: SL-407: post review

---
 .../manager/InstallerUserMessage.py                | 51 ++++++++++++----------
 1 file changed, 29 insertions(+), 22 deletions(-)

diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py
index bef2bf28a6..cf0a9da2a1 100644
--- a/indra/viewer_components/manager/InstallerUserMessage.py
+++ b/indra/viewer_components/manager/InstallerUserMessage.py
@@ -46,7 +46,11 @@ class InstallerUserMessage(tk.Tk):
     #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):
+
+    #Linden standard green color, from Marketing
+    linden_green = "#487A7B"
+
+    def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None, **kwargs):
         tk.Tk.__init__(self)
         self.grid()
         self.title(title)
@@ -56,15 +60,22 @@ class InstallerUserMessage(tk.Tk):
         # 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')
+        ttk.Style().configure("black.Horizontal.TProgressbar", foreground=InstallerUserMessage.linden_green, background='black')
 
+        #This bit of configuration centers the window on the screen
         # 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))
+
+        #find a few things
         self.script_dir = os.path.dirname(os.path.realpath(__file__))
         self.icon_dir = os.path.abspath(os.path.join(self.script_dir, 'icons'))
+
+        #finds the icon and creates the widget
+        self.find_icon(icon_path, icon_name)
+
         #defines what to do when window is closed
         self.protocol("WM_DELETE_WINDOW", self._delete_window)
 
@@ -79,7 +90,7 @@ class InstallerUserMessage(tk.Tk):
 
     def set_colors(self, widget):
         # #487A7B is "Linden Green"
-        widget.config(foreground = '#487A7B')
+        widget.config(foreground = InstallerUserMessage.linden_green)
         widget.config(background='black') 
 
     def find_icon(self, icon_path = None, icon_name = None):
@@ -110,22 +121,21 @@ class InstallerUserMessage(tk.Tk):
             else:
                 self.rowconfigure(x, weight=1)
 
-    def basic_message(self, message, icon_path = None, icon_name = None):
+    def basic_message(self, message, 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.auto_resize(row_count = 1, column_count = 2)
         self.mainloop()
 
-    def binary_choice_message(self, message, icon_path = None, icon_name = None, one = 'Yes', two = 'No'):
+    def binary_choice_message(self, message, true = 'Yes', false = '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
@@ -134,7 +144,7 @@ class InstallerUserMessage(tk.Tk):
         #   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
@@ -154,13 +164,12 @@ class InstallerUserMessage(tk.Tk):
         self.update()
         self.mainloop()
 
-    def progress_bar(self, message = None, icon_path = None, icon_name = None, size = 0, interval = 100, pb_queue = None):
+    def progress_bar(self, message = 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)
@@ -170,7 +179,7 @@ class InstallerUserMessage(tk.Tk):
         self.progress.grid(row = 3, column = 1, sticky = 'NSEW')
         self.value = 0
         self.progress["maximum"] = size
-        self.auto_resize(1, 3)
+        self.auto_resize(row_count = 1, column_count = 3)
         self.queue = pb_queue
         self.check_scheduler()
 
@@ -191,7 +200,8 @@ class InstallerUserMessage(tk.Tk):
                     self.progress.step(msg)
                     self.value = msg
             except Queue.Empty:
-                pass
+                #nothing to do
+                return
 
 class ThreadedClient(threading.Thread):
     #for test only, not part of the functional code
@@ -214,9 +224,6 @@ class ThreadedClient(threading.Thread):
 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
 
@@ -229,15 +236,15 @@ if __name__ == "__main__":
             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")
+    frame2 = InstallerUserMessage(text = "Something in the way she moves....", title = "Beatles Quotes for 100", icon_name="head-sl-logo.gif")
+    frame2.basic_message(message = "...attracts me like no other.")
     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')
+    frame3 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200", icon_name="head-sl-logo.gif")
+    frame3.binary_choice_message(message = "And all I have to do is think of her.", 
+        true = "Don't want to leave her now", false = 'You know I believe and how')
     print frame3.choice.get()
     sys.stdout.flush()
 
@@ -247,8 +254,8 @@ if __name__ == "__main__":
     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)
+    frame4 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 300", icon_name="head-sl-logo.gif")
+    frame4.progress_bar(message = "You're asking me will my love grow", size = 100, pb_queue = queue)
     print "frame defined"
     
     frame4.mainloop()
-- 
cgit v1.2.3


From f38439f4761abb73f6c7c93d52697cd9fa9f8368 Mon Sep 17 00:00:00 2001
From: Glenn Glazer <coyot@lindenlab.com>
Date: Fri, 17 Jun 2016 08:53:46 -0700
Subject: SL-407: post review change testing

---
 indra/viewer_components/manager/InstallerUserMessage.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py
index cf0a9da2a1..e6dba78169 100644
--- a/indra/viewer_components/manager/InstallerUserMessage.py
+++ b/indra/viewer_components/manager/InstallerUserMessage.py
@@ -121,7 +121,7 @@ class InstallerUserMessage(tk.Tk):
             else:
                 self.rowconfigure(x, weight=1)
 
-    def basic_message(self, message, icon_name = None):
+    def basic_message(self, message):
         #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
@@ -148,9 +148,9 @@ class InstallerUserMessage(tk.Tk):
         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, 
+        self.button_one = ttk.Radiobutton(text = true, variable = self.choice, value = True, 
             command = self._delete_window, style = 'Linden.TButton')
-        self.button_two = ttk.Radiobutton(text = two, variable = self.choice, value = False, 
+        self.button_two = ttk.Radiobutton(text = false, variable = self.choice, value = False, 
             command = self._delete_window, style = 'Linden.TButton')
         self.set_colors(self.text_label)
         self.set_colors(self.image_label)
-- 
cgit v1.2.3


From 53fe427e6fd2bf4701f98fb9896d69cfef9042b6 Mon Sep 17 00:00:00 2001
From: Glenn Glazer <coyot@lindenlab.com>
Date: Fri, 17 Jun 2016 09:58:28 -0700
Subject: SL-407: remove kwargs

---
 indra/viewer_components/manager/InstallerUserMessage.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/indra/viewer_components/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py
index e6dba78169..0ab7eafc78 100644
--- a/indra/viewer_components/manager/InstallerUserMessage.py
+++ b/indra/viewer_components/manager/InstallerUserMessage.py
@@ -50,7 +50,7 @@ class InstallerUserMessage(tk.Tk):
     #Linden standard green color, from Marketing
     linden_green = "#487A7B"
 
-    def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None, **kwargs):
+    def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None):
         tk.Tk.__init__(self)
         self.grid()
         self.title(title)
-- 
cgit v1.2.3