Loading ballontranslator/dl/inpaint/__init__.py +110 −1 Original line number Diff line number Diff line Loading @@ -307,3 +307,112 @@ class LamaInpainterMPE(InpainterBase): elif param_key == 'inpaint_size': self.inpaint_size = int(self.setup_params['inpaint_size']['select']) # LAMA_ORI: LamaFourier = None # @register_inpainter('lama_ori') # class LamaInpainterORI(InpainterBase): # setup_params = { # 'inpaint_size': { # 'type': 'selector', # 'options': [ # 1024, # 2048 # ], # 'select': 2048 # }, # 'device': { # 'type': 'selector', # 'options': [ # 'cpu', # 'cuda' # ], # 'select': DEFAULT_DEVICE # } # } # device = DEFAULT_DEVICE # inpaint_size = 2048 # def setup_inpainter(self): # global LAMA_ORI # self.device = self.setup_params['device']['select'] # if LAMA_ORI is None: # self.model = LAMA_ORI = load_lama_mpe(r'data/models/lama_org.ckpt', self.device, False) # else: # self.model = LAMA_ORI # self.model.to(self.device) # self.inpaint_by_block = True if self.device == 'cuda' else False # self.inpaint_size = int(self.setup_params['inpaint_size']['select']) # def inpaint_preprocess(self, img: np.ndarray, mask: np.ndarray) -> np.ndarray: # img_original = np.copy(img) # mask_original = np.copy(mask) # mask_original[mask_original < 127] = 0 # mask_original[mask_original >= 127] = 1 # mask_original = mask_original[:, :, None] # new_shape = self.inpaint_size if max(img.shape[0: 2]) > self.inpaint_size else None # # high resolution input could produce cloudy artifacts # img = resize_keepasp(img, new_shape, stride=64) # mask = resize_keepasp(mask, new_shape, stride=64) # im_h, im_w = img.shape[:2] # longer = max(im_h, im_w) # pad_bottom = longer - im_h if im_h < longer else 0 # pad_right = longer - im_w if im_w < longer else 0 # mask = cv2.copyMakeBorder(mask, 0, pad_bottom, 0, pad_right, cv2.BORDER_REFLECT) # img = cv2.copyMakeBorder(img, 0, pad_bottom, 0, pad_right, cv2.BORDER_REFLECT) # img_torch = torch.from_numpy(img).permute(2, 0, 1).unsqueeze_(0).float() / 255.0 # mask_torch = torch.from_numpy(mask).unsqueeze_(0).unsqueeze_(0).float() / 255.0 # mask_torch[mask_torch < 0.5] = 0 # mask_torch[mask_torch >= 0.5] = 1 # rel_pos, _, direct = self.model.load_masked_position_encoding(mask_torch[0][0].numpy()) # rel_pos = torch.LongTensor(rel_pos).unsqueeze_(0) # direct = torch.LongTensor(direct).unsqueeze_(0) # if self.device == 'cuda': # img_torch = img_torch.cuda() # mask_torch = mask_torch.cuda() # rel_pos = rel_pos.cuda() # direct = direct.cuda() # img_torch *= (1 - mask_torch) # return img_torch, mask_torch, rel_pos, direct, img_original, mask_original, pad_bottom, pad_right # @torch.no_grad() # def _inpaint(self, img: np.ndarray, mask: np.ndarray, textblock_list: List[TextBlock] = None) -> np.ndarray: # im_h, im_w = img.shape[:2] # img_torch, mask_torch, rel_pos, direct, img_original, mask_original, pad_bottom, pad_right = self.inpaint_preprocess(img, mask) # img_inpainted_torch = self.model(img_torch, mask_torch, rel_pos, direct) # img_inpainted = (img_inpainted_torch.cpu().squeeze_(0).permute(1, 2, 0).numpy() * 255).astype(np.uint8) # if pad_bottom > 0: # img_inpainted = img_inpainted[:-pad_bottom] # if pad_right > 0: # img_inpainted = img_inpainted[:, :-pad_right] # new_shape = img_inpainted.shape[:2] # if new_shape[0] != im_h or new_shape[1] != im_w : # img_inpainted = cv2.resize(img_inpainted, (im_w, im_h), interpolation = cv2.INTER_LINEAR) # img_inpainted = img_inpainted * mask_original + img_original * (1 - mask_original) # return img_inpainted # def updateParam(self, param_key: str, param_content): # super().updateParam(param_key, param_content) # if param_key == 'device': # param_device = self.setup_params['device']['select'] # self.model.to(param_device) # self.device = param_device # if param_device == 'cuda': # self.inpaint_by_block = False # else: # self.inpaint_by_block = True # elif param_key == 'inpaint_size': # self.inpaint_size = int(self.setup_params['inpaint_size']['select']) No newline at end of file ballontranslator/dl/inpaint/lama.py +4 −3 Original line number Diff line number Diff line Loading @@ -409,10 +409,11 @@ class LamaFourier: return rel_pos, abs_pos, direct def load_lama_mpe(model_path, device) -> LamaFourier: model = LamaFourier(build_discriminator=False, use_mpe=True) def load_lama_mpe(model_path, device, use_mpe=True) -> LamaFourier: model = LamaFourier(build_discriminator=False, use_mpe=use_mpe) sd = torch.load(model_path, map_location = 'cpu') model.generator.load_state_dict(sd['gen_state_dict']) if use_mpe: model.mpe.load_state_dict(sd['str_state_dict']) model.eval().to(device) return model No newline at end of file ballontranslator/ui/canvas.py +1 −2 Original line number Diff line number Diff line Loading @@ -416,4 +416,3 @@ class Canvas(QGraphicsScene): if item == self.stroke_path_item: self.stroke_path_item = None return super().removeItem(item) No newline at end of file No newline at end of file ballontranslator/ui/fontformatpanel.py +40 −10 Original line number Diff line number Diff line import functools from typing import List, Tuple, Union from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QFrame, QFontComboBox, QComboBox, QApplication, QPushButton, QRadioButton, QCheckBox from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QFrame, QFontComboBox, QComboBox, QApplication, QPushButton, QCheckBox from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QColor, QTextCharFormat, QIntValidator, QMouseEvent, QFont, QTextCursor, QTextFormat from .stylewidgets import Widget, ColorPicker, PaintQSlider from PyQt5.QtGui import QColor, QTextCharFormat, QIntValidator, QMouseEvent, QFont, QTextCursor from .stylewidgets import Widget, ColorPicker, PaintQSlider from .misc import FontFormat, set_html_color from .textitem import TextBlkItem, TextBlock from .canvas import Canvas # restore text cursor status after formatting Loading Loading @@ -111,18 +112,22 @@ def set_textblk_family(blkitem: TextBlkItem, cursor: QTextCursor, family: str): def set_textblk_linespacing(blkitem: TextBlkItem, cursor: QTextCursor, line_spacing: float): blkitem.setLineSpacing(line_spacing) class IncrementalBtn(QPushButton): pass class QFontChecker(QCheckBox): pass class AlignmentChecker(QCheckBox): def mousePressEvent(self, event: QMouseEvent) -> None: if self.isChecked(): return event.accept() return super().mousePressEvent(event) class AlignmentBtnGroup(QFrame): set_alignment = pyqtSignal(int) def __init__(self, *args, **kwargs): Loading Loading @@ -175,6 +180,7 @@ class AlignmentBtnGroup(QFrame): self.alignCenterChecker.setChecked(False) self.alignRightChecker.setChecked(True) class FormatGroupBtn(QFrame): set_bold = pyqtSignal(bool) set_italic = pyqtSignal(bool) Loading Loading @@ -205,6 +211,7 @@ class FormatGroupBtn(QFrame): def setUnderline(self): self.set_underline.emit(self.underlineBtn.isChecked()) class FontSizeBox(QFrame): fontsize_changed = pyqtSignal() def __init__(self, *args, **kwargs) -> None: Loading Loading @@ -270,6 +277,7 @@ class FontSizeBox(QFrame): self.btn_clicked = False return active class FontFormatPanel(Widget): textblk_item: TextBlkItem = None Loading @@ -280,9 +288,11 @@ class FontFormatPanel(Widget): global_format_changed = pyqtSignal() def __init__(self, app: QApplication, *args, **kwargs) -> None: def __init__(self, app: QApplication, canvas: Canvas, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.app = app self.canvas = canvas self.vlayout = QVBoxLayout(self) self.vlayout.setAlignment(Qt.AlignTop) self.familybox = QFontComboBox(self) Loading Loading @@ -324,11 +334,13 @@ class FontFormatPanel(Widget): self.strokeWidthSlider.setFixedHeight(50) self.strokeWidthSlider.setRange(0, 100) self.strokeWidthSlider.valueChanged.connect(self.onSrokeWidthChanged) self.strokeWidthSlider.mouse_released.connect(self.onStrokeSliderRealeased) self.lineSpacingSlider = PaintQSlider(self.tr("line spacing: ") + 'value%', Qt.Horizontal) self.lineSpacingSlider.setFixedHeight(50) self.lineSpacingSlider.setRange(0, 300) self.lineSpacingSlider.valueChanged.connect(self.onLinespacingChanged) self.lineSpacingSlider.mouse_released.connect(self.onLinespacingSliderReleased) hl1 = QHBoxLayout() hl1.addWidget(self.familybox) Loading Loading @@ -432,14 +444,32 @@ class FontFormatPanel(Widget): self.textblk_item.setVertical(self.active_format.vertical) def onSrokeWidthChanged(self): if len(self.canvas.selectedItems()) > 1 and self.strokeWidthSlider.pressed: return if self.strokeWidthSlider.pressed: self.active_format.stroke_width = self.strokeWidthSlider.value() / 100 self.update_stroke_width(self.strokeWidthSlider.value() / 100) def onStrokeSliderRealeased(self): if len(self.canvas.selectedItems()) > 1: self.update_stroke_width(self.strokeWidthSlider.value() / 100) def update_stroke_width(self, value: float): self.active_format.stroke_width = value self.restoreTextBlkItem() set_textblk_strokewidth(self.textblk_item, self.active_format.stroke_width) def onLinespacingChanged(self): if len(self.canvas.selectedItems()) > 1 and self.lineSpacingSlider.pressed: return if self.lineSpacingSlider.pressed: self.active_format.line_spacing = self.lineSpacingSlider.value() / 100 self.update_line_spacing(self.lineSpacingSlider.value() / 100) def onLinespacingSliderReleased(self): if len(self.canvas.selectedItems()) > 1: self.update_line_spacing(self.lineSpacingSlider.value() / 100) def update_line_spacing(self, value: float): self.active_format.line_spacing = value self.restoreTextBlkItem() set_textblk_linespacing(self.textblk_item, self.active_format.line_spacing) Loading ballontranslator/ui/imgtranspanel.py +8 −7 Original line number Diff line number Diff line from typing import List from PyQt5.QtWidgets import QSizePolicy, QLabel, QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QFrame, QFontComboBox, QColorDialog, QComboBox, QApplication, QPushButton, QRadioButton, QCheckBox from PyQt5.QtCore import pyqtSignal, Qt, QSize, QEvent, QObject from PyQt5.QtGui import QColor, QFocusEvent, QIntValidator, QMouseEvent, QFont, QTextCursor from .stylewidgets import Widget, SeparatorWidget, PaintQSlider from PyQt5.QtWidgets import QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QFrame, QApplication from PyQt5.QtCore import pyqtSignal, Qt, QSize, QEvent from PyQt5.QtGui import QColor, QFocusEvent from .stylewidgets import Widget, SeparatorWidget from typing import List from .textitem import TextBlock, TextBlkItem from .fontformatpanel import FontFormatPanel from .canvas import Canvas class SourceTextEdit(QTextEdit): hover_enter = pyqtSignal(int) Loading Loading @@ -116,12 +117,12 @@ class TextEditListScrollArea(QScrollArea): class TextPanel(Widget): def __init__(self, app: QApplication, *args, **kwargs) -> None: def __init__(self, app: QApplication, canvas: Canvas, *args, **kwargs) -> None: super().__init__(*args, **kwargs) layout = QVBoxLayout(self) self.textEditList = TextEditListScrollArea(self) self.activePair: TransPairWidget = None self.formatpanel = FontFormatPanel(app, self) self.formatpanel = FontFormatPanel(app, canvas, self) layout.addWidget(self.formatpanel) layout.addWidget(self.textEditList) layout.setContentsMargins(10, 0, 10, 0) Loading Loading
ballontranslator/dl/inpaint/__init__.py +110 −1 Original line number Diff line number Diff line Loading @@ -307,3 +307,112 @@ class LamaInpainterMPE(InpainterBase): elif param_key == 'inpaint_size': self.inpaint_size = int(self.setup_params['inpaint_size']['select']) # LAMA_ORI: LamaFourier = None # @register_inpainter('lama_ori') # class LamaInpainterORI(InpainterBase): # setup_params = { # 'inpaint_size': { # 'type': 'selector', # 'options': [ # 1024, # 2048 # ], # 'select': 2048 # }, # 'device': { # 'type': 'selector', # 'options': [ # 'cpu', # 'cuda' # ], # 'select': DEFAULT_DEVICE # } # } # device = DEFAULT_DEVICE # inpaint_size = 2048 # def setup_inpainter(self): # global LAMA_ORI # self.device = self.setup_params['device']['select'] # if LAMA_ORI is None: # self.model = LAMA_ORI = load_lama_mpe(r'data/models/lama_org.ckpt', self.device, False) # else: # self.model = LAMA_ORI # self.model.to(self.device) # self.inpaint_by_block = True if self.device == 'cuda' else False # self.inpaint_size = int(self.setup_params['inpaint_size']['select']) # def inpaint_preprocess(self, img: np.ndarray, mask: np.ndarray) -> np.ndarray: # img_original = np.copy(img) # mask_original = np.copy(mask) # mask_original[mask_original < 127] = 0 # mask_original[mask_original >= 127] = 1 # mask_original = mask_original[:, :, None] # new_shape = self.inpaint_size if max(img.shape[0: 2]) > self.inpaint_size else None # # high resolution input could produce cloudy artifacts # img = resize_keepasp(img, new_shape, stride=64) # mask = resize_keepasp(mask, new_shape, stride=64) # im_h, im_w = img.shape[:2] # longer = max(im_h, im_w) # pad_bottom = longer - im_h if im_h < longer else 0 # pad_right = longer - im_w if im_w < longer else 0 # mask = cv2.copyMakeBorder(mask, 0, pad_bottom, 0, pad_right, cv2.BORDER_REFLECT) # img = cv2.copyMakeBorder(img, 0, pad_bottom, 0, pad_right, cv2.BORDER_REFLECT) # img_torch = torch.from_numpy(img).permute(2, 0, 1).unsqueeze_(0).float() / 255.0 # mask_torch = torch.from_numpy(mask).unsqueeze_(0).unsqueeze_(0).float() / 255.0 # mask_torch[mask_torch < 0.5] = 0 # mask_torch[mask_torch >= 0.5] = 1 # rel_pos, _, direct = self.model.load_masked_position_encoding(mask_torch[0][0].numpy()) # rel_pos = torch.LongTensor(rel_pos).unsqueeze_(0) # direct = torch.LongTensor(direct).unsqueeze_(0) # if self.device == 'cuda': # img_torch = img_torch.cuda() # mask_torch = mask_torch.cuda() # rel_pos = rel_pos.cuda() # direct = direct.cuda() # img_torch *= (1 - mask_torch) # return img_torch, mask_torch, rel_pos, direct, img_original, mask_original, pad_bottom, pad_right # @torch.no_grad() # def _inpaint(self, img: np.ndarray, mask: np.ndarray, textblock_list: List[TextBlock] = None) -> np.ndarray: # im_h, im_w = img.shape[:2] # img_torch, mask_torch, rel_pos, direct, img_original, mask_original, pad_bottom, pad_right = self.inpaint_preprocess(img, mask) # img_inpainted_torch = self.model(img_torch, mask_torch, rel_pos, direct) # img_inpainted = (img_inpainted_torch.cpu().squeeze_(0).permute(1, 2, 0).numpy() * 255).astype(np.uint8) # if pad_bottom > 0: # img_inpainted = img_inpainted[:-pad_bottom] # if pad_right > 0: # img_inpainted = img_inpainted[:, :-pad_right] # new_shape = img_inpainted.shape[:2] # if new_shape[0] != im_h or new_shape[1] != im_w : # img_inpainted = cv2.resize(img_inpainted, (im_w, im_h), interpolation = cv2.INTER_LINEAR) # img_inpainted = img_inpainted * mask_original + img_original * (1 - mask_original) # return img_inpainted # def updateParam(self, param_key: str, param_content): # super().updateParam(param_key, param_content) # if param_key == 'device': # param_device = self.setup_params['device']['select'] # self.model.to(param_device) # self.device = param_device # if param_device == 'cuda': # self.inpaint_by_block = False # else: # self.inpaint_by_block = True # elif param_key == 'inpaint_size': # self.inpaint_size = int(self.setup_params['inpaint_size']['select']) No newline at end of file
ballontranslator/dl/inpaint/lama.py +4 −3 Original line number Diff line number Diff line Loading @@ -409,10 +409,11 @@ class LamaFourier: return rel_pos, abs_pos, direct def load_lama_mpe(model_path, device) -> LamaFourier: model = LamaFourier(build_discriminator=False, use_mpe=True) def load_lama_mpe(model_path, device, use_mpe=True) -> LamaFourier: model = LamaFourier(build_discriminator=False, use_mpe=use_mpe) sd = torch.load(model_path, map_location = 'cpu') model.generator.load_state_dict(sd['gen_state_dict']) if use_mpe: model.mpe.load_state_dict(sd['str_state_dict']) model.eval().to(device) return model No newline at end of file
ballontranslator/ui/canvas.py +1 −2 Original line number Diff line number Diff line Loading @@ -416,4 +416,3 @@ class Canvas(QGraphicsScene): if item == self.stroke_path_item: self.stroke_path_item = None return super().removeItem(item) No newline at end of file No newline at end of file
ballontranslator/ui/fontformatpanel.py +40 −10 Original line number Diff line number Diff line import functools from typing import List, Tuple, Union from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QFrame, QFontComboBox, QComboBox, QApplication, QPushButton, QRadioButton, QCheckBox from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QFrame, QFontComboBox, QComboBox, QApplication, QPushButton, QCheckBox from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QColor, QTextCharFormat, QIntValidator, QMouseEvent, QFont, QTextCursor, QTextFormat from .stylewidgets import Widget, ColorPicker, PaintQSlider from PyQt5.QtGui import QColor, QTextCharFormat, QIntValidator, QMouseEvent, QFont, QTextCursor from .stylewidgets import Widget, ColorPicker, PaintQSlider from .misc import FontFormat, set_html_color from .textitem import TextBlkItem, TextBlock from .canvas import Canvas # restore text cursor status after formatting Loading Loading @@ -111,18 +112,22 @@ def set_textblk_family(blkitem: TextBlkItem, cursor: QTextCursor, family: str): def set_textblk_linespacing(blkitem: TextBlkItem, cursor: QTextCursor, line_spacing: float): blkitem.setLineSpacing(line_spacing) class IncrementalBtn(QPushButton): pass class QFontChecker(QCheckBox): pass class AlignmentChecker(QCheckBox): def mousePressEvent(self, event: QMouseEvent) -> None: if self.isChecked(): return event.accept() return super().mousePressEvent(event) class AlignmentBtnGroup(QFrame): set_alignment = pyqtSignal(int) def __init__(self, *args, **kwargs): Loading Loading @@ -175,6 +180,7 @@ class AlignmentBtnGroup(QFrame): self.alignCenterChecker.setChecked(False) self.alignRightChecker.setChecked(True) class FormatGroupBtn(QFrame): set_bold = pyqtSignal(bool) set_italic = pyqtSignal(bool) Loading Loading @@ -205,6 +211,7 @@ class FormatGroupBtn(QFrame): def setUnderline(self): self.set_underline.emit(self.underlineBtn.isChecked()) class FontSizeBox(QFrame): fontsize_changed = pyqtSignal() def __init__(self, *args, **kwargs) -> None: Loading Loading @@ -270,6 +277,7 @@ class FontSizeBox(QFrame): self.btn_clicked = False return active class FontFormatPanel(Widget): textblk_item: TextBlkItem = None Loading @@ -280,9 +288,11 @@ class FontFormatPanel(Widget): global_format_changed = pyqtSignal() def __init__(self, app: QApplication, *args, **kwargs) -> None: def __init__(self, app: QApplication, canvas: Canvas, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.app = app self.canvas = canvas self.vlayout = QVBoxLayout(self) self.vlayout.setAlignment(Qt.AlignTop) self.familybox = QFontComboBox(self) Loading Loading @@ -324,11 +334,13 @@ class FontFormatPanel(Widget): self.strokeWidthSlider.setFixedHeight(50) self.strokeWidthSlider.setRange(0, 100) self.strokeWidthSlider.valueChanged.connect(self.onSrokeWidthChanged) self.strokeWidthSlider.mouse_released.connect(self.onStrokeSliderRealeased) self.lineSpacingSlider = PaintQSlider(self.tr("line spacing: ") + 'value%', Qt.Horizontal) self.lineSpacingSlider.setFixedHeight(50) self.lineSpacingSlider.setRange(0, 300) self.lineSpacingSlider.valueChanged.connect(self.onLinespacingChanged) self.lineSpacingSlider.mouse_released.connect(self.onLinespacingSliderReleased) hl1 = QHBoxLayout() hl1.addWidget(self.familybox) Loading Loading @@ -432,14 +444,32 @@ class FontFormatPanel(Widget): self.textblk_item.setVertical(self.active_format.vertical) def onSrokeWidthChanged(self): if len(self.canvas.selectedItems()) > 1 and self.strokeWidthSlider.pressed: return if self.strokeWidthSlider.pressed: self.active_format.stroke_width = self.strokeWidthSlider.value() / 100 self.update_stroke_width(self.strokeWidthSlider.value() / 100) def onStrokeSliderRealeased(self): if len(self.canvas.selectedItems()) > 1: self.update_stroke_width(self.strokeWidthSlider.value() / 100) def update_stroke_width(self, value: float): self.active_format.stroke_width = value self.restoreTextBlkItem() set_textblk_strokewidth(self.textblk_item, self.active_format.stroke_width) def onLinespacingChanged(self): if len(self.canvas.selectedItems()) > 1 and self.lineSpacingSlider.pressed: return if self.lineSpacingSlider.pressed: self.active_format.line_spacing = self.lineSpacingSlider.value() / 100 self.update_line_spacing(self.lineSpacingSlider.value() / 100) def onLinespacingSliderReleased(self): if len(self.canvas.selectedItems()) > 1: self.update_line_spacing(self.lineSpacingSlider.value() / 100) def update_line_spacing(self, value: float): self.active_format.line_spacing = value self.restoreTextBlkItem() set_textblk_linespacing(self.textblk_item, self.active_format.line_spacing) Loading
ballontranslator/ui/imgtranspanel.py +8 −7 Original line number Diff line number Diff line from typing import List from PyQt5.QtWidgets import QSizePolicy, QLabel, QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QFrame, QFontComboBox, QColorDialog, QComboBox, QApplication, QPushButton, QRadioButton, QCheckBox from PyQt5.QtCore import pyqtSignal, Qt, QSize, QEvent, QObject from PyQt5.QtGui import QColor, QFocusEvent, QIntValidator, QMouseEvent, QFont, QTextCursor from .stylewidgets import Widget, SeparatorWidget, PaintQSlider from PyQt5.QtWidgets import QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QFrame, QApplication from PyQt5.QtCore import pyqtSignal, Qt, QSize, QEvent from PyQt5.QtGui import QColor, QFocusEvent from .stylewidgets import Widget, SeparatorWidget from typing import List from .textitem import TextBlock, TextBlkItem from .fontformatpanel import FontFormatPanel from .canvas import Canvas class SourceTextEdit(QTextEdit): hover_enter = pyqtSignal(int) Loading Loading @@ -116,12 +117,12 @@ class TextEditListScrollArea(QScrollArea): class TextPanel(Widget): def __init__(self, app: QApplication, *args, **kwargs) -> None: def __init__(self, app: QApplication, canvas: Canvas, *args, **kwargs) -> None: super().__init__(*args, **kwargs) layout = QVBoxLayout(self) self.textEditList = TextEditListScrollArea(self) self.activePair: TransPairWidget = None self.formatpanel = FontFormatPanel(app, self) self.formatpanel = FontFormatPanel(app, canvas, self) layout.addWidget(self.formatpanel) layout.addWidget(self.textEditList) layout.setContentsMargins(10, 0, 10, 0) Loading