Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 1.5.1 #10

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

---

* 1.5.1 (2024-12-12)
* Added `Find in Page…` entry to the context menu.

* 1.5.0 (2024-12-11)
* Added support for mouse wheel scrolling.
* Added support for page zooming with `CTRL` + mouse wheel.
Expand Down
2 changes: 1 addition & 1 deletion websiteapp/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
class Version(Enum):
MAJOR = 1
MINOR = 5
PATCH = 0
PATCH = 1

@classmethod
def as_string(cls) -> str:
Expand Down
194 changes: 194 additions & 0 deletions websiteapp/toolbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"""
##################################################################################
#
# Website As App
# Run any website as standalone desktop application
#
# @author Marcin Orlowski <mail (#) marcinOrlowski (.) com>
# @copyright 2023-2024 Marcin Orlowski
# @license https://www.opensource.org/licenses/mit-license.php MIT
# @link https://github.com/MarcinOrlowski/website-as-app
#
# @file websiteapp/toolbar.py
#
##################################################################################
"""

from PySide6.QtCore import Qt, QTimer
from PySide6.QtWidgets import (
QToolBar,
QLineEdit,
QPushButton,
QHBoxLayout,
QWidget,
QLabel
)
from PySide6.QtGui import QAction, QKeySequence
from PySide6.QtWebEngineCore import QWebEnginePage

class SearchBarPosition:
"""Constants for search bar position"""
TOP = "top"
BOTTOM = "bottom"

class SearchToolBar(QToolBar):
def __init__(self, web_view, position=SearchBarPosition.TOP, parent=None):
"""
Initialize the search toolbar.

Args:
web_view: The QWebEngineView instance to search in
position: Where to place the search bar (TOP or BOTTOM)
parent: Parent widget
"""
super().__init__(parent)
self.web_view = web_view
self.position = position
self.setup_ui()
self.hide() # Hidden by default

def setup_ui(self):
"""Set up the user interface elements of the search toolbar."""
# Create widget to hold the search controls
container = QWidget()
layout = QHBoxLayout(container)
layout.setContentsMargins(5, 0, 5, 0)

# Close button
self.close_button = QPushButton("×")
self.close_button.setFixedSize(20, 20)
self.close_button.clicked.connect(self.hide)
layout.addWidget(self.close_button)

# Search input field
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Find in page...")
self.search_input.textChanged.connect(self.on_text_changed)
self.search_input.returnPressed.connect(lambda: self.find_text(True))
layout.addWidget(self.search_input)

# Previous/Next buttons
self.prev_button = QPushButton("◀")
self.prev_button.clicked.connect(lambda: self.find_text(False))
layout.addWidget(self.prev_button)

self.next_button = QPushButton("▶")
self.next_button.clicked.connect(lambda: self.find_text(True))
layout.addWidget(self.next_button)

# Match counter label
self.match_label = QLabel()
layout.addWidget(self.match_label)

# Add the container to the toolbar
self.addWidget(container)

# Setup search options
self.search_flags = QWebEnginePage.FindFlag(0) # Default search flags

# Timer for delayed search (improves performance)
self.search_timer = QTimer()
self.search_timer.setSingleShot(True)
self.search_timer.timeout.connect(lambda: self.find_text(True))

def show_search(self):
"""Show the search toolbar and focus the input field."""
self.show()
self.search_input.setFocus()
self.search_input.selectAll()

def on_text_changed(self, text):
"""
Handle search text changes with debouncing.

Args:
text: The current text in the search input
"""
# Reset the timer on each text change
self.search_timer.stop()
if text:
# Start a new timer
self.search_timer.start(300) # 300ms delay
else:
# Clear the search
self.web_view.findText("")
self.match_label.clear()

def find_text(self, forward=True):
"""
Perform the text search in the web view.

Args:
forward: Search direction (True for forward, False for backward)
"""
text = self.search_input.text()
if not text:
return

flags = self.search_flags
if not forward:
flags |= QWebEnginePage.FindFlag.FindBackward

def callback(found):
if not found:
# No matches found - show in red
self.search_input.setStyleSheet("QLineEdit { background-color: #fdd; }")
self.match_label.setText("No matches")
else:
# Matches found - restore normal style
self.search_input.setStyleSheet("")

self.web_view.findText(text, flags, callback)

def set_position(self, position):
"""
Changes the position of the search toolbar.

Args:
position: Either SearchBarPosition.TOP or SearchBarPosition.BOTTOM
"""
self.position = position
# If the toolbar is currently shown, update its position
if self.isVisible():
self.parent().removeToolBar(self)
area = Qt.ToolBarArea.TopToolBarArea if position == SearchBarPosition.TOP else Qt.ToolBarArea.BottomToolBarArea
self.parent().addToolBar(area, self)

def toggle_case_sensitivity(self):
"""Toggle case-sensitive search."""
if self.search_flags & QWebEnginePage.FindFlag.FindCaseSensitively:
self.search_flags &= ~QWebEnginePage.FindFlag.FindCaseSensitively
else:
self.search_flags |= QWebEnginePage.FindFlag.FindCaseSensitively
# Re-run the search with new flags
self.find_text(True)

@classmethod
def setup_for_window(cls, webapp_window, position=SearchBarPosition.TOP):
"""
Creates and sets up a SearchToolBar instance for the given webapp window.
"""
search_toolbar = cls(webapp_window.browser, position)

# Add toolbar to the specified position
area = Qt.ToolBarArea.TopToolBarArea if position == SearchBarPosition.TOP else Qt.ToolBarArea.BottomToolBarArea
webapp_window.addToolBar(area, search_toolbar)

# First, remove any existing actions with the same shortcuts
for action in webapp_window.actions():
if action.shortcut() in [QKeySequence.StandardKey.Find, Qt.Key.Key_Escape]:
webapp_window.removeAction(action)

# Add Ctrl+F shortcut
search_action = QAction("Find", webapp_window)
search_action.setShortcut(QKeySequence.StandardKey.Find)
search_action.triggered.connect(search_toolbar.show_search)
webapp_window.addAction(search_action)

# Add Escape shortcut only if search bar is visible
escape_action = QAction("Close Find", webapp_window)
escape_action.setShortcut(Qt.Key.Key_Escape)
escape_action.triggered.connect(lambda: search_toolbar.hide() if search_toolbar.isVisible() else None)
webapp_window.addAction(escape_action)

return search_toolbar
6 changes: 4 additions & 2 deletions websiteapp/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,12 @@ def __init__(self):
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)

self.search_toolbar = SearchToolBar.setup_for_window(self)

# Setup search toolbar
search_position = SearchBarPosition.TOP if self.args.search_top else SearchBarPosition.BOTTOM
self.search_toolbar = SearchToolBar.setup_for_window(self, position=search_position)
# Add this line to connect the search toolbar to the CustomWebEngineView
if not self.args.no_custom_webengine:
self.browser.set_search_toolbar(self.search_toolbar)

def acquire_lock(self) -> bool:
"""
Expand Down
22 changes: 22 additions & 0 deletions websiteapp/webengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(self, parent=None, debug=False):
super().__init__(parent)
self.debug = debug # Store the debug flag to conditionally add the dump action
self.loading = False # Keep track of whether a page is loading
self.search_toolbar = None # Reference to search toolbar

# Connect load progress to determine loading status
self.loadStarted.connect(self.on_load_started)
Expand All @@ -42,6 +43,15 @@ def __init__(self, parent=None, debug=False):
# Store current zoom factor
self._current_zoom = 1.0

def set_search_toolbar(self, toolbar):
"""
Set the reference to the search toolbar.

Args:
toolbar: The SearchToolBar instance to be used
"""
self.search_toolbar = toolbar

def on_load_started(self):
"""
Slot called when page load starts.
Expand Down Expand Up @@ -115,6 +125,11 @@ def contextMenuEvent(self, event):
# if q_action.isEnabled(): # Only add enabled actions
menu.addAction(q_action)

# Add "Find in Page" action
find_action = QAction("Find in Page…", self)
find_action.triggered.connect(self.show_find_dialog)
menu.addAction(find_action)

# Add custom action to copy URL
copy_url_action = QAction("Copy Current URL", self)
copy_url_action.triggered.connect(self.copy_url_to_clipboard)
Expand Down Expand Up @@ -146,6 +161,13 @@ def contextMenuEvent(self, event):
# Display the unified context menu at the cursor position
menu.exec(event.globalPos())

def show_find_dialog(self):
"""
Show the search toolbar when Find in Page is selected.
"""
if self.search_toolbar:
self.search_toolbar.show_search()

def copy_url_to_clipboard(self):
"""
Copies the current page URL to the clipboard.
Expand Down
Loading