3 Commits

Author SHA1 Message Date
5baadd9dbb Fix window icon and remove duplicate log 2026-04-18 23:39:20 +03:00
997bfef893 Reduce GUI vertical footprint 2026-04-18 23:33:42 +03:00
290d650f80 Respect system GTK theme 2026-04-18 23:27:22 +03:00

View File

@@ -36,6 +36,7 @@ from whipper.result import result
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
APP_ID = "com.github.whipper_team.Whipper"
class RipCancelledError(Exception): class RipCancelledError(Exception):
@@ -86,7 +87,9 @@ def _release_duration_distance(metadata, duration_ms):
class WhipperGui(Gtk.Application if Gtk is not None else object): class WhipperGui(Gtk.Application if Gtk is not None else object):
def __init__(self): def __init__(self):
require_gui_runtime() require_gui_runtime()
super().__init__(application_id="com.github.whipper_team.WhipperGui") super().__init__(application_id=APP_ID)
GLib.set_application_name("Whipper")
Gtk.Window.set_default_icon_name(APP_ID)
self.window = None self.window = None
self.main_pane = None self.main_pane = None
@@ -122,6 +125,7 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
self.logger_combo = None self.logger_combo = None
self.track_template_entry = None self.track_template_entry = None
self.disc_template_entry = None self.disc_template_entry = None
self.compact_mode_label = None
self.release_store = None self.release_store = None
self.release_view = None self.release_view = None
@@ -149,107 +153,32 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
def _build_window(self): def _build_window(self):
window = Gtk.ApplicationWindow(application=self) window = Gtk.ApplicationWindow(application=self)
window.set_title("Whipper") window.set_title("Whipper")
window.set_icon_name("com.github.whipper_team.Whipper") window.set_icon_name(APP_ID)
window.set_default_size(1380, 900) window.set_default_size(1380, 820)
window.set_border_width(8) window.set_border_width(6)
self._install_css()
root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
root.get_style_context().add_class("eac-shell")
window.add(root) window.add(root)
root.pack_start(self._build_transport_strip(), False, False, 0) root.pack_start(self._build_transport_strip(), False, False, 0)
root.pack_start(self._build_progress(), False, False, 0) root.pack_start(self._build_progress(), False, False, 0)
root.pack_start(self._build_main_content(), True, True, 0) root.pack_start(self._build_main_content(), True, True, 0)
root.pack_start(self._build_log(), False, True, 0)
self._refresh_devices() self._refresh_devices()
self._load_gui_settings() self._load_gui_settings()
window.show_all() window.show_all()
return window return window
@staticmethod
def _install_css():
provider = Gtk.CssProvider()
provider.load_from_data(
b"""
.eac-shell {
background: #d9d9d9;
}
frame > border {
border: 1px solid #8a8a8a;
border-radius: 0;
background: #ececec;
padding: 4px;
}
frame > label {
color: #1f1f1f;
font-weight: 700;
letter-spacing: 0.04em;
}
.eac-toolbar {
background: linear-gradient(to bottom, #f7f7f7, #d8d8d8);
border: 1px solid #8a8a8a;
padding: 6px;
}
.eac-toolbar button {
border-radius: 0;
padding: 4px 10px;
background: linear-gradient(to bottom, #fefefe, #dbdbdb);
border: 1px solid #7e7e7e;
color: #111111;
}
.eac-toolbar button.suggested-action {
background: linear-gradient(to bottom, #e5f2ff, #b7d4f3);
}
.eac-toolbar button.destructive-action {
background: linear-gradient(to bottom, #fbe7e7, #e7b7b7);
}
.eac-matrix,
.eac-log {
background: #fcfcfc;
color: #121212;
border: 1px solid #919191;
}
.eac-log text {
background: #fcfcfc;
color: #111111;
font: 10pt Monospace;
}
.eac-status {
font-weight: 700;
}
.eac-subtle {
color: #4c4c4c;
}
progressbar trough {
min-height: 16px;
border-radius: 0;
background: #cfcfcf;
border: 1px solid #8e8e8e;
}
progressbar progress {
border-radius: 0;
background: linear-gradient(to bottom, #8fc2f3, #4f84b6);
}
"""
)
Gtk.StyleContext.add_provider_for_screen(
Gtk.Window().get_screen(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
def _build_transport_strip(self): def _build_transport_strip(self):
frame = Gtk.Frame(label="Extraction Control Center") frame = Gtk.Frame(label="Extraction Control Center")
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8, margin=8) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin=6)
frame.add(box) frame.add(box)
box.pack_start(self._build_controls(), False, False, 0) box.pack_start(self._build_controls(), False, False, 0)
box.pack_start(self._build_actions(), False, False, 0) box.pack_start(self._build_actions(), False, False, 0)
return frame return frame
def _build_controls(self): def _build_controls(self):
grid = Gtk.Grid(column_spacing=8, row_spacing=8) grid = Gtk.Grid(column_spacing=8, row_spacing=6)
grid.attach(Gtk.Label(label="Drive", xalign=0), 0, 0, 1, 1) grid.attach(Gtk.Label(label="Drive", xalign=0), 0, 0, 1, 1)
self.device_combo = Gtk.ComboBoxText() self.device_combo = Gtk.ComboBoxText()
@@ -283,29 +212,50 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
self.logger_combo.connect("changed", self._on_settings_changed) self.logger_combo.connect("changed", self._on_settings_changed)
grid.attach(self.logger_combo, 4, 1, 2, 1) grid.attach(self.logger_combo, 4, 1, 2, 1)
grid.attach(Gtk.Label(label="Cover Art", xalign=0), 0, 2, 1, 1)
self.cover_art_combo = Gtk.ComboBoxText()
self.cover_art_combo.append("", "Disabled")
self.cover_art_combo.append("file", "Save file")
self.cover_art_combo.append("embed", "Embed only")
self.cover_art_combo.append("complete", "File + embed")
self.cover_art_combo.set_active(0)
self.cover_art_combo.connect("changed", self._on_settings_changed)
grid.attach(self.cover_art_combo, 1, 2, 1, 1)
grid.attach(Gtk.Label(label="Offset", xalign=0), 2, 2, 1, 1)
self.offset_spin = Gtk.SpinButton.new_with_range(-5000, 5000, 1)
self.offset_spin.set_value(0)
self.offset_spin.connect("value-changed", self._on_settings_changed)
grid.attach(self.offset_spin, 3, 2, 1, 1)
grid.attach(Gtk.Label(label="Retries", xalign=0), 4, 2, 1, 1)
self.max_retries_spin = Gtk.SpinButton.new_with_range(0, 20, 1)
self.max_retries_spin.set_value(5)
self.max_retries_spin.connect("value-changed", self._on_settings_changed)
grid.attach(self.max_retries_spin, 5, 2, 1, 1)
grid.attach(Gtk.Label(label="Working Dir", xalign=0), 0, 2, 1, 1) grid.attach(Gtk.Label(label="Working Dir", xalign=0), 0, 2, 1, 1)
self.working_directory_entry = Gtk.Entry() self.working_directory_entry = Gtk.Entry()
self.working_directory_entry.set_placeholder_text("Optional working directory") self.working_directory_entry.set_placeholder_text("Optional working directory")
self.working_directory_entry.connect("changed", self._on_settings_changed) self.working_directory_entry.connect("changed", self._on_settings_changed)
grid.attach(self.working_directory_entry, 1, 2, 2, 1) grid.attach(self.working_directory_entry, 1, 3, 2, 1)
grid.attach(Gtk.Label(label="Country", xalign=0), 0, 3, 1, 1) grid.attach(Gtk.Label(label="Country", xalign=0), 0, 4, 1, 1)
self.country_entry = Gtk.Entry() self.country_entry = Gtk.Entry()
self.country_entry.set_placeholder_text("Optional MusicBrainz country filter") self.country_entry.set_placeholder_text("Optional MusicBrainz country filter")
self.country_entry.connect("changed", self._on_settings_changed) self.country_entry.connect("changed", self._on_settings_changed)
grid.attach(self.country_entry, 1, 3, 1, 1) grid.attach(self.country_entry, 1, 4, 1, 1)
grid.attach(Gtk.Label(label="Release ID", xalign=0), 2, 3, 1, 1) grid.attach(Gtk.Label(label="Release ID", xalign=0), 2, 4, 1, 1)
self.release_id_entry = Gtk.Entry() self.release_id_entry = Gtk.Entry()
self.release_id_entry.set_placeholder_text("Optional release override") self.release_id_entry.set_placeholder_text("Optional release override")
self.release_id_entry.connect("changed", self._on_settings_changed) self.release_id_entry.connect("changed", self._on_settings_changed)
grid.attach(self.release_id_entry, 3, 3, 3, 1) grid.attach(self.release_id_entry, 3, 4, 3, 1)
return grid return grid
def _build_actions(self): def _build_actions(self):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
box.get_style_context().add_class("eac-toolbar")
self.read_button = Gtk.Button(label="Detect TOC") self.read_button = Gtk.Button(label="Detect TOC")
self.read_button.connect("clicked", self._on_read_clicked) self.read_button.connect("clicked", self._on_read_clicked)
@@ -316,33 +266,49 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
self.rip_button.connect("clicked", self._on_rip_clicked) self.rip_button.connect("clicked", self._on_rip_clicked)
self.rip_button.set_tooltip_text("Rip the current disc using the selected release metadata") self.rip_button.set_tooltip_text("Rip the current disc using the selected release metadata")
self.rip_button.set_sensitive(False) self.rip_button.set_sensitive(False)
self.rip_button.get_style_context().add_class("suggested-action")
box.pack_start(self.rip_button, False, False, 0) box.pack_start(self.rip_button, False, False, 0)
self.stop_button = Gtk.Button(label="Abort") self.stop_button = Gtk.Button(label="Abort")
self.stop_button.connect("clicked", self._on_stop_clicked) self.stop_button.connect("clicked", self._on_stop_clicked)
self.stop_button.set_tooltip_text("Cancel the current scan or rip") self.stop_button.set_tooltip_text("Cancel the current scan or rip")
self.stop_button.set_sensitive(False) self.stop_button.set_sensitive(False)
self.stop_button.get_style_context().add_class("destructive-action")
box.pack_start(self.stop_button, False, False, 0) box.pack_start(self.stop_button, False, False, 0)
box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), False, False, 6) box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), False, False, 6)
action_hint = Gtk.Label( self.unknown_check = Gtk.CheckButton(label="Unknown")
label="EAC-style flow: Detect TOC -> pick release -> Secure Rip", self.unknown_check.set_active(True)
self.unknown_check.connect("toggled", self._on_unknown_toggled)
box.pack_start(self.unknown_check, False, False, 0)
self.cdr_check = Gtk.CheckButton(label="CD-R")
self.cdr_check.connect("toggled", self._on_settings_changed)
box.pack_start(self.cdr_check, False, False, 0)
self.keep_going_check = Gtk.CheckButton(label="Keep Going")
self.keep_going_check.set_active(True)
self.keep_going_check.connect("toggled", self._on_settings_changed)
box.pack_start(self.keep_going_check, False, False, 0)
self.overread_check = Gtk.CheckButton(label="Overread")
self.overread_check.connect("toggled", self._on_settings_changed)
box.pack_start(self.overread_check, False, False, 0)
box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), False, False, 6)
self.compact_mode_label = Gtk.Label(
label="Compact EAC-style flow: Detect TOC -> pick release -> Secure Rip",
xalign=0, xalign=0,
) )
action_hint.get_style_context().add_class("eac-subtle") box.pack_start(self.compact_mode_label, True, True, 0)
box.pack_start(action_hint, True, True, 0)
return box return box
def _build_progress(self): def _build_progress(self):
frame = Gtk.Frame(label="Extraction Progress") frame = Gtk.Frame(label="Extraction Progress")
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin=8) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4, margin=6)
frame.add(box) frame.add(box)
self.progress_label = Gtk.Label(label="No task running", xalign=0) self.progress_label = Gtk.Label(label="No task running", xalign=0)
self.progress_label.get_style_context().add_class("eac-status")
box.pack_start(self.progress_label, False, False, 0) box.pack_start(self.progress_label, False, False, 0)
self.overall_bar = Gtk.ProgressBar(show_text=True) self.overall_bar = Gtk.ProgressBar(show_text=True)
@@ -360,6 +326,7 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
pane.set_wide_handle(True) pane.set_wide_handle(True)
pane.pack1(self._build_workspace(), resize=True, shrink=False) pane.pack1(self._build_workspace(), resize=True, shrink=False)
pane.pack2(self._build_log(), resize=False, shrink=False) pane.pack2(self._build_log(), resize=False, shrink=False)
pane.set_position(560)
self.main_pane = pane self.main_pane = pane
return pane return pane
@@ -368,13 +335,14 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
pane.set_wide_handle(True) pane.set_wide_handle(True)
pane.pack1(self._build_left_panel(), resize=False, shrink=False) pane.pack1(self._build_left_panel(), resize=False, shrink=False)
pane.pack2(self._build_right_panel(), resize=True, shrink=False) pane.pack2(self._build_right_panel(), resize=True, shrink=False)
pane.set_position(360)
return pane return pane
def _build_left_panel(self): def _build_left_panel(self):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
info_frame = Gtk.Frame(label="Disc") info_frame = Gtk.Frame(label="Disc")
info_grid = Gtk.Grid(column_spacing=10, row_spacing=6, margin=10) info_grid = Gtk.Grid(column_spacing=8, row_spacing=4, margin=6)
info_frame.add(info_grid) info_frame.add(info_grid)
rows = [ rows = [
@@ -394,7 +362,7 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
box.pack_start(info_frame, False, False, 0) box.pack_start(info_frame, False, False, 0)
release_frame = Gtk.Frame(label="Metadata / Releases") release_frame = Gtk.Frame(label="Metadata / Releases")
release_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8, margin=8) release_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin=6)
release_frame.add(release_box) release_frame.add(release_box)
self.release_store = Gtk.ListStore(str, str, str, str, str, object) self.release_store = Gtk.ListStore(str, str, str, str, str, object)
@@ -403,7 +371,6 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
self.release_view.set_enable_search(False) self.release_view.set_enable_search(False)
self.release_view.set_fixed_height_mode(True) self.release_view.set_fixed_height_mode(True)
self.release_view.set_grid_lines(Gtk.TreeViewGridLines.BOTH) self.release_view.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
self.release_view.get_style_context().add_class("eac-matrix")
self.release_view.get_selection().connect("changed", self._on_release_selected) self.release_view.get_selection().connect("changed", self._on_release_selected)
for index, title in enumerate(["Artist", "Title", "Year", "Type", "Country"]): for index, title in enumerate(["Artist", "Title", "Year", "Type", "Country"]):
renderer = Gtk.CellRendererText() renderer = Gtk.CellRendererText()
@@ -423,13 +390,12 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
details_scroll = Gtk.ScrolledWindow() details_scroll = Gtk.ScrolledWindow()
details_scroll.set_hexpand(True) details_scroll.set_hexpand(True)
details_scroll.set_vexpand(False) details_scroll.set_vexpand(False)
details_scroll.set_min_content_height(120) details_scroll.set_min_content_height(90)
details_frame.add(details_scroll) details_frame.add(details_scroll)
self.release_details = Gtk.Label(label="Select a release to inspect it.", xalign=0, yalign=0) self.release_details = Gtk.Label(label="Select a release to inspect it.", xalign=0, yalign=0)
self.release_details.set_line_wrap(True) self.release_details.set_line_wrap(True)
self.release_details.set_selectable(True) self.release_details.set_selectable(True)
self.release_details.get_style_context().add_class("eac-subtle")
details_scroll.add(self.release_details) details_scroll.add(self.release_details)
release_box.pack_start(details_frame, False, False, 0) release_box.pack_start(details_frame, False, False, 0)
@@ -438,10 +404,10 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
return box return box
def _build_right_panel(self): def _build_right_panel(self):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
tracks_frame = Gtk.Frame(label="Track Extraction Matrix") tracks_frame = Gtk.Frame(label="Track Extraction Matrix")
tracks_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin=8) tracks_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin=6)
tracks_frame.add(tracks_box) tracks_frame.add(tracks_box)
self.track_columns = { self.track_columns = {
@@ -460,7 +426,6 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
self.track_view.set_enable_search(False) self.track_view.set_enable_search(False)
self.track_view.set_fixed_height_mode(True) self.track_view.set_fixed_height_mode(True)
self.track_view.set_grid_lines(Gtk.TreeViewGridLines.BOTH) self.track_view.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
self.track_view.get_style_context().add_class("eac-matrix")
for index, title in enumerate(["#", "Status", "Artist", "Title", "Length", "Test CRC", "Copy CRC", "AR"]): for index, title in enumerate(["#", "Status", "Artist", "Title", "Length", "Test CRC", "Copy CRC", "AR"]):
renderer = Gtk.CellRendererText() renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(title, renderer, text=index) column = Gtk.TreeViewColumn(title, renderer, text=index)
@@ -483,68 +448,27 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
def _build_rip_options(self): def _build_rip_options(self):
frame = Gtk.Frame(label="Extraction Setup") frame = Gtk.Frame(label="Extraction Setup")
grid = Gtk.Grid(column_spacing=8, row_spacing=6, margin=8) grid = Gtk.Grid(column_spacing=8, row_spacing=4, margin=6)
frame.add(grid) frame.add(grid)
self.unknown_check = Gtk.CheckButton(label="Allow unknown disc") grid.attach(Gtk.Label(label="Track Tpl", xalign=0), 0, 0, 1, 1)
self.unknown_check.set_active(True)
self.unknown_check.connect("toggled", self._on_unknown_toggled)
grid.attach(self.unknown_check, 0, 0, 2, 1)
self.cdr_check = Gtk.CheckButton(label="Allow CD-R")
self.cdr_check.connect("toggled", self._on_settings_changed)
grid.attach(self.cdr_check, 2, 0, 1, 1)
self.keep_going_check = Gtk.CheckButton(label="Continue on failed tracks")
self.keep_going_check.set_active(True)
self.keep_going_check.connect("toggled", self._on_settings_changed)
grid.attach(self.keep_going_check, 0, 1, 2, 1)
self.overread_check = Gtk.CheckButton(label="Force overread")
self.overread_check.connect("toggled", self._on_settings_changed)
grid.attach(self.overread_check, 2, 1, 1, 1)
grid.attach(Gtk.Label(label="Cover Art", xalign=0), 0, 2, 1, 1)
self.cover_art_combo = Gtk.ComboBoxText()
self.cover_art_combo.append("", "Disabled")
self.cover_art_combo.append("file", "Save file")
self.cover_art_combo.append("embed", "Embed only")
self.cover_art_combo.append("complete", "File + embed")
self.cover_art_combo.set_active(0)
self.cover_art_combo.connect("changed", self._on_settings_changed)
grid.attach(self.cover_art_combo, 1, 2, 1, 1)
grid.attach(Gtk.Label(label="Max Retries", xalign=0), 2, 2, 1, 1)
self.max_retries_spin = Gtk.SpinButton.new_with_range(0, 20, 1)
self.max_retries_spin.set_value(5)
self.max_retries_spin.connect("value-changed", self._on_settings_changed)
grid.attach(self.max_retries_spin, 3, 2, 1, 1)
grid.attach(Gtk.Label(label="Offset", xalign=0), 0, 3, 1, 1)
self.offset_spin = Gtk.SpinButton.new_with_range(-5000, 5000, 1)
self.offset_spin.set_value(0)
self.offset_spin.connect("value-changed", self._on_settings_changed)
grid.attach(self.offset_spin, 1, 3, 1, 1)
grid.attach(Gtk.Label(label="Track Tpl", xalign=0), 0, 4, 1, 1)
self.track_template_entry = Gtk.Entry() self.track_template_entry = Gtk.Entry()
self.track_template_entry.set_text(cd_command.DEFAULT_TRACK_TEMPLATE) self.track_template_entry.set_text(cd_command.DEFAULT_TRACK_TEMPLATE)
self.track_template_entry.connect("changed", self._on_settings_changed) self.track_template_entry.connect("changed", self._on_settings_changed)
grid.attach(self.track_template_entry, 1, 4, 3, 1) grid.attach(self.track_template_entry, 1, 0, 3, 1)
grid.attach(Gtk.Label(label="Disc Tpl", xalign=0), 0, 5, 1, 1) grid.attach(Gtk.Label(label="Disc Tpl", xalign=0), 0, 1, 1, 1)
self.disc_template_entry = Gtk.Entry() self.disc_template_entry = Gtk.Entry()
self.disc_template_entry.set_text(cd_command.DEFAULT_DISC_TEMPLATE) self.disc_template_entry.set_text(cd_command.DEFAULT_DISC_TEMPLATE)
self.disc_template_entry.connect("changed", self._on_settings_changed) self.disc_template_entry.connect("changed", self._on_settings_changed)
grid.attach(self.disc_template_entry, 1, 5, 3, 1) grid.attach(self.disc_template_entry, 1, 1, 3, 1)
note = Gtk.Label( note = Gtk.Label(
label="Secure extraction is driven by whipper's native cdparanoia/cdrdao pipeline. The configured drive offset is loaded automatically when available.", label="Advanced templates stay here; fast extraction switches moved to the control center to reduce vertical space.",
xalign=0, xalign=0,
) )
note.set_line_wrap(True) note.set_line_wrap(True)
note.get_style_context().add_class("eac-subtle") grid.attach(note, 0, 2, 4, 1)
grid.attach(note, 0, 6, 4, 1)
return frame return frame
@@ -553,14 +477,13 @@ class WhipperGui(Gtk.Application if Gtk is not None else object):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
scroll.set_hexpand(True) scroll.set_hexpand(True)
scroll.set_vexpand(False) scroll.set_vexpand(False)
scroll.set_min_content_height(220) scroll.set_min_content_height(150)
frame.add(scroll) frame.add(scroll)
text_view = Gtk.TextView() text_view = Gtk.TextView()
text_view.set_editable(False) text_view.set_editable(False)
text_view.set_cursor_visible(False) text_view.set_cursor_visible(False)
text_view.set_monospace(True) text_view.set_monospace(True)
text_view.get_style_context().add_class("eac-log")
self.log_buffer = text_view.get_buffer() self.log_buffer = text_view.get_buffer()
scroll.add(text_view) scroll.add(text_view)
return frame return frame