diff --git a/CHANGELOG b/CHANGELOG
index 356e88d5..f7ecc75c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,11 @@
# Changelog
+## v0.6.0 (upcoming)
+
+- All: Breaking: Commandline argument `--mode {parse,raw}` is removed in favor of a new
+argument `--parse-text {True, False}`.
+
## v0.5.9 (2024-11-10)
- All: Add Chinese translation. Thanks, [@mofazhe](https://github.com/mofazhe)! ([#661](https://github.com/dynobo/normcap/pull/661))
diff --git a/docs/usage.md b/docs/usage.md
index 0f31cfa9..ae04758c 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -31,6 +31,7 @@ hide:
- The icons ★ or ☰ next to the selection-rectangle indicate the active "capture mode" (see below).
- To abort a capture or quit NormCap press ``
+
## Capture Modes
The settings menu ⚙ allows switching between the two capture modes: "parse" and "raw":
diff --git a/normcap/gui/menu_button.py b/normcap/gui/menu_button.py
index ecd34421..7acb0544 100644
--- a/normcap/gui/menu_button.py
+++ b/normcap/gui/menu_button.py
@@ -171,14 +171,10 @@ def on_item_click(self, action: QtGui.QAction) -> None:
return
# Menu items which change settings
-
- if group_name == "settings_group":
+ if group_name in ["settings_group", "detection_group"]:
setting = action_name
value = action.isChecked()
- elif group_name == "mode_group":
- setting = "mode"
- value = action_name
- elif group_name == "language_group":
+ if group_name == "language_group":
setting = "language"
languages = [a.objectName() for a in group.actions() if a.isChecked()]
if not languages:
@@ -200,8 +196,8 @@ def populate_menu_entries(self) -> None:
self._add_settings_section(menu)
menu.addSeparator()
# L10N: Section title in Main Menu
- self._add_title(menu, _("Capture mode"))
- self._add_mode_section(menu)
+ self._add_title(menu, _("Detection"))
+ self._add_detection_section(menu)
menu.addSeparator()
# L10N: Section title in Main Menu
self._add_title(menu, _("Languages"))
@@ -272,37 +268,24 @@ def _add_settings_section(self, menu: QtWidgets.QMenu) -> None:
)
menu.addAction(action)
- def _add_mode_section(self, menu: QtWidgets.QMenu) -> None:
- mode_group = QtGui.QActionGroup(menu)
- mode_group.setObjectName("mode_group")
- mode_group.setExclusive(True)
+ def _add_detection_section(self, menu: QtWidgets.QMenu) -> None:
+ detection_group = QtGui.QActionGroup(menu)
+ detection_group.setObjectName("detection_group")
+ detection_group.setExclusive(False)
- # L10N: Entry in main menu's 'Capture mode' section
- action = QtGui.QAction(_("parse"), mode_group)
- action.setObjectName("parse")
+ # L10N: Entry in main menu's 'Detection' section
+ action = QtGui.QAction(_("Parse text"), detection_group)
+ action.setObjectName("parse-text")
action.setCheckable(True)
- action.setChecked(self.settings.value("mode") == "parse")
- # L10N: Tooltip of main menu's 'parse' entry. Use <56 chars p. line.
+ action.setChecked(bool(self.settings.value("parse-text")))
+ # L10N: Tooltip of main menu's 'parse text' entry. Use <56 chars p. line.
action.setToolTip(
_(
"Tries to determine the text's type (e.g. line,\n"
"paragraph, URL, email) and formats the output\n"
"accordingly.\n"
- "If the result is unexpected, try 'raw' mode instead."
- )
- )
- menu.addAction(action)
-
- # L10N: Entry in main menu's 'Capture mode' section
- action = QtGui.QAction(_("raw"), mode_group)
- action.setObjectName("raw")
- action.setCheckable(True)
- action.setChecked(self.settings.value("mode") == "raw")
- # L10N: Tooltip of main menu's 'raw' entry. Use <56 chars p. line.
- action.setToolTip(
- _(
- "Returns the text exactly as detected by the Optical\n"
- "Character Recognition Software."
+ "Turn it off to return the text exactly as detected\n"
+ "by the Optical Character Recognition Software."
)
)
menu.addAction(action)
diff --git a/normcap/gui/models.py b/normcap/gui/models.py
index 7565a3ef..711dcd71 100644
--- a/normcap/gui/models.py
+++ b/normcap/gui/models.py
@@ -40,13 +40,6 @@ class DesktopEnvironment(enum.IntEnum):
AWESOME = enum.auto()
-class CaptureMode(enum.IntEnum):
- """Available transformation modes."""
-
- RAW = enum.auto()
- PARSE = enum.auto()
-
-
@dataclass
class Urls:
"""URLs used on various places."""
@@ -153,7 +146,7 @@ def scale(self, factor: Optional[float] = None): # noqa: ANN201
class Capture:
"""Store all information like screenshot and selected region."""
- mode: CaptureMode = CaptureMode.PARSE
+ parse_text: bool = True
# Image of selected region
image: QtGui.QImage = field(default_factory=QtGui.QImage)
diff --git a/normcap/gui/notification.py b/normcap/gui/notification.py
index fe4a542b..f8b6a3de 100644
--- a/normcap/gui/notification.py
+++ b/normcap/gui/notification.py
@@ -13,7 +13,7 @@
from normcap import ocr
from normcap.gui import system_info
from normcap.gui.localization import _, translate
-from normcap.gui.models import Capture, CaptureMode
+from normcap.gui.models import Capture
logger = logging.getLogger(__name__)
@@ -85,7 +85,7 @@ def _compose_notification(capture: Capture) -> tuple[str, str]:
title = translate.ngettext(
"1 URL captured", "{count} URLs captured", count
).format(count=count)
- elif capture.mode == CaptureMode.RAW:
+ elif capture.parse_text:
count = len(capture.ocr_text)
# Count linesep only as single char:
count -= (len(os.linesep) - 1) * capture.ocr_text.count(os.linesep)
diff --git a/normcap/gui/settings.py b/normcap/gui/settings.py
index 44e72f60..1b18900f 100644
--- a/normcap/gui/settings.py
+++ b/normcap/gui/settings.py
@@ -41,12 +41,15 @@ def _parse_str_to_bool(string: str) -> bool:
nargs="+",
),
Setting(
- key="mode",
- flag="m",
- type_=str,
- value="parse",
- help_="Set capture mode",
- choices=("raw", "parse"),
+ key="parse-text",
+ flag="p",
+ type_=_parse_str_to_bool,
+ value=True,
+ help_=(
+ "Try to determine the text's type (e.g. line, paragraph, URL, email) and "
+ "format the output accordingly."
+ ),
+ choices=(True, False),
cli_arg=True,
nargs=None,
),
@@ -145,10 +148,19 @@ def __init__(
self._prepare_and_sync()
def _prepare_and_sync(self) -> None:
+ self._migrate_deprecated()
self._set_missing_to_default()
self._update_from_init_settings()
self.sync()
+ def _migrate_deprecated(self) -> None:
+ # Migrations to v0.6.0
+ # ONHOLD: Delete in 2025/11
+ if self.value("mode", None):
+ mode = self.value("mode")
+ self.setValue("parse-text", mode == "parse")
+ self.remoce("mode")
+
def _set_missing_to_default(self) -> None:
for d in self.default_settings:
key, value = d.key, d.value
diff --git a/normcap/gui/tray.py b/normcap/gui/tray.py
index f3a8083e..ab4a9d03 100644
--- a/normcap/gui/tray.py
+++ b/normcap/gui/tray.py
@@ -26,7 +26,7 @@
from normcap.gui.language_manager import LanguageManager
from normcap.gui.localization import _
from normcap.gui.menu_button import MenuButton
-from normcap.gui.models import Capture, CaptureMode, Days, Rect, Screen, Seconds
+from normcap.gui.models import Capture, Days, Rect, Screen, Seconds
from normcap.gui.notification import Notifier
from normcap.gui.settings import Settings
from normcap.gui.update_check import UpdateChecker
@@ -246,7 +246,7 @@ def _crop_image(self, grab_info: tuple[Rect, int]) -> None:
if not screenshot:
raise TypeError("Screenshot is None!")
- self.capture.mode = CaptureMode[str(self.settings.value("mode")).upper()]
+ self.capture.parse_text = bool(self.settings.value("parse-text"))
self.capture.rect = rect
self.capture.screen = self.screens[screen_idx]
self.capture.image = screenshot.copy(QtCore.QRect(*rect.geometry))
@@ -273,7 +273,7 @@ def _capture_to_ocr(self) -> None:
languages=language,
image=self.capture.image,
tessdata_path=system_info.get_tessdata_path(),
- parse=self.capture.mode is CaptureMode.PARSE,
+ parse=self.capture.parse_text,
resize_factor=2,
padding_size=80,
)
diff --git a/normcap/gui/window.py b/normcap/gui/window.py
index c57bc749..8ee8f51a 100644
--- a/normcap/gui/window.py
+++ b/normcap/gui/window.py
@@ -17,7 +17,7 @@
from PySide6 import QtCore, QtGui, QtWidgets
from normcap.gui import dbus, system_info
-from normcap.gui.models import CaptureMode, DesktopEnvironment, Rect, Screen
+from normcap.gui.models import DesktopEnvironment, Rect, Screen
from normcap.gui.settings import Settings
logger = logging.getLogger(__name__)
@@ -87,7 +87,9 @@ def _add_image_container(self) -> None:
def _add_ui_container(self) -> None:
"""Add widget for showing selection rectangle and settings button."""
self.ui_container = UiContainerLabel(
- parent=self, color=self.color, capture_mode_func=self.get_capture_mode
+ parent=self,
+ color=self.color,
+ parse_text_func=lambda: bool(self.settings.value("parse-text")),
)
if logger.getEffectiveLevel() is logging.DEBUG:
@@ -183,16 +185,6 @@ def clear_selection(self) -> None:
self.ui_container.rect = self.selection_rect
self.update()
- def get_capture_mode(self) -> CaptureMode:
- """Read current capture mode from application settings."""
- mode_setting = str(self.settings.value("mode"))
- try:
- mode = CaptureMode[mode_setting.upper()]
- except KeyError:
- logger.warning("Unknown capture mode: %s. Fallback to PARSE.", mode_setting)
- mode = CaptureMode.PARSE
- return mode
-
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa: N802
"""Handle ESC key pressed.
@@ -270,7 +262,7 @@ def __init__(
self,
parent: QtWidgets.QWidget,
color: QtGui.QColor,
- capture_mode_func: Callable,
+ parse_text_func: Callable,
) -> None:
super().__init__(parent)
@@ -280,7 +272,7 @@ def __init__(
self.rect: QtCore.QRect = QtCore.QRect()
self.rect_pen = QtGui.QPen(self.color, 2, QtCore.Qt.PenStyle.DashLine)
- self.get_capture_mode = capture_mode_func
+ self.get_parse_text = parse_text_func
self.setObjectName("ui_container")
self.setStyleSheet(f"#ui_container {{border: 3px solid {self.color.name()};}}")
@@ -349,10 +341,12 @@ def paintEvent(self, event: QtGui.QPaintEvent) -> None: # noqa: N802
painter.setPen(self.rect_pen)
painter.drawRect(self.rect)
- if self.get_capture_mode() is CaptureMode.PARSE:
- mode_icon = QtGui.QIcon(":parse")
+ if self.get_parse_text():
+ selection_icon = QtGui.QIcon(":parse")
else:
- mode_icon = QtGui.QIcon(":raw")
- mode_icon.paint(painter, self.rect.right() - 24, self.rect.top() - 30, 24, 24)
+ selection_icon = QtGui.QIcon(":raw")
+ selection_icon.paint(
+ painter, self.rect.right() - 24, self.rect.top() - 30, 24, 24
+ )
painter.end()
diff --git a/normcap/ocr/structures.py b/normcap/ocr/structures.py
index eae45915..27f20ef0 100644
--- a/normcap/ocr/structures.py
+++ b/normcap/ocr/structures.py
@@ -123,7 +123,7 @@ def text(self) -> str:
"""Provides the resulting text of the OCR.
If parsed text (compiled by a transformer) is available, return that one,
- otherwise fallback to "raw".
+ otherwise fallback to un-parseds.
"""
return self.parsed or self.add_linebreaks()
diff --git a/normcap/resources/locales/README.md b/normcap/resources/locales/README.md
index d1f61c4c..af5289bd 100644
--- a/normcap/resources/locales/README.md
+++ b/normcap/resources/locales/README.md
@@ -20,6 +20,7 @@
| [fr_FR](./fr_FR/LC_MESSAGES/messages.po) | 100% | 68 of 68 |
| [hi_IN](./hi_IN/LC_MESSAGES/messages.po) | 8% | 6 of 68 |
| [it_IT](./it_IT/LC_MESSAGES/messages.po) | 100% | 68 of 68 |
+| [ja_JP](./ja_JP/LC_MESSAGES/messages.po) | 100% | 68 of 68 |
| [pl_PL](./pl_PL/LC_MESSAGES/messages.po) | 8% | 6 of 68 |
| [pt_BR](./pt_BR/LC_MESSAGES/messages.po) | 100% | 68 of 68 |
| [pt_PT](./pt_PT/LC_MESSAGES/messages.po) | 100% | 68 of 68 |
diff --git a/normcap/resources/locales/messages.pot b/normcap/resources/locales/messages.pot
index a37abcca..389b41a3 100644
--- a/normcap/resources/locales/messages.pot
+++ b/normcap/resources/locales/messages.pot
@@ -421,4 +421,3 @@ msgid ""
"\n"
"Do you want to view the changelog on GitHub now?"
msgstr ""
-
diff --git a/tests/conftest.py b/tests/conftest.py
index 4727dd36..6c4f7872 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -14,7 +14,7 @@
from normcap import app
from normcap.clipboard import system_info as clipboard_system_info
from normcap.gui import menu_button, system_info
-from normcap.gui.models import Capture, CaptureMode, Rect
+from normcap.gui.models import Capture, Rect
from normcap.ocr.structures import OEM, PSM, OcrResult, TessArgs
from normcap.ocr.transformers import email, url
from normcap.screengrab import system_info as screengrab_system_info
@@ -73,7 +73,7 @@ def capture() -> Capture:
image.fill(QtGui.QColor("#ff0000"))
return Capture(
- mode=CaptureMode.PARSE,
+ parse_text=True,
rect=Rect(20, 30, 220, 330),
ocr_text="one two three",
ocr_transformer=None,
diff --git a/tests/integration/test_normcap.py b/tests/integration/test_normcap.py
index b8c1cdee..5d7b0f44 100644
--- a/tests/integration/test_normcap.py
+++ b/tests/integration/test_normcap.py
@@ -15,7 +15,7 @@ def test_normcap_ocr_testcases(
"""Tests complete OCR workflow."""
# GIVEN NormCap is started with "language" set to english
- # and "parse"-mode
+ # and --parse-text True (default)
# and a certain test image as screenshot
monkeypatch.setattr(screengrab, "capture", lambda: [testcase.screenshot])
monkeypatch.setattr(sys, "exit", test_signal.on_event.emit)
diff --git a/tests/integration/test_settings_menu.py b/tests/integration/test_settings_menu.py
index 37bbc1b2..f3b7d81e 100644
--- a/tests/integration/test_settings_menu.py
+++ b/tests/integration/test_settings_menu.py
@@ -29,7 +29,7 @@ def test_settings_menu_creates_actions(monkeypatch, qtbot, run_normcap, test_sig
texts = [a.text().lower() for a in actions]
assert "show notification" in texts
- assert "parse" in texts
+ assert "parse-text" in texts
assert "languages" in texts
assert "about" in texts
assert "close" in texts
diff --git a/tests/test_utils.py b/tests/test_utils.py
index e44faf3b..dacf28a5 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -48,7 +48,7 @@ def test_argparser_defaults_are_complete():
"color",
"cli_mode",
"language",
- "mode",
+ "parse-text",
"notification",
"reset",
"tray",
diff --git a/tests/tests_gui/test_notification.py b/tests/tests_gui/test_notification.py
index 253a2094..4b261bc9 100644
--- a/tests/tests_gui/test_notification.py
+++ b/tests/tests_gui/test_notification.py
@@ -7,7 +7,7 @@
from PySide6 import QtGui, QtWidgets
from normcap.gui import notification
-from normcap.gui.models import Capture, CaptureMode, Rect
+from normcap.gui.models import Capture, Rect
from normcap.ocr.structures import Transformer
@@ -52,7 +52,7 @@ def test_compose_notification(ocr_transform, ocr_text, output_title, output_text
capture = Capture(
ocr_text=ocr_text,
ocr_transformer=ocr_transform,
- mode=CaptureMode.PARSE if ocr_transform != "RAW" else CaptureMode.RAW,
+ parse_text=ocr_transform != "RAW",
image=QtGui.QImage(),
screen=None,
scale_factor=1,
@@ -244,7 +244,7 @@ def mocked_qt_tray(cls, title, message, ocr_text, ocr_transformer):
capture = Capture(
ocr_text="text",
ocr_transformer=Transformer.SINGLE_LINE,
- mode=CaptureMode.PARSE,
+ parse_text=True,
image=QtGui.QImage(),
screen=None,
scale_factor=1,
diff --git a/tests/tests_gui/test_settings.py b/tests/tests_gui/test_settings.py
index 06b40bb5..b83bf77e 100644
--- a/tests/tests_gui/test_settings.py
+++ b/tests/tests_gui/test_settings.py
@@ -7,28 +7,28 @@
def test_reset_settings():
- default = "parse"
- non_default = "raw"
+ default = True
+ non_default = False
try:
settings = Settings(organization="normcap_TEST")
- settings.setValue("mode", non_default)
- assert settings.value("mode") == non_default
+ settings.setValue("parse-text", non_default)
+ assert settings.value("parse-text") == non_default
settings.reset()
- assert settings.value("mode") == default
+ assert settings.value("parse-text") == default
finally:
settings.clear()
def test_update_from_init_settings(caplog):
- init_setting = "raw"
+ initial_parse_text = False
try:
with caplog.at_level(logging.DEBUG):
settings = Settings(
organization="normcap_TEST",
- init_settings={"mode": init_setting, "non_existing": True},
+ init_settings={"parse-text": initial_parse_text, "non_existing": True},
)
- assert settings.value("mode") == init_setting
+ assert settings.value("parse-text") == initial_parse_text
assert settings.value("non_existing", False) is False
assert caplog.records[0].msg
finally:
@@ -36,16 +36,16 @@ def test_update_from_init_settings(caplog):
def test_set_missing_to_default(caplog):
- default_mode = "parse"
- non_default_mode = "raw"
+ default_parse_text = True
+ non_default_parse_text = False
default_language = "eng"
try:
settings = Settings(organization="normcap_TEST")
- assert settings.value("mode") == default_mode
+ assert settings.value("parse-text") == default_parse_text
- settings.setValue("mode", non_default_mode)
- assert settings.value("mode") == non_default_mode
+ settings.setValue("parse-text", non_default_parse_text)
+ assert settings.value("parse-text") == non_default_parse_text
assert settings.value("language") == default_language
settings.remove("language")
@@ -55,7 +55,7 @@ def test_set_missing_to_default(caplog):
with caplog.at_level(logging.DEBUG):
settings = Settings(organization="normcap_TEST")
- assert settings.value("mode") == non_default_mode
+ assert settings.value("parse-text") == non_default_parse_text
assert settings.value("language") == default_language
assert "Reset settings to" in caplog.records[0].msg
assert caplog.records[0].args == ("language", default_language)
diff --git a/tests/tests_gui/test_window.py b/tests/tests_gui/test_window.py
index 9c17f592..2899d764 100644
--- a/tests/tests_gui/test_window.py
+++ b/tests/tests_gui/test_window.py
@@ -1,5 +1,3 @@
-import sys
-
import pytest
from PySide6 import QtCore, QtGui
@@ -114,32 +112,3 @@ def test_window_esc_key_pressed_while_selecting(qtbot, temp_settings):
# THEN the selection should be cleared
assert not win.selection_rect
-
-
-@pytest.mark.skipif(sys.platform == "win32", reason="Fails for unknown reason") # FIXME
-def test_window_get_capture_mode_fallback_to_parse(temp_settings, caplog):
- # GIVEN a window with an invalid mode setting
- image = QtGui.QImage(600, 400, QtGui.QImage.Format.Format_RGB32)
- screen = models.Screen(
- device_pixel_ratio=1.0,
- left=0,
- top=0,
- right=600,
- bottom=400,
- index=0,
- screenshot=image,
- )
- invalid_mode = "some_deprecated_mode"
- temp_settings.setValue("mode", invalid_mode)
-
- win = window.Window(screen=screen, settings=temp_settings, parent=None)
-
- # WHEN the capture mode is read
- mode = win.get_capture_mode()
-
- # THEN a warning should be logged
- # and "parse" mode should be returned as fallback
- assert "warning" in caplog.text.lower()
- assert "unknown capture mode" in caplog.text.lower()
- assert invalid_mode in caplog.text.lower()
- assert mode == models.CaptureMode.PARSE