def __init__(self, app): View.__init__(self, app, sub_title='Tweak how files are synchronized') self._grid = Gtk.Grid() self._grid.set_margin_start(30) self._grid.set_margin_end(40) self._grid.set_margin_top(5) self._grid.set_margin_bottom(15) self._grid.set_hexpand(True) self._grid.set_vexpand(True) self._grid.set_halign(Gtk.Align.FILL) self._grid.set_valign(Gtk.Align.FILL) self.add(self._grid) self.save_settings = False self.sections = {} self.metadata = {} self.appy_btn = SuggestedButton() self.deny_btn = DestructiveButton('Reset to defaults') self.appy_btn.connect('clicked', self.on_apply_settings) self.deny_btn.connect('clicked', self.on_reset_to_defaults) self.search_entry.connect('search-changed', self.on_search_changed) # Initialize from current settings: self.build()
def __init__(self, app): View.__init__( self, app, sub_title='Configure how duplicates are searched' ) self._grid = Gtk.Grid() self._grid.set_margin_start(30) self._grid.set_margin_end(40) self._grid.set_margin_top(5) self._grid.set_margin_bottom(15) self._grid.set_hexpand(True) self._grid.set_vexpand(True) self._grid.set_halign(Gtk.Align.FILL) self._grid.set_valign(Gtk.Align.FILL) self.add(self._grid) self.save_settings = False self.sections = {} self.metadata = {} self.appy_btn = SuggestedButton() self.deny_btn = DestructiveButton('Reset to defaults') self.appy_btn.connect('clicked', self.on_apply_settings) self.deny_btn.connect('clicked', self.on_reset_to_defaults) self.search_entry.connect( 'search-changed', self.on_search_changed ) # Initialize from current settings: self.build()
def __init__(self, app): View.__init__(self, app) self.sub_title = 'View and pin some files' sidebar = Gtk.PlacesSidebar() browser = Gtk.IconView() browser.connect('button-press-event', on_button_press_event) model = Gtk.ListStore(str, GdkPixbuf.Pixbuf) model.append(['Schreibtisch', icon_pixbuf('folder')]) model.append(['Bilder', icon_pixbuf('folder')]) model.append(['Dokumente', icon_pixbuf('folder-download')]) model.append(['Downloads', icon_pixbuf('folder')]) model.append(['Musik', icon_pixbuf('folder')]) model.append(['Videos', icon_pixbuf('folder')]) model.append(['Public', icon_pixbuf('folder-publicshare')]) model.append(['notes.txt', icon_pixbuf('emblem-documents')]) browser.set_model(model) browser.set_markup_column(0) browser.set_pixbuf_column(1) paned = Gtk.Paned() paned.add1(sidebar) paned.add2(browser) paned.set_hexpand(True) paned.set_vexpand(True) self.add(paned)
def __init__(self, app): View.__init__(self, app, 'Running…') # Public: The runner. self.runner = None self.last_paths = [] # Disable scrolling for the main view: self.scw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) # Public flag for checking if the view is still # in running mode (thus en/disabling certain features) self.is_running = False self._script_generated = False self._is_filtered = False self.model = PathTreeModel([]) self.treeview = PathTreeView() self.treeview.set_model(self.model) self.treeview.get_selection().connect('changed', self.on_selection_changed) self.group_treeview = PathTreeView() self.group_treeview.set_vexpand(True) self.group_treeview.set_valign(Gtk.Align.FILL) # This is needed to make sure operations on the one update # the other. Internally the same nodes are updated, but it has # to be made sure that the models get updated. self.group_treeview.set_twin(self.treeview) self.treeview.set_twin(self.group_treeview) group_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) group_box.pack_start(scrolled(self.group_treeview), True, True, 0) group_box.pack_start(Gtk.HSeparator(), False, False, 0) self.group_revealer = Gtk.Revealer() self.group_revealer.set_vexpand(True) self.group_revealer.set_valign(Gtk.Align.FILL) self.group_revealer.add(group_box) self.group_revealer.set_no_show_all(True) self.group_revealer.set_size_request(-1, 70) for column in self.treeview.get_columns(): column.connect('clicked', lambda _: self.rerender_chart()) self.chart_stack = ChartStack() self.actionbar = ResultActionBar(self) self.actionbar.set_valign(Gtk.Align.END) self.actionbar.set_halign(Gtk.Align.FILL) # Right part of the view stats_box = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL) stats_box.pack1(self.group_revealer, True, True) stats_box.pack2(self.chart_stack, True, True) stats_box.props.position = 200 # Separator container for separator|chart (could have used grid) separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) right_pane = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) right_pane.pack_start(separator, False, False, 0) right_pane.pack_start(stats_box, True, True, 0) right_pane.set_size_request(100, -1) scw = scrolled(self.treeview) scw.set_size_request(200, -1) paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL) paned.set_vexpand(True) paned.set_valign(Gtk.Align.FILL) paned.pack1(scw, True, True) paned.pack2(right_pane, True, True) paned.props.position = 720 paned.set_hexpand(True) grid = Gtk.Grid() grid.attach(paned, 0, 0, 1, 1) grid.attach(self.actionbar, 0, 1, 1, 1) self.add(grid) self.search_entry.connect('search-changed', self.on_search_changed) self.actionbar.connect('generate-all-script', self.on_generate_script) self.actionbar.connect('generate-filtered-script', self.on_generate_filtered_script) self.actionbar.connect('generate-selection-script', self.on_generate_selection_script) self._menu = None
def __init__(self, app): View.__init__(self, app) self.selected_locations = [] self.known_paths = set() self._set_title() self.box = Gtk.ListBox() self.box.set_selection_mode(Gtk.SelectionMode.NONE) self.box.set_hexpand(True) self.box.set_placeholder(Gtk.Label('No locations mounted.')) self.box.set_valign(Gtk.Align.FILL) self.box.set_vexpand(True) self.chooser_button = IconButton('list-add-symbolic', 'Add Location') self.chooser_button.connect('clicked', self.on_chooser_button_clicked) self.file_chooser = Gtk.FileChooserWidget() self.file_chooser.set_select_multiple(True) self.file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER) self.file_chooser.set_create_folders(False) self.stack = Gtk.Stack() self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP) scw = Gtk.ScrolledWindow() scw.add(self.box) self.stack.add_named(scw, 'list') self.stack.add_named(self.file_chooser, 'chooser') self.box.set_activate_on_single_click(True) self.box.set_filter_func(self._filter_func) self.box.connect('row-activated', self.on_row_clicked) self.search_entry.connect('search-changed', self.on_search_changed) self.volume_monitor = Gio.VolumeMonitor.get() self.recent_mgr = Gtk.RecentManager.get_default() self.recent_mgr.connect('changed', self.refill_entries) self.volume_monitor.connect('volume-changed', self.refill_entries) self.volume_monitor.connect('drive-changed', self.refill_entries) self.volume_monitor.connect('mount-changed', self.refill_entries) self.refill_entries() run_button = IconButton('edit-find-symbolic', 'Scan folders') run_button.connect('clicked', self._run_clicked) run_button.get_style_context().add_class( Gtk.STYLE_CLASS_SUGGESTED_ACTION) del_button = IconButton('user-trash-symbolic', 'Remove from list') del_button.connect('clicked', self._del_clicked) self.selected_label = Gtk.Label() self.selected_label.get_style_context().add_class( Gtk.STYLE_CLASS_DIM_LABEL) action_bar = Gtk.ActionBar() action_bar.pack_start(del_button) action_bar.set_center_widget(self.selected_label) action_bar.pack_end(run_button) self.revealer = Gtk.Revealer() self.revealer.add(action_bar) self.revealer.set_hexpand(True) self.revealer.set_halign(Gtk.Align.FILL) grid = Gtk.Grid() grid.attach(self.stack, 0, 0, 1, 1) grid.attach(self.revealer, 0, 1, 1, 1) self.add(grid)
def __init__(self, win): View.__init__(self, win) self._last_runner = None self.script = Script.create_dummy() control_grid = Gtk.Grid() control_grid.set_hexpand(False) control_grid.set_vexpand(False) control_grid.set_halign(Gtk.Align.CENTER) control_grid.set_valign(Gtk.Align.CENTER) self.info_label = Gtk.Label(use_markup=True, justify=Gtk.Justification.CENTER) self.info_label.get_style_context().add_class( Gtk.STYLE_CLASS_DIM_LABEL) self.set_info_review_text() self.icon_stack = _create_icon_stack() self.text_view, buffer_ = _create_source_view() self.text_view.set_name('ShredderScriptEditor') self.text_view.set_vexpand(True) self.text_view.set_valign(Gtk.Align.FILL) self.text_view.set_hexpand(True) self.text_view.set_halign(Gtk.Align.FILL) self.save_button = OverlaySaveButton() self.save_button.add(scrolled(self.text_view)) self.save_chooser = ScriptSaverDialog(self) def on_save_button_clicked(_): """Switch to the save dialog in the stack.""" self.set_search_mode(False) self.left_stack.set_visible_child_name('chooser') self.save_chooser.show_controls() self.set_info_help_text() self.set_correct_icon() self.run_button.set_sensitive(False) def on_save_clicked(_): """Switch back when the user has saved.""" self.left_stack.set_visible_child_name('script') self.set_info_review_text() self.set_correct_icon() self.run_button.set_sensitive(True) self.save_button.connect('save-clicked', on_save_button_clicked) self.save_chooser.connect('saved', on_save_clicked) buffer_.create_tag("original", weight=Pango.Weight.BOLD) buffer_.create_tag("normal") self.run_label = RunningLabel() self.run_label.set_hexpand(False) self.run_label.set_halign(Gtk.Align.FILL) self.left_stack = Gtk.Stack() self.left_stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP) spinner = Gtk.Spinner() spinner.start() self.left_stack.add_named(spinner, 'loading') self.left_stack.add_named(self.save_button, 'script') self.left_stack.add_named(self.save_chooser, 'chooser') self.left_stack.add_named(scrolled(self.run_label), 'list') separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) left_pane = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) left_pane.pack_start(self.left_stack, True, True, 0) left_pane.pack_start(separator, False, False, 0) self.run_button = RunButton('user-trash-symbolic', 'Run Script') self.run_button.button.connect('clicked', self.on_run_script_clicked) self.run_button.set_halign(Gtk.Align.CENTER) self.run_button.connect('notify::dry-run', lambda *_: self.set_correct_icon()) control_grid.attach(self.info_label, 0, 0, 1, 1) control_grid.attach_next_to(self.run_button, self.info_label, Gtk.PositionType.BOTTOM, 1, 1) control_grid.attach_next_to(self.icon_stack, self.info_label, Gtk.PositionType.TOP, 1, 1) control_grid.set_border_width(15) self.stack = Gtk.Stack() self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP) self.stack.add_named(control_grid, 'danger') self.stack.add_named(_create_running_screen(), 'progressing') self.stack.add_named(_create_finished_screen(self._switch_back), 'finished') self.left_stack.set_visible_child_name('script') paned = Gtk.Paned() paned.pack1(left_pane, True, True) paned.pack2(self.stack, True, True) paned.props.position = 920 self.add(paned) self._last_search = None self.search_entry.connect('search-changed', self.on_search_changed) try: self.search_entry.connect('next-match', self.on_search_changed) except TypeError: LOGGER.warning( 'Old gtk version; skipping through matches will not work.')
def __init__(self, win): View.__init__(self, win) self._last_runner = None self.script = Script.create_dummy() control_grid = Gtk.Grid() control_grid.set_hexpand(False) control_grid.set_vexpand(False) control_grid.set_halign(Gtk.Align.CENTER) control_grid.set_valign(Gtk.Align.CENTER) self.info_label = Gtk.Label( use_markup=True, justify=Gtk.Justification.CENTER ) self.info_label.get_style_context().add_class( Gtk.STYLE_CLASS_DIM_LABEL ) self.set_info_review_text() self.icon_stack = _create_icon_stack() self.text_view, buffer_ = _create_source_view() self.text_view.set_name('ShredderScriptEditor') self.text_view.set_vexpand(True) self.text_view.set_valign(Gtk.Align.FILL) self.text_view.set_hexpand(True) self.text_view.set_halign(Gtk.Align.FILL) self.save_button = OverlaySaveButton() self.save_button.add(scrolled(self.text_view)) self.save_chooser = ScriptSaverDialog(self) def on_save_button_clicked(_): """Switch to the save dialog in the stack.""" self.set_search_mode(False) self.left_stack.set_visible_child_name('chooser') self.save_chooser.show_controls() self.set_info_help_text() self.set_correct_icon() self.run_button.set_sensitive(False) def on_save_clicked(_): """Switch back when the user has saved.""" self.left_stack.set_visible_child_name('script') self.set_info_review_text() self.set_correct_icon() self.run_button.set_sensitive(True) self.save_button.connect( 'save-clicked', on_save_button_clicked ) self.save_chooser.connect( 'saved', on_save_clicked ) buffer_.create_tag("original", weight=Pango.Weight.BOLD) buffer_.create_tag("normal") self.run_label = RunningLabel() self.run_label.set_hexpand(False) self.run_label.set_halign(Gtk.Align.FILL) self.left_stack = Gtk.Stack() self.left_stack.set_transition_type( Gtk.StackTransitionType.SLIDE_UP ) spinner = Gtk.Spinner() spinner.start() self.left_stack.add_named(spinner, 'loading') self.left_stack.add_named(self.save_button, 'script') self.left_stack.add_named(self.save_chooser, 'chooser') self.left_stack.add_named(scrolled(self.run_label), 'list') separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) left_pane = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) left_pane.pack_start(self.left_stack, True, True, 0) left_pane.pack_start(separator, False, False, 0) self.run_button = RunButton( 'user-trash-symbolic', 'Run Script' ) self.run_button.button.connect('clicked', self.on_run_script_clicked) self.run_button.set_halign(Gtk.Align.CENTER) self.run_button.connect( 'notify::dry-run', lambda *_: self.set_correct_icon() ) control_grid.attach(self.info_label, 0, 0, 1, 1) control_grid.attach_next_to( self.run_button, self.info_label, Gtk.PositionType.BOTTOM, 1, 1 ) control_grid.attach_next_to( self.icon_stack, self.info_label, Gtk.PositionType.TOP, 1, 1 ) control_grid.set_border_width(15) self.stack = Gtk.Stack() self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP) self.stack.add_named(control_grid, 'danger') self.stack.add_named(_create_running_screen(), 'progressing') self.stack.add_named( _create_finished_screen(self._switch_back), 'finished' ) self.left_stack.set_visible_child_name('script') paned = Gtk.Paned() paned.pack1(left_pane, True, True) paned.pack2(self.stack, True, True) paned.props.position = 920 self.add(paned) self._last_search = None self.search_entry.connect( 'search-changed', self.on_search_changed ) try: self.search_entry.connect( 'next-match', self.on_search_changed ) except TypeError: LOGGER.warning('Old gtk version; skipping through matches will not work.')
def __init__(self, app): View.__init__(self, app) self.selected_locations = [] self.known_paths = set() self._set_title() self.box = Gtk.ListBox() self.box.set_selection_mode(Gtk.SelectionMode.NONE) self.box.set_hexpand(True) self.box.set_placeholder(Gtk.Label('No locations mounted.')) self.box.set_valign(Gtk.Align.FILL) self.box.set_vexpand(True) self.chooser_button = IconButton( 'list-add-symbolic', 'Add Location' ) self.chooser_button.connect( 'clicked', self.on_chooser_button_clicked ) self.file_chooser = Gtk.FileChooserWidget() self.file_chooser.set_select_multiple(True) self.file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER) self.file_chooser.set_create_folders(False) self.stack = Gtk.Stack() self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP) scw = Gtk.ScrolledWindow() scw.add(self.box) self.stack.add_named(scw, 'list') self.stack.add_named(self.file_chooser, 'chooser') self.box.set_activate_on_single_click(True) self.box.set_filter_func(self._filter_func) self.box.connect('row-activated', self.on_row_clicked) self.search_entry.connect( 'search-changed', self.on_search_changed ) entries = load_saved_entries() if entries: self.load_entries_from_disk(entries) else: self.load_entries_initially() run_button = IconButton('edit-find-symbolic', 'Scan folders') run_button.connect('clicked', self._run_clicked) run_button.get_style_context().add_class( Gtk.STYLE_CLASS_SUGGESTED_ACTION ) del_button = IconButton('user-trash-symbolic', 'Remove from list') del_button.connect('clicked', self._del_clicked) self.selected_label = Gtk.Label() self.selected_label.get_style_context().add_class( Gtk.STYLE_CLASS_DIM_LABEL ) action_bar = Gtk.ActionBar() action_bar.pack_start(del_button) action_bar.set_center_widget(self.selected_label) action_bar.pack_end(run_button) self.revealer = Gtk.Revealer() self.revealer.add(action_bar) self.revealer.set_hexpand(True) self.revealer.set_halign(Gtk.Align.FILL) grid = Gtk.Grid() grid.attach(self.stack, 0, 0, 1, 1) grid.attach(self.revealer, 0, 1, 1, 1) self.add(grid)
def __init__(self, app): View.__init__(self, app, 'Running…') # Public: The runner. self.runner = None self.last_paths = [] # Disable scrolling for the main view: self.scw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) # Public flag for checking if the view is still # in running mode (thus en/disabling certain features) self.is_running = False self._script_generated = False self.model = PathTreeModel([]) self.treeview = PathTreeView() self.treeview.set_model(self.model) self.treeview.get_selection().connect( 'changed', self.on_selection_changed ) self.group_treeview = PathTreeView() self.group_treeview.set_vexpand(True) self.group_treeview.set_valign(Gtk.Align.FILL) # This is needed to make sure operations on the one update # the other. Interally the same nodes are updated, but it has # to be made sure that the models get updated. self.group_treeview.set_twin(self.treeview) self.treeview.set_twin(self.group_treeview) group_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) group_box.pack_start(scrolled(self.group_treeview), True, True, 0) group_box.pack_start(Gtk.HSeparator(), False, False, 0) self.group_revealer = Gtk.Revealer() self.group_revealer.set_vexpand(True) self.group_revealer.set_valign(Gtk.Align.FILL) self.group_revealer.add(group_box) self.group_revealer.set_no_show_all(True) self.group_revealer.set_size_request(-1, 70) for column in self.treeview.get_columns(): column.connect( 'clicked', lambda _: self.rerender_chart() ) self.chart_stack = ChartStack() self.actionbar = ResultActionBar(self) self.actionbar.set_valign(Gtk.Align.END) self.actionbar.set_halign(Gtk.Align.FILL) # Right part of the view stats_box = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL) stats_box.pack1(self.group_revealer, True, True) stats_box.pack2(self.chart_stack, True, True) stats_box.props.position = 200 # Separator container for separator|chart (could have used grid) separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) right_pane = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) right_pane.pack_start(separator, False, False, 0) right_pane.pack_start(stats_box, True, True, 0) right_pane.set_size_request(100, -1) scw = scrolled(self.treeview) scw.set_size_request(200, -1) paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL) paned.set_vexpand(True) paned.set_valign(Gtk.Align.FILL) paned.pack1(scw, True, True) paned.pack2(right_pane, True, True) paned.props.position = 720 paned.set_hexpand(True) grid = Gtk.Grid() grid.attach(paned, 0, 0, 1, 1) grid.attach(self.actionbar, 0, 1, 1, 1) self.add(grid) self.search_entry.connect( 'search-changed', self.on_search_changed ) self.actionbar.connect( 'generate-all-script', self.on_generate_script ) self.actionbar.connect( 'generate-filtered-script', self.on_generate_filtered_script ) self.actionbar.connect( 'generate-selection-script', self.on_generate_selection_script ) self._menu = None
def __init__(self, app): View.__init__(self, app) self.sub_title = 'Repository creating wizard' grid = Gtk.Grid() grid.set_hexpand(True) grid.set_vexpand(True) grid.set_valign(Gtk.Align.CENTER) grid.set_halign(Gtk.Align.FILL) grid.set_margin_start(30) grid.set_margin_end(30) header = Gtk.Label() header.set_markup('<b><big>We just need a tiny bit of info to get you connected…</big></b>') header.set_margin_bottom(30) header.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL) footer = Gtk.Label() footer.set_markup( '<big><b>🔌</b></big> If you want to use a Yubikey, plug it in now.' ) footer.set_margin_top(30) footer.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL) user_entry = Gtk.Entry() user_entry.set_hexpand(True) user_entry.set_vexpand(False) user_entry.set_valign(Gtk.Align.CENTER) user_entry.set_halign(Gtk.Align.FILL) user_entry.set_margin_start(15) user_label = Gtk.Label() user_label.set_markup('<span foreground="#BB9900">⚠This username seems to be taken.</span>') pass_entry = Gtk.Entry() pass_entry.set_hexpand(True) pass_entry.set_vexpand(False) pass_entry.set_valign(Gtk.Align.CENTER) pass_entry.set_halign(Gtk.Align.FILL) pass_entry.set_visibility(False) pass_entry.set_margin_start(15) level_bar = Gtk.LevelBar() level_bar.set_valign(Gtk.Align.START) level_bar.set_halign(Gtk.Align.CENTER) level_bar.set_vexpand(False) level_bar.set_size_request(150, 10) level_bar.set_value(0.9) pass_label = Gtk.Label() pass_label.set_markup('<span foreground="#00AA00">✔ Entropy 33/30</span>') pass_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) pass_box.pack_start(pass_label, True, False, 0) pass_box.pack_start(level_bar, True, False, 0) double_pass_entry = Gtk.Entry() double_pass_entry.set_hexpand(True) double_pass_entry.set_vexpand(False) double_pass_entry.set_valign(Gtk.Align.CENTER) double_pass_entry.set_halign(Gtk.Align.FILL) double_pass_entry.set_visibility(False) double_pass_entry.set_margin_start(15) double_pass_label = Gtk.Label() double_pass_label.set_markup('<span foreground="#AA0000">✗ Passwords do not match :-(</span>') file_chooser = Gtk.FileChooserButton() file_chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER) sub_grid = Gtk.Grid() sub_grid.attach(create_entry(make_double_widget(user_label, user_entry), 'Username', 'This is how you are known in the network.'), 1, 1, 1, 1) sub_grid.attach(create_separator(), 1, 2, 1, 1) sub_grid.attach(create_entry(make_double_widget(pass_box, pass_entry), 'Passphrase', 'Remember this, you will need it to access your files!'), 1, 3, 1, 1) sub_grid.attach(create_separator(), 1, 4, 1, 1) sub_grid.attach(create_entry(make_double_widget(double_pass_label, double_pass_entry), 'Repeat the Passphrase', 'Annoying, we know. But this is for your own safety.'), 1, 5, 1, 1) sub_grid.attach(create_separator(), 1, 6, 1, 1) sub_grid.attach(create_entry(file_chooser, 'Repository Path', 'Where shall we create the new repository?'), 1, 7, 1, 1) frame = Gtk.Frame() frame.add(sub_grid) grid.attach(header, 0, 0, 1, 1) grid.attach(frame, 0, 1, 1, 1) grid.attach(footer, 0, 2, 1, 1) self.set_border_width(50) self.add(grid)