|
@@ -0,0 +1,182 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
|
|
|
+ *
|
|
|
+ * SPDX-License-Identifier: BSD-2-Clause
|
|
|
+ */
|
|
|
+
|
|
|
+#include <UI/LadybirdWebViewBridge.h>
|
|
|
+
|
|
|
+#import <UI/LadybirdWebView.h>
|
|
|
+#import <UI/SearchPanel.h>
|
|
|
+#import <UI/Tab.h>
|
|
|
+#import <Utilities/Conversions.h>
|
|
|
+
|
|
|
+#if !__has_feature(objc_arc)
|
|
|
+# error "This project requires ARC"
|
|
|
+#endif
|
|
|
+
|
|
|
+static constexpr CGFloat const SEARCH_FIELD_HEIGHT = 30;
|
|
|
+static constexpr CGFloat const SEARCH_FIELD_WIDTH = 300;
|
|
|
+
|
|
|
+@interface SearchPanel () <NSSearchFieldDelegate>
|
|
|
+
|
|
|
+@property (nonatomic, strong) NSSearchField* search_field;
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation SearchPanel
|
|
|
+
|
|
|
+- (instancetype)init
|
|
|
+{
|
|
|
+ if (self = [super init]) {
|
|
|
+ self.search_field = [[NSSearchField alloc] init];
|
|
|
+ [self.search_field setPlaceholderString:@"Search"];
|
|
|
+ [self.search_field setDelegate:self];
|
|
|
+
|
|
|
+ auto* search_previous = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameGoLeftTemplate]
|
|
|
+ target:self
|
|
|
+ action:@selector(findPreviousMatch:)];
|
|
|
+ [search_previous setToolTip:@"Find Previous Match"];
|
|
|
+ [search_previous setBordered:NO];
|
|
|
+
|
|
|
+ auto* search_next = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameGoRightTemplate]
|
|
|
+ target:self
|
|
|
+ action:@selector(findNextMatch:)];
|
|
|
+ [search_next setToolTip:@"Find Next Match"];
|
|
|
+ [search_next setBordered:NO];
|
|
|
+
|
|
|
+ auto* search_done = [NSButton buttonWithTitle:@"Done"
|
|
|
+ target:self
|
|
|
+ action:@selector(cancelSearch:)];
|
|
|
+ [search_done setToolTip:@"Close Search Bar"];
|
|
|
+ [search_done setBezelStyle:NSBezelStyleAccessoryBarAction];
|
|
|
+
|
|
|
+ [self addView:self.search_field inGravity:NSStackViewGravityLeading];
|
|
|
+ [self addView:search_previous inGravity:NSStackViewGravityLeading];
|
|
|
+ [self addView:search_next inGravity:NSStackViewGravityLeading];
|
|
|
+ [self addView:search_done inGravity:NSStackViewGravityTrailing];
|
|
|
+
|
|
|
+ [self setOrientation:NSUserInterfaceLayoutOrientationHorizontal];
|
|
|
+ [self setEdgeInsets:NSEdgeInsets { 0, 8, 0, 8 }];
|
|
|
+
|
|
|
+ [[self heightAnchor] constraintEqualToConstant:SEARCH_FIELD_HEIGHT].active = YES;
|
|
|
+ [[self.search_field widthAnchor] constraintEqualToConstant:SEARCH_FIELD_WIDTH].active = YES;
|
|
|
+ }
|
|
|
+
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - Public methods
|
|
|
+
|
|
|
+- (void)find:(id)sender
|
|
|
+{
|
|
|
+ [self setHidden:NO];
|
|
|
+ [self setSearchTextFromPasteBoard];
|
|
|
+
|
|
|
+ [self.window makeFirstResponder:self.search_field];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)findNextMatch:(id)sender
|
|
|
+{
|
|
|
+ if ([self setSearchTextFromPasteBoard]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ [[[self tab] web_view] findInPageNextMatch];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)findPreviousMatch:(id)sender
|
|
|
+{
|
|
|
+ if ([self setSearchTextFromPasteBoard]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ [[[self tab] web_view] findInPagePreviousMatch];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)useSelectionForFind:(id)sender
|
|
|
+{
|
|
|
+ auto selected_text = [[[self tab] web_view] view].selected_text();
|
|
|
+ auto* query = Ladybird::string_to_ns_string(selected_text);
|
|
|
+
|
|
|
+ [self setPasteBoardContents:query];
|
|
|
+
|
|
|
+ if (![self isHidden]) {
|
|
|
+ [self.search_field setStringValue:query];
|
|
|
+ [[[self tab] web_view] findInPage:query];
|
|
|
+
|
|
|
+ [self.window makeFirstResponder:self.search_field];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - Private methods
|
|
|
+
|
|
|
+- (Tab*)tab
|
|
|
+{
|
|
|
+ return (Tab*)[self window];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setPasteBoardContents:(NSString*)query
|
|
|
+{
|
|
|
+ auto* paste_board = [NSPasteboard pasteboardWithName:NSPasteboardNameFind];
|
|
|
+ [paste_board clearContents];
|
|
|
+ [paste_board setString:query forType:NSPasteboardTypeString];
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)setSearchTextFromPasteBoard
|
|
|
+{
|
|
|
+ auto* paste_board = [NSPasteboard pasteboardWithName:NSPasteboardNameFind];
|
|
|
+ auto* query = [paste_board stringForType:NSPasteboardTypeString];
|
|
|
+
|
|
|
+ if (query) {
|
|
|
+ if (![[self.search_field stringValue] isEqual:query]) {
|
|
|
+ [self.search_field setStringValue:query];
|
|
|
+ [[[self tab] web_view] findInPage:query];
|
|
|
+
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return NO;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)cancelSearch:(id)sender
|
|
|
+{
|
|
|
+ [self setHidden:YES];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - NSSearchFieldDelegate
|
|
|
+
|
|
|
+- (void)controlTextDidChange:(NSNotification*)notification
|
|
|
+{
|
|
|
+ auto* query = [self.search_field stringValue];
|
|
|
+ [[[self tab] web_view] findInPage:query];
|
|
|
+
|
|
|
+ [self setPasteBoardContents:query];
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)control:(NSControl*)control
|
|
|
+ textView:(NSTextView*)text_view
|
|
|
+ doCommandBySelector:(SEL)selector
|
|
|
+{
|
|
|
+ if (selector == @selector(insertNewline:)) {
|
|
|
+ NSEvent* event = [[self tab] currentEvent];
|
|
|
+
|
|
|
+ if ((event.modifierFlags & NSEventModifierFlagShift) == 0) {
|
|
|
+ [self findNextMatch:nil];
|
|
|
+ } else {
|
|
|
+ [self findPreviousMatch:nil];
|
|
|
+ }
|
|
|
+
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selector == @selector(cancelOperation:)) {
|
|
|
+ [self cancelSearch:nil];
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NO;
|
|
|
+}
|
|
|
+
|
|
|
+@end
|