Chat-O-Matic/libs/librunview/RunView.cpp

392 lines
8.4 KiB
C++
Raw Normal View History

/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
2021-06-07 13:34:20 -05:00
#include "RunView.h"
2021-07-14 20:08:14 -05:00
#include <Cursor.h>
2021-07-14 22:10:23 -05:00
#include <Locale.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <ScrollBar.h>
#include <TextView.h>
2021-07-14 20:08:14 -05:00
#include <Window.h>
2021-06-07 13:34:20 -05:00
2021-07-14 22:10:23 -05:00
const uint32 kSearchDdg = 'RVse';
const uint32 kSearchDict = 'RVdc';
RunView::RunView(const char* name)
2012-10-18 16:04:45 -05:00
:
BTextView(name),
2021-07-14 20:08:14 -05:00
fLastStyled(false),
fUrlCursor(new BCursor(B_CURSOR_ID_FOLLOW_LINK)),
fMouseDown(false),
fSelecting(false)
{
MakeEditable(false);
SetStylable(true);
AdoptSystemColors();
2021-07-14 20:08:14 -05:00
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
text_run run = { 0, BFont(), ui_color(B_PANEL_TEXT_COLOR) };
fDefaultRun = { 1, {run} };
2021-07-14 15:13:36 -05:00
BFont urlFont;
urlFont.SetFace(B_UNDERSCORE_FACE);
text_run urlRun = { 0, urlFont, ui_color(B_LINK_TEXT_COLOR) };
fUrlRun = { 1, {urlRun} };
text_run urlHoverRun = { 0, urlFont, ui_color(B_LINK_HOVER_COLOR) };
fUrlHoverRun = { 1, {urlHoverRun} };
text_run urlVisitedRun = { 0, urlFont, ui_color(B_LINK_VISITED_COLOR) };
fUrlVisitedRun = { 1, {urlVisitedRun} };
}
2021-06-07 13:34:20 -05:00
void
2021-07-14 22:10:23 -05:00
RunView::MessageReceived(BMessage* msg)
{
2021-07-14 22:10:23 -05:00
switch (msg->what)
{
case kSearchDdg:
case kSearchDict:
{
int32 start = 0;
int32 end = 0;
GetSelection(&start, &end);
if (start == end)
break;
char* buffer = new char[end - start];
GetText(start, end - start, buffer);
// Build query
BString query;
if (msg->what == kSearchDict)
query = "https://%lang%.wiktionary.org/w/index.php?search=%q%";
else
query = "https://duckduckgo.com/?q=%q%";
BLanguage lang;
if (BLocale().GetLanguage(&lang) == B_OK)
query.ReplaceAll("%lang%", lang.Code());
else
query.ReplaceAll("%lang", "eo");
query.ReplaceAll("%q%", BUrl::UrlEncode(BString(buffer)));
// Send query
BUrl url(query.String());
if (url.IsValid())
url.OpenWithPreferredApplication(true);
break;
}
default:
BTextView::MessageReceived(msg);
}
}
2021-07-14 15:13:36 -05:00
void
RunView::Insert(const char* text, const text_run_array* runs)
{
BString buf(text);
int32 specStart = 0;
int32 specEnd = 0;
2021-07-14 15:13:36 -05:00
int32 lastEnd = 0;
int32 length = buf.CountChars();
2021-07-14 15:13:36 -05:00
while (_FindUrlString(buf, &specStart, &specEnd, lastEnd) == true) {
if (lastEnd < specStart) {
BString normie;
buf.CopyCharsInto(normie, lastEnd, specStart - lastEnd);
BTextView::Insert(normie.String(), runs);
}
BString special;
buf.CopyCharsInto(special, specStart, specEnd - specStart);
BTextView::Insert(special.String(), &fUrlRun);
2021-07-14 15:13:36 -05:00
lastEnd = specEnd;
}
if (lastEnd < length) {
BString remaining;
buf.CopyCharsInto(remaining, lastEnd, length - lastEnd);
BTextView::Insert(remaining.String(), runs);
2021-07-14 15:13:36 -05:00
}
}
2021-07-14 20:08:14 -05:00
void
RunView::MouseDown(BPoint where)
{
uint32 buttons = 0;
Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
2021-07-14 22:10:23 -05:00
if (buttons & B_SECONDARY_MOUSE_BUTTON)
_RightClickPopUp(where)->Go(ConvertToScreen(where), true, false);
else
2021-07-14 20:08:14 -05:00
BTextView::MouseDown(where);
if ((buttons & B_PRIMARY_MOUSE_BUTTON) && OverUrl(where)) {
2021-07-14 20:08:14 -05:00
fMouseDown = true;
BUrl url(WordAt(where));
if (url.IsValid() == true) {
2021-07-14 20:08:14 -05:00
fLastClicked = url;
}
2021-07-14 20:08:14 -05:00
}
}
void
RunView::MouseUp(BPoint where)
{
BTextView::MouseUp(where);
if (fMouseDown && fSelecting == false && fLastClicked.IsValid() == true) {
fLastClicked.OpenWithPreferredApplication(true);
fLastClicked = BUrl();
// When cursor moves off URL, change color
fCurrentUrlRuns = fUrlVisitedRun;
2021-07-14 20:08:14 -05:00
}
fMouseDown = false;
fSelecting = false;
}
void
RunView::MouseMoved(BPoint where, uint32 code, const BMessage* drag)
{
if (fSelecting == true)
return;
// Change the cursor and "hover-over" highlight for URLs
2021-07-14 20:08:14 -05:00
if (code == B_INSIDE_VIEW)
if (OverUrl(where) == true) {
int32 start = 0;
int32 end = 0;
FindWordAround(OffsetAt(where), &start, &end);
if (fCurrentUrlEnd == 0) {
fCurrentUrlRuns = *RunArray(start, end);
fCurrentUrlStart = start;
fCurrentUrlEnd = end;
ReplaceRuns(start, end, &fUrlHoverRun);
}
2021-07-14 20:08:14 -05:00
SetViewCursor(fUrlCursor);
}
else {
if (fCurrentUrlEnd != 0) {
ReplaceRuns(fCurrentUrlStart, fCurrentUrlEnd, &fCurrentUrlRuns);
fCurrentUrlStart = 0;
fCurrentUrlEnd = 0;
}
2021-07-14 20:08:14 -05:00
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
}
2021-07-14 20:08:14 -05:00
}
void
RunView::Select(int32 startOffset, int32 endOffset)
{
BTextView::Select(startOffset, endOffset);
if (startOffset < endOffset) {
fSelecting = true;
}
}
2021-07-14 22:10:23 -05:00
void
RunView::Append(const char* text, rgb_color color, uint16 fontFace)
{
BFont font;
font.SetFace(fontFace);
text_run run = { 0, font, color };
text_run_array array = { 1, {run} };
Insert(text, &array);
fLastStyled = true;
}
void
RunView::Append(const char* text)
{
if (fLastStyled == false)
Insert(text);
else
Insert(text, &fDefaultRun);
fLastStyled = false;
}
void
RunView::Replace(int32 start, int32 end, const char* text, text_run_array* runs)
{
Delete(start, end);
BTextView::Insert(start, text, strlen(text), runs);
}
void
RunView::ReplaceRuns(int32 start, int32 end, text_run_array* runs)
{
char* buffer = new char[end - start];
GetText(start, end - start, buffer);
float current = ScrollBar(B_VERTICAL)->Value();
Replace(start, end, buffer, runs);
ScrollBar(B_VERTICAL)->SetValue(current);
}
2021-07-14 20:08:14 -05:00
BString
RunView::WordAt(BPoint point)
{
2021-07-14 22:10:23 -05:00
int32 start = 0;
int32 end = 0;
BString word;
FindWordAround(OffsetAt(point), &start, &end, &word);
return word;
}
void
RunView::FindWordAround(int32 offset, int32* start, int32* end, BString* _word)
{
int32 lineOffset = OffsetAt(LineAt(offset));
const char* lineBuff = GetLine(LineAt(offset));
2021-07-14 20:08:14 -05:00
BString line(lineBuff);
delete lineBuff;
2021-07-14 22:10:23 -05:00
int32 wordStart = line.FindLast(" ", offset - lineOffset) + 1;
int32 wordEnd = line.FindFirst(" ", offset - lineOffset);
2021-07-14 20:08:14 -05:00
if (wordStart == B_ERROR)
wordStart = 0;
if (wordEnd == B_ERROR)
wordEnd = line.CountChars();
2021-07-14 22:10:23 -05:00
*start = lineOffset + wordStart;
*end = lineOffset + wordEnd;
if (_word != NULL)
line.CopyCharsInto(*_word, wordStart, wordEnd - wordStart);
2021-07-14 20:08:14 -05:00
}
const char*
RunView::GetLine(int32 line)
{
int32 length = 0;
int32 startOffset = OffsetAt(line);
int32 maxLength = TextLength() - startOffset;
while (length < maxLength && ByteAt(startOffset + length) != '\n')
length++;
char* buffer = new char[length];
GetText(startOffset, length, buffer);
return buffer;
}
bool
RunView::OverText(BPoint where)
{
int32 offset = OffsetAt(where);
BPoint point = PointAt(offset);
if (point.x + 10 < where.x)
return false;
return true;
}
bool
RunView::OverUrl(BPoint where)
2021-07-14 20:08:14 -05:00
{
if (OverText(where) == false)
return false;
int32 offset = OffsetAt(where);
text_run_array* rArray = RunArray(offset, offset + 1);
text_run run = rArray->runs[0];
text_run urlRun = fUrlRun.runs[0];
text_run urlHover = fUrlHoverRun.runs[0];
text_run urlVisit = fUrlVisitedRun.runs[0];
if ((run.font == urlRun.font || run.font == urlHover.font
|| run.font == urlVisit.font)
&& (run.color == urlRun.color || run.color == urlHover.color
|| run.color == urlVisit.color))
2021-07-14 20:08:14 -05:00
return true;
return false;
}
void
RunView::ScrollToBottom()
{
ScrollToOffset(TextLength());
}
2021-07-14 22:10:23 -05:00
BPopUpMenu*
RunView::_RightClickPopUp(BPoint where)
{
BPopUpMenu* menu = new BPopUpMenu("rightClickPopUp");
BMenuItem* ddgSearch =
new BMenuItem("Search" B_UTF8_ELLIPSIS, new BMessage(kSearchDdg));
BMenuItem* dictSearch =
new BMenuItem("Dictionary" B_UTF8_ELLIPSIS, new BMessage(kSearchDict));
BMenuItem* copy =
new BMenuItem("Copy", new BMessage(B_COPY), 'C', B_COMMAND_KEY);
BMenuItem* selectAll = new BMenuItem("Select all",
new BMessage(B_SELECT_ALL), 'A', B_COMMAND_KEY);
// Try and ensure we have something selected
int32 start = 0;
int32 end = 0;
GetSelection(&start, &end);
if (start == end && OverText(where) == true) {
start = 0;
end = 0;
FindWordAround(OffsetAt(where), &start, &end);
Select(start, end);
}
copy->SetEnabled(start < end);
dictSearch->SetEnabled(start < end);
ddgSearch->SetEnabled(start < end);
menu->AddItem(ddgSearch);
menu->AddItem(dictSearch);
menu->AddSeparatorItem();
menu->AddItem(copy);
menu->AddItem(selectAll);
menu->SetTargetForItems(this);
return menu;
}
bool
RunView::_FindUrlString(BString text, int32* start, int32* end, int32 offset)
{
int32 urlOffset = text.FindFirst("://", offset);
int32 urlStart = text.FindLast(" ", urlOffset) + 1;
int32 urlEnd = text.FindFirst(" ", urlOffset);
if (urlStart == B_ERROR) urlStart = 0;
if (urlEnd == B_ERROR) urlEnd = text.CountChars();
if (urlOffset != B_ERROR) {
*start = urlStart;
*end = urlEnd;
return true;
}
return false;
}