class ShowGroupName: def __init__(self, duration=0.8, x_incr=50, fmt="{}", **popup_config): """Display popup on screen when switching to a group for `duration` in seconds. `x_incr` should be roughly the width of a character in the given `font` (monospace) at the specified `font_size`.""" # HACK: changing width later doesn't paint more background popup_config["width"] = 1920 self.duration, self.x_incr, self.fmt = duration, x_incr, fmt self.popup = Popup(qtile, **popup_config) self.pending_hide = None # asyncio.Handle object from qtile.call_later. self.suppressed = False hook.subscribe.setgroup(self.show) hook.subscribe.current_screen_change(self.suppress_screen_change) def suppress_screen_change(self): """When the current screen changes, suppress the show() which would immediately follow due to the accompanying group change. Since the current_screen_change hook is fired before setgroup, this seems to be adequate.""" self.suppressed = True def cancel_hide(self): """Cancel the deferred popup hide. Important when switching groups before the the duration is up on the last show.""" if self.pending_hide is not None: self.pending_hide.cancel() def draw(self): self.popup.clear() self.popup.place() self.popup.unhide() self.popup.draw_text() self.popup.draw() def show(self): if not restarting: if self.suppressed: self.suppressed = False else: self.cancel_hide() grp = qtile.current_group scrn = grp.screen text = self.fmt.format(grp.name) self.popup.width = self.popup.horizontal_padding * 2 + ( len(text) * self.x_incr) self.popup.text = text self.popup.x = int(scrn.x + (scrn.width / 2 - self.popup.width / 2)) self.popup.y = int(scrn.y + (scrn.height / 2 - self.popup.height / 2)) self.draw() self.pending_hide = qtile.call_later(self.duration, self.popup.hide)
class Confirm: def __init__(self, label, action, x_incr=50, **popup_config): """Confirmation popup.""" # HACK: changing width later doesn't paint more background popup_config["width"] = 1920 self.label, self.action, self.x_incr = label, action, x_incr self.popup = Popup(qtile, **popup_config) self.build_message(label) self.popup.win.handle_ButtonPress = self.handle_button_press self.popup.win.handle_KeyPress = self.handle_key_press def build_message(self, label): self.question = "Are you sure you want to %s?" % label self.instruction = "y / n" self.pad = " " * ((len(self.question) - len(self.instruction)) // 2) self.message = self.question + "\n" + self.pad + self.instruction def handle_button_press(self, ev): self.popup.win.cmd_focus() def handle_key_press(self, ev): if ev.detail == 29: # y self.popup.hide() self.action() elif ev.detail == 57: # n self.popup.hide() def draw(self): self.popup.clear() self.popup.place() self.popup.unhide() self.popup.draw_text() self.popup.draw() def show(self, qtile): grp = qtile.current_group scrn = grp.screen self.popup.width = self.popup.horizontal_padding * 2 + ( len(self.question) * self.x_incr) self.popup.text = self.message self.popup.x = int(scrn.x + (scrn.width / 2 - self.popup.width / 2)) self.popup.y = int(scrn.y + (scrn.height / 2 - self.popup.height / 2)) self.draw() self.popup.win.cmd_focus()
class REPL(base._TextBox): """A flexible textbox that can be updated from bound keys, scripts, and qshell.""" defaults = [ ("font", "sans", "Text font."), ("fontsize", None, "Font pixel size. Calculated if None."), ("fontshadow", None, "font shadow color, default is None(no shadow)."), ("padding", None, "Padding left and right. Calculated if None."), ("foreground", "#ffffff", "Foreground colour."), ("win_pos", (0,0), "Initial window coordinates as tuple (x, y)."), ("win_size", (500,500), "Window size as tuple (w, h)."), ("win_bordercolor", "#ff0000", "Window border color. Can be list of multiple values."), ("win_borderwidth", 2, "Window Border width."), ("win_foreground", "#ffffff", "Window text color"), ("win_background", "#000000", "Window background color"), ("win_opacity", 1, "Window opacity."), ("win_font", "sans", "Window text font."), ("win_fontsize", 12, "Window font pixel size. Calculated if None."), ("win_fontshadow", None, "Window font shadow color, default is None(no shadow)."), ("win_mousebutton_move", 3, "Mouse button that when clicked on repl window causes it to re-center under mouse."), ] def __init__(self, text="PY REPL", width=bar.CALCULATED, **config): base._TextBox.__init__(self, text=text, width=width, **config) self.add_defaults(REPL.defaults) self.add_callbacks({"Button1": self.open_win, "Button3":self.leave}) self.og_bartext = self.text self.text = "_" + self.text self.win_opened = False def _configure(self, qtile, bar): base._TextBox._configure(self, qtile, bar) #self.popup = Popup(qtile, background="#000000", foreground="#00aa00", x=1280, y=720, width=500, height=500, font="Noto Mono", #font_size=12, border=["#0000ff", "#0000ff", "#ffff00", "#ffff00"], border_width=5, opacity=0.90, wrap=True) self.popup = Popup(qtile, background=self.win_background, foreground=self.win_foreground, x=self.win_pos[0], y=self.win_pos[1], width=self.win_size[0], height=self.win_size[1], font=self.win_font, font_size=self.win_fontsize, fontshadow=self.win_fontshadow, border=self.win_bordercolor, border_width=self.win_borderwidth, opacity=self.win_opacity, wrap=True) self.popup.layout.markup = False # TODO PR to add this to popup options self.popup.layout.width = self.popup.width # actually enforce line wrap. see PR above #self.popup.place() #self.popup.unhide() self.nl = "\n" self.old_text = ["Yobleck's simple python REPL (click to focus, esc to quit, F5/'cls'/'clear' to reset screen)", f"Qtile: {metadata.distribution('qtile').version}", f"Python: {sys.version.replace(self.nl, '')}", ">>> "] self.new_text = "" self.popup.text = lines_to_text(self.old_text) + "\u2588" #[-num_rows:] self.draw_y = 5 self.popup.draw_text(x=2, y=self.draw_y) self.popup.draw() self.history = [] # command history for this popup instance. save across session with global var? self.history_index = -1 # is there a way to do this without the var? self.indentation_level = 0 self.popup.win.process_key_press = self.key_press #self.popup.win.process_pointer_leave = self.leave self.popup.win.process_button_click = self.b_press self.popup.win.process_pointer_enter = self.enter def key_press(self, keycode): """Handle key presses.""" try: keychr = chr(keycode) log_test(f"key {keycode}={keychr} pressed") # TODO convert keycodes to text if keycode == 65307: # escape self.leave() return elif keycode == 65474: # F5 to clear screen self.old_text = [">>> "] self.new_text = "" # scrolling elif keycode == 65366: # page down self.draw_y -= 5 elif keycode == 65365: # page up self.draw_y += 5 elif keycode == 65288: # backspace if self.new_text[-1] == "\n": self.indentation_level -= 1 self.new_text = self.new_text[:-1] elif keycode == 65289: # tab. NOTE probably never going to have tab completion self.new_text += "\t" # history elif keycode in [65362, 65364]: # up/down arrow keys. TODO double check with another non 60% keyboard if self.history: if keycode == 65362 and self.history_index < len(self.history)-1: self.history_index += 1 self.new_text = self.history[self.history_index] elif keycode == 65364 and self.history_index > 0: self.history_index -= 1 self.new_text = self.history[self.history_index] elif keycode == 65293: # enter self.history.insert(0, self.new_text) self.history_index = -1 if self.new_text: if self.new_text in ["exit", "exit()", "quit", "quit()"] or "sys.exit" in self.new_text: # exit commands. WARNING will eval("quit()") kill qtile? sys.exit solution will kill regardless of conditionals self.leave() return elif self.new_text in ["clear", "cls"]: # clear screen commands self.old_text = [">>> "] self.new_text = "" elif self.new_text[-1] == ":": self.indentation_level += 1 self.new_text += "\n" + " "*indentation_level else: #old_text += new_text + "\n" + _simple_eval_exec(new_text) + ">>> " # append input text, eval and append results self.old_text[-1] += self.new_text self.old_text.extend([ _simple_eval_exec(self.new_text), ">>> "]) self.new_text = "" self.indentation_level = 0 elif keycode in [65505, 65507, 65508, 65506, 65513, 65514, 65515]: # range(65505, 65518) for mod keys? pass # ignore modifiers and other non text keys. TODO modifier list that is cleared after non modifier is pressed. act on list? else: self.new_text += keychr # actually drawing text self.popup.clear() self.popup.text = lines_to_text(self.old_text) + self.new_text + "\u2588" # TODO scroll to bottom after hitting enter self.popup.draw_text(x=2, y=self.draw_y) self.popup.draw() self.popup.text = lines_to_text(self.old_text) #[-num_rows:] [-num_rows:] except Exception as e: log_test(f"key press error: {e}") def leave(self, *args): # close window when mouse leaves """Handle hiding repl window.""" try: log_test("pointer leave") self.popup.hide() #self.popup.kill() self.cmd_update("_" + self.og_bartext)#"REPL Closed") self.win_opened = False except Exception as e: log_test(f"pointer leave error: {e}") def b_press(self, x, y, button): # two functions cause mouse click doesn't work with *args """ Handle mouse button presses.""" try: if button == 1: self.popup.win.focus(warp=False) elif button in [4,5]: if button == 4: # mouse wheel up self.draw_y += 5 elif button == 5: # down self.draw_y -= 5 self.popup.clear() # NOTE DRY violation self.popup.text = lines_to_text(self.old_text) + self.new_text self.popup.draw_text(x=2, y=self.draw_y) self.popup.draw() self.popup.text = lines_to_text(self.old_text) elif button == self.win_mousebutton_move: # move the window around (crudely) new_pos = (self.popup.x+x-self.popup.width//2, self.popup.y+y-self.popup.height//2) self.popup.win.place(new_pos[0], new_pos[1], self.popup.width, self.popup.height, self.popup.border_width, self.popup.border) self.popup.x, self.popup.y = new_pos # .place() doesn't change these log_test(f"button {button} clicked") except Exception as e: log_test(f"button click error: {e}") def enter(self, *args): """Handle mouse enter window to focus.""" try: self.popup.win.focus(warp=False) log_test("pointer enter") except Exception as e: log_test(f"pointer enter error: {e}") def open_win(self): """Open python repl in internal window.""" if not self.win_opened: self.win_opened = True self.cmd_update(self.og_bartext)#"REPL Opened") #simple_repl() self.popup.place() self.popup.unhide() """def close_win(self): #Close python repl if self.win_opened: self.win_opened = False self.cmd_update("REPL Closed") self.leave()""" def cmd_update(self, text): """Update the text in a TextBox widget.""" self.update(text) def cmd_get(self): """Retrieve the text in a TextBox widget.""" return self.text