summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-11-23 15:27:58 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-11-23 15:27:58 +0200
commit678ed94bf55bc15592d14aed1ace04863e5483d1 (patch)
tree4301f4ac3d7bc48db4e8c45f6646205418c679ed
parente509af0fc8eee4b03766ec5c1f4faf6dae7f41b4 (diff)
Sidebar: Populate with feed entries
-rw-r--r--src/feeds.c78
-rw-r--r--src/macos.m2
-rw-r--r--src/ui/sidebarwidget.c56
-rw-r--r--src/ui/sidebarwidget.h1
-rw-r--r--src/ui/window.c10
5 files changed, 113 insertions, 34 deletions
diff --git a/src/feeds.c b/src/feeds.c
index cbfc36d0..5034c616 100644
--- a/src/feeds.c
+++ b/src/feeds.c
@@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
35#include <the_Foundation/stringset.h> 35#include <the_Foundation/stringset.h>
36#include <the_Foundation/thread.h> 36#include <the_Foundation/thread.h>
37#include <SDL_timer.h> 37#include <SDL_timer.h>
38#include <ctype.h>
38 39
39iDeclareType(Feeds) 40iDeclareType(Feeds)
40iDeclareType(FeedJob) 41iDeclareType(FeedJob)
@@ -109,12 +110,24 @@ static void submit_FeedJob_(iFeedJob *d) {
109} 110}
110 111
111static iFeedJob *startNextJob_Feeds_(iFeeds *d) { 112static iFeedJob *startNextJob_Feeds_(iFeeds *d) {
113 if (isEmpty_PtrArray(&d->jobs)) {
114 return NULL;
115 }
112 iFeedJob *job; 116 iFeedJob *job;
113 if (take_PtrArray(&d->jobs, 0, (void **) &job)) { 117 take_PtrArray(&d->jobs, 0, (void **) &job);
114 submit_FeedJob_(job); 118 submit_FeedJob_(job);
115 return job; 119 return job;
120}
121
122static void trimTitle_(iString *title) {
123 const char *start = constBegin_String(title);
124 iConstForEach(String, i, title) {
125 start = i.pos;
126 if (!isSpace_Char(i.value) && !(i.value < 128 && ispunct(i.value))) {
127 break;
128 }
116 } 129 }
117 return NULL; 130 remove_Block(&title->chars, 0, start - constBegin_String(title));
118} 131}
119 132
120static void parseResult_FeedJob_(iFeedJob *d) { 133static void parseResult_FeedJob_(iFeedJob *d) {
@@ -123,7 +136,7 @@ static void parseResult_FeedJob_(iFeedJob *d) {
123 iBeginCollect(); 136 iBeginCollect();
124 iRegExp *linkPattern = 137 iRegExp *linkPattern =
125 new_RegExp("^=>\\s*([^\\s]+)\\s+" 138 new_RegExp("^=>\\s*([^\\s]+)\\s+"
126 "([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])" 139 "([0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9])"
127 "([^0-9].*)", 140 "([^0-9].*)",
128 0); 141 0);
129 iString src; 142 iString src;
@@ -141,8 +154,11 @@ static void parseResult_FeedJob_(iFeedJob *d) {
141 iFeedEntry *entry = new_FeedEntry(); 154 iFeedEntry *entry = new_FeedEntry();
142 entry->bookmarkId = d->bookmarkId; 155 entry->bookmarkId = d->bookmarkId;
143 setRange_String(&entry->url, url); 156 setRange_String(&entry->url, url);
144 set_String(&entry->url, collect_String(lower_String(&entry->url))); 157 set_String(&entry->url,
158 absoluteUrl_String(url_GmRequest(d->request),
159 collect_String(lower_String(&entry->url))));
145 setRange_String(&entry->title, title); 160 setRange_String(&entry->title, title);
161 trimTitle_(&entry->title);
146 int year, month, day; 162 int year, month, day;
147 sscanf(date.start, "%04d-%02d-%02d", &year, &month, &day); 163 sscanf(date.start, "%04d-%02d-%02d", &year, &month, &day);
148 init_Time( 164 init_Time(
@@ -177,7 +193,7 @@ static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) {
177 delete_FeedEntry(entry); 193 delete_FeedEntry(entry);
178 if (changed) { 194 if (changed) {
179 /* TODO: better to use a new flag for read feed entries? */ 195 /* TODO: better to use a new flag for read feed entries? */
180 removeUrl_Visited(visited_App(), &entry->url); 196 removeUrl_Visited(visited_App(), &existing->url);
181 gotNew = iTrue; 197 gotNew = iTrue;
182 } 198 }
183 } 199 }
@@ -206,6 +222,7 @@ static iThreadResult fetch_Feeds_(iThread *thread) {
206 } 222 }
207 } 223 }
208 sleep_Thread(0.5); /* TODO: wait on a Condition so we can exit quickly */ 224 sleep_Thread(0.5); /* TODO: wait on a Condition so we can exit quickly */
225 if (d->stopWorker) break;
209 size_t ongoing = 0; 226 size_t ongoing = 0;
210 iForIndices(i, work) { 227 iForIndices(i, work) {
211 if (work[i]) { 228 if (work[i]) {
@@ -241,16 +258,18 @@ static iBool startWorker_Feeds_(iFeeds *d) {
241 if (d->worker) { 258 if (d->worker) {
242 return iFalse; /* Oops? */ 259 return iFalse; /* Oops? */
243 } 260 }
244 d->worker = new_Thread(fetch_Feeds_);
245 /* Queue up all the subscriptions for the worker. */ 261 /* Queue up all the subscriptions for the worker. */
246 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, isSubscribed_, NULL)) { 262 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, isSubscribed_, NULL)) {
247 iFeedJob job; 263 iFeedJob* job = new_FeedJob(i.ptr);
248 init_FeedJob(&job, i.ptr); 264 pushBack_PtrArray(&d->jobs, job);
249 pushBack_Array(&d->jobs, &job); 265 }
266 if (!isEmpty_Array(&d->jobs)) {
267 d->worker = new_Thread(fetch_Feeds_);
268 d->stopWorker = iFalse;
269 start_Thread(d->worker);
270 return iTrue;
250 } 271 }
251 d->stopWorker = iFalse; 272 return iFalse;
252 start_Thread(d->worker);
253 return iTrue;
254} 273}
255 274
256static uint32_t refresh_Feeds_(uint32_t interval, void *data) { 275static uint32_t refresh_Feeds_(uint32_t interval, void *data) {
@@ -265,7 +284,11 @@ static void stopWorker_Feeds_(iFeeds *d) {
265 join_Thread(d->worker); 284 join_Thread(d->worker);
266 iReleasePtr(&d->worker); 285 iReleasePtr(&d->worker);
267 } 286 }
268 /* TODO: Clear jobs */ 287 /* Clear remaining jobs. */
288 iForEach(PtrArray, i, &d->jobs) {
289 delete_FeedJob(i.ptr);
290 }
291 clear_PtrArray(&d->jobs);
269} 292}
270 293
271static int cmp_FeedEntryPtr_(const void *a, const void *b) { 294static int cmp_FeedEntryPtr_(const void *a, const void *b) {
@@ -308,7 +331,7 @@ iDeclareType(FeedHashNode)
308 331
309struct Impl_FeedHashNode { 332struct Impl_FeedHashNode {
310 iHashNode node; 333 iHashNode node;
311 uint32_t bookmarkId; 334 uint32_t bookmarkId;
312}; 335};
313 336
314static void load_Feeds_(iFeeds *d) { 337static void load_Feeds_(iFeeds *d) {
@@ -353,27 +376,26 @@ static void load_Feeds_(iFeeds *d) {
353 } 376 }
354 case 2: { 377 case 2: {
355 const uint32_t feedId = strtoul(line.start, NULL, 16); 378 const uint32_t feedId = strtoul(line.start, NULL, 16);
356 nextSplit_Rangecc(range_Block(src), "\n", &line); 379 if (!nextSplit_Rangecc(range_Block(src), "\n", &line)) break;
357 const unsigned long long ts = strtoull(line.start, NULL, 10); 380 const unsigned long long ts = strtoull(line.start, NULL, 10);
358 nextSplit_Rangecc(range_Block(src), "\n", &line); 381 if (!nextSplit_Rangecc(range_Block(src), "\n", &line)) break;
359 iString url; 382 const iRangecc urlRange = line;
360 initRange_String(&url, line); 383 if (!nextSplit_Rangecc(range_Block(src), "\n", &line)) break;
361 nextSplit_Rangecc(range_Block(src), "\n", &line); 384 const iRangecc titleRange = line;
362 iString title; 385 iString *url = newRange_String(urlRange);
363 initRange_String(&title, line); 386 iString *title = newRange_String(titleRange);
364 nextSplit_Rangecc(range_Block(src), "\n", &line);
365 /* Look it up in the hash. */ 387 /* Look it up in the hash. */
366 const iFeedHashNode *node = (iFeedHashNode *) value_Hash(feeds, feedId); 388 const iFeedHashNode *node = (iFeedHashNode *) value_Hash(feeds, feedId);
367 if (node) { 389 if (node) {
368 iFeedEntry *entry = new_FeedEntry(); 390 iFeedEntry *entry = new_FeedEntry();
369 entry->bookmarkId = node->bookmarkId; 391 entry->bookmarkId = node->bookmarkId;
370 entry->timestamp.ts.tv_sec = ts; 392 entry->timestamp.ts.tv_sec = ts;
371 set_String(&entry->url, &url); 393 set_String(&entry->url, url);
372 set_String(&entry->title, &title); 394 set_String(&entry->title, title);
373 insert_SortedArray(&d->entries, &entry); 395 insert_SortedArray(&d->entries, &entry);
374 } 396 }
375 deinit_String(&title); 397 delete_String(title);
376 deinit_String(&url); 398 delete_String(url);
377 break; 399 break;
378 } 400 }
379 } 401 }
diff --git a/src/macos.m b/src/macos.m
index 24a6fecb..ceaf14f4 100644
--- a/src/macos.m
+++ b/src/macos.m
@@ -287,7 +287,7 @@ static void appearanceChanged_MacOS_(NSString *name) {
287 else if ([identifier isEqualToString:sidebarMode_TouchId_]) { 287 else if ([identifier isEqualToString:sidebarMode_TouchId_]) {
288 NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:sidebarMode_TouchId_]; 288 NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:sidebarMode_TouchId_];
289 NSSegmentedControl *seg = 289 NSSegmentedControl *seg =
290 [NSSegmentedControl segmentedControlWithLabels:@[ @"Bookmarks", @"History", @"Identities", @"Outline"] 290 [NSSegmentedControl segmentedControlWithLabels:@[ @"Feeds", @"Bookmarks", @"History", @"Idents", @"Outline"]
291 trackingMode:NSSegmentSwitchTrackingMomentary 291 trackingMode:NSSegmentSwitchTrackingMomentary
292 target:[[NSApplication sharedApplication] delegate] 292 target:[[NSApplication sharedApplication] delegate]
293 action:@selector(sidebarModePressed:)]; 293 action:@selector(sidebarModePressed:)];
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index f17f82a1..aac7459e 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -26,6 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
26#include "bookmarks.h" 26#include "bookmarks.h"
27#include "command.h" 27#include "command.h"
28#include "documentwidget.h" 28#include "documentwidget.h"
29#include "feeds.h"
29#include "gmcerts.h" 30#include "gmcerts.h"
30#include "gmdocument.h" 31#include "gmdocument.h"
31#include "inputwidget.h" 32#include "inputwidget.h"
@@ -111,6 +112,27 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
111 destroy_Widget(d->menu); 112 destroy_Widget(d->menu);
112 d->menu = NULL; 113 d->menu = NULL;
113 switch (d->mode) { 114 switch (d->mode) {
115 case feeds_SidebarMode: {
116 iConstForEach(PtrArray, i, listEntries_Feeds()) {
117 const iFeedEntry *entry = i.ptr;
118 /* TODO: Insert date separators. */
119 iSidebarItem *item = new_SidebarItem();
120 item->icon = 0;
121 const iTime visitTime = urlVisitTime_Visited(visited_App(), &entry->url);
122 if (!isValid_Time(&visitTime)) {
123 item->icon = 0x25cf; /* black circle */
124 }
125 set_String(&item->url, &entry->url);
126 set_String(&item->label, &entry->title);
127 const iBookmark *bm = get_Bookmarks(bookmarks_App(), entry->bookmarkId);
128 if (bm) {
129 set_String(&item->meta, &bm->title);
130 }
131 addItem_ListWidget(d->list, item);
132 iRelease(item);
133 }
134 break;
135 }
114 case documentOutline_SidebarMode: { 136 case documentOutline_SidebarMode: {
115 const iGmDocument *doc = document_DocumentWidget(document_App()); 137 const iGmDocument *doc = document_DocumentWidget(document_App());
116 iConstForEach(Array, i, headings_GmDocument(doc)) { 138 iConstForEach(Array, i, headings_GmDocument(doc)) {
@@ -281,7 +303,7 @@ iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
281 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) { 303 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) {
282 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode); 304 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode);
283 } 305 }
284 const float heights[max_SidebarMode] = { 1.333f, 1.333f, 3.5f, 1.2f }; 306 const float heights[max_SidebarMode] = { 2.5f, 1.333f, 1.333f, 3.5f, 1.2f };
285 setBackgroundColor_Widget(as_Widget(d->list), 307 setBackgroundColor_Widget(as_Widget(d->list),
286 d->mode == documentOutline_SidebarMode ? tmBannerBackground_ColorId 308 d->mode == documentOutline_SidebarMode ? tmBannerBackground_ColorId
287 : uiBackground_ColorId); 309 : uiBackground_ColorId);
@@ -300,6 +322,7 @@ int width_SidebarWidget(const iSidebarWidget *d) {
300} 322}
301 323
302static const char *normalModeLabels_[max_SidebarMode] = { 324static const char *normalModeLabels_[max_SidebarMode] = {
325 "\U00002605 Feeds",
303 "\U0001f588 Bookmarks", 326 "\U0001f588 Bookmarks",
304 "\U0001f553 History", 327 "\U0001f553 History",
305 "\U0001f464 Identities", 328 "\U0001f464 Identities",
@@ -307,6 +330,7 @@ static const char *normalModeLabels_[max_SidebarMode] = {
307}; 330};
308 331
309static const char *tightModeLabels_[max_SidebarMode] = { 332static const char *tightModeLabels_[max_SidebarMode] = {
333 "\U00002605",
310 "\U0001f588", 334 "\U0001f588",
311 "\U0001f553", 335 "\U0001f553",
312 "\U0001f464", 336 "\U0001f464",
@@ -401,6 +425,11 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *it
401 postCommandf_App("document.goto loc:%p", head->text.start); 425 postCommandf_App("document.goto loc:%p", head->text.start);
402 break; 426 break;
403 } 427 }
428 case feeds_SidebarMode:
429 if (!isEmpty_String(&item->url)) {
430 postCommandf_App("open url:%s", cstr_String(&item->url));
431 }
432 break;
404 case bookmarks_SidebarMode: 433 case bookmarks_SidebarMode:
405 case history_SidebarMode: { 434 case history_SidebarMode: {
406 if (!isEmpty_String(&item->url)) { 435 if (!isEmpty_String(&item->url)) {
@@ -826,6 +855,31 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
826 fg, 855 fg,
827 range_String(&d->label)); 856 range_String(&d->label));
828 } 857 }
858 else if (sidebar->mode == feeds_SidebarMode) {
859 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
860 : uiText_ColorId;
861 const int h1 = lineHeight_Text(uiLabel_FontId);
862 const int h2 = lineHeight_Text(uiContent_FontId);
863 const iRect iconArea = { addY_I2(pos, h1), init_I2(7 * gap_UI, itemHeight - h1) };
864 if (d->icon) {
865 iString str;
866 initUnicodeN_String(&str, &d->icon, 1);
867 drawCentered_Text(uiContent_FontId, iconArea, iFalse, iconColor, "%s", cstr_String(&str));
868 deinit_String(&str);
869 }
870 pos = add_I2(pos, init_I2(7 * gap_UI, (itemHeight - h1 - h2) / 2));
871 draw_Text(uiLabel_FontId,
872 pos,
873 isPressing ? fg : uiHeading_ColorId,
874 "%s",
875 cstr_String(&d->meta));
876 pos.y += h1;
877 draw_Text(uiContent_FontId,
878 pos,
879 isPressing ? fg : uiTextStrong_ColorId,
880 "%s",
881 cstr_String(&d->label));
882 }
829 else if (sidebar->mode == bookmarks_SidebarMode) { 883 else if (sidebar->mode == bookmarks_SidebarMode) {
830 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) 884 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
831 : uiText_ColorId; 885 : uiText_ColorId;
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h
index acb7d93a..001c8ac1 100644
--- a/src/ui/sidebarwidget.h
+++ b/src/ui/sidebarwidget.h
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25#include "widget.h" 25#include "widget.h"
26 26
27enum iSidebarMode { 27enum iSidebarMode {
28 feeds_SidebarMode,
28 bookmarks_SidebarMode, 29 bookmarks_SidebarMode,
29 history_SidebarMode, 30 history_SidebarMode,
30 identities_SidebarMode, 31 identities_SidebarMode,
diff --git a/src/ui/window.c b/src/ui/window.c
index 1cad63f5..ee90f12b 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -142,10 +142,11 @@ static const iMenuItem identityMenuItems[] = {
142}; 142};
143 143
144static const iMenuItem viewMenuItems[] = { 144static const iMenuItem viewMenuItems[] = {
145 { "Show Bookmarks", '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1" }, 145 { "Show Feeds", '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1" },
146 { "Show History", '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1" }, 146 { "Show Bookmarks", '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1" },
147 { "Show Identities", '3', KMOD_PRIMARY, "sidebar.mode arg:2 toggle:1" }, 147 { "Show History", '3', KMOD_PRIMARY, "sidebar.mode arg:2 toggle:1" },
148 { "Show Page Outline", '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1" }, 148 { "Show Identities", '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1" },
149 { "Show Page Outline", '5', KMOD_PRIMARY, "sidebar.mode arg:4 toggle:1" },
149 { "Toggle Sidebar", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, 150 { "Toggle Sidebar", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" },
150 { "---", 0, 0, NULL }, 151 { "---", 0, 0, NULL },
151 { "Go Back", SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, 152 { "Go Back", SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" },
@@ -485,6 +486,7 @@ static void setupUserInterface_Window(iWindow *d) {
485 addAction_Widget(d->root, '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1"); 486 addAction_Widget(d->root, '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1");
486 addAction_Widget(d->root, '3', KMOD_PRIMARY, "sidebar.mode arg:2 toggle:1"); 487 addAction_Widget(d->root, '3', KMOD_PRIMARY, "sidebar.mode arg:2 toggle:1");
487 addAction_Widget(d->root, '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1"); 488 addAction_Widget(d->root, '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1");
489 addAction_Widget(d->root, '5', KMOD_PRIMARY, "sidebar.mode arg:4 toggle:1");
488 } 490 }
489} 491}
490 492