From 5214106737fc1e80c17afa35159ad18a2d6306ab Mon Sep 17 00:00:00 2001 From: Jim906 Date: Wed, 18 Jan 2023 11:47:28 -0500 Subject: [PATCH] WebPositive: draggable page icon * Enable user to drag the WebPositive web page icon to the bookmark bar to create a bookmark there, or drag it to a Tracker window to create a bookmark in the displayed directory. * Send a message to the drag target that can be handled by Tracker's generic drop routine. * Overload _CreateBookmark with a more flexible version and a _CreateBookmark(BMessage*) that handles both the icon being dropped on the bookmark bar, and the message that Tracker sends if the icon is dropped on Tracker. * Account for the fact that, when _CreateBookmark(BMessage*) is called, Tracker may or may not have already determined the file name to use and created the file, depending on whether the icon was dragged to Tracker or not. * Use page-specific small and large icons for the bookmark file, if they are available (currently Haiku WebKit doesn't seem to provide them though). * Follows CharacterMap as a model for dragging an icon and ShowImage for dragging to Tracker. * Fixes #10795. Change-Id: I7f32013cc1372dab1894b5d92335d3a4cbfb671f Reviewed-on: https://review.haiku-os.org/c/haiku/+/6007 Tested-by: Commit checker robot Reviewed-by: Adrien Destugues --- src/apps/webpositive/BrowserWindow.cpp | 294 ++++++++++++++++--------- src/apps/webpositive/BrowserWindow.h | 5 + src/apps/webpositive/URLInputGroup.cpp | 87 +++++++- 3 files changed, 283 insertions(+), 103 deletions(-) diff --git a/src/apps/webpositive/BrowserWindow.cpp b/src/apps/webpositive/BrowserWindow.cpp index 675d1eeea7..e82a9d3f34 100644 --- a/src/apps/webpositive/BrowserWindow.cpp +++ b/src/apps/webpositive/BrowserWindow.cpp @@ -146,6 +146,7 @@ static const char* kHandledProtocols[] = { "gopher" }; +static const char* kBookmarkBarSubdir = B_TRANSLATE("Bookmark bar"); static BLayoutItem* layoutItemFor(BView* view) @@ -500,7 +501,7 @@ BrowserWindow::BrowserWindow(BRect frame, SettingsMessage* appSettings, mainMenu->AddItem(bookmarkMenu); BDirectory barDir(&bookmarkRef); - BEntry bookmarkBar(&barDir, "Bookmark bar"); + BEntry bookmarkBar(&barDir, kBookmarkBarSubdir); entry_ref bookmarkBarRef; // TODO we could also check if the folder is empty here. if (bookmarkBar.Exists() && bookmarkBar.GetRef(&bookmarkBarRef) @@ -966,6 +967,25 @@ BrowserWindow::MessageReceived(BMessage* message) case B_SIMPLE_DATA: { + const char* filetype = message->GetString("be:filetypes"); + if (filetype != NULL + && strcmp(filetype, "application/x-vnd.Be-bookmark") == 0 + && LastMouseMovedView() == fBookmarkBar) { + // Something that can be made into a bookmark (e.g. the page icon) + // was dragged and dropped on the bookmark bar. + BPath path; + if (_BookmarkPath(path) == B_OK && path.Append(kBookmarkBarSubdir) == B_OK) { + entry_ref ref; + if (BEntry(path.Path()).GetRef(&ref) == B_OK) { + message->AddRef("directory", &ref); + // Add under the same name that Tracker would use, if + // the ref had been added by dragging and dropping to Tracker. + _CreateBookmark(message); + } + } + break; + } + // User possibly dropped files on this window. // If there is more than one entry_ref, let the app handle it // (open one new page per ref). If there is one ref, open it in @@ -1195,6 +1215,20 @@ BrowserWindow::MessageReceived(BMessage* message) BWebWindow::MessageReceived(message); break; + case B_COPY_TARGET: + { + const char* filetype = message->GetString("be:filetypes"); + if (filetype != NULL && strcmp(filetype, "application/x-vnd.Be-bookmark") == 0) { + // Tracker replied after the user dragged and dropped something + // that can be bookmarked (e.g. the page icon) to a Tracker window. + _CreateBookmark(message); + break; + } else { + BWebWindow::MessageReceived(message); + break; + } + } + default: BWebWindow::MessageReceived(message); break; @@ -1900,76 +1934,69 @@ BrowserWindow::_BookmarkPath(BPath& path) const return create_directory(path.Path(), 0777); } - +/*! If fileName is an empty BString, a valid file name will be derived from title. + miniIcon and largeIcon may be NULL. +*/ void -BrowserWindow::_CreateBookmark() +BrowserWindow::_CreateBookmark(const BPath& path, BString fileName, const BString& title, + const BString& url, const BBitmap* miniIcon, const BBitmap* largeIcon) { - BPath path; - status_t status = _BookmarkPath(path); - if (status != B_OK) { - BString message(B_TRANSLATE_COMMENT("There was an error retrieving " - "the bookmark folder.\n\nError: %error", "Don't translate the " - "variable %error")); - message.ReplaceFirst("%error", strerror(status)); - BAlert* alert = new BAlert(B_TRANSLATE("Bookmark error"), - message.String(), B_TRANSLATE("OK"), NULL, NULL, - B_WIDTH_AS_USUAL, B_STOP_ALERT); - alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); - alert->Go(); - return; - } - BWebView* webView = CurrentWebView(); - BString url(webView->MainFrameURL()); - // Create a bookmark file - BFile bookmarkFile; - BString bookmarkName(webView->MainFrameTitle()); - if (bookmarkName.Length() == 0) { - bookmarkName = url; - int32 leafPos = bookmarkName.FindLast('/'); - if (leafPos >= 0) - bookmarkName.Remove(0, leafPos + 1); - } - // Make sure the bookmark title does not contain chars that are not - // allowed in file names, and is within allowed name length. - bookmarkName.ReplaceAll('/', '-'); - bookmarkName.Truncate(B_FILE_NAME_LENGTH - 1); - - // Check that the bookmark exists nowhere in the bookmark hierarchy, - // though the intended file name must match, we don't search the stored - // URLs, only for matching file names. - BDirectory directory(path.Path()); - if (status == B_OK && _CheckBookmarkExists(directory, bookmarkName, url)) { - BString message(B_TRANSLATE_COMMENT("A bookmark for this page " - "(%bookmarkName) already exists.", "Don't translate variable " - "%bookmarkName")); - message.ReplaceFirst("%bookmarkName", bookmarkName); - BAlert* alert = new BAlert(B_TRANSLATE("Bookmark info"), - message.String(), B_TRANSLATE("OK")); - alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); - alert->Go(); - return; + // Determine the file name if one was not provided + bool presetFileName = true; + if (fileName.IsEmpty() == true) { + presetFileName = false; + fileName = title; + if (fileName.Length() == 0) { + fileName = url; + int32 leafPos = fileName.FindLast('/'); + if (leafPos >= 0) + fileName.Remove(0, leafPos + 1); + } + fileName.ReplaceAll('/', '-'); + fileName.Truncate(B_FILE_NAME_LENGTH - 1); } BPath entryPath(path); - status = entryPath.Append(bookmarkName); + status_t status = entryPath.Append(fileName); BEntry entry; if (status == B_OK) status = entry.SetTo(entryPath.Path(), true); - if (status == B_OK) { - int32 tries = 1; - while (entry.Exists()) { - // Find a unique name for the bookmark, there may still be a - // file in the way that stores a different URL. - bookmarkName = webView->MainFrameTitle(); - bookmarkName << " " << tries++; - entryPath = path; - status = entryPath.Append(bookmarkName); - if (status == B_OK) - status = entry.SetTo(entryPath.Path(), true); - if (status != B_OK) - break; + + // There are several reasons why an entry matching the path argument could already exist. + if (status == B_OK && entry.Exists() == true) { + off_t size; + entry.GetSize(&size); + char attrName[B_ATTR_NAME_LENGTH]; + BNode node(&entry); + status_t attrStatus = node.GetNextAttrName(attrName); + if (strcmp(attrName, "_trk/pinfo_le") == 0) + attrStatus = node.GetNextAttrName(attrName); + + if (presetFileName == true && size == 0 && attrStatus == B_ENTRY_NOT_FOUND) { + // Tracker's drag-and-drop routine created an empty entry for us to fill in. + // Go ahead and write to the existing entry. + } else { + BDirectory directory(path.Path()); + if (_CheckBookmarkExists(directory, fileName, url) == true) { + // The existing entry is a bookmark with the same URL. No further action needed. + return; + } else { + // Find a unique name for the bookmark. + int32 tries = 1; + while (entry.Exists()) { + fileName << " " << tries++; + entryPath = path; + status = entryPath.Append(fileName); + if (status == B_OK) + status = entry.SetTo(entryPath.Path(), true); + if (status != B_OK) + break; + } + } } } + + BFile bookmarkFile; if (status == B_OK) { status = bookmarkFile.SetTo(&entry, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY); @@ -1979,33 +2006,33 @@ BrowserWindow::_CreateBookmark() if (status == B_OK) status = bookmarkFile.WriteAttrString("META:url", &url); if (status == B_OK) { - BString title = webView->MainFrameTitle(); bookmarkFile.WriteAttrString("META:title", &title); } BNodeInfo nodeInfo(&bookmarkFile); if (status == B_OK) { status = nodeInfo.SetType("application/x-vnd.Be-bookmark"); + // Replace the standard Be-bookmark file icons with the argument icons, + // if any were provided. if (status == B_OK) { - PageUserData* userData = static_cast( - webView->GetUserData()); - if (userData != NULL && userData->PageIcon() != NULL) { - BBitmap miniIcon(BRect(0, 0, 15, 15), B_BITMAP_NO_SERVER_LINK, - B_CMAP8); - status_t ret = miniIcon.ImportBits(userData->PageIcon()); - if (ret == B_OK) - ret = nodeInfo.SetIcon(&miniIcon, B_MINI_ICON); + status_t ret = B_OK; + if (miniIcon != NULL) { + ret = nodeInfo.SetIcon(miniIcon, B_MINI_ICON); if (ret != B_OK) { fprintf(stderr, "Failed to store mini icon for bookmark: " "%s\n", strerror(ret)); } - BBitmap largeIcon(BRect(0, 0, 31, 31), B_BITMAP_NO_SERVER_LINK, + } + if (largeIcon != NULL && ret == B_OK) + ret = nodeInfo.SetIcon(largeIcon, B_LARGE_ICON); + else if (largeIcon == NULL && miniIcon != NULL && ret == B_OK) { + // If largeIcon is not available but miniIcon is, use a magnified miniIcon instead. + BBitmap substituteLargeIcon(BRect(0, 0, 31, 31), B_BITMAP_NO_SERVER_LINK, B_CMAP8); - // TODO: Store 32x32 favicon which is often provided by sites. - const uint8* src = (const uint8*)miniIcon.Bits(); - uint32 srcBPR = miniIcon.BytesPerRow(); - uint8* dst = (uint8*)largeIcon.Bits(); - uint32 dstBPR = largeIcon.BytesPerRow(); + const uint8* src = (const uint8*)miniIcon->Bits(); + uint32 srcBPR = miniIcon->BytesPerRow(); + uint8* dst = (uint8*)substituteLargeIcon.Bits(); + uint32 dstBPR = substituteLargeIcon.BytesPerRow(); for (uint32 y = 0; y < 16; y++) { const uint8* s = src; uint8* d = dst; @@ -2022,12 +2049,12 @@ BrowserWindow::_CreateBookmark() dst += dstBPR; src += srcBPR; } - if (ret == B_OK) - ret = nodeInfo.SetIcon(&largeIcon, B_LARGE_ICON); - if (ret != B_OK) { - fprintf(stderr, "Failed to store large icon for bookmark: " - "%s\n", strerror(ret)); - } + ret = nodeInfo.SetIcon(&substituteLargeIcon, B_LARGE_ICON); + } else + ret = B_OK; + if (ret != B_OK) { + fprintf(stderr, "Failed to store large icon for bookmark: " + "%s\n", strerror(ret)); } } } @@ -2047,6 +2074,83 @@ BrowserWindow::_CreateBookmark() } +void +BrowserWindow::_CreateBookmark(BMessage* message) +{ + entry_ref ref; + BMessage originatorData; + const char* url; + const char* title; + bool validData = (message->FindRef("directory", &ref) == B_OK + && message->FindMessage("be:originator-data", &originatorData) == B_OK + && originatorData.FindString("url", &url) == B_OK + && originatorData.FindString("title", &title) == B_OK); + + // Optional data + const char* fileName; + if (message->FindString("name", &fileName) != B_OK) { + // This string is only present if the message originated from Tracker (drag and drop). + fileName = ""; + } + const BBitmap* miniIcon = NULL; + const BBitmap* largeIcon = NULL; + originatorData.FindData("miniIcon", B_COLOR_8_BIT_TYPE, + reinterpret_cast(&miniIcon), NULL); + originatorData.FindData("largeIcon", B_COLOR_8_BIT_TYPE, + reinterpret_cast(&miniIcon), NULL); + + if (validData == true) { + _CreateBookmark(BPath(&ref), BString(fileName), BString(title), BString(url), + miniIcon, largeIcon); + } else { + BString message(B_TRANSLATE("There was an error setting up " + "the bookmark.")); + BAlert* alert = new BAlert(B_TRANSLATE("Bookmark error"), + message.String(), B_TRANSLATE("OK"), NULL, NULL, + B_WIDTH_AS_USUAL, B_STOP_ALERT); + alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); + alert->Go(); + } + return; +} + + +void +BrowserWindow::_CreateBookmark() +{ + BString fileName; + // A name will be derived from the title later. + BString title(CurrentWebView()->MainFrameTitle()); + BString url(CurrentWebView()->MainFrameURL()); + BPath path; + status_t status = _BookmarkPath(path); + + BBitmap* miniIcon = NULL; + BBitmap* largeIcon = NULL; + PageUserData* userData = static_cast(CurrentWebView()->GetUserData()); + if (userData != NULL && userData->PageIcon() != NULL) { + miniIcon = new BBitmap(BRect(0, 0, 15, 15), B_BITMAP_NO_SERVER_LINK, B_CMAP8); + miniIcon->ImportBits(userData->PageIcon()); + // TODO: retrieve the large icon too, once PageUserData can provide it. + } + + if (status == B_OK) + _CreateBookmark(path, fileName, title, url, miniIcon, largeIcon); + else { + BString message(B_TRANSLATE_COMMENT("There was an error retrieving " + "the bookmark folder.\n\nError: %error", "Don't translate the " + "variable %error")); + message.ReplaceFirst("%error", strerror(status)); + BAlert* alert = new BAlert(B_TRANSLATE("Bookmark error"), + message.String(), B_TRANSLATE("OK"), NULL, NULL, + B_WIDTH_AS_USUAL, B_STOP_ALERT); + alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); + alert->Go(); + } + return; +} + + void BrowserWindow::_ShowBookmarks() { @@ -2078,25 +2182,15 @@ bool BrowserWindow::_CheckBookmarkExists(BDirectory& directory, { BEntry entry; while (directory.GetNextEntry(&entry) == B_OK) { - if (entry.IsDirectory()) { - BDirectory subBirectory(&entry); - // At least preserve the entry file handle when recursing into - // sub-folders... eventually we will run out, though, with very - // deep hierarchy. - entry.Unset(); - if (_CheckBookmarkExists(subBirectory, bookmarkName, url)) + char entryName[B_FILE_NAME_LENGTH]; + if (entry.GetName(entryName) != B_OK || bookmarkName != entryName) + continue; + BString storedURL; + BFile file(&entry, B_READ_ONLY); + if (_ReadURLAttr(file, storedURL)) { + // Just bail if the bookmark already exists + if (storedURL == url) return true; - } else { - char entryName[B_FILE_NAME_LENGTH]; - if (entry.GetName(entryName) != B_OK || bookmarkName != entryName) - continue; - BString storedURL; - BFile file(&entry, B_READ_ONLY); - if (_ReadURLAttr(file, storedURL)) { - // Just bail if the bookmark already exists - if (storedURL == url) - return true; - } } } return false; diff --git a/src/apps/webpositive/BrowserWindow.h b/src/apps/webpositive/BrowserWindow.h index 1a296dcd61..c2c6303f48 100644 --- a/src/apps/webpositive/BrowserWindow.h +++ b/src/apps/webpositive/BrowserWindow.h @@ -179,6 +179,11 @@ private: void _TabChanged(int32 index); status_t _BookmarkPath(BPath& path) const; + void _CreateBookmark(const BPath& path, + BString fileName, const BString& title, + const BString& url, const BBitmap* miniIcon, + const BBitmap* largeIcon); + void _CreateBookmark(BMessage* message); void _CreateBookmark(); void _ShowBookmarks(); bool _CheckBookmarkExists(BDirectory& directory, diff --git a/src/apps/webpositive/URLInputGroup.cpp b/src/apps/webpositive/URLInputGroup.cpp index 155dce32ac..d7023b6313 100644 --- a/src/apps/webpositive/URLInputGroup.cpp +++ b/src/apps/webpositive/URLInputGroup.cpp @@ -25,10 +25,13 @@ #include "BaseURL.h" #include "BitmapButton.h" +#include "BrowserWindow.h" #include "BrowsingHistory.h" #include "IconButton.h" #include "IconUtils.h" #include "TextViewCompleter.h" +#include "WebView.h" +#include "WebWindow.h" #undef B_TRANSLATION_CONTEXT @@ -499,7 +502,9 @@ public: PageIconView() : BView("page icon view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), - fIcon(NULL) + fIcon(NULL), + fClickPoint(-1, 0), + fPageIconSet(false) { SetDrawingMode(B_OP_ALPHA); SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); @@ -539,21 +544,97 @@ public: return MinSize(); } + void MouseDown(BPoint where) + { + int32 buttons; + if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) == B_OK) { + if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) { + // Memorize click point for dragging + fClickPoint = where; + } + } + return; + } + + void MouseUp(BPoint where) + { + fClickPoint.x = -1; + } + + virtual void MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) + { + if (dragMessage != NULL) + return; + + if (fClickPoint.x >= 0 + && (fabs(where.x - fClickPoint.x) > 4 || fabs(where.y - fClickPoint.y) > 4)) { + // Start dragging + BPoint offset = fClickPoint - Frame().LeftTop(); + + const char* url = static_cast(Parent())->Text(); + const char* title = + static_cast(Window())->CurrentWebView()->MainFrameTitle(); + + // Validate the file name to be set for the clipping if user drags to Tracker. + BString fileName(title); + if (fileName.Length() == 0) { + fileName = url; + int32 leafPos = fileName.FindLast('/'); + if (leafPos >= 0) + fileName.Remove(0, leafPos + 1); + } + fileName.ReplaceAll('/', '-'); + fileName.Truncate(B_FILE_NAME_LENGTH - 1); + + BBitmap miniIcon(BRect(0, 0, 15, 15), B_BITMAP_NO_SERVER_LINK, + B_CMAP8); + miniIcon.ImportBits(fIcon); + // TODO: obtain and send the large icon in addition to the mini icon. + // Currently PageUserData does not provide a function that returns this. + + BMessage drag(B_SIMPLE_DATA); + drag.AddInt32("be:actions", B_COPY_TARGET); + drag.AddString("be:clip_name", fileName.String()); + drag.AddString("be:filetypes", "application/x-vnd.Be-bookmark"); + // Support the "Passing Data via File" protocol + drag.AddString("be:types", B_FILE_MIME_TYPE); + BMessage data(B_SIMPLE_DATA); + data.AddString("url", url); + data.AddString("title", title); + // The title may differ from the validated filename + if (fPageIconSet == true) { + // Don't bother sending the placeholder web icon, if that is all we have. + data.AddData("miniIcon", B_COLOR_8_BIT_TYPE, &miniIcon, sizeof(miniIcon)); + } + drag.AddMessage("be:originator-data", &data); + + BBitmap* iconClone = new BBitmap(fIcon); + // Needed because DragMessage will delete the bitmap when it's done. + + DragMessage(&drag, iconClone, B_OP_ALPHA, offset); + } + return; + } + void SetIcon(const BBitmap* icon) { delete fIcon; - if (icon) + if (icon) { fIcon = new BBitmap(icon); - else { + fPageIconSet = true; + } else { fIcon = new BBitmap(BRect(0, 0, 15, 15), B_RGB32); BIconUtils::GetVectorIcon(kPlaceholderIcon, sizeof(kPlaceholderIcon), fIcon); + fPageIconSet = false; } Invalidate(); } private: BBitmap* fIcon; + BPoint fClickPoint; + bool fPageIconSet; };