diff options
| -rw-r--r-- | indra/viewer_components/manager/InstallerUserMessage.py | 258 | 
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() + + + + | 
