mirror of
https://github.com/Guake/guake.git
synced 2025-10-26 11:27:13 +00:00
722 lines
26 KiB
Python
722 lines
26 KiB
Python
import logging
|
|
import time
|
|
|
|
import gi
|
|
|
|
gi.require_version("Vte", "2.91") # vte-0.42
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import GObject
|
|
from gi.repository import Gdk
|
|
from gi.repository import Gio
|
|
from gi.repository import Gtk
|
|
from gi.repository import Vte
|
|
|
|
from guake.callbacks import MenuHideCallback
|
|
from guake.callbacks import TerminalContextMenuCallbacks
|
|
from guake.dialogs import PromptResetColorsDialog
|
|
from guake.dialogs import RenameDialog
|
|
from guake.globals import PCRE2_MULTILINE
|
|
from guake.menus import mk_tab_context_menu
|
|
from guake.menus import mk_terminal_context_menu
|
|
from guake.utils import HidePrevention
|
|
from guake.utils import TabNameUtils
|
|
from guake.utils import get_server_time
|
|
from guake.utils import save_tabs_when_changed
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# TODO remove calls to guake
|
|
|
|
|
|
class TerminalHolder:
|
|
UP = 0
|
|
DOWN = 1
|
|
RIGHT = 2
|
|
LEFT = 3
|
|
|
|
def get_terminals(self):
|
|
raise NotImplementedError
|
|
|
|
def iter_terminals(self):
|
|
raise NotImplementedError
|
|
|
|
def replace_child(self, old, new):
|
|
raise NotImplementedError
|
|
|
|
def get_guake(self):
|
|
raise NotImplementedError
|
|
|
|
def get_window(self):
|
|
raise NotImplementedError
|
|
|
|
def get_settings(self):
|
|
raise NotImplementedError
|
|
|
|
def get_root_box(self):
|
|
raise NotImplementedError
|
|
|
|
def get_notebook(self):
|
|
raise NotImplementedError
|
|
|
|
def remove_dead_child(self, child):
|
|
raise NotImplementedError
|
|
|
|
|
|
class RootTerminalBox(Gtk.Overlay, TerminalHolder):
|
|
def __init__(self, guake, parent_notebook):
|
|
super().__init__()
|
|
self.guake = guake
|
|
self.notebook = parent_notebook
|
|
self.child = None
|
|
self.last_terminal_focused = None
|
|
|
|
self.searchstring = None
|
|
self.searchre = None
|
|
self._add_search_box()
|
|
|
|
def _add_search_box(self):
|
|
"""--------------------------------------|
|
|
| Revealer |
|
|
| |-----------------------------------|
|
|
| | Frame |
|
|
| | |---------------------------------|
|
|
| | | HBox |
|
|
| | | |---| |-------| |----| |------| |
|
|
| | | | x | | Entry | |Prev| | Next | |
|
|
| | | |---| |-------| |----| |------| |
|
|
--------------------------------------|
|
|
"""
|
|
self.search_revealer = Gtk.Revealer()
|
|
self.search_frame = Gtk.Frame(name="search-frame")
|
|
self.search_box = Gtk.HBox()
|
|
|
|
# Search
|
|
self.search_close_btn = Gtk.Button()
|
|
self.search_close_btn.set_can_focus(False)
|
|
close_icon = Gio.ThemedIcon(name="window-close-symbolic")
|
|
close_image = Gtk.Image.new_from_gicon(close_icon, Gtk.IconSize.BUTTON)
|
|
self.search_close_btn.set_image(close_image)
|
|
self.search_entry = Gtk.SearchEntry()
|
|
self.search_prev_btn = Gtk.Button()
|
|
self.search_prev_btn.set_can_focus(False)
|
|
prev_icon = Gio.ThemedIcon(name="go-up-symbolic")
|
|
prev_image = Gtk.Image.new_from_gicon(prev_icon, Gtk.IconSize.BUTTON)
|
|
self.search_prev_btn.set_image(prev_image)
|
|
self.search_next_btn = Gtk.Button()
|
|
self.search_next_btn.set_can_focus(False)
|
|
next_icon = Gio.ThemedIcon(name="go-down-symbolic")
|
|
next_image = Gtk.Image.new_from_gicon(next_icon, Gtk.IconSize.BUTTON)
|
|
self.search_next_btn.set_image(next_image)
|
|
|
|
# Pack into box
|
|
self.search_box.pack_start(self.search_close_btn, False, False, 0)
|
|
self.search_box.pack_start(self.search_entry, False, False, 0)
|
|
self.search_box.pack_start(self.search_prev_btn, False, False, 0)
|
|
self.search_box.pack_start(self.search_next_btn, False, False, 0)
|
|
|
|
# Add into frame
|
|
self.search_frame.add(self.search_box)
|
|
|
|
# Frame
|
|
self.search_frame.set_margin_end(12)
|
|
self.search_frame.get_style_context().add_class("background")
|
|
css_provider = Gtk.CssProvider()
|
|
css_provider.load_from_data(
|
|
b"#search-frame border {" b" padding: 5px 5px 5px 5px;" b" border: none;" b"}"
|
|
)
|
|
Gtk.StyleContext.add_provider_for_screen(
|
|
Gdk.Screen.get_default(),
|
|
css_provider,
|
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
)
|
|
|
|
# Add to revealer
|
|
self.search_revealer.add(self.search_frame)
|
|
self.search_revealer.set_transition_duration(500)
|
|
self.search_revealer.set_transition_type(Gtk.RevealerTransitionType.CROSSFADE)
|
|
self.search_revealer.set_valign(Gtk.Align.END)
|
|
self.search_revealer.set_halign(Gtk.Align.END)
|
|
|
|
# Welcome to the overlay
|
|
self.add_overlay(self.search_revealer)
|
|
|
|
# Events
|
|
self.search_entry.connect("key-press-event", self.on_search_entry_keypress)
|
|
self.search_entry.connect("changed", self.set_search)
|
|
self.search_entry.connect("activate", self.do_search)
|
|
self.search_entry.connect("focus-in-event", self.on_search_entry_focus_in)
|
|
self.search_entry.connect("focus-out-event", self.on_search_entry_focus_out)
|
|
self.search_next_btn.connect("clicked", self.on_search_next_clicked)
|
|
self.search_prev_btn.connect("clicked", self.on_search_prev_clicked)
|
|
self.search_close_btn.connect("clicked", self.close_search_box)
|
|
self.search_prev = True
|
|
|
|
# Search revealer visible
|
|
def search_revealer_show_cb(widget):
|
|
if not widget.get_child_revealed():
|
|
widget.hide()
|
|
|
|
self.search_revealer.hide()
|
|
self.search_revealer_show_cb_id = self.search_revealer.connect(
|
|
"show", search_revealer_show_cb
|
|
)
|
|
self.search_frame.connect("unmap", lambda x: self.search_revealer.hide())
|
|
|
|
def get_terminals(self):
|
|
return self.get_child().get_terminals()
|
|
|
|
def iter_terminals(self):
|
|
if self.get_child() is not None:
|
|
yield from self.get_child().iter_terminals()
|
|
|
|
def replace_child(self, old, new):
|
|
self.remove(old)
|
|
self.set_child(new)
|
|
|
|
def set_child(self, terminal_holder):
|
|
if isinstance(terminal_holder, TerminalHolder):
|
|
self.child = terminal_holder
|
|
self.add(self.child)
|
|
else:
|
|
raise RuntimeError(f"Error adding (RootTerminalBox.add({type(terminal_holder)}))")
|
|
|
|
def get_child(self):
|
|
return self.child
|
|
|
|
def get_guake(self):
|
|
return self.guake
|
|
|
|
def get_window(self):
|
|
return self.guake.window
|
|
|
|
def get_settings(self):
|
|
return self.guake.settings
|
|
|
|
def get_root_box(self):
|
|
return self
|
|
|
|
def save_box_layout(self, box, panes: list):
|
|
"""Save box layout with pre-order traversal, it should result `panes` with
|
|
a full binary tree in list.
|
|
"""
|
|
if not box:
|
|
panes.append({"type": None, "directory": None})
|
|
return
|
|
if isinstance(box, DualTerminalBox):
|
|
btype = "dual" + ("_h" if box.orient is DualTerminalBox.ORIENT_V else "_v")
|
|
panes.append({"type": btype, "directory": None})
|
|
self.save_box_layout(box.get_child1(), panes)
|
|
self.save_box_layout(box.get_child2(), panes)
|
|
elif isinstance(box, TerminalBox):
|
|
btype = "term"
|
|
directory = box.terminal.get_current_directory()
|
|
panes.append(
|
|
{
|
|
"type": btype,
|
|
"directory": directory,
|
|
"custom_colors": box.terminal.get_custom_colors_dict(),
|
|
}
|
|
)
|
|
|
|
def restore_box_layout(self, box, panes: list):
|
|
"""Restore box layout by `panes`"""
|
|
if not panes or not isinstance(panes, list) or not box or not isinstance(box, TerminalBox):
|
|
return
|
|
|
|
cur = panes.pop(0)
|
|
if cur["type"].startswith("dual"):
|
|
while True:
|
|
if self.guake:
|
|
# If Guake is not visible, we should pending the restore, then do the
|
|
# restore when Guake is visible again.
|
|
#
|
|
# Otherwise we will stuck in the infinite loop, since new DualTerminalBox
|
|
# cannot get any allocation when Guake is invisible
|
|
if (
|
|
not self.guake.window.get_property("visible")
|
|
or self.get_notebook()
|
|
is not self.guake.notebook_manager.get_current_notebook()
|
|
):
|
|
panes.insert(0, cur)
|
|
self.guake._failed_restore_page_split.append((self, box, panes))
|
|
return
|
|
|
|
# UI didn't update, wait for it
|
|
alloc = box.get_allocation()
|
|
if alloc.width == 1 and alloc.height == 1:
|
|
time.sleep(0.01)
|
|
else:
|
|
break
|
|
|
|
# Waiting for UI update..
|
|
while Gtk.events_pending():
|
|
Gtk.main_iteration()
|
|
|
|
if cur["type"].endswith("v"):
|
|
box = box.split_v_no_save()
|
|
else:
|
|
box = box.split_h_no_save()
|
|
self.restore_box_layout(box.get_child1(), panes)
|
|
self.restore_box_layout(box.get_child2(), panes)
|
|
else:
|
|
if box.terminal:
|
|
term = box.terminal
|
|
# Remove signal handler from terminal
|
|
for i in term.handler_ids:
|
|
term.disconnect(i)
|
|
term.handler_ids = []
|
|
box.remove(box.scroll)
|
|
box.remove(term)
|
|
box.unset_terminal()
|
|
|
|
# Replace term in the TerminalBox
|
|
term = self.get_notebook().terminal_spawn(cur["directory"])
|
|
term.set_custom_colors_from_dict(cur.get("custom_colors", None))
|
|
box.set_terminal(term)
|
|
self.get_notebook().terminal_attached(term)
|
|
|
|
def set_last_terminal_focused(self, terminal):
|
|
self.last_terminal_focused = terminal
|
|
self.get_notebook().set_last_terminal_focused(terminal)
|
|
|
|
def get_last_terminal_focused(self, terminal):
|
|
return self.last_terminal_focused
|
|
|
|
def get_notebook(self):
|
|
return self.notebook
|
|
|
|
def remove_dead_child(self, child):
|
|
page_num = self.get_notebook().page_num(self)
|
|
self.get_notebook().remove_page(page_num)
|
|
|
|
def block_notebook_on_button_press_id(self):
|
|
GObject.signal_handler_block(
|
|
self.get_notebook(), self.get_notebook().notebook_on_button_press_id
|
|
)
|
|
|
|
def unblock_notebook_on_button_press_id(self):
|
|
GObject.signal_handler_unblock(
|
|
self.get_notebook(), self.get_notebook().notebook_on_button_press_id
|
|
)
|
|
|
|
def show_search_box(self):
|
|
if not self.search_revealer.get_reveal_child():
|
|
GObject.signal_handler_block(self.search_revealer, self.search_revealer_show_cb_id)
|
|
self.search_revealer.set_visible(True)
|
|
self.search_revealer.set_reveal_child(True)
|
|
GObject.signal_handler_unblock(self.search_revealer, self.search_revealer_show_cb_id)
|
|
# XXX: Mestery line to avoid Gtk-CRITICAL stuff
|
|
# (guake:22694): Gtk-CRITICAL **: 18:04:57.345:
|
|
# gtk_widget_event: assertion 'WIDGET_REALIZED_FOR_EVENT (widget, event)' failed
|
|
self.search_entry.realize()
|
|
self.search_entry.grab_focus()
|
|
|
|
def hide_search_box(self):
|
|
if self.search_revealer.get_reveal_child():
|
|
self.search_revealer.set_reveal_child(False)
|
|
self.last_terminal_focused.grab_focus()
|
|
self.last_terminal_focused.unselect_all()
|
|
|
|
def close_search_box(self, event):
|
|
self.hide_search_box()
|
|
|
|
def on_search_entry_focus_in(self, event, user_data):
|
|
self.block_notebook_on_button_press_id()
|
|
|
|
def on_search_entry_focus_out(self, event, user_data):
|
|
self.unblock_notebook_on_button_press_id()
|
|
|
|
def on_search_prev_clicked(self, widget):
|
|
term = self.last_terminal_focused
|
|
result = term.search_find_previous()
|
|
if not result:
|
|
term.search_find_previous()
|
|
|
|
def on_search_next_clicked(self, widget):
|
|
term = self.last_terminal_focused
|
|
result = term.search_find_next()
|
|
if not result:
|
|
term.search_find_next()
|
|
|
|
def on_search_entry_keypress(self, widget, event):
|
|
key = Gdk.keyval_name(event.keyval)
|
|
if key == "Escape":
|
|
self.hide_search_box()
|
|
elif key == "Return":
|
|
# Combine with Shift?
|
|
if event.state & Gdk.ModifierType.SHIFT_MASK:
|
|
self.search_prev = False
|
|
self.do_search(None)
|
|
else:
|
|
self.search_prev = True
|
|
|
|
def reset_term_search(self, term):
|
|
term.search_set_regex(None, 0)
|
|
term.search_find_next()
|
|
|
|
def set_search(self, widget):
|
|
term = self.last_terminal_focused
|
|
text = self.search_entry.get_text()
|
|
if not text:
|
|
self.reset_term_search(term)
|
|
return
|
|
|
|
if text != self.searchstring:
|
|
self.reset_term_search(term)
|
|
|
|
# Set search regex on term
|
|
self.searchstring = text
|
|
self.searchre = Vte.Regex.new_for_search(
|
|
text, -1, Vte.REGEX_FLAGS_DEFAULT | PCRE2_MULTILINE
|
|
)
|
|
term.search_set_regex(self.searchre, 0)
|
|
self.do_search(None)
|
|
|
|
def do_search(self, widget):
|
|
if self.search_prev:
|
|
self.on_search_prev_clicked(None)
|
|
else:
|
|
self.on_search_next_clicked(None)
|
|
|
|
|
|
class TerminalBox(Gtk.Box, TerminalHolder):
|
|
"""A box to group the terminal and a scrollbar."""
|
|
|
|
def __init__(self):
|
|
super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
|
|
self.terminal = None
|
|
|
|
def set_terminal(self, terminal):
|
|
"""Packs the terminal widget."""
|
|
if self.terminal is not None:
|
|
raise RuntimeError("TerminalBox: terminal already set")
|
|
self.terminal = terminal
|
|
self.terminal.handler_ids.append(
|
|
self.terminal.connect("grab-focus", self.on_terminal_focus)
|
|
)
|
|
self.terminal.handler_ids.append(
|
|
self.terminal.connect("button-press-event", self.on_button_press, None)
|
|
)
|
|
self.terminal.handler_ids.append(
|
|
self.terminal.connect("child-exited", self.on_terminal_exited)
|
|
)
|
|
self.pack_start(self.terminal, True, True, 0)
|
|
self.terminal.show()
|
|
self.add_scroll_bar()
|
|
|
|
def add_scroll_bar(self):
|
|
"""Packs the scrollbar."""
|
|
adj = self.terminal.get_vadjustment()
|
|
self.scroll = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, adj)
|
|
self.scroll.show()
|
|
self.pack_start(self.scroll, False, False, 0)
|
|
|
|
self.terminal.handler_ids.append(
|
|
self.terminal.connect("scroll-event", self.__scroll_event_cb)
|
|
)
|
|
|
|
def __scroll_event_cb(self, widget, event):
|
|
# Adjust scrolling speed when adding "shift" or "shift + ctrl"
|
|
adj = self.scroll.get_adjustment()
|
|
page_size = adj.get_page_size()
|
|
if (
|
|
event.get_state() & Gdk.ModifierType.SHIFT_MASK
|
|
and event.get_state() & Gdk.ModifierType.CONTROL_MASK
|
|
):
|
|
# Ctrl + Shift + Mouse Scroll (4 pages)
|
|
adj.set_page_increment(page_size * 40)
|
|
elif event.get_state() & Gdk.ModifierType.SHIFT_MASK:
|
|
# Shift + Mouse Scroll (1 page)
|
|
adj.set_page_increment(page_size * 10)
|
|
else:
|
|
# Mouse Scroll
|
|
adj.set_page_increment(page_size)
|
|
|
|
def get_terminal(self):
|
|
return self.terminal
|
|
|
|
def get_terminals(self):
|
|
if self.terminal is not None:
|
|
return [self.terminal]
|
|
return []
|
|
|
|
def iter_terminals(self):
|
|
if self.terminal is not None:
|
|
yield self.terminal
|
|
|
|
def replace_child(self, old, new):
|
|
print("why would you call this on me?")
|
|
pass
|
|
|
|
def unset_terminal(self, *args):
|
|
self.terminal = None
|
|
|
|
def split_h(self, split_percentage: int = 50):
|
|
return self.split(DualTerminalBox.ORIENT_V, split_percentage)
|
|
|
|
def split_v(self, split_percentage: int = 50):
|
|
return self.split(DualTerminalBox.ORIENT_H, split_percentage)
|
|
|
|
def split_h_no_save(self, split_percentage: int = 50):
|
|
return self.split_no_save(DualTerminalBox.ORIENT_V, split_percentage)
|
|
|
|
def split_v_no_save(self, split_percentage: int = 50):
|
|
return self.split_no_save(DualTerminalBox.ORIENT_H, split_percentage)
|
|
|
|
@save_tabs_when_changed
|
|
def split(self, orientation, split_percentage: int = 50):
|
|
self.split_no_save(orientation, split_percentage)
|
|
|
|
def split_no_save(self, orientation, split_percentage: int = 50):
|
|
notebook = self.get_notebook()
|
|
parent = self.get_parent() # RootTerminalBox
|
|
|
|
if orientation == DualTerminalBox.ORIENT_H:
|
|
position = self.get_allocation().width * ((100 - split_percentage) / 100)
|
|
else:
|
|
position = self.get_allocation().height * ((100 - split_percentage) / 100)
|
|
|
|
terminal_box = TerminalBox()
|
|
terminal = notebook.terminal_spawn()
|
|
terminal_box.set_terminal(terminal)
|
|
dual_terminal_box = DualTerminalBox(orientation)
|
|
dual_terminal_box.set_position(position)
|
|
parent.replace_child(self, dual_terminal_box)
|
|
dual_terminal_box.set_child_first(self)
|
|
dual_terminal_box.set_child_second(terminal_box)
|
|
terminal_box.show()
|
|
dual_terminal_box.show()
|
|
if self.terminal is not None:
|
|
# preserve font and font_scale in the new terminal
|
|
terminal.set_font(self.terminal.font)
|
|
terminal.font_scale = self.terminal.font_scale
|
|
notebook.terminal_attached(terminal)
|
|
|
|
return dual_terminal_box
|
|
|
|
def get_guake(self):
|
|
return self.get_parent().get_guake()
|
|
|
|
def get_window(self):
|
|
return self.get_parent().get_window()
|
|
|
|
def get_settings(self):
|
|
return self.get_parent().get_settings()
|
|
|
|
def get_root_box(self):
|
|
return self.get_parent().get_root_box()
|
|
|
|
def get_notebook(self):
|
|
return self.get_parent().get_notebook()
|
|
|
|
def remove_dead_child(self, child):
|
|
print('Can\'t do, have no "child"')
|
|
|
|
def on_terminal_focus(self, *args):
|
|
self.get_root_box().set_last_terminal_focused(self.terminal)
|
|
|
|
def on_terminal_exited(self, terminal, status):
|
|
if not self.get_parent():
|
|
return
|
|
self.get_parent().remove_dead_child(self)
|
|
|
|
def on_button_press(self, target, event, user_data):
|
|
if event.button == 3:
|
|
# First send to background process if handled, do nothing else
|
|
if (
|
|
not event.get_state() & Gdk.ModifierType.SHIFT_MASK
|
|
and Vte.Terminal.do_button_press_event(self.terminal, event)
|
|
):
|
|
return True
|
|
|
|
menu = mk_terminal_context_menu(
|
|
self.terminal,
|
|
self.get_window(),
|
|
self.get_settings(),
|
|
TerminalContextMenuCallbacks(
|
|
self.terminal,
|
|
self.get_window(),
|
|
self.get_settings(),
|
|
self.get_root_box().get_notebook(),
|
|
),
|
|
)
|
|
menu.connect("hide", MenuHideCallback(self.get_window()).on_hide)
|
|
HidePrevention(self.get_window()).prevent()
|
|
try:
|
|
menu.popup_at_pointer(event)
|
|
except AttributeError:
|
|
# Gtk 3.18 fallback ("'Menu' object has no attribute 'popup_at_pointer'")
|
|
menu.popup(None, None, None, None, event.button, event.time)
|
|
self.terminal.grab_focus()
|
|
return True
|
|
self.terminal.grab_focus()
|
|
return False
|
|
|
|
|
|
class DualTerminalBox(Gtk.Paned, TerminalHolder):
|
|
|
|
ORIENT_H = 0
|
|
ORIENT_V = 1
|
|
|
|
def __init__(self, orientation):
|
|
super().__init__()
|
|
|
|
self.orient = orientation
|
|
if orientation is DualTerminalBox.ORIENT_H:
|
|
self.set_orientation(orientation=Gtk.Orientation.HORIZONTAL)
|
|
else:
|
|
self.set_orientation(orientation=Gtk.Orientation.VERTICAL)
|
|
|
|
def set_child_first(self, terminal_holder):
|
|
if isinstance(terminal_holder, TerminalHolder):
|
|
self.add1(terminal_holder)
|
|
else:
|
|
print("wtf, what have you added to me???")
|
|
|
|
def set_child_second(self, terminal_holder):
|
|
if isinstance(terminal_holder, TerminalHolder):
|
|
self.add2(terminal_holder)
|
|
else:
|
|
print("wtf, what have you added to me???")
|
|
|
|
def get_terminals(self):
|
|
return self.get_child1().get_terminals() + self.get_child2().get_terminals()
|
|
|
|
def iter_terminals(self):
|
|
yield from self.get_child1().iter_terminals()
|
|
yield from self.get_child2().iter_terminals()
|
|
|
|
def replace_child(self, old, new):
|
|
if self.get_child1() is old:
|
|
self.remove(old)
|
|
self.set_child_first(new)
|
|
elif self.get_child2() is old:
|
|
self.remove(old)
|
|
self.set_child_second(new)
|
|
else:
|
|
print("I have never seen this widget!")
|
|
|
|
def get_guake(self):
|
|
return self.get_parent().get_guake()
|
|
|
|
def get_window(self):
|
|
return self.get_parent().get_window()
|
|
|
|
def get_settings(self):
|
|
return self.get_parent().get_settings()
|
|
|
|
def get_root_box(self):
|
|
return self.get_parent().get_root_box()
|
|
|
|
def get_notebook(self):
|
|
return self.get_parent().get_notebook()
|
|
|
|
def grab_box_terminal_focus(self, box):
|
|
if isinstance(box, DualTerminalBox):
|
|
try:
|
|
next(box.iter_terminals()).grab_focus()
|
|
except StopIteration:
|
|
log.error("Both panes are empty")
|
|
else:
|
|
box.get_terminal().grab_focus()
|
|
|
|
@save_tabs_when_changed
|
|
def remove_dead_child(self, child):
|
|
if self.get_child1() is child:
|
|
living_child = self.get_child2()
|
|
self.remove(living_child)
|
|
self.get_parent().replace_child(self, living_child)
|
|
self.grab_box_terminal_focus(living_child)
|
|
elif self.get_child2() is child:
|
|
living_child = self.get_child1()
|
|
self.remove(living_child)
|
|
self.get_parent().replace_child(self, living_child)
|
|
self.grab_box_terminal_focus(living_child)
|
|
else:
|
|
print("I have never seen this widget!")
|
|
|
|
|
|
class TabLabelEventBox(Gtk.EventBox):
|
|
def __init__(self, notebook, text, settings):
|
|
super().__init__()
|
|
self.notebook = notebook
|
|
self.box = Gtk.Box(homogeneous=Gtk.Orientation.HORIZONTAL, spacing=0, visible=True)
|
|
self.label = Gtk.Label(label=text, visible=True)
|
|
self.close_button = Gtk.Button(
|
|
image=Gtk.Image.new_from_icon_name("window-close", Gtk.IconSize.MENU),
|
|
relief=Gtk.ReliefStyle.NONE,
|
|
)
|
|
self.close_button.connect("clicked", self.on_close)
|
|
settings.general.bind(
|
|
"tab-close-buttons", self.close_button, "visible", Gio.SettingsBindFlags.GET
|
|
)
|
|
self.box.pack_start(self.label, True, True, 0)
|
|
self.box.pack_end(self.close_button, False, False, 0)
|
|
self.add(self.box)
|
|
self.connect("button-press-event", self.on_button_press, self.label)
|
|
|
|
def set_text(self, text):
|
|
self.label.set_text(text)
|
|
|
|
def get_text(self):
|
|
return self.label.get_text()
|
|
|
|
def grab_focus_on_last_focused_terminal(self):
|
|
server_time = get_server_time(self.notebook.guake.window)
|
|
self.notebook.guake.window.get_window().focus(server_time)
|
|
self.notebook.get_current_terminal().grab_focus()
|
|
|
|
def on_button_press(self, target, event, user_data):
|
|
if event.button == 3:
|
|
menu = mk_tab_context_menu(self)
|
|
menu.connect("hide", MenuHideCallback(self.get_toplevel()).on_hide)
|
|
HidePrevention(self.get_toplevel()).prevent()
|
|
try:
|
|
menu.popup_at_pointer(event)
|
|
except AttributeError:
|
|
# Gtk 3.18 fallback ("'Menu' object has no attribute 'popup_at_pointer'")
|
|
menu.popup(None, None, None, None, event.button, event.get_time())
|
|
return True
|
|
if event.button == 2:
|
|
prompt_cfg = self.notebook.guake.settings.general.get_int("prompt-on-close-tab")
|
|
self.notebook.delete_page_by_label(self, prompt=prompt_cfg)
|
|
return True
|
|
if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
|
|
self.on_rename(None)
|
|
|
|
return False
|
|
|
|
@save_tabs_when_changed
|
|
def on_new_tab(self, user_data):
|
|
self.notebook.new_page_with_focus()
|
|
|
|
@save_tabs_when_changed
|
|
def on_rename(self, user_data):
|
|
HidePrevention(self.get_toplevel()).prevent()
|
|
dialog = RenameDialog(self.notebook.guake.window, self.label.get_text())
|
|
r = dialog.run()
|
|
if r == Gtk.ResponseType.ACCEPT:
|
|
new_text = TabNameUtils.shorten(dialog.get_text(), self.notebook.guake.settings)
|
|
page_num = self.notebook.find_tab_index_by_label(self)
|
|
self.notebook.rename_page(page_num, new_text, True)
|
|
dialog.destroy()
|
|
HidePrevention(self.get_toplevel()).allow()
|
|
|
|
self.grab_focus_on_last_focused_terminal()
|
|
|
|
@save_tabs_when_changed
|
|
def on_reset_custom_colors(self, user_data):
|
|
HidePrevention(self.get_toplevel()).prevent()
|
|
if PromptResetColorsDialog(self.notebook.guake.window).reset_tab_custom_colors():
|
|
page_num = self.notebook.find_tab_index_by_label(self)
|
|
for t in self.notebook.get_nth_page(page_num).iter_terminals():
|
|
t.reset_custom_colors()
|
|
self.notebook.guake.set_colors_from_settings_on_page(page_num=page_num)
|
|
HidePrevention(self.get_toplevel()).allow()
|
|
|
|
self.grab_focus_on_last_focused_terminal()
|
|
|
|
def on_close(self, user_data):
|
|
prompt_cfg = self.notebook.guake.settings.general.get_int("prompt-on-close-tab")
|
|
self.notebook.delete_page_by_label(self, prompt=prompt_cfg)
|