Beispiel #1
0
class NodeTree:
    def __init__(self):
        self._client = KazooClient(hosts='localhost:2181')
        # self._client = KazooClient(hosts='10.128.62.34:2181,10.128.62.34:2182,10.128.62.34:2183')
        self._client.start()
        self._selected_node = None
        self._win = None
        self._root = None
        self._tree_canvas = None
        self._tree_frame_id = None
        self.init_ui()
        self.init_tree()
        self._count = 0
        # self.treeify()

        print(self._count)
        self._win.mainloop()

    def init_ui(self):
        self._win = tk.Tk()
        self._win.title("zk-client")
        self._win.geometry('900x700+500+300')
        self._win.resizable(height=False, width=False)
        tree_wrapper = tk.Frame(self._win, bg='red', width=300)
        tree_wrapper.pack(side=tk.LEFT, fill=tk.Y)

        canvas = self._tree_canvas = tk.Canvas(tree_wrapper,
                                               width=300,
                                               height=700,
                                               scrollregion=(0, 0, 300, 700),
                                               bg='gray')  # 创建canvas
        canvas.place(x=0, y=0)  # 放置canvas的位置

        frame = tk.Frame(canvas)  # 把frame放在canvas里
        # frame.place(width=180, height=600)  # frame的长宽,和canvas差不多的
        vbar = Scrollbar(canvas, orient=tk.VERTICAL)  # 竖直滚动条
        vbar.place(x=281, width=20, height=700)
        vbar.configure(command=canvas.yview)
        hbar = Scrollbar(canvas, orient=tk.HORIZONTAL)  # 水平滚动条
        hbar.place(x=0, y=680, width=280, height=20)
        hbar.configure(command=canvas.xview)
        canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)  # 设置
        canvas.bind_all(
            "<MouseWheel>", lambda event: canvas.yview_scroll(
                int(-1 * (event.delta / 120)), "units"))

        self._tree_frame_id = canvas.create_window(
            0, 0, window=frame, anchor='nw')  # create_window)

        self._root = Treeview(frame)
        #
        self._root.pack(expand=True, fill=tk.BOTH)
        # self._root.bind("<Button-1>", self.clear_pre_selected)
        # self._root.bind("<< TreeviewClose>>", self.clear_pre_selected)
        self._root.bind("<<TreeviewOpen>>", self.open_node)

    def adjust_size(self):
        self._root.update()
        width = self._root.winfo_width()
        height = self._root.winfo_height()
        print(width, height)
        self._tree_canvas.itemconfigure(self._tree_frame_id,
                                        height=height)  # 设定窗口tv_frame的高度
        self._tree_canvas.config(scrollregion=(0, 0, width, height))
        self._tree_canvas.config(
            scrollregion=self._tree_canvas.bbox("all"))  # 滚动指定的范围
        self._tree_canvas.config(height=height)
        pass

    def init_tree(self):
        tops = self._client.get_children("/")
        for index, top in enumerate(tops):
            self._root.insert('', index, text=top)
            children = self._client.get_children("/" + top + "/")
            if children is not None and len(children) > 0:
                pass

    def clear_pre_selected(self, event):
        focus = self._root.focus()
        if focus == '':
            return
        children = self._root.get_children(focus)
        for child in children:
            self._root.delete(child)

    def open_node(self, event):
        iid = self._root.focus()
        path = self.get_current_path(iid)
        children = self._client.get_children(path)
        for index, p in enumerate(children):
            self._root.insert(iid, index, text=p)
        self.adjust_size()

    def get_current_path(self, item):
        curr_item = self._root.item(item)
        pid = self._root.parent(item)
        if pid == '':
            return '/' + curr_item['text'] + "/"
        else:
            return self.get_current_path(pid) + curr_item['text'] + "/"

    def treeify(self):
        self._client.start()
        self.treeify_recursive("/", parent='', index=0)

    def treeify_recursive(self, path, parent, index):
        self._count += 1
        cd = self._client.get_children(path=path)
        if cd and len(cd) > 0:
            for i, c in enumerate(cd):
                tmp_path = path + c + '/'
                tc = self._client.get_children(tmp_path)
                if tc and len(tc) > 0:
                    insert = self._root.insert(parent, i, text=c)
                    self.treeify_recursive(path=tmp_path,
                                           parent=insert,
                                           index=i)
                else:
                    self._root.insert(parent, i, text=c)
Beispiel #2
0
class SearchBox(Frame):
    '''A Treeview widget on top, Entry on bottom, using queues for
     interaction with outside functions'''
    def __init__(self, parent=None, db=DB, fdict={}):
        Frame.__init__(self, parent)
        self.Tests = Tests
        self.db = db
        self.fdict = dbm.open(self.db, 'c')
        #self.fdict['**']=''
        self.db_update = False
        #will hold the query to be processed
        self.query = None
        self.drives = self.get_drives()
        self.start_func = StartFunc(
        )  #platform dependent double-click response

        self.results = iter(
            ())  #initiating query results as an empty generator
        self.total_width = self.winfo_screenwidth(
        )  #to adjust column widths relative to screen size
        #Remember scorch mode
        self.scorch = False
        self.encoding = ENCODING

        #for scorch mode
        #self.keylist=self.fdict.keys()
        self.keylist_index = 0
        self.keylist_counter = 0

        #keystroke indicator
        self.counter = 0

        #queues for passing search queries and results
        self.query_queue = Queue()
        self.result_queue = Queue()

        #for usage by db generating function
        self.dbinit_queue = Queue()

        #--Search Results panel at top
        self.panel = Treeview(columns=('Path', 'Size', 'Date'))
        self.panel.pack(expand=True, fill='both')
        self.panel.heading('#0', text='Name')
        self.panel.heading(0, text='Path')
        self.panel.heading(1, text='Size')
        self.panel.heading(2, text='Date Modified')

        #--Starting geometry of the search panel
        try:  #start maximized
            self.panel.master.attributes('-zoomed', 1)
        except:
            self.panel.master.state('zoomed')

        self.panel_width = self.panel.winfo_width()
        #Name - 2/5-----Path - 2/5-----Size -1/25-----Date -4/25
        # '#0' is the 'Name' column
        self.panel.column('#0', width=int(self.total_width * 0.4))
        self.panel.column('Path', width=int(self.total_width * 0.4))
        self.panel.column('Size', width=int(self.total_width * 0.06))
        self.panel.column('Date', width=int(self.total_width * 0.14))

        #--Panel font, style
        self.font = Font(family='Helvetica', size=11)
        '''TkDefaultFont - {'family': 'Segoe UI',  'overstrike': 0, 'size': 9, 
                       'slant': 'roman', 'underline': 0, 'weight': normal'}'''
        self.style = Style()

        #linespace - adjust the row height to the font, doesn't happen on its own in tkinter
        self.style.configure('SearchBox.Treeview',
                             font=self.font,
                             rowheight=self.font.metrics('linespace'))
        self.panel.config(style='SearchBox.Treeview')

        #alternating background colors
        self.panel.tag_configure('color1',
                                 background='gray85')  #, foreground='white')
        self.panel.tag_configure('color2',
                                 background='gray90')  #, foreground='white')
        #'dark sea green', 'wheat3', 'black'

        #--App title and icon, currently transparent
        self.panel.master.title('Jiffy')
        self.icon = PhotoImage(height=16, width=16)
        self.icon.blank()  #transparent icon, works on all but Py35/Win

        #loading the transparent icon. black on Py35/Win
        try:
            self.master.wm_iconphoto('True', self.icon)
        except:
            #For some reason this jammed Python 3.5 with Tk 8.6 on Windows
            self.tk.call('wm', 'iconphoto', self.master._w, self.icon)

        #--A string variable to monitor input to the Entry box
        self.entry_var = StringVar()
        #self.entry_var.set('Type to search. [F5 - Refresh Database]. [F12 - Scorch Mode]')
        # [Ctrl-O - Options]. [Ctrl-I - Info]
        self.entry_var.trace('w', self.update_query)

        #--Entry line on the bottom
        self.entry_box = Entry(textvariable=self.entry_var)
        #keep it as a single line on all window sizes
        self.entry_box.pack(side='bottom', fill='x')

        #--Widget Bindings
        #self.master.bind('<Ctrl-Z>', self.quit)  #alternative to Alt-F4
        self.master.bind('<Control-equal>', self.scaleup)
        self.master.bind('<Control-Button-4>', self.scaleup)
        self.master.bind('<Control-minus>', self.scaledown)
        self.master.bind('<Control-Button-5>', self.scaledown)
        self.master.bind('<Control-MouseWheel>', self.scale_mouse)

        self.panel.bind('<Double-1>', self.doubleclick)
        self.panel.bind('<Return>', self.doubleclick)

        #allow scrolling and typing without switching focus
        self.entry_box.bind('<MouseWheel>', self.scroll_from_entry)
        self.entry_box.bind('<Button-4>', self.scroll_from_entry)
        self.entry_box.bind('<Button-5>', self.scroll_from_entry)

        self.master.bind('<F5>', self.make_database)
        #self.master.bind('<F12>', self.scorch_mode)

        #--Starting up with entry box active
        self.entry_box.focus_set()

        #--Generating a starting message based on existence of a database
        if not self.fdict:
            self.panel.insert('',
                              'end',
                              text='No cache database found',
                              values=('Hit F5 to generate database', ))
        else:
            self.panel.insert(
                '',
                'end',
                text='Type to search.   [F5 - Refresh Database]',
                values=('[Ctrl - +/-/MouseWheel - Adjust font size]', ))
        # [Ctrl-O - Options]. [Ctrl-I - Info]
        #self.panel.insert('', 'end', text='Scorch Mode is faster but uses more memory', values=('Loads the entire database into RAM',))

        self.update_searchbox()
        #Initializing the query managing function in a separate thread (upgrade to pool?)
        thread.start_new_thread(
            TMakeSearch, (self.fdict, self.query_queue, self.result_queue))

    ##GUI functionality--------------------------------------------------------
    #O change to initiation from parameter to SearchBox for more modularity
    def get_drives(event):
        return GetDrives()

    def scroll_from_entry(self, event):
        '''Scroll results without deactivating entry box, called from entry_box'''

        self.panel.yview_scroll(1, 'units')

    def scaleup(self, event):
        '''The Treeview widget won't auto-adjust the row height, 
        so requires manual resetting upon font changing'''
        self.font['size'] += 1
        self.style.configure('SearchBox.Treeview',
                             rowheight=self.font.metrics('linespace') + 1)

    def scaledown(self, event):
        self.font['size'] -= 1
        self.style.configure('SearchBox.Treeview',
                             rowheight=self.font.metrics('linespace') + 1)

    def scale_mouse(self, event):
        self.scaleup(event) if event.delta > 0 else self.scaledown(event)

    def doubleclick(self, event):
        '''Invoke default app on double-click or Enter'''

        #getting file/folder name and removing '[' and ']' for folders
        selection = self.panel.item(self.panel.focus())
        filename = selection['text']
        #remove folder indicating square brackets
        if filename[0] == '[':
            filename = filename[1:-2]

        #SPLIT_TOKEN='\\' if 'win' in sys.platform else '/'
        full_path = selection['values'][0] + SPLIT_TOKEN + filename
        self.start_func(full_path)

    def quit(self, event):
        '''Currently Alt-F4 exits program, in case I want to add more shortcuts.
        Also add thread closing management here'''

        self.master.destroy()

    ##Cheese: update_database()->is_sb_generated(), trace_results(), update_query()
    def make_database(self, event):
        '''Using a thread to generate the dictionary to prevent GUI freezing'''

        #* dbm might not be thread safe - best might be to restart TMakeSearch
        self.gtime = time()  # for testing
        self.entry_var.set('Updating Database')
        self.entry_box.icursor('end')

        #Resulting dicitionay will be passed via dbinint_queue
        thread.start_new_thread(RecursiveCreateDict,
                                (self.drives, self.dbinit_queue, self.fdict))

        #Wait for the dictionary to be generated
        self.is_db_generated()

    def is_db_generated(self):
        '''Update database if available or sleep and try again'''

        if not self.dbinit_queue.empty():
            #A new dictionary was passed

            #retrieving new dict
            self.newdict, self.unsearched = self.dbinit_queue.get()
            #Messaging TMakeSearch to stop querying the dictionary
            self.db_update = True
            self.query_queue.put(None)
            sleep(0.11)  #TMakeSearch takes 0.1s naps. Check further '''

            if whichdb(self.db) == ('dbhash'):
                '''For dumbdbm, this jams the app, as does manual updating. it's
                not dumb, it's just not worthy'''
                self.fdict.update(self.newdict)
            else:
                for key in self.newdict:
                    self.fdict[key] = self.newdict[key]
            print('fdict is created')
            self.db_update = False
            #save new database
            self.fdict.sync()
            print('fdict synced')

            #Open a new TMakeSearch with the updated database
            #thread.start_new_thread(TMakeSearch, (self.fdict, self.query_queue, self.result_queue))

            #Cleaning up
            self.newdict.clear()
            self.newdict = None

            self.gtime = time() - self.gtime
            #to read about {}.format              #also, a label may be simpler
            self.entry_var.set('Database generation time- ' + str(self.gtime) +
                               's. Type to search. [F5 - Refresh Database]')

            #Pass a signal to close TMakeSearch, then reopen it
            self.query_queue.put(True)
            thread.start_new_thread(
                TMakeSearch, (self.fdict, self.query_queue, self.result_queue))

            self.entry_box.icursor(0)
            #self.loading.destroy()
            self.panel.delete(*self.panel.get_children())
            self.panel.insert(
                '',
                0,
                text='Scorch Mode is faster but uses more memory',
                values=('Loads database into RAM', ))
            #self.keylist=fdict.keys()   #for scorch mode
            self.counter = 0
            #self.IS_1ST_PRESS=True
            #for testing
            #print time()-self.start
            #print self.dict_size()
        else:
            self.after(100, self.is_db_generated)

    def update_searchbox(self):
        '''Update GUI with new result batches '''

        self.even = True
        #for splitting size and date from the keys
        self.separator = ' * '.encode(self.encoding)
        while not self.result_queue.empty():
            qresult = self.result_queue.get()
            #print ('is_batch_recieved:', qresult)
            #if qcounter==self.counter:  #currently assuming results will arrive by querying order
            #break
        try:
            #if nothing in queue this will raise an error, saves a preemptive if clause
            self.results, self.is_new = qresult
            if Tests.is_batch_recieved:
                print('is_batch_recieved:', self.results)
        except:
            pass  #no new results
            if Tests.is_batch_recieved:
                print('is_batch_recieved: no new results')
        else:
            #if self.panel.get_children()!=():
            #results for a newer query, erase old results
            if self.is_new:
                self.panel.delete(*self.panel.get_children())

            for key in self.results:
                try:
                    name, size, date = key.decode(self.encoding).split(u'*')
                    #name, size, date=key.split(self.separator)
                    if Tests.is_result_parsed:
                        print(name)
                except:
                    if Tests.is_result_parsed:
                        print('parsing issue with', key)
                else:
                    path = self.fdict[key].decode(self.encoding)
                    '''if 'win' in sys.platform and top[0] is u'/':
                        top=u'C:\\'+top[1:] '''
                    color = 'color1' if self.even else 'color2'
                    self.even = not self.even
                    self.panel.insert('',
                                      'end',
                                      text=name,
                                      values=(path, size, date),
                                      tags=(color, ))

        self.after(60, self.update_searchbox)

    def update_query(self, x=None, y=None, z=None):
        '''Invoked by StringVar().trace() method, which passes 3 arguments that are honorably ditched '''

        #Deactivate while switching dictionaries
        if self.db_update:
            pass

        #Cleaning up for 1st keystroke or after a message in the Entry box
        if not self.counter:
            '''Entry box needs to be cleaned, the new char put in and the cursor
           placed after it'''
            #get&set the 1st search char. user may've changed cursor location b4 typing
            self.entry_var.set(
                self.entry_var.get()[self.entry_box.index(INSERT) - 1])
            #move cursor after the first char
            self.entry_box.icursor(1)

        #counter goes up either way
        self.counter += 1
        self.query = self.entry_var.get()
        self.query_queue.put(self.query)
        if Tests.is_query_sent:
            print(self.query)
            print(self.counter)

    ##Not in use ----------------------------------------------------------------

    def trace_query(self):
        '''If I opt for periodically checking the StringVar'''

        if self.counter:  #when counter=0 there's a message/notification in the entry box
            if self.query != self.entry_var.get():
                self.query = self.entry_var.get()
                self.query_queue.put(self.query)

        self.after(100, self.trace_query)

    def trace_and_update(self):
        ''' In-GUI implementation of query searching, uses a list for iterating 
        over the keys'''
        '''works smoother when results are generated quickly, but with
        sparse results GUI becomes unresponsive for short whiles. Relevant
        only if results are guaranteed to be generated swiftly'''

        #print self.query
        #if new query, resetting search parameters and GUI
        if self.query != self.entry_var.get():
            self.keylist_counter = 0
            self.query = self.entry_var.get()
            self.search_list = self.query.lower().split()
            self.panel.delete(*self.panel.get_children())

        self.insertion_counter = 0
        self.keylist_index = self.keylist_counter
        for key in self.keylist[self.keylist_index:]:
            filename = key.split('*')[0].lower()
            #If a match, parse and add to the Treeview
            if self.all(token in filename for token in self.search_list):
                name, size, date = key.split('*')
                self.panel.insert('',
                                  'end',
                                  text=name,
                                  values=(self.fdict[key], size, date))
                self.insertion_counter += 1
            self.keylist_counter += 1

            if self.insertion_counter >= self.INSERTIONS_PER_CYCLE:  #50  ##or not dict_counter:
                break  #nap time

        self.after(60, self.trace_and_update)