Merge Stable Changesto Master #9
							
								
								
									
										3
									
								
								plugins/searcher/mixins/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								plugins/searcher/mixins/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Mixins Module | ||||||
|  | """ | ||||||
							
								
								
									
										71
									
								
								plugins/searcher/mixins/file_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								plugins/searcher/mixins/file_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | # Python imports | ||||||
|  | import threading, subprocess, signal, time, json, shlex | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from ..widgets.file_preview_widget import FilePreviewWidget | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL die with parent's destruction. | ||||||
|  | def daemon_threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FileSearchMixin: | ||||||
|  |     def _run_find_file_query(self, widget=None, eve=None): | ||||||
|  |         self._handle_find_file_query(query=widget) | ||||||
|  |  | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _handle_find_file_query(self, widget=None, eve=None, query=None): | ||||||
|  |         # NOTE: Freeze IPC consumption | ||||||
|  |         self.pause_fifo_update = True | ||||||
|  |  | ||||||
|  |         # NOTE: Kill the former process | ||||||
|  |         if self._list_proc: | ||||||
|  |             if self._list_proc.poll(): | ||||||
|  |                 self._list_proc.send_signal(signal.SIGKILL) | ||||||
|  |                 while self._list_proc.poll(): | ||||||
|  |                     pass | ||||||
|  |  | ||||||
|  |                 self._list_proc = None | ||||||
|  |             else: | ||||||
|  |                 self._list_proc = None | ||||||
|  |  | ||||||
|  |         GLib.idle_add(self.clear_children, self._file_list) | ||||||
|  |         while len(self._file_list.get_children()) > 0: | ||||||
|  |             ... | ||||||
|  |  | ||||||
|  |         # NOTE: Make sure ui thread redraws | ||||||
|  |         time.sleep(0.5) | ||||||
|  |         self.pause_fifo_update = False | ||||||
|  |  | ||||||
|  |         # NOTE: If query create new process and do all new loop. | ||||||
|  |         if query: | ||||||
|  |             GLib.idle_add(self._exec_find_file_query, query) | ||||||
|  |  | ||||||
|  |     def _exec_find_file_query(self, widget=None, eve=None): | ||||||
|  |         query = widget.get_text() | ||||||
|  |  | ||||||
|  |         if not query in ("", None): | ||||||
|  |             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) | ||||||
|  |             command = ["python", f"{self.path}/utils/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] | ||||||
|  |             self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) | ||||||
|  |  | ||||||
|  |     def _load_file_ui(self, data): | ||||||
|  |         if not data in ("", None) and not self.pause_fifo_update: | ||||||
|  |             jdata  = json.loads( data ) | ||||||
|  |             target = jdata[0] | ||||||
|  |             file   = jdata[1] | ||||||
|  |  | ||||||
|  |             widget = FilePreviewWidget(target, file) | ||||||
|  |             self._file_list.add(widget) | ||||||
							
								
								
									
										73
									
								
								plugins/searcher/mixins/grep_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								plugins/searcher/mixins/grep_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | # Python imports | ||||||
|  | import threading, subprocess, signal, time, json, shlex | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from ..widgets.grep_preview_widget import GrepPreviewWidget | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL die with parent's destruction. | ||||||
|  | def daemon_threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GrepSearchMixin: | ||||||
|  |     def _run_grep_query(self, widget=None, eve=None): | ||||||
|  |         self._handle_grep_query(query=widget) | ||||||
|  |  | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _handle_grep_query(self, widget=None, eve=None, query=None): | ||||||
|  |         # NOTE: Freeze IPC consumption | ||||||
|  |         self.pause_fifo_update = True | ||||||
|  |  | ||||||
|  |         # NOTE: Kill the former process | ||||||
|  |         if self._grep_proc: | ||||||
|  |             if self._grep_proc.poll(): | ||||||
|  |                 self._grep_proc.send_signal(signal.SIGKILL) | ||||||
|  |                 while self._grep_proc.poll(): | ||||||
|  |                     pass | ||||||
|  |  | ||||||
|  |                 self._grep_proc = None | ||||||
|  |             else: | ||||||
|  |                 self._grep_proc = None | ||||||
|  |  | ||||||
|  |         # NOTE: Clear children from ui | ||||||
|  |         GLib.idle_add(self.clear_children, self._grep_list) | ||||||
|  |         while len(self._grep_list.get_children()) > 0: | ||||||
|  |             ... | ||||||
|  |  | ||||||
|  |         # NOTE: Make sure ui thread redraws | ||||||
|  |         time.sleep(0.5) | ||||||
|  |         self.pause_fifo_update = False | ||||||
|  |  | ||||||
|  |         # NOTE: If query create new process and do all new loop. | ||||||
|  |         if query: | ||||||
|  |             GLib.idle_add(self._exec_grep_query, query) | ||||||
|  |  | ||||||
|  |     def _exec_grep_query(self, widget=None, eve=None): | ||||||
|  |         query = widget.get_text() | ||||||
|  |         if not query in ("", None): | ||||||
|  |             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) | ||||||
|  |             command = ["python", f"{self.path}/utils/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] | ||||||
|  |             self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) | ||||||
|  |  | ||||||
|  |     def _load_grep_ui(self, data): | ||||||
|  |         if not data in ("", None) and not self.pause_fifo_update: | ||||||
|  |             jdata = json.loads( data ) | ||||||
|  |             jkeys = jdata.keys() | ||||||
|  |             for key in jkeys: | ||||||
|  |                 sub_keys    = jdata[key].keys() | ||||||
|  |                 grep_result = jdata[key] | ||||||
|  |  | ||||||
|  |                 widget = GrepPreviewWidget(key, sub_keys, grep_result) | ||||||
|  |                 self._grep_list.add(widget) | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, threading, subprocess, inspect, time, json, base64, shlex, select, signal | import os, threading, inspect | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| @@ -8,7 +8,9 @@ from gi.repository import Gtk, GLib | |||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from plugins.plugin_base import PluginBase | from plugins.plugin_base import PluginBase | ||||||
| from .ipc_server import IPCServer | from .mixins.file_search_mixin import FileSearchMixin | ||||||
|  | from .mixins.grep_search_mixin import GrepSearchMixin | ||||||
|  | from .utils.ipc_server import IPCServer | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -27,54 +29,7 @@ def daemon_threaded(fn): | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FilePreviewWidget(Gtk.LinkButton): | class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): | ||||||
|     def __init__(self, path, file): |  | ||||||
|         super(FilePreviewWidget, self).__init__() |  | ||||||
|         self.set_label(file) |  | ||||||
|         self.set_uri(f"file://{path}") |  | ||||||
|         self.show_all() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrepPreviewWidget(Gtk.Box): |  | ||||||
|     def __init__(self, _path, sub_keys, data): |  | ||||||
|         super(GrepPreviewWidget, self).__init__() |  | ||||||
|         self.set_orientation(Gtk.Orientation.VERTICAL) |  | ||||||
|         self.line_color = "#e0cc64" |  | ||||||
|  |  | ||||||
|         path   = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') |  | ||||||
|         _label = '/'.join( path.split("/")[-3:] ) |  | ||||||
|         title  = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) |  | ||||||
|  |  | ||||||
|         self.add(title) |  | ||||||
|         for key in sub_keys: |  | ||||||
|             line_num     = key |  | ||||||
|             text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             box          = Gtk.Box() |  | ||||||
|             number_label = Gtk.Label() |  | ||||||
|             text_view    = Gtk.Label(label=text[:-1]) |  | ||||||
|             label_text   = f"<span foreground='{self.line_color}'>{line_num}</span>" |  | ||||||
|  |  | ||||||
|             number_label.set_markup(label_text) |  | ||||||
|             number_label.set_margin_left(15) |  | ||||||
|             number_label.set_margin_right(5) |  | ||||||
|             number_label.set_margin_top(5) |  | ||||||
|             number_label.set_margin_bottom(5) |  | ||||||
|             text_view.set_margin_top(5) |  | ||||||
|             text_view.set_margin_bottom(5) |  | ||||||
|             text_view.set_line_wrap(True) |  | ||||||
|  |  | ||||||
|             box.add(number_label) |  | ||||||
|             box.add(text_view) |  | ||||||
|             self.add(box) |  | ||||||
|  |  | ||||||
|         self.show_all() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| pause_fifo_update = False |  | ||||||
|  |  | ||||||
| class Plugin(IPCServer, PluginBase): |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|  |  | ||||||
| @@ -89,11 +44,13 @@ class Plugin(IPCServer, PluginBase): | |||||||
|         self._grep_list        = None |         self._grep_list        = None | ||||||
|         self._grep_proc        = None |         self._grep_proc        = None | ||||||
|         self._list_proc        = None |         self._list_proc        = None | ||||||
|  |         self.pause_fifo_update = False | ||||||
|  |         self.update_list_ui_buffer = () | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_ui_element(self): |     def get_ui_element(self): | ||||||
|         button = Gtk.Button(label=self.name) |         button = Gtk.Button(label=self.name) | ||||||
|         button.connect("button-release-event", self._show_grep_list_page) |         button.connect("button-release-event", self._show_page) | ||||||
|         return button |         return button | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
| @@ -123,7 +80,7 @@ class Plugin(IPCServer, PluginBase): | |||||||
|         self.create_ipc_listener() |         self.create_ipc_listener() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _show_grep_list_page(self, widget=None, eve=None): |     def _show_page(self, widget=None, eve=None): | ||||||
|         self._event_system.emit("get_current_state") |         self._event_system.emit("get_current_state") | ||||||
|  |  | ||||||
|         state               = self._fm_state |         state               = self._fm_state | ||||||
| @@ -134,74 +91,7 @@ class Plugin(IPCServer, PluginBase): | |||||||
|         self._search_dialog.hide() |         self._search_dialog.hide() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _run_find_file_query(self, widget=None, eve=None): |     def clear_children(self, widget: type) -> None: | ||||||
|         self._stop_find_file_query() |         ''' Clear children of a gtk widget. ''' | ||||||
|  |         for child in widget.get_children(): | ||||||
|         query = widget.get_text() |             widget.remove(child) | ||||||
|         if not query in ("", None): |  | ||||||
|             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) |  | ||||||
|             command = ["python", f"{self.path}/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] |  | ||||||
|             process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) |  | ||||||
|  |  | ||||||
|     def _stop_find_file_query(self, widget=None, eve=None): |  | ||||||
|         pause_fifo_update = True |  | ||||||
|  |  | ||||||
|         if self._list_proc: |  | ||||||
|             if self._list_proc.poll(): |  | ||||||
|                 self._list_proc.send_signal(signal.SIGKILL) |  | ||||||
|                 while self._list_proc.poll(): |  | ||||||
|                     pass |  | ||||||
|  |  | ||||||
|                 self._list_proc = None |  | ||||||
|             else: |  | ||||||
|                 self._list_proc = None |  | ||||||
|  |  | ||||||
|         self.clear_children(self._file_list) |  | ||||||
|         pause_fifo_update = False |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _run_grep_query(self, widget=None, eve=None): |  | ||||||
|         self._stop_grep_query() |  | ||||||
|  |  | ||||||
|         query = widget.get_text() |  | ||||||
|         if not query in ("", None): |  | ||||||
|             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) |  | ||||||
|             command = ["python", f"{self.path}/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] |  | ||||||
|             process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) |  | ||||||
|  |  | ||||||
|     def _stop_grep_query(self, widget=None, eve=None): |  | ||||||
|         pause_fifo_update = True |  | ||||||
|  |  | ||||||
|         if self._grep_proc: |  | ||||||
|             if self._grep_proc.poll(): |  | ||||||
|                 self._grep_proc.send_signal(signal.SIGKILL) |  | ||||||
|                 while self._grep_proc.poll(): |  | ||||||
|                     pass |  | ||||||
|  |  | ||||||
|                 self._grep_proc = None |  | ||||||
|             else: |  | ||||||
|                 self._grep_proc = None |  | ||||||
|  |  | ||||||
|         self.clear_children(self._grep_list) |  | ||||||
|         pause_fifo_update = False |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _load_file_ui(self, data): |  | ||||||
|         if not data in ("", None) and not pause_fifo_update: |  | ||||||
|             jdata  = json.loads( data ) |  | ||||||
|             target = jdata[0] |  | ||||||
|             file   = jdata[1] |  | ||||||
|  |  | ||||||
|             widget = FilePreviewWidget(target, file) |  | ||||||
|             self._file_list.add(widget) |  | ||||||
|  |  | ||||||
|     def _load_grep_ui(self, data): |  | ||||||
|         if not data in ("", None) and not pause_fifo_update: |  | ||||||
|             jdata = json.loads( data ) |  | ||||||
|             jkeys = jdata.keys() |  | ||||||
|             for key in jkeys: |  | ||||||
|                 sub_keys    = jdata[key].keys() |  | ||||||
|                 grep_result = jdata[key] |  | ||||||
|  |  | ||||||
|                 widget = GrepPreviewWidget(key, sub_keys, grep_result) |  | ||||||
|                 self._grep_list.add(widget) |  | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ | |||||||
|                     <property name="primary-icon-sensitive">False</property> |                     <property name="primary-icon-sensitive">False</property> | ||||||
|                     <property name="placeholder-text" translatable="yes">Search for file...</property> |                     <property name="placeholder-text" translatable="yes">Search for file...</property> | ||||||
|                     <signal name="search-changed" handler="_run_find_file_query" swapped="no"/> |                     <signal name="search-changed" handler="_run_find_file_query" swapped="no"/> | ||||||
|                     <signal name="stop-search" handler="_stop_find_file_query" swapped="no"/> |                     <signal name="stop-search" handler="_handle_find_file_query" swapped="no"/> | ||||||
|                   </object> |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="expand">False</property> |                     <property name="expand">False</property> | ||||||
| @@ -150,7 +150,7 @@ | |||||||
|                     <property name="primary-icon-sensitive">False</property> |                     <property name="primary-icon-sensitive">False</property> | ||||||
|                     <property name="placeholder-text" translatable="yes">Query string in file...</property> |                     <property name="placeholder-text" translatable="yes">Query string in file...</property> | ||||||
|                     <signal name="search-changed" handler="_run_grep_query" swapped="no"/> |                     <signal name="search-changed" handler="_run_grep_query" swapped="no"/> | ||||||
|                     <signal name="stop-search" handler="_stop_grep_query" swapped="no"/> |                     <signal name="stop-search" handler="_handle_grep_query" swapped="no"/> | ||||||
|                   </object> |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="expand">False</property> |                     <property name="expand">False</property> | ||||||
|   | |||||||
| @@ -57,8 +57,8 @@ class IPCServer: | |||||||
|     def handle_message(self, conn, start_time) -> None: |     def handle_message(self, conn, start_time) -> None: | ||||||
|         while True: |         while True: | ||||||
|             msg  = conn.recv() |             msg  = conn.recv() | ||||||
|             data = msg |  | ||||||
| 
 | 
 | ||||||
|  |             if not self.pause_fifo_update: | ||||||
|                 if "SEARCH|" in msg: |                 if "SEARCH|" in msg: | ||||||
|                     file = msg.split("SEARCH|")[1].strip() |                     file = msg.split("SEARCH|")[1].strip() | ||||||
|                     if file: |                     if file: | ||||||
| @@ -85,6 +85,9 @@ class IPCServer: | |||||||
|                 if (end_time - start_time) > self._ipc_timeout: |                 if (end_time - start_time) > self._ipc_timeout: | ||||||
|                     conn.close() |                     conn.close() | ||||||
|                     break |                     break | ||||||
|  |             else: | ||||||
|  |                 conn.close() | ||||||
|  |                 break | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def send_ipc_message(self, message: str = "Empty Data...") -> None: |     def send_ipc_message(self, message: str = "Empty Data...") -> None: | ||||||
							
								
								
									
										3
									
								
								plugins/searcher/widgets/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								plugins/searcher/widgets/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Widgets Module | ||||||
|  | """ | ||||||
							
								
								
									
										16
									
								
								plugins/searcher/widgets/file_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								plugins/searcher/widgets/file_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Gtk imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FilePreviewWidget(Gtk.LinkButton): | ||||||
|  |     def __init__(self, path, file): | ||||||
|  |         super(FilePreviewWidget, self).__init__() | ||||||
|  |         self.set_label(file) | ||||||
|  |         self.set_uri(f"file://{path}") | ||||||
|  |         self.show_all() | ||||||
							
								
								
									
										46
									
								
								plugins/searcher/widgets/grep_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								plugins/searcher/widgets/grep_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | # Python imports | ||||||
|  | import base64 | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GrepPreviewWidget(Gtk.Box): | ||||||
|  |     def __init__(self, _path, sub_keys, data): | ||||||
|  |         super(GrepPreviewWidget, self).__init__() | ||||||
|  |         self.set_orientation(Gtk.Orientation.VERTICAL) | ||||||
|  |         self.line_color = "#e0cc64" | ||||||
|  |  | ||||||
|  |         path   = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') | ||||||
|  |         _label = '/'.join( path.split("/")[-3:] ) | ||||||
|  |         title  = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) | ||||||
|  |  | ||||||
|  |         self.add(title) | ||||||
|  |         for key in sub_keys: | ||||||
|  |             line_num     = key | ||||||
|  |             text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             box          = Gtk.Box() | ||||||
|  |             number_label = Gtk.Label() | ||||||
|  |             text_view    = Gtk.Label(label=text[:-1]) | ||||||
|  |             label_text   = f"<span foreground='{self.line_color}'>{line_num}</span>" | ||||||
|  |  | ||||||
|  |             number_label.set_markup(label_text) | ||||||
|  |             number_label.set_margin_left(15) | ||||||
|  |             number_label.set_margin_right(5) | ||||||
|  |             number_label.set_margin_top(5) | ||||||
|  |             number_label.set_margin_bottom(5) | ||||||
|  |             text_view.set_margin_top(5) | ||||||
|  |             text_view.set_margin_bottom(5) | ||||||
|  |             text_view.set_line_wrap(True) | ||||||
|  |  | ||||||
|  |             box.add(number_label) | ||||||
|  |             box.add(text_view) | ||||||
|  |             self.add(box) | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
		Reference in New Issue
	
	Block a user