From 955986d024155e6c00a82e187a94b46d1507e66a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 11 Jul 2022 18:56:04 +0200 Subject: [PATCH] qt: PayToEdit and OverlayControlMixin: move around input buttons --- electrum/gui/icons/menu_vertical.png | Bin 0 -> 997 bytes electrum/gui/icons/menu_vertical_white.png | Bin 0 -> 5565 bytes electrum/gui/icons/picture_in_picture.png | Bin 0 -> 1519 bytes electrum/gui/qt/qrtextedit.py | 46 +++- electrum/gui/qt/transaction_dialog.py | 6 +- electrum/gui/qt/util.py | 246 ++++++++++++++------- 6 files changed, 211 insertions(+), 87 deletions(-) create mode 100644 electrum/gui/icons/menu_vertical.png create mode 100644 electrum/gui/icons/menu_vertical_white.png create mode 100644 electrum/gui/icons/picture_in_picture.png diff --git a/electrum/gui/icons/menu_vertical.png b/electrum/gui/icons/menu_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..c6329c2294b66a09f536e5ccaa57d6c1b451d0ba GIT binary patch literal 997 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 literal 0 HcmV?d00001 diff --git a/electrum/gui/icons/picture_in_picture.png b/electrum/gui/icons/picture_in_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..b000d3de0d5c7fa83f56fb62fb8bbb980f519e8e GIT binary patch literal 1519 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcalY)RhkE)4%caKYZ?lNlITmw383 zhE&XXd*^=k)l!+`AFuze-Z%U96|QCprzJ0yCbk%Gc&*yFG(*CzGsXPkR>?MT$9ktP z&c7H-r9&oU26#Lz5))$8oO*-*tgna8t{E$CpSyF;^1WWs;~mq^ZT#xq4zQy$tudxiL$sEt+Ppe9|OxwR>-Qjb7k3UYob~*m$lXvSqvgS{1 zbujds`DW(3=+ASfS6$f8cH#DWUD43^>A!_o1J`&gyBduJTlW)wNnFQUN{rS-F+hXO&5>+Hbsi z|HF6Jtb!{2=lt36?M|%Jsy%PtiPv7-uhB5?->I_c*Ti>oYqZUl`?+WRrnUd}bxPU& zEKSMO)Rq;S|Mu~%vU_`WEvoiB9k}wH?1B)jw*u<+>uQVdcYD}s z4lflvaCJ%8=iS$Ia_xS6w|4NoedJ`ngV(g&i7LB4%=|Au>6zL+7Lk{CJiPDC>)jS+ zee`61#k1|bVeilT{BwAv>cF*iR(YD*S`S+mj}%w6O7~Zy4pL>&YB!s%7&n;III7h# zxW#-n_T44N!J+c_iF*c1!vzk8g-=(qyA&;0?w{^AJ^1{NRW@4+E1l!#GjM%5{m}mF z?~|whE$6$ES;A1m-2zfA$l#>R(Bj6x(aRtp#i(GyP|e_X8)B-fz|l!=~czvdnd64_cL;pZL?ATZxnqw&w0{&uf1#< zQ*N@q7Qa=X=9yO&IZ1sUi^x&q)qB-qfB1fQb@c50nO_QT`8pV$`I>lr(KF|uyGsSa z*5*w%nUo_M(6i~&t8X8rg&9jqx5$`Z|D0*Yc+}wY^IiFwFK6$Ft-o-0?eg`TyqEDF z^=Fnkz2y6DvFEeT zv}Wy+^IWspOeW0y)*JfS6zIGUPm+WG**ZKgl9pNhjDPp-*}e1GO(yL%G?=pyT&kTEmA*@8hHI*4yvMh^IK8*C z&UMtb55{rQaZGn1!Io%hpdx~|!E8*^n2lQ}!5-!;kjRG0eg@r#JMjM( str: + return "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png" + + +def get_iconname_camera() -> str: + return "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png" + + class OverlayControlMixin: STYLE_SHEET_COMMON = ''' QPushButton { border-width: 1px; padding: 0px; margin: 0px; } @@ -916,13 +924,11 @@ class OverlayControlMixin: *, setText: Callable[[str], None] = None, ): - if setText is None: - setText = self.setText - def on_paste(): - app = QApplication.instance() - setText(app.clipboard().text()) - - self.addButton("copy.png", on_paste, _("Paste from clipboard")) + input_paste_from_clipboard = partial( + self.input_paste_from_clipboard, + setText=setText, + ) + self.addButton("copy.png", input_paste_from_clipboard, _("Paste from clipboard")) def add_qr_show_button(self, *, config: 'SimpleConfig', title: Optional[str] = None): if title is None: @@ -943,12 +949,44 @@ class OverlayControlMixin: config=config, ).exec_() - icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png" - self.addButton(icon, qr_show, _("Show as QR code")) + self.addButton(get_iconname_qrcode(), qr_show, _("Show as QR code")) # side-effect: we export this method: self.on_qr_show_btn = qr_show - def add_qr_input_button( + def add_qr_input_combined_button( + self, + *, + config: 'SimpleConfig', + allow_multi: bool = False, + show_error: Callable[[str], None], + setText: Callable[[str], None] = None, + ): + input_qr_from_camera = partial( + self.input_qr_from_camera, + config=config, + allow_multi=allow_multi, + show_error=show_error, + setText=setText, + ) + input_qr_from_screenshot = partial( + self.input_qr_from_screenshot, + allow_multi=allow_multi, + show_error=show_error, + setText=setText, + ) + self.add_menu_button( + icon=get_iconname_camera(), + tooltip=_("Read QR code"), + options=[ + (get_iconname_camera(), _("Read QR code from camera"), input_qr_from_camera), + ("picture_in_picture.png", _("Read QR code from screen"), input_qr_from_screenshot), + ], + ) + # side-effect: we export these methods: + self.on_qr_from_camera_input_btn = input_qr_from_camera + self.on_qr_from_screenshot_input_btn = input_qr_from_screenshot + + def add_qr_input_from_camera_button( self, *, config: 'SimpleConfig', @@ -956,90 +994,148 @@ class OverlayControlMixin: show_error: Callable[[str], None], setText: Callable[[str], None] = None, ): + input_qr_from_camera = partial( + self.input_qr_from_camera, + config=config, + allow_multi=allow_multi, + show_error=show_error, + setText=setText, + ) + self.addButton(get_iconname_camera(), input_qr_from_camera, _("Read QR code from camera")) + # side-effect: we export these methods: + self.on_qr_from_camera_input_btn = input_qr_from_camera + + def add_file_input_button( + self, + *, + config: 'SimpleConfig', + show_error: Callable[[str], None], + setText: Callable[[str], None] = None, + ) -> None: + input_file = partial( + self.input_file, + config=config, + show_error=show_error, + setText=setText, + ) + self.addButton("file.png", input_file, _("Read file")) + + def add_menu_button( + self, + *, + options: Sequence[Tuple[Optional[str], str, Callable[[], None]]], # list of (icon, text, cb) + icon: Optional[str] = None, + tooltip: Optional[str] = None, + ): + if icon is None: + icon = "menu_vertical_white.png" if ColorScheme.dark_scheme else "menu_vertical.png" + if tooltip is None: + tooltip = _("Other options") + btn = self.addButton(icon, lambda: None, tooltip) + menu = QMenu() + for opt_icon, opt_text, opt_cb in options: + if opt_icon is None: + menu.addAction(opt_text, opt_cb) + else: + menu.addAction(read_QIcon(opt_icon), opt_text, opt_cb) + btn.setMenu(menu) + + def input_qr_from_camera( + self, + *, + config: 'SimpleConfig', + allow_multi: bool = False, + show_error: Callable[[str], None], + setText: Callable[[str], None] = None, + ) -> None: if setText is None: setText = self.setText - def qr_from_camera_input() -> None: - def cb(success: bool, error: str, data): - if not success: - if error: - show_error(error) - return - if not data: - data = '' - if allow_multi: - new_text = self.text() + data + '\n' - else: - new_text = data - setText(new_text) - - from .qrreader import scan_qrcode - scan_qrcode(parent=self, config=config, callback=cb) - - def qr_from_screenshot_input() -> None: - from .qrreader import scan_qr_from_image - scanned_qr = None - for screen in QApplication.instance().screens(): - try: - scan_result = scan_qr_from_image(screen.grabWindow(0).toImage()) - except MissingQrDetectionLib as e: - show_error(_("Unable to scan image.") + "\n" + repr(e)) - return - if len(scan_result) > 0: - if (scanned_qr is not None) or len(scan_result) > 1: - show_error(_("More than one QR code was found on the screen.")) - return - scanned_qr = scan_result - if scanned_qr is None: - show_error(_("No QR code was found on the screen.")) + def cb(success: bool, error: str, data): + if not success: + if error: + show_error(error) return - data = scanned_qr[0].data + if not data: + data = '' if allow_multi: new_text = self.text() + data + '\n' else: new_text = data setText(new_text) - icon = "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png" - btn = self.addButton(icon, lambda: None, _("Read QR code")) - menu = QMenu() - menu.addAction(_("Read QR code from camera"), qr_from_camera_input) - menu.addAction(_("Read QR code from screen"), qr_from_screenshot_input) - btn.setMenu(menu) - # side-effect: we export these methods: - self.on_qr_from_camera_input_btn = qr_from_camera_input - self.on_qr_from_screenshot_input_btn = qr_from_screenshot_input + from .qrreader import scan_qrcode + scan_qrcode(parent=self, config=config, callback=cb) - def add_file_input_button( + def input_qr_from_screenshot( self, *, - config: 'SimpleConfig', + allow_multi: bool = False, show_error: Callable[[str], None], setText: Callable[[str], None] = None, ) -> None: if setText is None: setText = self.setText - def file_input(): - fileName = getOpenFileName( - parent=self, - title='select file', - config=config, - ) - if not fileName: + from .qrreader import scan_qr_from_image + scanned_qr = None + for screen in QApplication.instance().screens(): + try: + scan_result = scan_qr_from_image(screen.grabWindow(0).toImage()) + except MissingQrDetectionLib as e: + show_error(_("Unable to scan image.") + "\n" + repr(e)) return + if len(scan_result) > 0: + if (scanned_qr is not None) or len(scan_result) > 1: + show_error(_("More than one QR code was found on the screen.")) + return + scanned_qr = scan_result + if scanned_qr is None: + show_error(_("No QR code was found on the screen.")) + return + data = scanned_qr[0].data + if allow_multi: + new_text = self.text() + data + '\n' + else: + new_text = data + setText(new_text) + + def input_file( + self, + *, + config: 'SimpleConfig', + show_error: Callable[[str], None], + setText: Callable[[str], None] = None, + ) -> None: + if setText is None: + setText = self.setText + fileName = getOpenFileName( + parent=self, + title='select file', + config=config, + ) + if not fileName: + return + try: try: - try: - with open(fileName, "r") as f: - data = f.read() - except UnicodeError as e: - with open(fileName, "rb") as f: - data = f.read() - data = data.hex() - except BaseException as e: - show_error(_('Error opening file') + ':\n' + repr(e)) - else: - setText(data) + with open(fileName, "r") as f: + data = f.read() + except UnicodeError as e: + with open(fileName, "rb") as f: + data = f.read() + data = data.hex() + except BaseException as e: + show_error(_('Error opening file') + ':\n' + repr(e)) + else: + setText(data) - self.addButton("file.png", file_input, _("Read file")) + def input_paste_from_clipboard( + self, + *, + setText: Callable[[str], None] = None, + ) -> None: + if setText is None: + setText = self.setText + app = QApplication.instance() + setText(app.clipboard().text()) class ButtonsLineEdit(OverlayControlMixin, QLineEdit):