diff --git a/src/apps/aboutsystem/AboutSystem.cpp b/src/apps/aboutsystem/AboutSystem.cpp index ca08e8b5b4..062fc7673e 100644 --- a/src/apps/aboutsystem/AboutSystem.cpp +++ b/src/apps/aboutsystem/AboutSystem.cpp @@ -7,6 +7,10 @@ * RenĂ© Gollent */ +#include +#include +#include +#include #include #include @@ -14,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -32,11 +36,13 @@ #include #include +#include +#include #include -#include -#include -#include +#include "HyperTextActions.h" +#include "HyperTextView.h" +#include "Utilities.h" #define SCROLL_CREDITS_VIEW 'mviv' @@ -78,15 +84,20 @@ class AboutView : public BView { virtual void MessageReceived(BMessage *msg); virtual void MouseDown(BPoint pt); + void AddCopyrightEntry(const char *name, const char *text, + const Licenses& licenses, const char *url); void AddCopyrightEntry(const char *name, const char *text, const char *url = NULL); + void AddCopyrightEntry(const BMessage& packageDescription); void PickRandomHaiku(); private: + void _AddCopyrightsFromAttribute(); + BStringView *fMemView; BStringView *fUptimeView; BView *fInfoView; - BTextView *fCreditsView; + HyperTextView *fCreditsView; BBitmap *fLogo; @@ -318,7 +329,7 @@ AboutView::AboutView(const BRect &rect) r.left += fInfoView->Bounds().right + 1; r.right -= B_V_SCROLL_BAR_WIDTH; - fCreditsView = new BTextView(r, "credits", + fCreditsView = new HyperTextView(r, "credits", r.OffsetToCopy(0, 0).InsetByCopy(5, 5), B_FOLLOW_ALL); fCreditsView->SetFlags(fCreditsView->Flags() | B_FRAME_EVENTS ); fCreditsView->SetStylable(true); @@ -352,7 +363,9 @@ AboutView::AboutView(const BRect &rect) fCreditsView->Insert(string); fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kLinkBlue); - fCreditsView->Insert("http://haiku-os.org\n\n"); + fCreditsView->InsertHyperText("http://haiku-os.org", + new URLAction("http://haiku-os.org")); + fCreditsView->Insert("\n\n"); fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuOrange); fCreditsView->Insert("Team Leads:\n"); @@ -507,7 +520,8 @@ AboutView::AboutView(const BRect &rect) "gdb, wget, ncurses, termcap, " "Bourne Again Shell.\n" "Copyright " B_UTF8_COPYRIGHT " The Free Software Foundation.", - "www.gnu.org"); + Licenses("GNU LGPL v2.1", "GNU GPL v2", "GNU GPL v3", NULL), + "http://www.gnu.org"); // FreeBSD copyrights AddCopyrightEntry("The FreeBSD Project", @@ -516,7 +530,7 @@ AboutView::AboutView(const BRect &rect) "ping, telnet, telnetd, traceroute\n" "Copyright " B_UTF8_COPYRIGHT " 1994-2008 The FreeBSD Project. " "All rights reserved.", - "www.freebsd.org"); + "http://www.freebsd.org"); // NetBSD copyrights AddCopyrightEntry("The NetBSD Project", @@ -525,36 +539,36 @@ AboutView::AboutView(const BRect &rect) "ftp\n" "Copyright " B_UTF8_COPYRIGHT " 1996-2008 The NetBSD Foundation, Inc. " "All rights reserved.", - "www.netbsd.org"); + "http://www.netbsd.org"); // FFMpeg copyrights AddCopyrightEntry("FFMpeg libavcodec", "Copyright " B_UTF8_COPYRIGHT " 2000-2007 Fabrice Bellard, et al.", - "www.ffmpeg.org"); + "http://www.ffmpeg.org"); // AGG copyrights AddCopyrightEntry("AntiGrain Geometry", "Copyright " B_UTF8_COPYRIGHT " 2002-2006 Maxim Shemanarev (McSeem).", - "www.antigrain.com"); + "http://www.antigrain.com"); // PDFLib copyrights AddCopyrightEntry("PDFLib", "Copyright " B_UTF8_COPYRIGHT " 1997-2006 PDFlib GmbH and Thomas Merz. " "All rights reserved.\n" "PDFlib and PDFlib logo are registered trademarks of PDFlib GmbH.", - "www.pdflib.com"); + "http://www.pdflib.com"); // FreeType copyrights AddCopyrightEntry("FreeType2", "Portions of this software are copyright " B_UTF8_COPYRIGHT " 1996-2006 " "The FreeType Project. All rights reserved.", - "www.freetype.org"); + "http://www.freetype.org"); // Mesa3D (http://www.mesa3d.org) copyrights AddCopyrightEntry("Mesa", "Copyright " B_UTF8_COPYRIGHT " 1999-2006 Brian Paul. " "Mesa3D project. All rights reserved.", - "www.mesa3d.org"); + "http://www.mesa3d.org"); // SGI's GLU implementation copyrights AddCopyrightEntry("GLU", @@ -619,7 +633,7 @@ AboutView::AboutView(const BRect &rect) // Bullet copyrights AddCopyrightEntry("Bullet", "Copyright " B_UTF8_COPYRIGHT " 2003-2008 Erwin Coumans", - "www.bulletphysics.com"); + "http://www.bulletphysics.com"); // atftp copyrights AddCopyrightEntry("atftp", @@ -673,12 +687,14 @@ AboutView::AboutView(const BRect &rect) AddCopyrightEntry("Xiph.org Foundation", "libvorbis, libogg, libtheora, libspeex" "Copyright " B_UTF8_COPYRIGHT " 1994-2008 Xiph.Org. All rights " - "reserved.", "www.xiph.org"); + "reserved.", "http://www.xiph.org"); // The Tcpdump Group AddCopyrightEntry("The Tcpdump Group", "tcpdump, libpcap", - "www.tcpdump.org"); + "http://www.tcpdump.org"); + + _AddCopyrightsFromAttribute(); // Build a list of installed applications and show their // long version info. Well-behaved apps usually give @@ -848,6 +864,14 @@ AboutView::MessageReceived(BMessage *msg) void AboutView::AddCopyrightEntry(const char *name, const char *text, const char *url) +{ + AddCopyrightEntry(name, text, NULL, url); +} + + +void +AboutView::AddCopyrightEntry(const char *name, const char *text, + const Licenses& licenses, const char *url) { BFont font(be_bold_font); //font.SetSize(be_bold_font->Size()); @@ -859,14 +883,61 @@ AboutView::AddCopyrightEntry(const char *name, const char *text, fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); fCreditsView->Insert(text); fCreditsView->Insert("\n"); + + if (licenses.CountLicenses() > 0) { + if (licenses.CountLicenses() > 1) + fCreditsView->Insert("Licenses: "); + else + fCreditsView->Insert("License: "); + + for (int32 i = 0; i < licenses.CountLicenses(); i++) { + const char* license = licenses.LicenseAt(i); + BString licensePath("/etc/licenses/"); + licensePath += license; + + if (i > 0) + fCreditsView->Insert(", "); + + fCreditsView->InsertHyperText(license, + new OpenFileAction(licensePath)); + } + + fCreditsView->Insert("\n"); + } + if (url) { fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kLinkBlue); - fCreditsView->Insert(url); + fCreditsView->InsertHyperText(url, new URLAction(url)); fCreditsView->Insert("\n"); } fCreditsView->Insert("\n"); } + +void +AboutView::AddCopyrightEntry(const BMessage& packageDescription) +{ + const char* package; + const char* copyright; + const char* url; + + // package and copyright are mandatory + if (packageDescription.FindString("Package", &package) != B_OK + || packageDescription.FindString("Copyright", ©right) != B_OK) { + return; + } + + // URL is optional + if (packageDescription.FindString("URL", &url) != B_OK) + url = NULL; + + BString copyrightLine("Copyright " B_UTF8_COPYRIGHT " "); + copyrightLine += copyright; + + AddCopyrightEntry(package, copyrightLine.String(), packageDescription, url); +} + + void AboutView::PickRandomHaiku() { @@ -918,6 +989,91 @@ AboutView::PickRandomHaiku() } +void +AboutView::_AddCopyrightsFromAttribute() +{ + // open the app executable file + char appPath[B_PATH_NAME_LENGTH]; + int appFD; + if (BPrivate::get_app_path(appPath) != B_OK + || (appFD = open(appPath, O_RDONLY)) < 0) { + return; + } + + // open the attribute + int attrFD = fs_open_attr(appFD, "COPYRIGHTS", B_STRING_TYPE, O_RDONLY); + close(appFD); + if (attrFD < 0) + return; + + // attach it to a FILE + FILE* attrFile = fdopen(attrFD, "r"); + if (attrFile == NULL) { + close(attrFD); + return; + } + CObjectDeleter _(attrFile, fclose); + + // read and parse the copyrights + BMessage package; + BString fieldName; + BString fieldValue; + char lineBuffer[LINE_MAX]; + while (char* line = fgets(lineBuffer, sizeof(lineBuffer), attrFile)) { + // chop off line break + size_t lineLen = strlen(line); + if (lineLen > 0 && line[lineLen - 1] == '\n') + line[--lineLen] = '\0'; + + // flush previous field, if a new field begins, otherwise append + if (lineLen == 0 || !isspace(line[0])) { + // new field -- flush the previous one + if (fieldName.Length() > 0) { + fieldValue = trim_string(fieldValue.String(), + fieldValue.Length()); + package.AddString(fieldName.String(), fieldValue); + fieldName = ""; + } + } else if (fieldName.Length() > 0) { + // append to current field + fieldValue += line; + continue; + } else { + // bogus line -- ignore + continue; + } + + if (lineLen == 0) + continue; + + // parse new field + char* colon = strchr(line, ':'); + if (colon == NULL) { + // bogus line -- ignore + continue; + } + + fieldName.SetTo(line, colon - line); + fieldName = trim_string(line, colon - line); + if (fieldName.Length() == 0) { + // invalid field name + continue; + } + + fieldValue = colon + 1; + + if (fieldName == "Package") { + // flush the current package + AddCopyrightEntry(package); + package.MakeEmpty(); + } + } + + // flush current package + AddCopyrightEntry(package); +} + + // #pragma mark - diff --git a/src/apps/aboutsystem/HyperTextActions.cpp b/src/apps/aboutsystem/HyperTextActions.cpp new file mode 100644 index 0000000000..5c8770ca26 --- /dev/null +++ b/src/apps/aboutsystem/HyperTextActions.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT license. + */ + +#include "HyperTextActions.h" + +#include +#include +#include + + +// #pragma mark - URLAction + + +URLAction::URLAction(const BString& url) + : + fURL(url) +{ +} + + +URLAction::~URLAction() +{ +} + + +void +URLAction::Clicked(HyperTextView* view, BPoint where, BMessage* message) +{ + // be lazy and let /bin/open open the URL + entry_ref ref; + if (get_ref_for_path("/bin/open", &ref)) + return; + + const char* args[] = { "/bin/open", fURL.String(), NULL }; + be_roster->Launch(&ref, 2, args); + +} + + +// #pragma mark - OpenFileAction + + +OpenFileAction::OpenFileAction(const BString& file) + : + fFile(file) +{ +} + + +OpenFileAction::~OpenFileAction() +{ +} + + +void +OpenFileAction::Clicked(HyperTextView* view, BPoint where, BMessage* message) +{ + // get the entry ref and let Tracker open the file + entry_ref ref; + if (get_ref_for_path(fFile.String(), &ref) != B_OK + || !BEntry(&ref).Exists()) { + return; + } + + BMessenger tracker("application/x-vnd.Be-TRAK"); + if (tracker.IsValid()) { + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", &ref); + tracker.SendMessage(&message); + } else + be_roster->Launch(&ref); +} diff --git a/src/apps/aboutsystem/HyperTextActions.h b/src/apps/aboutsystem/HyperTextActions.h new file mode 100644 index 0000000000..4f32b388f0 --- /dev/null +++ b/src/apps/aboutsystem/HyperTextActions.h @@ -0,0 +1,37 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT license. + */ +#ifndef HYPER_TEXT_ACTIONS_H +#define HYPER_TEXT_ACTIONS_H + +#include + +#include "HyperTextView.h" + + +class URLAction : public HyperTextAction { +public: + URLAction(const BString& url); + virtual ~URLAction(); + + virtual void Clicked(HyperTextView* view, BPoint where, + BMessage* message); +private: + BString fURL; +}; + + +class OpenFileAction : public HyperTextAction { +public: + OpenFileAction(const BString& file); + virtual ~OpenFileAction(); + + virtual void Clicked(HyperTextView* view, BPoint where, + BMessage* message); +private: + BString fFile; +}; + + +#endif // HYPER_TEXT_ACTIONS_H diff --git a/src/apps/aboutsystem/HyperTextView.cpp b/src/apps/aboutsystem/HyperTextView.cpp new file mode 100644 index 0000000000..3537c7425c --- /dev/null +++ b/src/apps/aboutsystem/HyperTextView.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT license. + */ + +#include "HyperTextView.h" + +#include +#include +#include + +#include + + +// #pragma mark - HyperTextAction + + +HyperTextAction::HyperTextAction() +{ +} + + +HyperTextAction::~HyperTextAction() +{ +} + + +void +HyperTextAction::Clicked(HyperTextView* view, BPoint where, BMessage* message) +{ +} + + +// #pragma mark - HyperTextView + + +struct HyperTextView::ActionInfo { + ActionInfo(int32 startOffset, int32 endOffset, HyperTextAction* action) + : + startOffset(startOffset), + endOffset(endOffset), + action(action) + { + } + + ~ActionInfo() + { + delete action; + } + + static int Compare(const ActionInfo* a, const ActionInfo* b) + { + return a->startOffset - b->startOffset; + } + + static int CompareEqualIfIntersecting(const ActionInfo* a, + const ActionInfo* b) + { + if (a->startOffset < b->endOffset && b->startOffset < a->endOffset) + return 0; + return a->startOffset - b->startOffset; + } + + int32 startOffset; + int32 endOffset; + HyperTextAction* action; +}; + + + +class HyperTextView::ActionInfoList + : public BObjectList { +public: + ActionInfoList(int32 itemsPerBlock = 20, bool owning = false) + : BObjectList(itemsPerBlock, owning) + { + } +}; + + + +HyperTextView::HyperTextView(BRect frame, const char* name, BRect textRect, + uint32 resizeMask, uint32 flags) + : + BTextView(frame, name, textRect, resizeMask, flags), + fActionInfos(new ActionInfoList(100, true)) +{ +} + + +HyperTextView::~HyperTextView() +{ + delete fActionInfos; +} + + +void +HyperTextView::MouseDown(BPoint where) +{ + // We eat all mouse button events. +} + + +void +HyperTextView::MouseUp(BPoint where) +{ + BMessage* message = Window()->CurrentMessage(); + + int32 offset = OffsetAt(where); + + ActionInfo pointer(offset, offset + 1, NULL); + + const ActionInfo* action = fActionInfos->BinarySearch(pointer, + ActionInfo::CompareEqualIfIntersecting); + if (action != NULL) { + // verify that the text region was hit + BRegion textRegion; + GetTextRegion(action->startOffset, action->endOffset, &textRegion); + if (textRegion.Contains(where)) + action->action->Clicked(this, where, message); + } +} + + +void +HyperTextView::AddHyperTextAction(int32 startOffset, int32 endOffset, + HyperTextAction* action) +{ + if (action == NULL || startOffset >= endOffset) { + delete action; + return; + } + + fActionInfos->BinaryInsert(new ActionInfo(startOffset, endOffset, action), + ActionInfo::Compare); + + // TODO: Of course we should check for overlaps... +} + + +void +HyperTextView::InsertHyperText(const char* inText, HyperTextAction* action, + const text_run_array* inRuns) +{ + int32 startOffset = TextLength(); + Insert(inText, inRuns); + int32 endOffset = TextLength(); + + AddHyperTextAction(startOffset, endOffset, action); +} + + +void +HyperTextView::InsertHyperText(const char* inText, int32 inLength, + HyperTextAction* action, const text_run_array* inRuns) +{ + int32 startOffset = TextLength(); + Insert(inText, inLength, inRuns); + int32 endOffset = TextLength(); + + AddHyperTextAction(startOffset, endOffset, action); +} diff --git a/src/apps/aboutsystem/HyperTextView.h b/src/apps/aboutsystem/HyperTextView.h new file mode 100644 index 0000000000..57f5c1f669 --- /dev/null +++ b/src/apps/aboutsystem/HyperTextView.h @@ -0,0 +1,56 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT license. + */ +#ifndef HYPER_TEXT_VIEW_H +#define HYPER_TEXT_VIEW_H + +#include + + +// TODO: The current implementation works correctly only for insertions at the +// end of the text. It doesn't keep track of any other insertions or deletions. + + +class HyperTextView; + + +class HyperTextAction { +public: + HyperTextAction(); + virtual ~HyperTextAction(); + + virtual void Clicked(HyperTextView* view, BPoint where, + BMessage* message); +}; + + +class HyperTextView : public BTextView { +public: + HyperTextView(BRect frame, const char* name, + BRect textRect, uint32 resizeMask, + uint32 flags = B_WILL_DRAW + | B_PULSE_NEEDED); + virtual ~HyperTextView(); + + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + + void AddHyperTextAction(int32 startOffset, + int32 endOffset, HyperTextAction* action); + + void InsertHyperText(const char* inText, + HyperTextAction* action, + const text_run_array* inRuns = NULL); + void InsertHyperText(const char* inText, + int32 inLength, HyperTextAction* action, + const text_run_array* inRuns = NULL); +private: + struct ActionInfo; + class ActionInfoList; + + ActionInfoList* fActionInfos; +}; + + +#endif // HYPER_TEXT_VIEW_H diff --git a/src/apps/aboutsystem/Jamfile b/src/apps/aboutsystem/Jamfile index e1b1c2dc3e..8e96534955 100644 --- a/src/apps/aboutsystem/Jamfile +++ b/src/apps/aboutsystem/Jamfile @@ -1,6 +1,6 @@ SubDir HAIKU_TOP src apps aboutsystem ; -UsePrivateHeaders shared ; +UsePrivateHeaders app shared ; SetSubDirSupportedPlatformsBeOSCompatible ; @@ -16,6 +16,9 @@ if $(TARGET_PLATFORM) = dano { Application AboutSystem : AboutSystem.cpp + HyperTextActions.cpp + HyperTextView.cpp + Utilities.cpp : be translation : AboutSystem.rdef ; diff --git a/src/apps/aboutsystem/Utilities.cpp b/src/apps/aboutsystem/Utilities.cpp new file mode 100644 index 0000000000..95074afea0 --- /dev/null +++ b/src/apps/aboutsystem/Utilities.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT license. + */ + +#include "Utilities.h" + +#include +#include + +#include + + +BString +trim_string(const char* string, size_t len) +{ + BString trimmed; + size_t i = 0; + bool addSpace = false; + + while (i < len) { + // skip space block + while (i < len && isspace(string[i])) + i++; + + // write non-spaced (if any) + if (i < len && !isspace(string[i])) { + // pad with a single space, for all but the first block + if (addSpace) + trimmed << ' '; + else + addSpace = true; + + // append chars + while (i < len && !isspace(string[i])) + trimmed << string[i++]; + } + } + + return trimmed; +} + + +// #pragma mark - Licenses + + +Licenses::Licenses(const char* license,...) + : + fLicenses(NULL), + fCount(0) +{ + if (license == NULL) + return; + + va_list list; + + // count licenses + va_start(list, license); + fCount = 1; + while (va_arg(list, const char*) != NULL) + fCount++; + va_end(list); + + // create array and copy them + fLicenses = new BString[fCount]; + fLicenses[0] = license; + + va_start(list, license); + for (int32 i = 1; i < fCount; i++) + fLicenses[i] = va_arg(list, const char*); + va_end(list); +} + + +Licenses::Licenses(const BMessage& licenses) + : + fLicenses(NULL), + fCount(0) +{ + type_code type; + int32 count; + if (licenses.GetInfo("License", &type, &count) != B_OK + || type != B_STRING_TYPE) { + return; + } + + fLicenses = new BString[count]; + + for (int32 i = 0; i < count; i++) { + if (licenses.FindString("License", i, &fLicenses[i]) != B_OK) + return; + fCount++; + } +} + + +Licenses::~Licenses() +{ + delete[] fLicenses; +} + + +const char* +Licenses::LicenseAt(int32 index) const +{ + return (index >= 0 && index < fCount ? fLicenses[index].String() : NULL); +} diff --git a/src/apps/aboutsystem/Utilities.h b/src/apps/aboutsystem/Utilities.h new file mode 100644 index 0000000000..62947b982a --- /dev/null +++ b/src/apps/aboutsystem/Utilities.h @@ -0,0 +1,36 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT license. + */ +#ifndef UTILITIES_H +#define UTILITIES_H + +#include + + +class BMessage; + + +BString trim_string(const char* string, size_t len); + // Removes leading and trailing white space and replaces all sequences + // of white space by a single space. + + +class Licenses { +public: + Licenses(const char* license,...); + // NULL terminated list + Licenses(const BMessage& licenses); + // all elements of field "License" + ~Licenses(); + + int32 CountLicenses() const { return fCount; } + const char* LicenseAt(int32 index) const; + +private: + BString* fLicenses; + int32 fCount; +}; + + +#endif // UTILITIES_H