def test_tabs(): a = Tk() n = Notebook( a, TOP ) # uses the Notebook's frame f1 = Frame( n() ) b1 = Button( f1, text="Ignore me" ) e1 = Entry( f1 ) # pack your widgets before adding the frame # to the Notebook (but not the frame itself)! b1.pack( fill=BOTH, expand=1 ) e1.pack( fill=BOTH, expand=1 ) # keeps the reference to the radiobutton (optional) x1 = n.add_screen( f1, "Tab 1" ) f2 = Frame( n() ) # this button destroys the 1st screen radiobutton b2 = Button( f2, text='Remove Tab 1', command=lambda:x1.destroy() ) b3 = Button( f2, text='Beep...', command=lambda:Tk.bell( a ) ) b2.pack( fill=BOTH, expand=1 ) b3.pack( fill=BOTH, expand=1 ) f3 = Frame( n() ) n.add_screen( f2, "Tab 2" ) n.add_screen( f3, "Minimize" ) a.mainloop()
class NvimTk(object): """Wraps all nvim/tk event handling.""" def __init__(self, nvim): """Initialize with a Nvim instance.""" self._nvim = nvim self._attrs = {} self._nvim_updates = deque() self._canvas = None self._fg = '#000000' self._bg = '#ffffff' def run(self): """Start the UI.""" self._tk_setup() t = Thread(target=self._nvim_event_loop) t.daemon = True t.start() self._root.mainloop() def _tk_setup(self): self._root = Tk() self._root.bind('<<nvim_redraw>>', self._tk_nvim_redraw) self._root.bind('<<nvim_detach>>', self._tk_nvim_detach) self._root.bind('<Key>', self._tk_key) def _tk_nvim_redraw(self, *args): update = self._nvim_updates.popleft() for update in update: handler = getattr(self, '_tk_nvim_' + update[0]) for args in update[1:]: handler(*args) def _tk_nvim_detach(self, *args): self._root.destroy() def _tk_nvim_resize(self, width, height): self._tk_redraw_canvas(width, height) def _tk_nvim_clear(self): self._tk_clear_region(0, self._height - 1, 0, self._width - 1) def _tk_nvim_eol_clear(self): row, col = ( self._cursor_row, self._cursor_col, ) self._tk_clear_region(row, row, col, self._scroll_right) def _tk_nvim_cursor_goto(self, row, col): self._cursor_row = row self._cursor_col = col def _tk_nvim_cursor_on(self): pass def _tk_nvim_cursor_off(self): pass def _tk_nvim_mouse_on(self): pass def _tk_nvim_mouse_off(self): pass def _tk_nvim_insert_mode(self): pass def _tk_nvim_normal_mode(self): pass def _tk_nvim_set_scroll_region(self, top, bot, left, right): self._scroll_top = top self._scroll_bot = bot self._scroll_left = left self._scroll_right = right def _tk_nvim_scroll(self, count): top, bot = ( self._scroll_top, self._scroll_bot, ) left, right = ( self._scroll_left, self._scroll_right, ) if count > 0: destroy_top = top destroy_bot = top + count - 1 move_top = destroy_bot + 1 move_bot = bot fill_top = move_bot + 1 fill_bot = fill_top + count - 1 else: destroy_top = bot + count + 1 destroy_bot = bot move_top = top move_bot = destroy_top - 1 fill_bot = move_top - 1 fill_top = fill_bot + count + 1 # destroy items that would be moved outside the scroll region after # scrolling # self._tk_clear_region(destroy_top, destroy_bot, left, right) # self._tk_clear_region(move_top, move_bot, left, right) self._tk_destroy_region(destroy_top, destroy_bot, left, right) self._tk_tag_region('move', move_top, move_bot, left, right) self._canvas.move('move', 0, -count * self._rowsize) self._canvas.dtag('move', 'move') # self._tk_fill_region(fill_top, fill_bot, left, right) def _tk_nvim_highlight_set(self, attrs): self._attrs = attrs def _tk_nvim_put(self, data): # choose a Font instance font = self._fnormal if self._attrs.get('bold', False): font = self._fbold if self._attrs.get('italic', False): font = self._fbolditalic if font == self._fbold else self._fitalic # colors fg = "#{0:0{1}x}".format(self._attrs.get('foreground', self._fg), 6) bg = "#{0:0{1}x}".format(self._attrs.get('background', self._bg), 6) # get the "text" and "rect" which correspond to the current cell x, y = self._tk_get_coords(self._cursor_row, self._cursor_col) items = self._canvas.find_overlapping(x, y, x + 1, y + 1) if len(items) != 2: # caught part the double-width character in the cell to the left, # filter items which dont have the same horizontal coordinate as # "x" predicate = lambda item: self._canvas.coords(item)[0] == x items = filter(predicate, items) # rect has lower id than text, sort to unpack correctly rect, text = sorted(items) self._canvas.itemconfig(text, fill=fg, font=font, text=data or ' ') self._canvas.itemconfig(rect, fill=bg) self._tk_nvim_cursor_goto(self._cursor_row, self._cursor_col + 1) def _tk_nvim_bell(self): self._root.bell() def _tk_nvim_update_fg(self, fg): self._fg = "#{0:0{1}x}".format(fg, 6) def _tk_nvim_update_bg(self, bg): self._bg = "#{0:0{1}x}".format(bg, 6) def _tk_redraw_canvas(self, width, height): if self._canvas: self._canvas.destroy() self._fnormal = Font(family='Monospace', size=13) self._fbold = Font(family='Monospace', weight='bold', size=13) self._fitalic = Font(family='Monospace', slant='italic', size=13) self._fbolditalic = Font(family='Monospace', weight='bold', slant='italic', size=13) self._colsize = self._fnormal.measure('A') self._rowsize = self._fnormal.metrics('linespace') self._canvas = Canvas(self._root, width=self._colsize * width, height=self._rowsize * height) self._tk_fill_region(0, height - 1, 0, width - 1) self._cursor_row = 0 self._cursor_col = 0 self._scroll_top = 0 self._scroll_bot = height - 1 self._scroll_left = 0 self._scroll_right = width - 1 self._width, self._height = ( width, height, ) self._canvas.pack() def _tk_fill_region(self, top, bot, left, right): # create columns from right to left so the left columns have a # higher z-index than the right columns. This is required to # properly display characters that cross cell boundary for rownum in range(bot, top - 1, -1): for colnum in range(right, left - 1, -1): x1 = colnum * self._colsize y1 = rownum * self._rowsize x2 = (colnum + 1) * self._colsize y2 = (rownum + 1) * self._rowsize # for each cell, create two items: The rectangle is used for # filling background and the text is for cell contents. self._canvas.create_rectangle(x1, y1, x2, y2, fill=self._bg, width=0) self._canvas.create_text(x1, y1, anchor='nw', font=self._fnormal, width=1, fill=self._fg, text=' ') def _tk_clear_region(self, top, bot, left, right): self._tk_tag_region('clear', top, bot, left, right) self._canvas.itemconfig('clear', fill=self._bg) self._canvas.dtag('clear', 'clear') def _tk_destroy_region(self, top, bot, left, right): self._tk_tag_region('destroy', top, bot, left, right) self._canvas.delete('destroy') self._canvas.dtag('destroy', 'destroy') def _tk_tag_region(self, tag, top, bot, left, right): x1, y1 = self._tk_get_coords(top, left) x2, y2 = self._tk_get_coords(bot, right) self._canvas.addtag_overlapping(tag, x1, y1, x2 + 1, y2 + 1) def _tk_get_coords(self, row, col): x = col * self._colsize y = row * self._rowsize return x, y def _tk_key(self, event): if 0xffe1 <= event.keysym_num <= 0xffee: # this is a modifier key, ignore. Source: # https://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm return # Translate to Nvim representation of keys send = [] if event.state & 0x1: send.append('S') if event.state & 0x4: send.append('C') if event.state & (0x8 | 0x80): send.append('A') special = len(send) > 0 key = event.char if _is_invalid_key(key): special = True key = event.keysym send.append(SPECIAL_KEYS.get(key, key)) send = '-'.join(send) if special: send = '<' + send + '>' nvim = self._nvim nvim.session.threadsafe_call(lambda: nvim.input(send)) def _nvim_event_loop(self): self._nvim.session.run(self._nvim_request, self._nvim_notification, lambda: self._nvim.attach_ui(80, 24)) self._root.event_generate('<<nvim_detach>>', when='tail') def _nvim_request(self, method, args): raise Exception('This UI does not implement any methods') def _nvim_notification(self, method, args): if method == 'redraw': self._nvim_updates.append(args) self._root.event_generate('<<nvim_redraw>>', when='tail')
class Cap(Frame): def __init__(self): ' set defaults, create widgets, bind callbacks, start live view ' self.root = Tk() # menu: self.menu = Menu(self.root) self.root.config(menu=self.menu) # bind global keypresses: self.root.bind('q', lambda e: self.root.quit()) self.root.bind('x', lambda e: self.root.quit()) self.root.bind('<Destroy>', self.do_stop_video) self.root.bind('<space>', self.do_single_shot) self.root.bind('<Return>', self.do_single_shot) self.root.bind('<Button-3>', self.do_single_shot) self.root.bind('<Left>', lambda e: self.degree.set(-90)) self.root.bind('<Right>', lambda e: self.degree.set(90)) self.root.bind('<Up>', lambda e: self.degree.set(0)) self.root.bind('a', lambda e: self.autocontrast.set(not self.autocontrast.get())) self.root.bind('e', lambda e: self.equalize.set(not self.equalize.get())) self.root.bind('g', lambda e: self.grayscale.set(not self.grayscale.get())) self.root.bind('i', lambda e: self.invert.set(not self.invert.get())) self.root.bind('s', lambda e: self.solarize.set(not self.solarize.get())) # config: self.config = RawConfigParser() self.config.read('filmroller.conf') if not self.config.has_section('global'): self.config.add_section('global') self.video = None self.invert = BooleanVar(name='invert') self.invert.set(self.config_get('invert', True)) self.invert.trace('w', self.do_configure) self.grayscale = BooleanVar(name='grayscale') self.grayscale.set(self.config_get('grayscale', False)) self.grayscale.trace('w', self.do_configure) self.autocontrast = BooleanVar(name='autocontrast') self.autocontrast.set(self.config_get('autocontrast', True)) self.autocontrast.trace('w', self.do_configure) self.equalize = BooleanVar(name='equalize') self.equalize.set(self.config_get('equalize', False)) self.equalize.trace('w', self.do_configure) self.solarize = BooleanVar(name='solarize') self.solarize.set(self.config_get('solarize', False)) self.solarize.trace('w', self.do_configure) self.degree = IntVar(name='degree') self.degree.set(0) self.filename = StringVar(name='filename') self.videodevice = StringVar(name='videodevice') dev_names = sorted(['/dev/{}'.format(x) for x in listdir('/dev') if x.startswith('video')]) d = self.config_get('videodevice', dev_names[-1]) if not d in dev_names: d = dev_names[-1] self.videodevice.set(d) self.videodevice.trace('w', self.do_configure) # self.path = 'filmroller' if not exists(self.path): makedirs(self.path) # create gui: Frame.__init__(self, self.root) self.grid() self.x_canvas = Canvas(self, width=640, height=640, ) self.x_canvas.pack(side='top') self.x_canvas.bind('<Button-1>', self.do_change_rotation) Checkbutton(self, text='Invert', variable=self.invert).pack(side='left') Checkbutton(self, text='Gray', variable=self.grayscale).pack(side='left') Checkbutton(self, text='Auto', variable=self.autocontrast).pack(side='left') OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video).pack(side='left') Button(self, text='First role', command=self.do_first_role).pack(side='left') Label(self, textvariable=self.filename).pack(side='left') Button(self, text='Next role', command=self.do_inc_role).pack(side='left') Button(self, text='Take!', command=self.do_single_shot).pack(side='right') #filemenu = Menu(self.menu) #self.menu.add_cascade(label=self.videodevice.get(), menu=filemenu, ) #for n in dev_names: # filemenu.add_command(label=n, ) #filemenu.add_separator() # start operation: self.do_first_role() self.do_start_video() def do_change_rotation(self, event): ' determine where the image was clicked and turn that to the top ' if event.x < 200: self.degree.set(-90) elif event.x > 640 - 200: self.degree.set(90) else: self.degree.set(0) def config_get(self, name, default): ' read a configuration entry, fallback to default if not already stored ' if not self.config.has_option('global', name): return default if isinstance(default, bool): return self.config.getboolean('global', name) else: return self.config.get('global', name) def do_configure(self, name, mode, cbname): ' change a configuration entry ' if cbname == 'w': value = getattr(self, name).get() self.config.set('global', name, str(value)) self.config.write(open('filmroller.conf', 'w')) def do_first_role(self, *args): ' jump back to first role ' self.role = 'aa' self.serial = 0 self.inc_picture() def inc_picture(self): ' increment the picture number, jump over existing files ' self.filename.set('{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, )) while exists(self.filename.get()): self.serial += 1 self.filename.set('{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, )) self.root.title('filmroller - ' + self.filename.get()) def do_inc_role(self, *args): ' increment to next role ' self.serial = 0 self.role = ascii_increment(self.role) self.inc_picture() def set_pauseimage(self): ' show pause image (during shot) ' self.image = fromfile('filmroller.pause.png') self.image.thumbnail((self.previewsize['size_x'], self.previewsize['size_y'], ), ) self.photo = PhotoImage(self.image) self.x_canvas.create_image(640/2, 640/2, image=self.photo) def do_stop_video(self, *args): ' stop video and release device ' if self.video is not None: self.video.stop() self.video.close() self.video = None def restart_video(self, *args): ' restart video (if device changes or hangs) ' self.do_stop_video() self.root.after(1, self.do_start_video) def do_start_video(self, *args): ' init video and start live view ' if self.video is None: self.video = Video_device(self.videodevice.get()) _, _, self.fourcc = self.video.get_format() caps = sorted(self.video.get_framesizes(self.fourcc), cmp=lambda a, b: cmp(a['size_x']*a['size_y'], b['size_x']*b['size_y'])) self.previewsize, self.highressize = caps[0], caps[-1] self.previewsize['size_x'], self.previewsize['size_y'] = self.video.set_format( self.previewsize['size_x'], self.previewsize['size_y'], 0, 'MJPEG') try: self.video.set_auto_white_balance(True) except: pass try: self.video.set_exposure_auto(True) except: pass try: self.video.set_focus_auto(True) except: pass self.video.create_buffers(30) self.video.queue_all_buffers() self.video.start() self.root.after(1, self.do_live_view) #self.x_canvas.config(width=640, height=480) #self.x_canvas.pack(side='top') self.degree.set(0) def do_live_view(self, *args): ' show single pic live view and ask tk to call us again later ' if self.video is not None: select((self.video, ), (), ()) data = self.video.read_and_queue() self.image = frombytes('RGB', (self.previewsize['size_x'], self.previewsize['size_y']), data) if self.invert.get(): self.image = invert(self.image) if self.grayscale.get(): self.image = grayscale(self.image) if self.autocontrast.get(): self.image = autocontrast(self.image) if self.equalize.get(): self.image = equalize(self.image) if self.solarize.get(): self.image = solarize(self.image) if self.degree.get(): self.image = self.image.rotate(self.degree.get()) self.photo = PhotoImage(self.image) self.x_canvas.create_image(640/2, 640/2, image=self.photo) self.root.after(3, self.do_live_view) def do_single_shot(self, *args): ' do a high res single shot and store it ' def _go(): self.video = Video_device(self.videodevice.get()) try: self.highressize['size_x'], self.highressize['size_y'] = self.video.set_format( self.highressize['size_x'], self.highressize['size_y'], 0, 'MJPEG') try: self.video.set_auto_white_balance(True) except: pass try: self.video.set_exposure_auto(True) except: pass try: self.video.set_focus_auto(True) except: pass self.video.create_buffers(7) self.video.queue_all_buffers() self.video.start() stop_time = time() + 3.0 # wait for auto while stop_time >= time(): select((self.video, ), (), ()) self.update_idletasks() data = self.video.read_and_queue() image = frombytes('RGB', (self.highressize['size_x'], self.highressize['size_y'], ), data) if self.invert.get(): image = invert(image) if self.grayscale.get(): image = grayscale(image) if self.autocontrast.get(): image = autocontrast(image) if self.equalize.get(): self.image = equalize(self.image) if self.solarize.get(): self.image = solarize(self.image) if self.degree.get(): image = image.rotate(self.degree.get()) image.save(self.filename.get()) self.inc_picture() self.root.bell() self.video.stop() finally: self.video.close() self.video = None self.root.after(1, self.do_start_video) self.do_stop_video() self.set_pauseimage() self.update_idletasks() self.root.after(1, _go)
class NvimTk(object): """Wraps all nvim/tk event handling.""" def __init__(self, nvim): """Initialize with a Nvim instance.""" self._nvim = nvim self._attrs = {} self._nvim_updates = deque() self._canvas = None self._fg = '#000000' self._bg = '#ffffff' def run(self): """Start the UI.""" self._tk_setup() t = Thread(target=self._nvim_event_loop) t.daemon = True t.start() self._root.mainloop() def _tk_setup(self): self._root = Tk() self._root.bind('<<nvim_redraw>>', self._tk_nvim_redraw) self._root.bind('<<nvim_detach>>', self._tk_nvim_detach) self._root.bind('<Key>', self._tk_key) def _tk_nvim_redraw(self, *args): update = self._nvim_updates.popleft() for update in update: handler = getattr(self, '_tk_nvim_' + update[0]) for args in update[1:]: handler(*args) def _tk_nvim_detach(self, *args): self._root.destroy() def _tk_nvim_resize(self, width, height): self._tk_redraw_canvas(width, height) def _tk_nvim_clear(self): self._tk_clear_region(0, self._height - 1, 0, self._width - 1) def _tk_nvim_eol_clear(self): row, col = (self._cursor_row, self._cursor_col,) self._tk_clear_region(row, row, col, self._scroll_right) def _tk_nvim_cursor_goto(self, row, col): self._cursor_row = row self._cursor_col = col def _tk_nvim_cursor_on(self): pass def _tk_nvim_cursor_off(self): pass def _tk_nvim_mouse_on(self): pass def _tk_nvim_mouse_off(self): pass def _tk_nvim_insert_mode(self): pass def _tk_nvim_normal_mode(self): pass def _tk_nvim_set_scroll_region(self, top, bot, left, right): self._scroll_top = top self._scroll_bot = bot self._scroll_left = left self._scroll_right = right def _tk_nvim_scroll(self, count): top, bot = (self._scroll_top, self._scroll_bot,) left, right = (self._scroll_left, self._scroll_right,) if count > 0: destroy_top = top destroy_bot = top + count - 1 move_top = destroy_bot + 1 move_bot = bot fill_top = move_bot + 1 fill_bot = fill_top + count - 1 else: destroy_top = bot + count + 1 destroy_bot = bot move_top = top move_bot = destroy_top - 1 fill_bot = move_top - 1 fill_top = fill_bot + count + 1 # destroy items that would be moved outside the scroll region after # scrolling # self._tk_clear_region(destroy_top, destroy_bot, left, right) # self._tk_clear_region(move_top, move_bot, left, right) self._tk_destroy_region(destroy_top, destroy_bot, left, right) self._tk_tag_region('move', move_top, move_bot, left, right) self._canvas.move('move', 0, -count * self._rowsize) self._canvas.dtag('move', 'move') # self._tk_fill_region(fill_top, fill_bot, left, right) def _tk_nvim_highlight_set(self, attrs): self._attrs = attrs def _tk_nvim_put(self, data): # choose a Font instance font = self._fnormal if self._attrs.get('bold', False): font = self._fbold if self._attrs.get('italic', False): font = self._fbolditalic if font == self._fbold else self._fitalic # colors fg = "#{0:0{1}x}".format(self._attrs.get('foreground', self._fg), 6) bg = "#{0:0{1}x}".format(self._attrs.get('background', self._bg), 6) # get the "text" and "rect" which correspond to the current cell x, y = self._tk_get_coords(self._cursor_row, self._cursor_col) items = self._canvas.find_overlapping(x, y, x + 1, y + 1) if len(items) != 2: # caught part the double-width character in the cell to the left, # filter items which dont have the same horizontal coordinate as # "x" predicate = lambda item: self._canvas.coords(item)[0] == x items = filter(predicate, items) # rect has lower id than text, sort to unpack correctly rect, text = sorted(items) self._canvas.itemconfig(text, fill=fg, font=font, text=data or ' ') self._canvas.itemconfig(rect, fill=bg) self._tk_nvim_cursor_goto(self._cursor_row, self._cursor_col + 1) def _tk_nvim_bell(self): self._root.bell() def _tk_nvim_update_fg(self, fg): self._fg = "#{0:0{1}x}".format(fg, 6) def _tk_nvim_update_bg(self, bg): self._bg = "#{0:0{1}x}".format(bg, 6) def _tk_redraw_canvas(self, width, height): if self._canvas: self._canvas.destroy() self._fnormal = Font(family='Monospace', size=13) self._fbold = Font(family='Monospace', weight='bold', size=13) self._fitalic = Font(family='Monospace', slant='italic', size=13) self._fbolditalic = Font(family='Monospace', weight='bold', slant='italic', size=13) self._colsize = self._fnormal.measure('A') self._rowsize = self._fnormal.metrics('linespace') self._canvas = Canvas(self._root, width=self._colsize * width, height=self._rowsize * height) self._tk_fill_region(0, height - 1, 0, width - 1) self._cursor_row = 0 self._cursor_col = 0 self._scroll_top = 0 self._scroll_bot = height - 1 self._scroll_left = 0 self._scroll_right = width - 1 self._width, self._height = (width, height,) self._canvas.pack() def _tk_fill_region(self, top, bot, left, right): # create columns from right to left so the left columns have a # higher z-index than the right columns. This is required to # properly display characters that cross cell boundary for rownum in range(bot, top - 1, -1): for colnum in range(right, left - 1, -1): x1 = colnum * self._colsize y1 = rownum * self._rowsize x2 = (colnum + 1) * self._colsize y2 = (rownum + 1) * self._rowsize # for each cell, create two items: The rectangle is used for # filling background and the text is for cell contents. self._canvas.create_rectangle(x1, y1, x2, y2, fill=self._background, width=0) self._canvas.create_text(x1, y1, anchor='nw', font=self._fnormal, width=1, fill=self._foreground, text=' ') def _tk_clear_region(self, top, bot, left, right): self._tk_tag_region('clear', top, bot, left, right) self._canvas.itemconfig('clear', fill=self._bg) self._canvas.dtag('clear', 'clear') def _tk_destroy_region(self, top, bot, left, right): self._tk_tag_region('destroy', top, bot, left, right) self._canvas.delete('destroy') self._canvas.dtag('destroy', 'destroy') def _tk_tag_region(self, tag, top, bot, left, right): x1, y1 = self._tk_get_coords(top, left) x2, y2 = self._tk_get_coords(bot, right) self._canvas.addtag_overlapping(tag, x1, y1, x2 + 1, y2 + 1) def _tk_get_coords(self, row, col): x = col * self._colsize y = row * self._rowsize return x, y def _tk_key(self, event): if 0xffe1 <= event.keysym_num <= 0xffee: # this is a modifier key, ignore. Source: # https://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm return # Translate to Nvim representation of keys send = [] if event.state & 0x1: send.append('S') if event.state & 0x4: send.append('C') if event.state & (0x8 | 0x80): send.append('A') special = len(send) > 0 key = event.char if _is_invalid_key(key): special = True key = event.keysym send.append(SPECIAL_KEYS.get(key, key)) send = '-'.join(send) if special: send = '<' + send + '>' nvim = self._nvim nvim.session.threadsafe_call(lambda: nvim.input(send)) def _nvim_event_loop(self): self._nvim.session.run(self._nvim_request, self._nvim_notification, lambda: self._nvim.attach_ui(80, 24)) self._root.event_generate('<<nvim_detach>>', when='tail') def _nvim_request(self, method, args): raise Exception('This UI does not implement any methods') def _nvim_notification(self, method, args): if method == 'redraw': self._nvim_updates.append(args) self._root.event_generate('<<nvim_redraw>>', when='tail')