UI: Shorten web URLs in location fields
Show web URLs without their scheme, a leading host www., or the root slash while the location field is not being edited. Restore the fully serialized URL while editing so the original scheme is preserved, but keep mouse focus stable until release so clicks target the visible text. Keep the domain emphasis for shortened Qt location text by deriving the highlight ranges from the serialized URL and remapping them to the visible display form. Special URLs like about, data, and file remain unchanged. Add LibWebView coverage for the shared display helper used by both UI frontends.
This commit is contained in:
parent
7701f5e78f
commit
3f45f8cce0
6 changed files with 343 additions and 31 deletions
|
|
@ -175,6 +175,48 @@ Vector<URL::URL> sanitize_urls(ReadonlySpan<ByteString> raw_urls)
|
|||
return sanitized_urls;
|
||||
}
|
||||
|
||||
String url_for_display(URL::URL const& url)
|
||||
{
|
||||
if (!url.scheme().is_one_of("http"sv, "https"sv))
|
||||
return url.serialize();
|
||||
|
||||
StringBuilder builder;
|
||||
|
||||
if (!url.username().is_empty() || !url.password().is_empty()) {
|
||||
builder.append(url.username());
|
||||
if (!url.password().is_empty()) {
|
||||
builder.append(':');
|
||||
builder.append(url.password());
|
||||
}
|
||||
builder.append('@');
|
||||
}
|
||||
|
||||
auto host = url.serialized_host();
|
||||
auto host_view = host.bytes_as_string_view();
|
||||
if (host_view.starts_with("www."sv, CaseSensitivity::CaseInsensitive))
|
||||
host_view = host_view.substring_view(4);
|
||||
builder.append(host_view);
|
||||
|
||||
if (url.port().has_value())
|
||||
builder.appendff(":{}", *url.port());
|
||||
|
||||
auto path = url.serialize_path();
|
||||
if (path != "/"sv || url.query().has_value() || url.fragment().has_value())
|
||||
builder.append(path);
|
||||
|
||||
if (url.query().has_value()) {
|
||||
builder.append('?');
|
||||
builder.append(*url.query());
|
||||
}
|
||||
|
||||
if (url.fragment().has_value()) {
|
||||
builder.append('#');
|
||||
builder.append(*url.fragment());
|
||||
}
|
||||
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
static URLParts break_internal_url_into_parts(URL::URL const& url, StringView url_string)
|
||||
{
|
||||
auto scheme = url_string.substring_view(0, url.scheme().bytes_as_string_view().length() + ":"sv.length());
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
|
@ -21,6 +22,7 @@ enum class AppendTLD {
|
|||
WEBVIEW_API Optional<URL::URL> sanitize_url(StringView, Optional<SearchEngine> const& search_engine = {}, AppendTLD = AppendTLD::No);
|
||||
WEBVIEW_API bool location_looks_like_url(StringView, AppendTLD = AppendTLD::No);
|
||||
WEBVIEW_API Vector<URL::URL> sanitize_urls(ReadonlySpan<ByteString> raw_urls);
|
||||
WEBVIEW_API String url_for_display(URL::URL const&);
|
||||
|
||||
struct URLParts {
|
||||
StringView scheme_and_subdomain;
|
||||
|
|
|
|||
|
|
@ -80,6 +80,13 @@ static void expect_autocomplete_url_cannot_complete(StringView query, StringView
|
|||
EXPECT(!WebView::autocomplete_url_can_complete(query, suggestion));
|
||||
}
|
||||
|
||||
static void expect_url_for_display(StringView expected, StringView url)
|
||||
{
|
||||
auto parsed_url = URL::create_with_url_or_path(url);
|
||||
VERIFY(parsed_url.has_value());
|
||||
EXPECT_EQ(WebView::url_for_display(*parsed_url), expected);
|
||||
}
|
||||
|
||||
TEST_CASE(invalid_url)
|
||||
{
|
||||
EXPECT(!WebView::break_url_into_parts(""sv).has_value());
|
||||
|
|
@ -184,6 +191,19 @@ TEST_CASE(data_url)
|
|||
EXPECT(!is_sanitized_url_the_same("text/html data:"sv));
|
||||
}
|
||||
|
||||
TEST_CASE(url_for_display)
|
||||
{
|
||||
expect_url_for_display("example.com"sv, "https://example.com/"sv);
|
||||
expect_url_for_display("example.com/path?query#fragment"sv, "http://www.example.com/path?query#fragment"sv);
|
||||
expect_url_for_display("example.com/path/"sv, "https://www.example.com/path/"sv);
|
||||
expect_url_for_display("example.com/?query#fragment"sv, "http://www.example.com/?query#fragment"sv);
|
||||
expect_url_for_display("user:password@example.com/path"sv, "https://user:password@www.example.com/path"sv);
|
||||
|
||||
expect_url_for_display("about:version"sv, "about:version"sv);
|
||||
expect_url_for_display("data:text/html,Hello"sv, "data:text/html,Hello"sv);
|
||||
expect_url_for_display("file:///tmp/index.html"sv, "file:///tmp/index.html"sv);
|
||||
}
|
||||
|
||||
TEST_CASE(location_to_search_or_url)
|
||||
{
|
||||
expect_search_url_equals_sanitized_url("hello"sv); // Search.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ static NSString* const TOOLBAR_BOOKMARK_IDENTIFIER = @"ToolbarBookmarkIdentifier
|
|||
static NSString* const TOOLBAR_NEW_TAB_IDENTIFIER = @"ToolbarNewTabIdentifier";
|
||||
static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIdentifier";
|
||||
|
||||
enum class LocationFieldDisplay {
|
||||
Editing,
|
||||
NotEditing,
|
||||
};
|
||||
|
||||
static NSString* candidate_by_trimming_root_trailing_slash(NSString* candidate);
|
||||
|
||||
static bool query_matches_candidate_exactly(NSString* query, NSString* candidate)
|
||||
|
|
@ -182,6 +187,8 @@ static NSImage* location_field_globe_icon()
|
|||
- (void)setFavicon:(NSImage*)favicon;
|
||||
- (void)setShowsPageIcon:(BOOL)showsPageIcon;
|
||||
|
||||
@property (nonatomic, copy) void (^willBeginEditing)(void);
|
||||
|
||||
@end
|
||||
|
||||
@implementation LocationSearchField
|
||||
|
|
@ -205,11 +212,22 @@ static NSImage* location_field_globe_icon()
|
|||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
BOOL result = [super becomeFirstResponder];
|
||||
if (result)
|
||||
if (result) {
|
||||
if (self.willBeginEditing)
|
||||
self.willBeginEditing();
|
||||
[self performSelector:@selector(selectText:) withObject:self afterDelay:0];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent*)event
|
||||
{
|
||||
[super mouseDown:event];
|
||||
|
||||
if (self.willBeginEditing)
|
||||
self.willBeginEditing();
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
[super layout];
|
||||
|
|
@ -465,6 +483,7 @@ static NSImage* location_field_globe_icon()
|
|||
|
||||
- (void)focusLocationToolbarItem
|
||||
{
|
||||
[self restoreLocationFieldForEditing];
|
||||
[self tab].preferred_first_responder = self.location_toolbar_item.view;
|
||||
[self.window makeFirstResponder:self.location_toolbar_item.view];
|
||||
}
|
||||
|
|
@ -500,9 +519,14 @@ static NSImage* location_field_globe_icon()
|
|||
self.tab.titlebarAppearsTransparent = YES;
|
||||
}
|
||||
|
||||
- (void)setLocationFieldText:(StringView)url
|
||||
- (void)setLocationFieldText:(StringView)url display:(LocationFieldDisplay)display
|
||||
{
|
||||
NSMutableAttributedString* attributed_url;
|
||||
auto maybe_url = URL::create_with_url_or_path(url);
|
||||
auto display_url = MUST(String::from_utf8(url));
|
||||
if (display == LocationFieldDisplay::NotEditing && maybe_url.has_value())
|
||||
display_url = WebView::url_for_display(*maybe_url);
|
||||
|
||||
auto url_parts = WebView::break_url_into_parts(url);
|
||||
|
||||
auto* dark_attributes = @{
|
||||
|
|
@ -515,8 +539,19 @@ static NSImage* location_field_globe_icon()
|
|||
if (url_parts.has_value()) {
|
||||
attributed_url = [[NSMutableAttributedString alloc] init];
|
||||
|
||||
auto scheme_and_subdomain = url_parts->scheme_and_subdomain;
|
||||
auto remainder = url_parts->remainder;
|
||||
if (display == LocationFieldDisplay::NotEditing && maybe_url.has_value() && maybe_url->scheme().is_one_of("http"sv, "https"sv)) {
|
||||
auto scheme_prefix_length = maybe_url->scheme().bytes_as_string_view().length() + "://"sv.length();
|
||||
scheme_and_subdomain = scheme_and_subdomain.substring_view(scheme_prefix_length);
|
||||
if (scheme_and_subdomain.starts_with("www."sv, CaseSensitivity::CaseInsensitive))
|
||||
scheme_and_subdomain = scheme_and_subdomain.substring_view(4);
|
||||
if (remainder == "/"sv)
|
||||
remainder = {};
|
||||
}
|
||||
|
||||
auto* attributed_scheme_and_subdomain = [[NSAttributedString alloc]
|
||||
initWithString:Ladybird::string_to_ns_string(url_parts->scheme_and_subdomain)
|
||||
initWithString:Ladybird::string_to_ns_string(scheme_and_subdomain)
|
||||
attributes:dark_attributes];
|
||||
|
||||
auto* attributed_effective_tld_plus_one = [[NSAttributedString alloc]
|
||||
|
|
@ -524,7 +559,7 @@ static NSImage* location_field_globe_icon()
|
|||
attributes:highlight_attributes];
|
||||
|
||||
auto* attributed_remainder = [[NSAttributedString alloc]
|
||||
initWithString:Ladybird::string_to_ns_string(url_parts->remainder)
|
||||
initWithString:Ladybird::string_to_ns_string(remainder)
|
||||
attributes:dark_attributes];
|
||||
|
||||
[attributed_url appendAttributedString:attributed_scheme_and_subdomain];
|
||||
|
|
@ -532,7 +567,13 @@ static NSImage* location_field_globe_icon()
|
|||
[attributed_url appendAttributedString:attributed_remainder];
|
||||
} else {
|
||||
attributed_url = [[NSMutableAttributedString alloc]
|
||||
initWithString:Ladybird::string_to_ns_string(url)
|
||||
initWithString:Ladybird::string_to_ns_string(display_url)
|
||||
attributes:highlight_attributes];
|
||||
}
|
||||
|
||||
if (display == LocationFieldDisplay::NotEditing && maybe_url.has_value() && ![[attributed_url string] isEqualToString:Ladybird::string_to_ns_string(display_url)]) {
|
||||
attributed_url = [[NSMutableAttributedString alloc]
|
||||
initWithString:Ladybird::string_to_ns_string(display_url)
|
||||
attributes:highlight_attributes];
|
||||
}
|
||||
|
||||
|
|
@ -541,6 +582,30 @@ static NSImage* location_field_globe_icon()
|
|||
[location_search_field setShowsPageIcon:url_parts.has_value()];
|
||||
}
|
||||
|
||||
- (void)setLocationFieldText:(StringView)url
|
||||
{
|
||||
[self setLocationFieldText:url display:LocationFieldDisplay::NotEditing];
|
||||
}
|
||||
|
||||
- (void)restoreLocationFieldForEditing
|
||||
{
|
||||
auto const& url = [[[self tab] web_view] view].url();
|
||||
auto* location_search_field = (LocationSearchField*)[self.location_toolbar_item view];
|
||||
if (![[location_search_field stringValue] isEqualToString:Ladybird::string_to_ns_string(WebView::url_for_display(url))])
|
||||
return;
|
||||
|
||||
m_is_applying_inline_autocomplete = true;
|
||||
[self setLocationFieldText:url.serialize() display:LocationFieldDisplay::Editing];
|
||||
|
||||
auto* editor = (NSTextView*)[location_search_field currentEditor];
|
||||
if (editor != nil && [self.window firstResponder] == editor && ![editor hasMarkedText]) {
|
||||
auto* serialized_url = Ladybird::string_to_ns_string(url.serialize());
|
||||
[editor setString:serialized_url];
|
||||
[editor setSelectedRange:NSMakeRange(0, serialized_url.length)];
|
||||
}
|
||||
m_is_applying_inline_autocomplete = false;
|
||||
}
|
||||
|
||||
- (NSString*)currentLocationFieldQuery
|
||||
{
|
||||
auto* location_search_field = (LocationSearchField*)[self.location_toolbar_item view];
|
||||
|
|
@ -805,6 +870,10 @@ static NSImage* location_field_globe_icon()
|
|||
[location_search_field setPlaceholderString:@"Enter web address"];
|
||||
[location_search_field setTextColor:[NSColor textColor]];
|
||||
[location_search_field setDelegate:self];
|
||||
__weak TabController* weak_self = self;
|
||||
[location_search_field setWillBeginEditing:^{
|
||||
[weak_self restoreLocationFieldForEditing];
|
||||
}];
|
||||
|
||||
if (@available(macOS 26, *)) {
|
||||
[location_search_field setBordered:YES];
|
||||
|
|
@ -1092,6 +1161,11 @@ static NSImage* location_field_globe_icon()
|
|||
|
||||
#pragma mark - NSSearchFieldDelegate
|
||||
|
||||
- (void)controlTextDidBeginEditing:(NSNotification*)notification
|
||||
{
|
||||
[self restoreLocationFieldForEditing];
|
||||
}
|
||||
|
||||
- (BOOL)control:(NSControl*)control
|
||||
textView:(NSTextView*)text_view
|
||||
doCommandBySelector:(SEL)selector
|
||||
|
|
@ -1135,14 +1209,24 @@ static NSImage* location_field_globe_icon()
|
|||
- (void)controlTextDidEndEditing:(NSNotification*)notification
|
||||
{
|
||||
auto* location_search_field = (LocationSearchField*)[self.location_toolbar_item view];
|
||||
NSString* url_string = [[location_search_field stringValue] copy];
|
||||
|
||||
auto url_string = Ladybird::ns_string_to_string([location_search_field stringValue]);
|
||||
m_autocomplete->cancel_pending_query();
|
||||
self.current_inline_autocomplete_suggestion = nil;
|
||||
self.suppressed_inline_autocomplete_query = nil;
|
||||
m_should_suppress_inline_autocomplete_on_next_change = false;
|
||||
[self.autocomplete close];
|
||||
[self setLocationFieldText:url_string];
|
||||
// AppKit can send this while focus is still settling into the field
|
||||
// editor. Wait until the next turn so transient notifications do not
|
||||
// format the live editor contents as a non-editing URL.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
auto* location_search_field = (LocationSearchField*)[self.location_toolbar_item view];
|
||||
auto* editor = (NSTextView*)[location_search_field currentEditor];
|
||||
if (editor != nil && [self.window firstResponder] == editor)
|
||||
return;
|
||||
|
||||
m_autocomplete->cancel_pending_query();
|
||||
self.current_inline_autocomplete_suggestion = nil;
|
||||
self.suppressed_inline_autocomplete_query = nil;
|
||||
m_should_suppress_inline_autocomplete_on_next_change = false;
|
||||
[self.autocomplete close];
|
||||
[self setLocationFieldText:Ladybird::ns_string_to_string(url_string)];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification*)notification
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <QGuiApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QLatin1String>
|
||||
#include <QMouseEvent>
|
||||
#include <QPalette>
|
||||
#include <QResizeEvent>
|
||||
#include <QStyle>
|
||||
|
|
@ -230,11 +231,11 @@ LocationEdit::LocationEdit(QWidget* parent)
|
|||
if (text().isEmpty())
|
||||
return;
|
||||
|
||||
auto query = ak_string_from_qstring(text());
|
||||
|
||||
reset_autocomplete_state();
|
||||
clearFocus();
|
||||
|
||||
auto query = ak_string_from_qstring(text());
|
||||
|
||||
auto ctrl_held = QApplication::keyboardModifiers() & Qt::ControlModifier;
|
||||
auto append_tld = ctrl_held ? WebView::AppendTLD::Yes : WebView::AppendTLD::No;
|
||||
|
||||
|
|
@ -313,11 +314,21 @@ void LocationEdit::changeEvent(QEvent* event)
|
|||
|
||||
void LocationEdit::focusInEvent(QFocusEvent* event)
|
||||
{
|
||||
auto should_defer_full_url = event->reason() == Qt::MouseFocusReason
|
||||
&& m_url.has_value()
|
||||
&& text() == display_url();
|
||||
|
||||
QLineEdit::focusInEvent(event);
|
||||
|
||||
m_should_show_full_url_on_mouse_release = should_defer_full_url;
|
||||
|
||||
if (!should_defer_full_url && m_url.has_value() && text() == display_url())
|
||||
setText(serialized_url());
|
||||
|
||||
highlight_location();
|
||||
animate_focus_glow(58);
|
||||
|
||||
if (event->reason() != Qt::PopupFocusReason)
|
||||
if (event->reason() != Qt::PopupFocusReason && !should_defer_full_url)
|
||||
QTimer::singleShot(0, this, &QLineEdit::selectAll);
|
||||
}
|
||||
|
||||
|
|
@ -329,12 +340,15 @@ void LocationEdit::focusOutEvent(QFocusEvent* event)
|
|||
reset_autocomplete_state();
|
||||
m_autocomplete->cancel_pending_query();
|
||||
m_autocomplete->close();
|
||||
m_should_show_full_url_on_mouse_release = false;
|
||||
|
||||
if (m_url_is_hidden) {
|
||||
m_url_is_hidden = false;
|
||||
m_has_user_edited_hidden_url = false;
|
||||
if (text().isEmpty() && m_url.has_value())
|
||||
setText(qstring_from_ak_string(m_url->serialize()));
|
||||
setText(display_url());
|
||||
} else if (m_url.has_value() && text() == serialized_url()) {
|
||||
setText(display_url());
|
||||
}
|
||||
|
||||
if (event->reason() != Qt::PopupFocusReason) {
|
||||
|
|
@ -368,7 +382,7 @@ void LocationEdit::keyPressEvent(QKeyEvent* event)
|
|||
return;
|
||||
reset_autocomplete_state();
|
||||
if (m_url.has_value())
|
||||
setText(qstring_from_ak_string(m_url->serialize()));
|
||||
setText(serialized_url());
|
||||
clearFocus();
|
||||
return;
|
||||
}
|
||||
|
|
@ -398,6 +412,14 @@ void LocationEdit::keyPressEvent(QKeyEvent* event)
|
|||
QLineEdit::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void LocationEdit::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
QLineEdit::mouseReleaseEvent(event);
|
||||
|
||||
if (event->button() == Qt::LeftButton && m_should_show_full_url_on_mouse_release)
|
||||
show_full_url_preserving_display_selection();
|
||||
}
|
||||
|
||||
void LocationEdit::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QLineEdit::resizeEvent(event);
|
||||
|
|
@ -537,36 +559,56 @@ void LocationEdit::highlight_location()
|
|||
auto url = ak_string_from_qstring(text());
|
||||
QList<QInputMethodEvent::Attribute> attributes;
|
||||
|
||||
if (auto url_parts = WebView::break_url_into_parts(url); url_parts.has_value()) {
|
||||
auto darkened_text_color = ChromeStyle::chrome_text(palette());
|
||||
darkened_text_color.setAlpha(127);
|
||||
auto darkened_text_color = ChromeStyle::chrome_text(palette());
|
||||
darkened_text_color.setAlpha(127);
|
||||
|
||||
QTextCharFormat dark_attributes;
|
||||
dark_attributes.setForeground(darkened_text_color);
|
||||
QTextCharFormat dark_attributes;
|
||||
dark_attributes.setForeground(darkened_text_color);
|
||||
|
||||
QTextCharFormat highlight_attributes;
|
||||
highlight_attributes.setForeground(ChromeStyle::chrome_text(palette()));
|
||||
QTextCharFormat highlight_attributes;
|
||||
highlight_attributes.setForeground(ChromeStyle::chrome_text(palette()));
|
||||
|
||||
auto append_attributes = [&](StringView scheme_and_subdomain, StringView effective_tld_plus_one, StringView remainder) {
|
||||
attributes.append({
|
||||
QInputMethodEvent::TextFormat,
|
||||
-cursorPosition(),
|
||||
static_cast<int>(url_parts->scheme_and_subdomain.length()),
|
||||
static_cast<int>(scheme_and_subdomain.length()),
|
||||
dark_attributes,
|
||||
});
|
||||
|
||||
attributes.append({
|
||||
QInputMethodEvent::TextFormat,
|
||||
static_cast<int>(url_parts->scheme_and_subdomain.length() - cursorPosition()),
|
||||
static_cast<int>(url_parts->effective_tld_plus_one.length()),
|
||||
static_cast<int>(scheme_and_subdomain.length() - cursorPosition()),
|
||||
static_cast<int>(effective_tld_plus_one.length()),
|
||||
highlight_attributes,
|
||||
});
|
||||
|
||||
attributes.append({
|
||||
QInputMethodEvent::TextFormat,
|
||||
static_cast<int>(url_parts->scheme_and_subdomain.length() + url_parts->effective_tld_plus_one.length() - cursorPosition()),
|
||||
static_cast<int>(url_parts->remainder.length()),
|
||||
static_cast<int>(scheme_and_subdomain.length() + effective_tld_plus_one.length() - cursorPosition()),
|
||||
static_cast<int>(remainder.length()),
|
||||
dark_attributes,
|
||||
});
|
||||
};
|
||||
|
||||
if (m_url.has_value() && text() == display_url() && m_url->scheme().is_one_of("http"sv, "https"sv)) {
|
||||
auto serialized_url = m_url->serialize();
|
||||
if (auto url_parts = WebView::break_url_into_parts(serialized_url); url_parts.has_value()) {
|
||||
auto scheme_and_subdomain = url_parts->scheme_and_subdomain;
|
||||
auto remainder = url_parts->remainder;
|
||||
|
||||
auto scheme_prefix_length = m_url->scheme().bytes_as_string_view().length() + "://"sv.length();
|
||||
if (scheme_and_subdomain.length() >= scheme_prefix_length)
|
||||
scheme_and_subdomain = scheme_and_subdomain.substring_view(scheme_prefix_length);
|
||||
if (scheme_and_subdomain.starts_with("www."sv, CaseSensitivity::CaseInsensitive))
|
||||
scheme_and_subdomain = scheme_and_subdomain.substring_view(4);
|
||||
if (remainder == "/"sv)
|
||||
remainder = {};
|
||||
|
||||
append_attributes(scheme_and_subdomain, url_parts->effective_tld_plus_one, remainder);
|
||||
}
|
||||
} else if (auto url_parts = WebView::break_url_into_parts(url); url_parts.has_value()) {
|
||||
append_attributes(url_parts->scheme_and_subdomain, url_parts->effective_tld_plus_one, url_parts->remainder);
|
||||
}
|
||||
|
||||
QInputMethodEvent event(QString(), attributes);
|
||||
|
|
@ -581,18 +623,133 @@ void LocationEdit::set_url(Optional<URL::URL> url)
|
|||
if (!m_has_user_edited_hidden_url)
|
||||
clear();
|
||||
} else if (m_url.has_value()) {
|
||||
setText(qstring_from_ak_string(m_url->serialize()));
|
||||
setText(hasFocus() ? serialized_url() : display_url());
|
||||
setCursorPosition(0);
|
||||
}
|
||||
|
||||
update_location_icon();
|
||||
}
|
||||
|
||||
void LocationEdit::show_full_url_preserving_display_selection()
|
||||
{
|
||||
if (!m_should_show_full_url_on_mouse_release)
|
||||
return;
|
||||
|
||||
m_should_show_full_url_on_mouse_release = false;
|
||||
|
||||
if (!m_url.has_value() || text() != display_url())
|
||||
return;
|
||||
|
||||
auto selection_start = selectionStart();
|
||||
auto selection_length = selectedText().length();
|
||||
auto cursor_position = cursorPosition();
|
||||
|
||||
setText(serialized_url());
|
||||
|
||||
if (selection_start != -1) {
|
||||
auto serialized_selection_start = serialized_url_position_for_display_position(selection_start);
|
||||
auto serialized_selection_end = serialized_url_position_for_display_position(selection_start + selection_length);
|
||||
setSelection(serialized_selection_start, serialized_selection_end - serialized_selection_start);
|
||||
} else {
|
||||
setCursorPosition(serialized_url_position_for_display_position(cursor_position));
|
||||
}
|
||||
|
||||
highlight_location();
|
||||
}
|
||||
|
||||
int LocationEdit::serialized_url_position_for_display_position(int display_position) const
|
||||
{
|
||||
VERIFY(m_url.has_value());
|
||||
|
||||
auto display = display_url();
|
||||
auto serialized = serialized_url();
|
||||
display_position = qBound(0, display_position, display.length());
|
||||
|
||||
if (display == serialized || !m_url->scheme().is_one_of("http"sv, "https"sv))
|
||||
return min(display_position, serialized.length());
|
||||
|
||||
int display_index = 0;
|
||||
int last_serialized_position = 0;
|
||||
auto map_visible_range = [&](int serialized_start, int length) -> Optional<int> {
|
||||
if (length <= 0)
|
||||
return {};
|
||||
|
||||
if (display_position < display_index + length)
|
||||
return serialized_start + display_position - display_index;
|
||||
|
||||
display_index += length;
|
||||
last_serialized_position = serialized_start + length;
|
||||
return {};
|
||||
};
|
||||
|
||||
auto serialized_index = qstring_from_ak_string(m_url->scheme()).length() + "://"sv.length();
|
||||
|
||||
if (!m_url->username().is_empty() || !m_url->password().is_empty()) {
|
||||
auto username = qstring_from_ak_string(m_url->username());
|
||||
auto password = qstring_from_ak_string(m_url->password());
|
||||
auto userinfo_length = username.length() + 1;
|
||||
if (!password.isEmpty())
|
||||
userinfo_length += 1 + password.length();
|
||||
|
||||
if (auto position = map_visible_range(serialized_index, userinfo_length); position.has_value())
|
||||
return *position;
|
||||
serialized_index += userinfo_length;
|
||||
}
|
||||
|
||||
auto host = qstring_from_ak_string(m_url->serialized_host());
|
||||
auto host_offset = host.startsWith("www.", Qt::CaseInsensitive) ? 4 : 0;
|
||||
if (auto position = map_visible_range(serialized_index + host_offset, host.length() - host_offset); position.has_value())
|
||||
return *position;
|
||||
serialized_index += host.length();
|
||||
|
||||
if (auto port = m_url->port(); port.has_value()) {
|
||||
auto port_text = QString::number(*port);
|
||||
auto port_length = 1 + port_text.length();
|
||||
if (auto position = map_visible_range(serialized_index, port_length); position.has_value())
|
||||
return *position;
|
||||
serialized_index += port_length;
|
||||
}
|
||||
|
||||
auto path = qstring_from_ak_string(m_url->serialize_path());
|
||||
if (path != "/" || m_url->query().has_value() || m_url->fragment().has_value()) {
|
||||
if (auto position = map_visible_range(serialized_index, path.length()); position.has_value())
|
||||
return *position;
|
||||
}
|
||||
serialized_index += path.length();
|
||||
|
||||
if (m_url->query().has_value()) {
|
||||
auto query_length = 1 + qstring_from_ak_string(*m_url->query()).length();
|
||||
if (auto position = map_visible_range(serialized_index, query_length); position.has_value())
|
||||
return *position;
|
||||
serialized_index += query_length;
|
||||
}
|
||||
|
||||
if (m_url->fragment().has_value()) {
|
||||
auto fragment_length = 1 + qstring_from_ak_string(*m_url->fragment()).length();
|
||||
if (auto position = map_visible_range(serialized_index, fragment_length); position.has_value())
|
||||
return *position;
|
||||
}
|
||||
|
||||
return last_serialized_position;
|
||||
}
|
||||
|
||||
bool LocationEdit::text_matches_current_url() const
|
||||
{
|
||||
return m_url.has_value()
|
||||
&& !m_url_is_hidden
|
||||
&& text() == qstring_from_ak_string(m_url->serialize());
|
||||
&& (text() == serialized_url() || text() == display_url());
|
||||
}
|
||||
|
||||
QString LocationEdit::serialized_url() const
|
||||
{
|
||||
VERIFY(m_url.has_value());
|
||||
return qstring_from_ak_string(m_url->serialize());
|
||||
}
|
||||
|
||||
QString LocationEdit::display_url() const
|
||||
{
|
||||
VERIFY(m_url.has_value());
|
||||
return qstring_from_ak_string(WebView::url_for_display(*m_url));
|
||||
}
|
||||
|
||||
QString LocationEdit::current_query() const
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
class QAction;
|
||||
class QEvent;
|
||||
class QGraphicsDropShadowEffect;
|
||||
class QMouseEvent;
|
||||
class QResizeEvent;
|
||||
class QToolButton;
|
||||
class QVariantAnimation;
|
||||
|
|
@ -47,10 +48,13 @@ private:
|
|||
virtual void focusInEvent(QFocusEvent* event) override;
|
||||
virtual void focusOutEvent(QFocusEvent* event) override;
|
||||
virtual void keyPressEvent(QKeyEvent* event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
virtual void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
virtual void search_engine_changed() override;
|
||||
|
||||
void show_full_url_preserving_display_selection();
|
||||
int serialized_url_position_for_display_position(int) const;
|
||||
void update_placeholder();
|
||||
void update_chrome_style();
|
||||
void update_location_icon();
|
||||
|
|
@ -59,6 +63,8 @@ private:
|
|||
void animate_focus_glow(int target_alpha);
|
||||
void highlight_location();
|
||||
bool text_matches_current_url() const;
|
||||
QString serialized_url() const;
|
||||
QString display_url() const;
|
||||
|
||||
int apply_inline_autocomplete(Vector<WebView::AutocompleteSuggestion> const&);
|
||||
bool apply_inline_autocomplete_suggestion_text(QString const& suggestion_text, QString const& query);
|
||||
|
|
@ -78,6 +84,7 @@ private:
|
|||
bool m_has_user_edited_hidden_url { false };
|
||||
bool m_is_updating_chrome_style { false };
|
||||
bool m_has_pending_chrome_style_update { false };
|
||||
bool m_should_show_full_url_on_mouse_release { false };
|
||||
int m_focus_glow_alpha { 0 };
|
||||
|
||||
bool m_is_applying_inline_autocomplete { false };
|
||||
|
|
|
|||
Loading…
Reference in a new issue