Beispiel #1
0
 def set(self, lo, hi):
     if float(lo) <= 0.0 and float(hi) >= 1.0:
         # grid_remove is currently missing from Tkinter!
         self.tk.call("grid", "remove", self)
     else:
         self.grid()
     Scrollbar.set(self, lo, hi)
Beispiel #2
0
 def set(self, lo, hi):
     if float(lo) <= 0.0 and float(hi) >= 1.0:
         # grid_remove is currently missing from Tkinter!
         self.tk.call("grid", "remove", self)
     else:
         self.grid()
     Scrollbar.set(self, lo, hi)
Beispiel #3
0
 def set(self, lo, hi):
     if self.showing and lo == "0.0" and hi == "1.0":
         self.grid_remove()
         self.showing = False
     elif not self.showing and not (lo == "0.0" and hi == "1.0"):
         self.grid()
         self.showing = True
     Scrollbar.set(self, lo, hi)
Beispiel #4
0
 def set(self, lo, hi):
     if float(lo) <= 0.0 and float(hi) >= 1.0:
         # grid_remove è attualmente assente da Tkinter.
         # Il metodo tk.call viene infatti richiamato sull'oggetto
         # scrollbar
         self.tk.call("grid", "remove", self)
     else:
         self.grid()
     Scrollbar.set(self, lo, hi)
Beispiel #5
0
 def set(self, lo, hi):
     if float(lo) <= 0.0 and float(hi) >= 1.0:
         self.tk.call("grid", "remove", self)
     else:
         self.grid()
         Scrollbar.set(self, lo, hi)
class CompetitorTable(Frame):
    def __init__(self, parent, db, *args, **kwargs):
        Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.db = db

        self.labelfont = ['Arial', -0.017, 'bold']
        self.font = ['Arial', -0.017]
        self.labelfont[1] = round(self.font[1] * self.parent.parent.parent.SCREEN_HEIGHT)
        self.font[1] = round(self.font[1] * self.parent.parent.parent.SCREEN_HEIGHT)

        self.config(bg=LLBLUE)

        self.tablebg = 'white'
        self.tableborder = LLBLUE
        self.labelfg = 'white'
        self.labelbg = LBLUE

        self.scrollbar = Scrollbar(self)
        self.idLabel = Label(self, text='ID', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw', highlightbackground=self.labelfg)
        self.fnameLabel = Label(self, text='First Name', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw')
        self.lnameLabel = Label(self, text='Last Name', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw')
        self.levelLabel = Label(self, text='Level', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw')
        self.sexLabel = Label(self, text='Sex', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw')
        self.ageLabel = Label(self, text='Age', fg=self.labelfg, bg=self.labelbg, font=self.labelfont, height=1, anchor='sw')
        self.idLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=5, font=self.font)
        self.fnameLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=25, font=self.font)
        self.lnameLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=25, font=self.font)
        self.levelLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=12, font=self.font)
        self.sexLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=2, font=self.font)
        self.ageLB = Listbox(self, yscrollcommand=self.y_scroll, background=self.tablebg, highlightbackground=self.tableborder, borderwidth=0, width=3, font=self.font)
        #self.registerButton = Button(self, bg=BLUE, fg='white', text="REGISTER\n\nNEW", font=self.font, width=5, wraplength=1,
        #                             borderwidth=1, command=self.register_new)
        #self.deleteButton = Button(self, bg=BLUE, fg='white', text="DELETE\n\nSELECTED", font=self.font, width=5, wraplength=1,
        #                           borderwidth=1, command=self.delete_competitor)
        #self.editButton = Button(self, bg=BLUE, fg='white', text="EDIT\n\nSELECTED", font=self.font, width=5, wraplength=1,
        #                         borderwidth=1, command=self.edit_competitor)

        self.listboxes = (self.idLB, self.fnameLB, self.lnameLB, self.levelLB, self.sexLB, self.ageLB)
        for lb in self.listboxes:
            lb.bind('<Delete>', self.delete_competitor)
            lb.bind('<Double-Button-1>', self.edit_competitor)

        self.idLB.bind('<FocusOut>', lambda e: self.idLB.selection_clear(0, 'end'))  # these binds ensure that when
        self.fnameLB.bind('<FocusOut>', lambda e: self.fnameLB.selection_clear(0, 'end'))  # leaving the table there
        self.lnameLB.bind('<FocusOut>', lambda e: self.lnameLB.selection_clear(0, 'end'))  # doesn't remain a selection
        self.levelLB.bind('<FocusOut>', lambda e: self.levelLB.selection_clear(0, 'end'))  # despite not having focus
        self.sexLB.bind('<FocusOut>', lambda e: self.sexLB.selection_clear(0, 'end'))
        self.ageLB.bind('<FocusOut>', lambda e: self.ageLB.selection_clear(0, 'end'))

        self.scrollbar.config(command=self.set_scrollables)

        self.idLabel.grid(row=0, column=0, sticky='nsew')
        self.fnameLabel.grid(row=0, column=1, sticky='nsew')
        self.lnameLabel.grid(row=0, column=2, sticky='nsew')
        self.levelLabel.grid(row=0, column=3, sticky='nsew')
        self.sexLabel.grid(row=0, column=4, sticky='nsew')
        self.ageLabel.grid(row=0, column=5, sticky='nsew')
        self.idLB.grid(row=1, column=0, sticky='nsew')
        self.fnameLB.grid(row=1, column=1, sticky='nsew')
        self.lnameLB.grid(row=1, column=2, sticky='nsew')
        self.levelLB.grid(row=1, column=3, sticky='nsew')
        self.sexLB.grid(row=1, column=4, sticky='nsew')
        self.ageLB.grid(row=1, column=5, sticky='nsew')
        #self.editButton.grid(row=0, column=6, sticky='nsew')
        #self.registerButton.grid(row=1, column=6, sticky='nsew')
        #self.deleteButton.grid(row=2, column=6, sticky='nsew')
        self.scrollbar.grid(row=0, column=7, sticky='nsew', rowspan=2)
        self.rowconfigure(0, weight=1)
        self.rowconfigure(1, weight=100)
        self.columnconfigure(0, weight=5)
        self.columnconfigure(1, weight=25)
        self.columnconfigure(2, weight=25)
        self.columnconfigure(3, weight=12)
        self.columnconfigure(4, weight=2)
        self.columnconfigure(5, weight=3)

        self.competitorRegistrationWindow = None

    def register_new(self, *args):
        if not self.competitorRegistrationWindow:
            self.competitorRegistrationWindow = CompetitorRegistrationWindow(self, self.db)
            self.competitorRegistrationWindow.protocol('WM_DELETE_WINDOW', self.close_competitor_registration_window)
        else:
            print('competitor registration window already open')

    def close_competitor_registration_window(self):
        self.competitorRegistrationWindow.destroy()
        self.competitorRegistrationWindow = None

    def get_selected_competitor(self):
        if len(self.idLB.curselection()) > 0:
            row = self.idLB.curselection()
        elif len(self.fnameLB.curselection()) > 0:
            row = self.fnameLB.curselection()
        elif len(self.lnameLB.curselection()) > 0:
            row = self.lnameLB.curselection()
        elif len(self.levelLB.curselection()) > 0:
            row = self.levelLB.curselection()
        elif len(self.sexLB.curselection()) > 0:
            row = self.sexLB.curselection()
        elif len(self.ageLB.curselection()) > 0:
            row = self.ageLB.curselection()  # this block searches for any selection in all the list boxes

        return row

    def delete_competitor(self, *args):
        try:
            competitor_id = self.idLB.get(self.get_selected_competitor())
        except UnboundLocalError:  # occurs when there is no competitor selected
            print('cannot delete a competitor when there is none selected')
            return
        self.db.delete_row(competitor_id)

    def edit_competitor(self, *args):
        try:
            id = self.get_selected_competitor()
        except UnboundLocalError:  # occurs when there is no competitor selected
            print('cannot edit a competitor when there is none selected')
            return

        if id:
            entrytab = self.parent.parent.parent.entryTab

            entrytab.routeAttemptsEntryFrame.reset()
            enable_frame(entrytab.competitorInfoFrame)
            enable_frame(entrytab.routeAttemptsEntryFrame)
            entrytab.competitorInfoFrame.fill_competitor(self.idLB.get(id))  # fills info to entry

    def set_scrollables(self, *args):
        self.idLB.yview(*args)
        self.fnameLB.yview(*args)
        self.lnameLB.yview(*args)
        self.levelLB.yview(*args)
        self.sexLB.yview(*args)
        self.ageLB.yview(*args)
        
    def y_scroll(self, *args):  # keeps all listboxes at same scroll position always
        for lb in self.listboxes:
            lb.yview_moveto(args[0])
        self.scrollbar.set(*args)

    def update_table(self, pattern=None):
        self.clear_table()
        if pattern and not pattern == 'Search competitors...':
            rows = self.db.get_specific(pattern)
        else:
            rows = self.db.get_all()
        for i, row in enumerate(rows):
            self.idLB.insert(i, row[0])
            self.fnameLB.insert(i, row[1])
            self.lnameLB.insert(i, row[2])
            self.levelLB.insert(i, row[3])
            self.sexLB.insert(i, row[4])
            self.ageLB.insert(i, row[5])

    def clear_table(self):
        self.idLB.delete(0, 'end')
        self.fnameLB.delete(0, 'end')
        self.lnameLB.delete(0, 'end')
        self.levelLB.delete(0, 'end')
        self.sexLB.delete(0, 'end')
        self.ageLB.delete(0, 'end')
class CategoricalStandings(Frame):
    def __init__(self, parent, db, level=None, sex=None, *args, **kwargs):
        Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.db = db

        self.titleFont = ['Arial', -0.016]
        self.font = ['Arial', -0.014]

        self.titleFont[1] = round(self.titleFont[1] * self.parent.parent.parent.parent.SCREEN_HEIGHT)
        self.font[1] = round(self.font[1] * self.parent.parent.parent.parent.SCREEN_HEIGHT)

        self.titlebg = LBLUE
        self.titlefg = 'black'

        self.selectedAttributes = {'level': level, 'sex': sex}
        self.titleString = StringVar()
        self.titleString.set('{} | {} ({})'.format(level, sex, 0))

        self.scrollbar = Scrollbar(self)
        self.scrollbar.config(command=self.set_scrollables)

        self.title = Label(self, textvariable=self.titleString, bg=self.titlebg, fg=self.titlefg, font=self.titleFont)
        self.idLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=3, activestyle='none', font=self.font)
        self.fnameLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=15, activestyle='none', font=self.font)
        self.lnameLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=15, activestyle='none', font=self.font)
        self.scoreLB = Listbox(self, yscrollcommand=self.y_scroll, borderwidth=0, width=10, activestyle='none', font=self.font)

        self.listboxes = (self.idLB, self.fnameLB, self.lnameLB, self.scoreLB)
        for lb in self.listboxes:
            lb.bind('<Delete>', self.delete_competitor)
            lb.bind('<Double-Button-1>', self.edit_competitor)

        self.title.grid(row=0, column=0, columnspan=5, sticky='nsew')
        self.idLB.grid(row=1, column=0, sticky='nsew')
        self.fnameLB.grid(row=1, column=1, sticky='nsew')
        self.lnameLB.grid(row=1, column=2, sticky='nsew')
        self.scoreLB.grid(row=1, column=3, sticky='nsew')
        self.scrollbar.grid(row=1, column=4, sticky='nsew')
        self.rowconfigure(0, weight=1)
        self.rowconfigure(1, weight=6)
        self.columnconfigure(0, weight=3)
        self.columnconfigure(1, weight=10)
        self.columnconfigure(2, weight=18)
        self.columnconfigure(3, weight=10)
        self.columnconfigure(4, weight=1)

        self.update_table()

    def set_scrollables(self, *args):
        self.idLB.yview(*args)
        self.fnameLB.yview(*args)
        self.lnameLB.yview(*args)
        self.scoreLB.yview(*args)

    def y_scroll(self, *args):
        for lb in self.listboxes:
            lb.yview_moveto(args[0])
        self.scrollbar.set(*args)

    def update_table(self, pattern=None):
        self.clear_table()
        if pattern and not pattern == 'Search competitors...':
            rows = self.db.get_specific_rows_by_score(pattern=pattern, **self.selectedAttributes)
        else:
            rows = self.db.get_specific_rows_by_score(**self.selectedAttributes)
        if rows:
            self.titleString.set('{} | {} ({})'.format(self.selectedAttributes['level'],
                                                       self.selectedAttributes['sex'],
                                                       len(rows)))
            for i, row in enumerate(rows):
                self.idLB.insert(i, row[0])
                self.fnameLB.insert(i, row[1])
                self.lnameLB.insert(i, row[2])
                self.scoreLB.insert(i, row[3])
        else:
            self.titleString.set('{} | {} ({})'.format(self.selectedAttributes['level'],
                                                       self.selectedAttributes['sex'],
                                                       0))

    def get_selected_competitor(self):
        if len(self.idLB.curselection()) > 0:
            row = self.idLB.curselection()
        elif len(self.fnameLB.curselection()) > 0:
            row = self.fnameLB.curselection()
        elif len(self.lnameLB.curselection()) > 0:
            row = self.lnameLB.curselection()
        elif len(self.scoreLB.curselection()) > 0:
            row = self.scoreLB.curselection()  # this block searches for any selection in all the list boxes

        return row

    def edit_competitor(self, *args):
        try:
            id = self.get_selected_competitor()
        except UnboundLocalError:  # occurs when there is no competitor selected
            print('cannot edit a competitor when there is none selected')
            return

        if id:
            entrytab = self.parent.parent.parent.parent.entryTab

            entrytab.routeAttemptsEntryFrame.reset()
            enable_frame(entrytab.competitorInfoFrame)
            enable_frame(entrytab.routeAttemptsEntryFrame)
            entrytab.competitorInfoFrame.fill_competitor(self.idLB.get(id))  # fills info to entry

    def delete_competitor(self, *args):
        try:
            competitor_id = self.idLB.get(self.get_selected_competitor())
        except UnboundLocalError:  # occurs when there is no competitor selected
            print('cannot delete a competitor when there is none selected')
            return
        self.db.delete_row(competitor_id)

    def clear_table(self):
        self.idLB.delete(0, 'end')
        self.fnameLB.delete(0, 'end')
        self.lnameLB.delete(0, 'end')
        self.scoreLB.delete(0, 'end')
Beispiel #8
0
class DataViewer:
    ''' DataViewer
        ======================
        David Miller, 2020
        The University of Sheffield 2020

        Wrapper GUI for a Matplotlib Figure showing the data given on creation. Called by HDF5Viewer when the user
        double clicks on a dataset.

        This GUI is designed a means of performing basic inspections of data stored in HDF5 files. If users want to
        perform something intensive or particular custom, they are advised to do so elsewhere.

        On creation, the DataViewer opens up the file specified by filename and accesses the dataset specified by the
        path dataName. It then decides how to plot the data based on the number of dimensions in the dataset. The used
        plots are as follows using their respective default options:

        |No. dims | Plots    |
        |---------|----------|
        |   1     | Line     |
        |   2     | Contourf |
        |   3     | Contourf |

        In the case of three dimensions, a scrollbar is added on top of the plot and provides a means for the user
        to select which 2D slice of the 3D dataset to show.

        The scrollbar only supports drag operations.

        Any higher dimensional data is ignored.

        The title above the Figure displays the name of the dataset and which index, if any is being displayed.

        Methods
        -------------------------
        on_key_press(event):
            Handler for key presses used on the Matploblib canvas

        scroll_data(self,*args):
            Handler for changing which slice of a 3D dataset is displayed. A new data index is chosen based on where
            the scrollbar cursor is dragged to. The index is calculated by multiplying the scrollbar positon [0,1] by
            the number of frames in the dataset and then converted to an integer.
            Updates the title and canvas on exit. Sets the scrollbar position to where the user left it.
            
            Currently this only has support for clicking and dragging the scrollbar cursor. Other operations are
            ignored.
    '''
    # using filename and name of dataset
    # create a figure and display the data
    def __init__(self,master,dataName,filename):
        self.master = master
        # save creation options
        self.dataName = dataName
        self.filename = filename
        # set title
        self.master.title("Data Viewer")
        # current colormap, default
        self.curr_cmap = getattr(matplotlib.cm,matplotlib.rcParams['image.cmap'])
        # currnt line color
        self.curr_lcol = matplotlib.rcParams['axes.prop_cycle'].by_key()['color'][0]
        ## menu bar for customisation
        # root menu
        menu = Menu(master)
        # options menu
        optsmenu = Menu(menu,tearoff=0)
        # label for graph
        self.title = StringVar()
        self.title.set(f'Displaying {dataName}')
        self.graph_title = Label(master,textvariable=self.title)
        self.graph_title.pack(side=TOP,pady=10,padx=10)
        # create figure
        self.fig = Figure(figsize=(5,5),dpi=100)
        self.axes = self.fig.add_subplot(111)
        # get data from dataset and plot data
        with h5py.File(filename,mode='r') as f:
            self.data_shape = f[dataName].shape
            # if the data is 1D, plot as line
            if len(self.data_shape)==1:
                self.axes.plot(f[dataName][()],self.curr_lcol)
                optsmenu.add_command(label="Set color",command=self.set_color)
            # if data is 2D, plot as filled contour
            elif len(self.data_shape)==2:
                self.axes.contourf(f[dataName][()],cmap=self.curr_cmap)
                optsmenu.add_command(label="Set colormap",command=self.set_colormap)
            # if data is 3D plot as contourf, but also add a scrollbar for navigation
            elif len(self.data_shape)==3:
                optsmenu.add_command(label="Set colormap",command=self.set_colormap)
                # create scroll bar for viewing different slices
                self.plot_scroll=Scrollbar(master,orient="horizontal",command=self.scroll_data)
                # add too gui
                self.plot_scroll.pack(side=TOP,fill=BOTH,expand=True)
                # plot first slice of data
                self.axes.contourf(f[dataName][:,:,0],cmap=self.curr_cmap)
                # create index for current depth index
                self.depth_index = 0
                self.title.set(f"Displaying {dataName} [{self.depth_index}]")
        # add to root menu
        menu.add_cascade(label="Options",menu=optsmenu)
        self.master.config(menu=menu)
        # create canvas to render figure
        self.fig_canvas = FigureCanvasTkAgg(self.fig,self.master)
        # update result
        self.fig_canvas.draw()
        # update canvas to set position and expansion options
        self.fig_canvas.get_tk_widget().pack(side=TOP,fill=BOTH,expand=True)

        ## add matplotlib toolbar
        self.fig_toolbar = NavigationToolbar2Tk(self.fig_canvas,self.master)
        self.fig_toolbar.update()
        # add to gui. always one row below the canvas
        self.fig_canvas._tkcanvas.pack(side=TOP,fill=BOTH,expand=True)
        ## add key press handlers
        self.fig_canvas.mpl_connect("key_press_event",self.on_key_press)
        # ensure elements are expandable in grid
        num_cols,num_rows = master.grid_size()
        for c in range(num_cols):
            master.columnconfigure(c,weight=1)
        for r in range(num_rows):
            master.rowconfigure(r,weight=1)
        # finish any idle tasks and set the minimum size of the window to cur
        master.update_idletasks()
        master.after_idle(lambda: master.minsize(master.winfo_width(), master.winfo_height()))
        
    # handler for matplotlib keypress events
    def on_key_press(event):
        key_press_handler(event,self.fig_canvas,self.fig_toolbar)

    # handler for using the scrollbar to view slices of data
    def scroll_data(self,*args):
        #print(args)
        # if the user has dragged the scrollbar
        if args[0] == "moveto":
            # args is only one element in this case and is a number between 0 and 1
            # 0 is left most position and 1 is right most position
            # the data index is calculated as this number times depth and converted to integer
            self.depth_index = int(float(args[1])*self.data_shape[2])
            # if index exceeds dataset limits
            # correct it
            if self.depth_index>=self.data_shape[-1]:
                self.depth_index = self.data_shape[-1]-1
            # set the scrollbar position to where the user dragged it to
            self.plot_scroll.set(float(args[1]),(self.depth_index+1)/self.data_shape[2])
            # reopen file
            with h5py.File(self.filename,mode='r') as f:
                self.axes.contourf(f[self.dataName][:,:,self.depth_index],cmap=self.curr_cmap)
            # update canvas
            self.fig_canvas.draw()
            # update title
            self.title.set(f"Displaying {self.dataName} [{self.depth_index}]")

    def set_colormap(self):
        ch = ColormapChooser(self.master).show()
        if ch:
            self.curr_cmap = ch
            self.scroll_data("moveto",str(self.plot_scroll.get()[0]))

    def update_line(self):
        # remove first line from plot
        self.axes.lines.pop(0)
        with h5py.File(self.filename,mode='r') as f:
            self.axes.plot(f[self.dataName][()],self.curr_lcol)
        # update canvas
        self.fig_canvas.draw()

    def set_color(self):
        col = colorchooser.askcolor()
        if col[1]:
            self.curr_lcol = col[1]
            self.update_line()
Beispiel #9
0
class Plotter(Canvas):
    def __init__(self,
                 parent: tk.Frame = None,
                 interval=20,
                 linenum=10,
                 save=True,
                 drag=True,
                 adaptation=True,
                 lengthx=20000):
        Canvas.__init__(self, parent, bg='#345', closeenough=2)
        parent.update_idletasks()
        self.pack(fill=tk.BOTH, expand=True)
        self.update_idletasks()
        self.hScroll = Scrollbar(parent,
                                 orient='horizontal',
                                 command=self.hScrolled)
        self.hScroll.pack(side=tk.BOTTOM, fill=tk.X)
        self.configure(yscrollcommand=None,
                       xscrollcommand=self._canvashScrolled)
        self.bind("<Button-1>", self.__mouseDown)
        self.bind('<Button-3>', self.__mouseRightDown)
        self.bind("<MouseWheel>", self.__mouseScale)
        self.bind("<B1-Motion>", self.__mouseDownMove)
        self.bind("<B1-ButtonRelease>", self.__mouseUp)
        self.bind("<Enter>", self.__mouseEnter)
        self.bind("<Leave>", self.__mouseLeave)
        self.bind("<Motion>", self.__mouseHoverMove)
        self["xscrollincrement"] = 1
        self["yscrollincrement"] = 1
        self.__dataList = []
        self._vernierdata = [0] * linenum
        self.x = 0
        self.scrollx = 3.0
        self.offsety = 0
        self._interval = interval  # main loop interval to handle input data
        self._lengthx = lengthx  # scrollregion x
        self._vernierOn = False
        self._doubleVernierOn = False
        self._dragOn = drag  # enable mouse move items
        self._gridOn = True  # draw grid lines
        self._loopOn = True
        self._tipOn = False
        self._adaptation = adaptation
        self._vernier = None
        self._vernierX = -1
        self._lastVernierCanvasX = -1
        # double verniers
        self._rightVernier = None
        self._leftVernier = None
        self._rightVernierX = -1
        self._leftVernierX = -1
        # force update double-vernier by reset the value to -1
        self._lastRightVernierCanvasX = -1
        self._lastLeftVernierCanvasX = -1
        self._doubleVernierDistance = tk.IntVar()  # 0-any distance
        self._save = save  # 保存数据
        self._dataWriter = None  # 数据保存writer
        self._datafile = ''  # 保存文件名称
        self._scrollOn = False
        self._lineNum = linenum
        self._firststart = True
        self._scrollDelay = 0
        self._popLock = 0  # 防止多个toplevel
        # this data is used to keep track of an item being dragged
        # in this app, only vertical position
        self._drag_data = {
            "sx": 0,
            "sy": 0,
            "x": 0,
            "y": 0,
            "item": None,
            "type": 'signal'
        }
        self._lines = []
        self.after(200, self.__initCanvas)

    def __main(self):
        if self._drag_data['item'] is None:
            pass
        else:
            _item = self._drag_data['item']
            if _item._lastoffsety != _item._offsety:
                pass

        self.after(20, self.__main)

    def __loop(self):
        '''mainloop
        '''

        if self._loopOn:
            for _line in self._lines:
                # _line.adaptation()
                if _line.getLineLen() > 0:
                    _line.plot()

            self.setVernierValue()

            self.setDoubleVernierValue()

            self.autoScrollToEnd()

            self.setSignalTip()

        # always make vernier at top
        if self._doubleVernierOn:
            self.tag_raise('double_valuey')
            self.tag_raise('left_vernier')
            self.tag_raise('right_vernier')
        if self._vernierOn:
            self.tag_raise('valuey')
            self.tag_raise('vernier')

        self.after(self._interval, self.__loop)

    def __initCanvas(self):
        self.update_idletasks()
        self._width = self.winfo_width()
        self._height = self.winfo_height()
        self._originHeight = self.winfo_height()
        self._initHeight = self.winfo_height()
        self.drawGridLines(50)
        self.configure(scrollregion=(0, 0, self._lengthx, self.winfo_height()))
        self.xview_moveto(0)
        self.bind("<Configure>", self.__resize)
        self.__loop()

    # --------------- global API ---------------
    def setLoopStatus(self, on=False):
        '''set the mainloop status

        Keyword Arguments:
            on {bool} -- status (default: {False})
        '''

        self._loopOn = on

    def setDrag(self, on=False):
        '''enable or disable the mouse drag

        Keyword Arguments:
            on {bool} -- status (default: {False})
        '''

        self._dragOn = on

    def setVernier(self, on=False):
        '''show or hide coordinates when mouse move

        Keyword Arguments:
            on {bool} -- true for show vernier (default: {False})
        '''

        self._vernierOn = on

    def setDoubleVernier(self, on=False):
        '''enable or disable double vernier function 

        Keyword Arguments:
            on {bool} -- true for enable double vernier (default: {False})
        '''

        self._doubleVernierOn = on

    def setInterval(self, interval=20):
        '''set mainloop interval

        Keyword Arguments:
            interval {int} -- loop interval (default: {20})
        '''

        self._interval = interval

    def setGrid(self, on=True):
        '''show or hide the grids

        Keyword Arguments:
            on {bool} -- true for show grid lines (default: {True})
        '''

        self._gridOn = on

    def setSignalTip(self):
        if self._tipOn and len(self._lines) > 0:
            self.delete('sigtip')
            maxlen = max([len(line.name) for line in self._lines])
            gap = (maxlen + 1) * 7
            gap = max(gap, 100)
            for i, line in enumerate(self._lines):
                color = line.color
                if line.hidden:
                    color = "#aaa"
                _row = int(i / 5)
                _column = i % 5
                self.create_rectangle(self.canvasx(10) + gap * _column,
                                      10 + 20 * _row,
                                      gap * _column + self.canvasx(10) + 10,
                                      20 * _row + 20,
                                      fill=color,
                                      tags=(
                                          'sigtip',
                                          'tip' + line.id,
                                      ))
                _text = line.id
                if line.name != '' and line.name is not None:
                    _text += '-' + line.name
                self.create_text(self.canvasx(10) + 15 + gap * _column,
                                 15 + 20 * _row,
                                 text=_text,
                                 fill=color,
                                 font=('微软雅黑', 8),
                                 tags=(
                                     'sigtip',
                                     'tip' + line.id,
                                 ),
                                 anchor='w')
        else:
            self.delete('sigtip')

    def setVernierValue(self):
        '''draw vernier and valuey tip by given vernierx
        '''

        x = self.canvasx(self._vernierX)
        if self._lastVernierCanvasX == x:
            return
        self.delete('valuey')
        self.delete('vernier')
        if not self._vernierOn or self._vernierX < 0:
            self._lastVernierCanvasX = -1
            return
        self.drawOneVernierValue(x)
        self._lastVernierCanvasX = x

    def setDoubleVernierValue(self):
        x1 = self.canvasx(self._rightVernierX)
        x2 = self.canvasx(self._leftVernierX)
        if self._lastRightVernierCanvasX == x1 and self._lastLeftVernierCanvasX == x2:
            return
        self.delete('double_valuey')
        self.delete('left_vernier')
        self.delete('right_vernier')
        if not self._doubleVernierOn:
            return
        self.drawOneVernierValue(x1,
                                 linetag='right_vernier',
                                 texttag='double_valuey')
        self.drawOneVernierValue(x2,
                                 linetag='left_vernier',
                                 texttag='double_valuey')
        self.drawTriangle(x1, tagname='right_vernier')
        self.drawTriangle(x2, tagname='left_vernier')
        self._lastRightVernierCanvasX = x1
        self._lastLeftVernierCanvasX = x2

    def drawOneVernierValue(self,
                            x,
                            linecolor='#FFFAFA',
                            textcolor='#FFFZFA',
                            linetag='vernier',
                            texttag='valuey'):
        self.create_line((x, 0, x, self._height),
                         fill=linecolor,
                         tags=(linetag, ))
        self.create_text(x + 5,
                         self._height - 12,
                         text='X-' + str(self.canvasx(x)),
                         fill='#808090',
                         font=('微软雅黑', 7),
                         tags=(texttag, ),
                         anchor='w')
        for _line in self._lines:
            if x > _line.getLineLen() or _line.hidden:
                continue
            tipy = _line.getTipY(x)
            valuey = _line.getScreenY(x)
            y = valuey
            # self.create_rectangle(x+5, y-15, x+15, y-5,  tags=('valuey', ), fill=_line.color, outline=_line.color)
            self.create_text(x + 5,
                             y - 5,
                             text=tipy,
                             fill=textcolor,
                             font=('微软雅黑', 9),
                             tags=(texttag, ),
                             anchor='w')

    def drawTriangle(self, x, tagname='double_vernier', color='#FFFZFA'):
        self.create_polygon([x - 5, 0, x + 5, 0, x, 10],
                            outline=color,
                            fill=color,
                            tags=(tagname, ))
        self.create_polygon([
            x - 5, self._height - 2, x + 5, self._height - 2, x,
            self._height - 12
        ],
                            outline=color,
                            fill=color,
                            tags=(tagname, ))

    def setScroll(self, on=True):
        self._scrollOn = on

    def clear(self):  # Fill strip with background color
        self._loopOn = False
        self._lines = []
        self.drawGridLines()

    def stopLoop(self):
        self._loopOn = False

    def drawGridLines(self, width=50):
        '''draw grid lines

        Keyword Arguments:
            width {int} -- distance between grid lines (default: {50})
        '''

        _height = self.winfo_height()
        offsety = math.floor(_height / 2) + 1
        offsetx = self._lengthx if self._lengthx > self.winfo_width(
        ) else self.winfo_width()
        self.delete('grid')
        self.create_line((0, offsety, offsetx, offsety),
                         fill="#708090",
                         dash=(2, 6),
                         tags=('grid'))
        if self._gridOn is True:
            xnum = math.floor(offsetx / width)
            for i in range(xnum):
                self.create_line((i * 50 + 50, 0, i * 50 + 50, _height),
                                 fill="#708090",
                                 dash=(1, 6),
                                 tags=('grid'))
            ynum = math.floor(_height / (2 * width))
            for j in range(ynum):
                self.create_line(
                    (0, j * 50 + 50 + offsety, offsetx, j * 50 + 50 + offsety),
                    fill="#708090",
                    dash=(2, 6),
                    tags=('grid'))
                self.create_line((0, -j * 50 - 50 + offsety, offsetx,
                                  -j * 50 - 50 + offsety),
                                 fill="#708090",
                                 dash=(2, 6),
                                 tags=('grid'))

    def getSignalbyId(self, id):
        '''get signal by given id

        Arguments:
            id {str} -- signal id

        Returns:
            signal -- signal
        '''

        for _line in self._lines:
            if _line.id == id:
                return _line
        return None

    def getSignalbyTags(self, tags):
        '''get signal by given tag list

        Arguments:
            tags {list} -- signal's tag list

        Returns:
            [signal] -- signal
        '''

        for _line in self._lines:
            if _line.id in tags:  # 求交集
                return _line
        return None

    def sortSignals(self):
        '''sort signals by order in self._lines
        '''

        self.delete('auxiliary')
        _height = self._height - 40  # 上下各留20像素
        _lines = [
            line for line in self._lines
            if (len(line._points) > 0 and line.hidden is not True)
        ]
        if len(_lines) == 0:
            return
        _blockHeight = _height / len(_lines)
        self._lastLeftVernierCanvasX = -1

        for i, _line in enumerate(_lines):
            desty = 20 + (i + 1) * _blockHeight - _blockHeight / 2
            y = self._height / 2 - desty
            self.create_line(0,
                             desty,
                             self._lengthx,
                             desty,
                             fill='red',
                             dash=(6, 6),
                             tags=('auxiliary', ))
            _offsety = (_line.getMaxY() + _line.getMinY()) / 2 - y
            _offsety = _offsety + (self._height - self._originHeight) / 2
            _line.setOffsetY(_offsety)

            _nowHeight = (_line.getMaxY() - _line.getMinY()) * \
                _line.getScaleY()
            _delta = 1
            if _nowHeight > 0:
                _delta = (_blockHeight - 10) / _nowHeight
            _line.setScaleY(_delta * _line.getScaleY())

    def resortSignals(self):
        # if self.find_withtag('auxiliary'):
        self.delete('auxiliary')
        self._lastLeftVernierCanvasX = -1
        for _line in self._lines:
            _line.restore()

    def stopSave(self):
        self._save = False
        if self._dataWriter is not None:
            self._dataWriter.close()

    def loadData(self, filename):
        '''load csv file stored

        Arguments:
            filename {str} -- csv file name
        '''
        # if self._loopOn is True:
        # return

        self._width = self.winfo_width()
        self._height = self.winfo_height()
        self._originHeight = self.winfo_height()
        self._initHeight = self.winfo_height()

        with open(filename, mode='r', encoding='gbk') as f:
            reader = csv.reader(f)
            ids = next(reader)
            names = next(reader)
            historys = next(reader)
            colors = next(reader)
            decimals = next(reader)
            units = next(reader)
            valtips = next(reader)

            if set([
                    len(ids),
                    len(historys),
                    len(colors),
                    len(decimals),
                    len(units),
                    len(valtips)
            ]) == 1:
                logger.warning('error param count in csv file')
                return

            self._lines = []
            num = len(ids)
            for i in range(num):
                _tips = {}
                if valtips[i] != '{}':
                    # print(valtips[i])
                    _tips = json.loads(valtips[i])
                _line = Signal(canvas=self,
                               id=ids[i],
                               name=names[i],
                               history=20000,
                               color=colors[i],
                               unit=str(units[i]),
                               decimal=int(decimals[i]),
                               valuetip=_tips)
                self._lines.append(_line)

        with open(filename, mode='r', encoding='gbk') as f:
            lines = f.readlines()
            datalen = len(lines)
            data = lines[7:]
            for i, _line in enumerate(self._lines):
                _line.setHistory(datalen)
                _line.addPoints(
                    [float(row.strip().split(',')[i]) for row in data])

    def _selectOneSignal(self, id):
        for _line in self._lines:
            if _line.id == id:
                _line.setSelected(True)
            else:
                _line.setSelected(False)

    def deleteSignal(self, id):
        for _line in self._lines:
            if _line.id == id:
                _line.setSelected(False)
                _line.hide()

    def initLineList(self,
                     lineNumber,
                     names=[],
                     decimals=[],
                     valuetips=[],
                     units=[]):
        '''init all lines by given number and other params

        Arguments:
            number {int} -- number of lines
        '''

        self._width = self.winfo_width()
        self._height = self.winfo_height()
        self._originHeight = self.winfo_height()
        self._initHeight = self.winfo_height()

        self._lines = []  #
        self._currentId = None  # 当前选择曲线id
        _list = list(colorList)
        _lineid = []
        _linenames = []
        _linedecimals = []
        _linevaluetips = []
        _lineunits = []
        self.x = 0
        _lineid = list(range(0, lineNumber))
        if len(names) == lineNumber:
            _linenames = names
        else:
            _linenames = [''] * lineNumber

        if len(decimals) == lineNumber:
            _linedecimals = decimals
        else:
            _linedecimals = [0] * lineNumber

        if len(valuetips) == lineNumber:
            _linevaluetips = valuetips
        else:
            _linevaluetips = [{}] * lineNumber

        if len(units) == lineNumber:
            _lineunits = units
        else:
            _lineunits = [''] * lineNumber

        while len(self._lines) < lineNumber:
            _len = len(self._lines)
            _index = random.randint(0, len(_list) - 1)
            _random = _list[_index]
            _list.remove(_random)
            _line = Signal(id=str(_lineid[_len]),
                           name=str(_linenames[_len]),
                           canvas=self,
                           history=20000,
                           color=_random,
                           decimal=_linedecimals[_len],
                           unit=_lineunits[_len],
                           valuetip=dict(_linevaluetips[_len]))
            self._lines.append(_line)

        if self._save:
            self._datafile = 'data/' + \
                time.strftime("%Y_%m_%d_%H_%M_%S",
                              time.localtime()) + '.csv'
            # logger.debug(self._datafile)
            with open(self._datafile, mode='a', encoding='gbk',
                      newline='') as f:
                self._dataWriter = csv.writer(f)
                self._dataWriter.writerow([line.id for line in self._lines])
                self._dataWriter.writerow([line.name for line in self._lines])
                self._dataWriter.writerow(
                    [line.getHistory() for line in self._lines])
                self._dataWriter.writerow([line.color for line in self._lines])
                self._dataWriter.writerow(
                    [line.decimal for line in self._lines])
                self._dataWriter.writerow([line.unit for line in self._lines])
                self._dataWriter.writerow(
                    [json.dumps(line.valuetip) for line in self._lines])

    def addLinePoint(self, values=[]):
        if len(values) != len(self._lines):
            return
        for i, _line in enumerate(self._lines):
            _line.addPoint(values[i])

        if self._save:
            with open(self._datafile, mode='a', encoding='gbk',
                      newline='') as f:
                self._dataWriter = csv.writer(f)
                self._dataWriter.writerow(values)

        self.x = self.x + 1

    # --------------- inner API ---------------
    def hScrolled(self, *args):
        self.xview(*args)
        # delta = self.canvasx(10) - self.scrollx
        # print(self.canvasx(10), delta)
        # for _tip in self.find_withtag('sigtip'):
        #     self.move(_tip, delta, 0)
        # self.scrollx = self.canvasx(10)

    def autoScrollToEnd(self):
        '''scrool canvas to signal end when needed
        '''

        if self._scrollOn is True:
            try:
                maxlen = max([_line.getLineLen() for _line in self._lines])
            except Exception:
                maxlen = 0
            if maxlen < self._width * 0.75:
                maxlen = 0
            elif maxlen > self._lengthx - self._width * 0.25:
                maxlen = int(self._lengthx - self._width * 0.25)
            else:
                maxlen = maxlen - int(self._width * 0.75)
            fraction = maxlen / self._lengthx
            self.xview_moveto(fraction)
        else:
            pass

    def _canvashScrolled(self, *args):
        self.hScroll.set(*args)
        # delta = self.canvasx(10) - self.scrollx
        # for _tip in self.find_withtag('sigtip'):
        #     self.move(_tip, delta, 0)
        # self.scrollx = self.canvasx(10)

    def __resize(self, event):
        # self.update_idletasks()
        if len(self._lines) == 0:
            self._originHeight = self.winfo_height()
        _deltay = (self.winfo_height() - self._height) / 2
        self.drawGridLines(50)
        for _line in self._lines:
            _line.setSelected(False)
            _line.moveY(_deltay)
        for _item in self.find_withtag('auxiliary'):
            self.move(_item, 0, _deltay)
        self._height = self.winfo_height()
        self._width = self.winfo_width()
        self._lastRightVernierCanvasX = -1  # redraw double vernier

    def __mouseScale(self, event):
        if self._drag_data["item"] is None:
            return
        if (event.delta > 0):
            self._drag_data["item"].scaleY(1.2)
        else:
            self._drag_data["item"].scaleY(0.8)

    # def updateLines(self, lineList:list):
    def __mouseDown(self, event):
        for signal in self._lines:
            signal.setSelected(selected=False)
        self._drag_data['item'] = None
        # print('down', event.x, event.y, self.canvasx(0))
        # _items = self.find_closest(event.x, event.y)
        # print('closest', self.gettags(_items[0]))
        _x = self.canvasx(0)
        _items1 = self.find_overlapping(event.x - 3 + _x, event.y - 3,
                                        event.x + 3 + _x, event.y + 3)
        _currentItem = None
        for _item in _items1:
            _tags = self.gettags(_item)
            if 'signal' in _tags:
                _currentItem = _item
                self._drag_data['type'] = 'signal'
                break
            elif 'sigtip' in _tags:
                # if selected item is sigtip, show or hide the signal
                selectedid = _tags[1][3:]
                print(selectedid, _tags)
                _line = self.getSignalbyId(selectedid)
                if _line.hidden:
                    _line.show()
                else:
                    _line.hide()
                break
            elif 'left_vernier' in _tags:
                _currentItem = _item
                self._drag_data['type'] = 'left_vernier'
                break
            elif 'right_vernier' in _tags:
                _currentItem = _item
                self._drag_data['type'] = 'right_vernier'
                break

        if _currentItem is not None:
            if self._drag_data['type'] == 'signal':
                _tags = self.gettags(_currentItem)
                self._drag_data['item'] = self.getSignalbyTags(_tags)
                self._drag_data['x'] = 0
                self._drag_data['y'] = event.y

                if hasattr(self._drag_data['item'], 'id'):
                    self._selectOneSignal(self._drag_data['item'].id)
                    self.tag_raise(
                        self._drag_data['item'].id)  # raise this signal
            else:
                self._drag_data['item'] = _currentItem
                self._drag_data['x'] = event.x
                self._drag_data['y'] = event.y

            self._drag_data['sy'] = event.y
            self._drag_data['sx'] = event.x

    def __mouseRightDown(self, event):
        if self._doubleVernierOn and self._popLock == 0:
            self._popLock = 1
            self._paramTop = Toplevel()
            w = 260
            h = 100
            # 计算 x, y 位置
            x = (self._width / 2) - (w / 2)
            y = (self._height / 2) - (h / 2) - 20
            self._paramTop.attributes('-alpha', 1.0)
            self._paramTop.attributes('-topmost', True)
            self._paramTop.positionfrom(who='user')
            self._paramTop.resizable(width=False, height=False)
            self._paramTop.title('设置距离')
            self._paramTop.geometry('{}x{}+{}+{}'.format(w, h, int(x), int(y)))

            tk.Entry(self._paramTop,
                     textvariable=self._doubleVernierDistance).pack(fill=tk.X)

            _row3 = tk.Frame(self._paramTop)
            _row3.pack(pady=20, fill=tk.BOTH, expand=tk.TRUE)
            tk.Button(_row3,
                      text='确定',
                      font=('微软雅黑', 9),
                      width=6,
                      command=self._setDoubleVernierDistance).pack(
                          side=tk.LEFT, padx=40)
            tk.Button(_row3,
                      text='取消',
                      font=('微软雅黑', 9),
                      width=6,
                      command=self._cancelDoubleVernierDistance).pack(
                          side=tk.LEFT, padx=40)
            self._paramTop.protocol('WM_DELETE_WINDOW',
                                    self._cancelDoubleVernierDistance)

    def __mouseDownMove(self, event):
        '''Handle dragging of an object'''
        if self._drag_data["item"] is None:
            return
        if self._drag_data['type'] == 'signal':
            if self._dragOn is not True:
                return
            # compute how much the mouse has moved
            # delta_x = 0 # event.x - self._drag_data["x"]
            delta_y = event.y - self._drag_data["y"]
            # offset_y = event.y - self._drag_data["sy"]
            self._drag_data["item"].moveY(delta_y)
            # record the new position
            self._drag_data["x"] = 0
            self._drag_data["y"] = event.y
        else:
            if self._doubleVernierOn:
                _distance = self._doubleVernierDistance.get()
                if self._drag_data['type'] == 'right_vernier':
                    self._rightVernierX = event.x
                    if _distance != 0:
                        self._leftVernierX = self._rightVernierX - _distance
                    elif self._rightVernierX <= self._leftVernierX:
                        self._rightVernierX = self._leftVernierX + 1
                elif self._drag_data['type'] == 'left_vernier':
                    self._leftVernierX = event.x
                    if _distance != 0:
                        self._rightVernierX = self._leftVernierX + _distance
                    elif self._rightVernierX <= self._leftVernierX:
                        self._leftVernierX = self._rightVernierX - 1

    def __mouseHoverMove(self, event):
        if self._vernierOn:
            # delta_x = event.x - self._vernierX
            # self.after(80, self.__delayMove, delta_x)
            self._vernierX = event.x
            # print(self.gettags(self.find_withtag(tk.CURRENT)))

    def __delayMove(self, delta_x):
        if self._vernier:
            self.move(self._vernier, delta_x, 0)

    def __mouseUp(self, event):
        # print(event.x, event.y)
        # force update the double-vernier
        self._lastLeftVernierCanvasX = -1
        pass

    def __mouseEnter(self, event):
        if self._vernierOn:
            #     self._vernier = self.create_line(
            #         (event.x, 0, event.x, self._height), fill="#FFFAFA")
            self._vernierX = event.x
        pass

    def __mouseLeave(self, event):
        self.delete('vernier')
        self._vernierX = -1

    def _setDoubleVernierDistance(self):
        self._popLock = 0
        _distance = self._doubleVernierDistance.get()
        if _distance > self._width - 10:
            _distance = self._width - 10
            self._doubleVernierDistance.set(_distance)
        print('distance', _distance)
        if _distance != 0:
            if self._leftVernierX + _distance < self._width:
                self._rightVernierX = self._leftVernierX + _distance
            elif self._rightVernierX - _distance > 0:
                self._leftVernierX = self._rightVernierX - _distance
            else:
                self._leftVernierX = 5
                self._rightVernierX = self._leftVernierX + _distance
        self._paramTop.destroy()

    def _cancelDoubleVernierDistance(self):
        self._doubleVernierDistance.set(0)
        self._popLock = 0
        self._paramTop.destroy()

    # --------------- below code just for testing the function ---------------

    def _autoTest(self):
        if len(self._lines) != 5:
            self._lines = []
            valuetip = {"0": "disable", "1": "enable", "2": "rsvd"}
            self._lines.append(
                Signal(id='Y1',
                       name='A',
                       canvas=self,
                       history=20000,
                       color='#ff4'))
            self._lines.append(
                Signal(id='Y2',
                       name='B',
                       canvas=self,
                       history=20000,
                       color='#f40',
                       decimal=2,
                       unit='A'))
            self._lines.append(
                Signal(id='Y3',
                       name='B999999',
                       canvas=self,
                       history=20000,
                       color='#4af'))
            self._lines.append(
                Signal(id='Y4',
                       name='A',
                       canvas=self,
                       history=20000,
                       color='#080'))
            self._lines.append(
                Signal(id='Y5',
                       name='C',
                       canvas=self,
                       history=20000,
                       color='purple',
                       valuetip=valuetip))
            if self._save:
                self._datafile = 'data/' + \
                    time.strftime("%Y_%m_%d_%H_%M_%S",
                                  time.localtime()) + '.csv'
                logger.debug(self._datafile)
                with open(self._datafile, mode='a', encoding='gbk',
                          newline='') as f:
                    self._dataWriter = csv.writer(f)
                    self._dataWriter.writerow(
                        [line.id for line in self._lines])
                    self._dataWriter.writerow(
                        [line.name for line in self._lines])
                    self._dataWriter.writerow(
                        [line.getHistory() for line in self._lines])
                    self._dataWriter.writerow(
                        [line.color for line in self._lines])
                    self._dataWriter.writerow(
                        [line.decimal for line in self._lines])
                    self._dataWriter.writerow(
                        [line.unit for line in self._lines])
                    self._dataWriter.writerow(
                        [json.dumps(line.valuetip) for line in self._lines])

        y1 = 10 * math.sin(0.02 * math.pi * self.x)
        y2 = 20 + 5 * (random.random() - 0.5)
        y3 = 50
        y4 = -30 if (self.x % 20 == 0) else 20
        y5 = random.randint(-100, 2000)

        self._lines[0].addPoint(y1)
        self._lines[1].addPoint(y2)
        self._lines[2].addPoint(y3)
        self._lines[3].addPoint(y4)
        self._lines[4].addPoint(y5)
        if self._save:
            with open(self._datafile, mode='a', encoding='gbk',
                      newline='') as f:
                self._dataWriter = csv.writer(f)
                self._dataWriter.writerow([y1, y2, y3, y4, y5])

        self.x = self.x + 1
        self.after(10, self._autoTest)

    def toggleVernier(self):
        self._vernierOn = True if self._vernierOn is False else False
        if self._vernierOn is True:
            self._doubleVernierOn = False
            self._lastRightVernierCanvasX = -1
            self._lastLeftVernierCanvasX = -1

    def toggleDoubleVernier(self):
        self._doubleVernierOn = True if self._doubleVernierOn is False else False
        if self._doubleVernierOn is True:
            self._leftVernierX = 5
            self._rightVernierX = self._width - 5
            self._vernierOn = False
        else:
            self._lastRightVernierCanvasX = -1
            self._lastLeftVernierCanvasX = -1

    def toggleDrag(self):
        self._dragOn = True if self._dragOn is False else False

    def toggleGrid(self):
        self._gridOn = True if self._gridOn is False else False
        self.drawGridLines(50)

    def _selectTest(self):
        _line = self._lines[random.randint(0, 3)]
        if _line.selected is True:
            _line.setSelected(False)
        else:
            _line.setSelected(True)

    def toggleTip(self):
        self._tipOn = True if self._tipOn is False else False

    def _restoreTest(self):
        for _line in self._lines:
            _line.restore()

    def _scaleTest(self):
        _scale = random.randint(1, 3)
        if self._drag_data['item'] is not None:
            self._drag_data['item'].scaleY(_scale)

    def _sortTest(self):
        self.sortSignals()

    def _resortTest(self):
        self.resortSignals()

    def _scrollTest(self):
        self._scrollOn = True if self._scrollOn is False else False

    def _adapationTest(self):
        for _line in self._lines:
            if not _line.hidden:
                _line.adaptation()

    def _loadTest(self):
        filename = filedialog.askopenfilename(title='载入数据',
                                              filetypes=[('csv', '*.csv')])
        self.loadData(filename)

    def _clear(self):
        if len(self._lines) != 0:
            self._lines = []
Beispiel #10
0
class ListFrame(Frame):
    def __init__(self, master,
                 root, recursive,
                 regex, repl,
                 options):
        Frame.__init__(self, master)

        self._left_list = Listbox(self)
        self._left_list.pack(side=LEFT, fill=BOTH, expand=True)
        
        self._right_list = Listbox(self)
        self._right_list.pack(side=LEFT, fill=BOTH, expand=True)
        self._right_scroll = Scrollbar(self._right_list, orient=VERTICAL)
        self._right_list.config(yscrollcommand=self._right_scroll.set)
        self._right_scroll.config(command=self._right_list.yview)

        self._scrollbar = Scrollbar(self, orient=VERTICAL, command=self._scroll_scrollbar)
        self._scrollbar.pack(side=RIGHT, fill=Y)
        self._left_list.config(yscrollcommand=self._scroll_left)
        self._right_list.config(yscrollcommand=self._scroll_right)

        self._regex = regex
        self._repl = repl
        self._settings = options
        self._root = None
        self._recursive = None
        self._names = None
        self._mapping = None
        self._errors = None
        self._update_root(root, recursive)

        master.bind('<<RootUpdate>>', self._on_root_update)
        master.bind('<<RegexUpdate>>', self._on_regex_update)
        master.bind('<<OptionsUpdate>>', self._on_options_update)
        master.bind('<<Refresh>>', self._on_refresh)

    def _scroll_left(self, sfrom, sto):
        self._scrollbar.set(sfrom, sto)
        self._right_list.yview('moveto', sfrom)

    def _scroll_right(self, sfrom, sto):
        self._scrollbar.set(sfrom, sto)
        self._left_list.yview('moveto', sfrom)

    def _scroll_scrollbar(self, *args):
        self._left_list.yview(*args)
        self._right_list.yview(*args)

    def _on_root_update(self, event):
        self._update_root(event.widget.root, event.widget.recursive)

    def _on_regex_update(self, event):
        self._update_regex(event.widget.regex, event.widget.repl)

    def _on_refresh(self, event):
        self._update_root(self._root, self._recursive)

    def _on_options_update(self, event):
        self._settings = event.widget.options
        self._update_lists()

    def _update_regex(self, regex, repl):
        self._regex = regex
        self._repl = repl
        self._update_lists()

    def _is_type_enabled(self, ftype):
        if ftype is True:
            return self._settings.files
        elif ftype is False:
            return self._settings.dirs
        else:
            return self._settings.others

    def _walk(self):
        for root, dirs, files in os.walk(self._root):
            for name in files + dirs:
                path = os.path.join(root, name)
                yield os.path.relpath(path, self._root)

    def _entries(self):
        if self._recursive:
            return self._walk()
        else:
            return os.listdir(self._root)

    def _update_root(self, root, recursive):
        self._root = root
        self._recursive = recursive
        self._left_list.delete(0, END)
        self._names = []
        if self._root:
            for name in sorted(self._entries()):
                path = os.path.join(self._root, name)
                ftype = None
                if os.path.isfile(path):
                    ftype = True
                if os.path.isdir(path):
                    ftype = False
                self._names.append((name, ftype))

        self._update_lists()

    def _insert_name_both(self, name, color, color_right_only=False):
        idx = self._left_list.size()
        self._left_list.insert(END, name)
        if not color_right_only:
            self._left_list.itemconfig(idx, dict(fg=color))
        self._right_list.insert(END, name)
        self._right_list.itemconfig(idx, dict(fg=color))

    def _update_lists(self):
        self._mapping = []
        self._errors = []
        rev_mapping = {}
        self._left_list.delete(0, END)
        self._right_list.delete(0, END)

        if not self._repl:
            self._errors.append('Invalid replacement string')
        
        for name, ftype in self._names:
            enabled = self._is_type_enabled(ftype)
            if enabled or not self._settings.hide_wrong_type:
                if not enabled or not self._regex:
                    self._insert_name_both(name, 'gray')
                elif self._regex and not self._regex.match(name):
                    if not self._settings.hide_mismatches:
                        self._insert_name_both(name, 'gray')
                elif not self._repl:
                    self._insert_name_both(name, 'gray', color_right_only=True)
                else:
                    idx = self._left_list.size()
                    right_name = self._regex.sub(self._repl, name)
                    self._left_list.insert(END, name)
                    self._right_list.insert(END, right_name)

                    if name != right_name:
                        self._mapping.append((name, right_name))

                        right_path = os.path.join(self._root, right_name)
                        if os.path.exists(right_path):
                            error = 'File already exists: %s' % right_name
                        elif right_name in rev_mapping:
                            other_name, other_idx = rev_mapping[right_name]
                            colliding_sources = name, other_name
                            error = 'Name collision: %s <- %s | %s' % (right_name, *colliding_sources)
                            self._right_list.itemconfig(other_idx, dict(fg='red'))
                        else:
                            error = None
                            rev_mapping[right_name] = name, idx

                        if error:
                            self._errors.append(error)
                            self._right_list.itemconfig(idx, dict(fg='red'))

    @property
    def mapping(self):
        if not self._errors:
            return self._mapping

    @property
    def errors(self):
        return self._errors