2 * Copyright 2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
10 #include "CookieWindow.h"
14 #include <ColumnListView.h>
15 #include <ColumnTypes.h>
16 #include <GroupLayoutBuilder.h>
17 #include <NetworkCookieJar.h>
18 #include <OutlineListView.h>
19 #include <ScrollView.h>
20 #include <StringView.h>
23 #undef B_TRANSLATION_CONTEXT
24 #define B_TRANSLATION_CONTEXT "Cookie Manager"
27 COOKIE_IMPORT
= 'cimp',
28 COOKIE_EXPORT
= 'cexp',
29 COOKIE_DELETE
= 'cdel',
30 COOKIE_REFRESH
= 'rfsh',
32 DOMAIN_SELECTED
= 'dmsl'
36 class CookieDateColumn
: public BDateColumn
39 CookieDateColumn(const char* title
, float width
)
41 BDateColumn(title
, width
, width
/ 2, width
* 2)
45 void DrawField(BField
* field
, BRect rect
, BView
* parent
) {
46 BDateField
* dateField
= (BDateField
*)field
;
47 if (dateField
->UnixTime() == -1) {
48 DrawString(B_TRANSLATE("Session cookie"), parent
, rect
);
50 BDateColumn::DrawField(field
, rect
, parent
);
56 class CookieRow
: public BRow
59 CookieRow(BColumnListView
* list
, const BNetworkCookie
& cookie
)
65 SetField(new BStringField(cookie
.Name().String()), 0);
66 SetField(new BStringField(cookie
.Path().String()), 1);
67 time_t expiration
= cookie
.ExpirationDate();
68 SetField(new BDateField(&expiration
), 2);
69 SetField(new BStringField(cookie
.Value().String()), 3);
74 if (cookie
.HttpOnly())
77 if (cookie
.IsHostOnly())
79 SetField(new BStringField(flags
.String()), 4);
82 BNetworkCookie
& Cookie() {
87 BNetworkCookie fCookie
;
91 class DomainItem
: public BStringItem
94 DomainItem(BString text
, bool empty
)
106 CookieWindow::CookieWindow(BRect frame
, BNetworkCookieJar
& jar
)
108 BWindow(frame
, B_TRANSLATE("Cookie manager"), B_TITLED_WINDOW
,
109 B_NORMAL_WINDOW_FEEL
,
110 B_AUTO_UPDATE_SIZE_LIMITS
| B_ASYNCHRONOUS_CONTROLS
| B_NOT_ZOOMABLE
),
113 BGroupLayout
* root
= new BGroupLayout(B_HORIZONTAL
, 0.0);
116 fDomains
= new BOutlineListView("domain list");
117 root
->AddView(new BScrollView("scroll", fDomains
, 0, false, true), 1);
119 fHeaderView
= new BStringView("label",
120 B_TRANSLATE("The cookie jar is empty!"));
121 fCookies
= new BColumnListView("cookie list", B_WILL_DRAW
, B_FANCY_BORDER
,
124 int em
= fCookies
->StringWidth("M");
125 int flagsLength
= fCookies
->StringWidth("Mhttps hostOnly" B_UTF8_ELLIPSIS
);
127 fCookies
->AddColumn(new BStringColumn(B_TRANSLATE("Name"),
128 20 * em
, 10 * em
, 50 * em
, 0), 0);
129 fCookies
->AddColumn(new BStringColumn(B_TRANSLATE("Path"),
130 10 * em
, 10 * em
, 50 * em
, 0), 1);
131 fCookies
->AddColumn(new CookieDateColumn(B_TRANSLATE("Expiration"),
132 fCookies
->StringWidth("88/88/8888 88:88:88 AM")), 2);
133 fCookies
->AddColumn(new BStringColumn(B_TRANSLATE("Value"),
134 20 * em
, 10 * em
, 50 * em
, 0), 3);
135 fCookies
->AddColumn(new BStringColumn(B_TRANSLATE("Flags"),
136 flagsLength
, flagsLength
, flagsLength
, 0), 4);
138 root
->AddItem(BGroupLayoutBuilder(B_VERTICAL
, B_USE_DEFAULT_SPACING
)
139 .SetInsets(5, 5, 5, 5)
140 .AddGroup(B_HORIZONTAL
, B_USE_DEFAULT_SPACING
)
145 .AddGroup(B_HORIZONTAL
, B_USE_DEFAULT_SPACING
)
146 .SetInsets(5, 5, 5, 5)
148 .Add(new BButton("import", B_TRANSLATE("Import" B_UTF8_ELLIPSIS
),
150 .Add(new BButton("export", B_TRANSLATE("Export" B_UTF8_ELLIPSIS
),
154 .Add(new BButton("delete", B_TRANSLATE("Delete"),
155 new BMessage(COOKIE_DELETE
))), 3);
157 fDomains
->SetSelectionMessage(new BMessage(DOMAIN_SELECTED
));
162 CookieWindow::MessageReceived(BMessage
* message
)
164 switch(message
->what
) {
165 case DOMAIN_SELECTED
:
167 int32 index
= message
->FindInt32("index");
168 BStringItem
* item
= (BStringItem
*)fDomains
->ItemAt(index
);
170 BString domain
= item
->Text();
171 _ShowCookiesForDomain(domain
);
184 BWindow::MessageReceived(message
);
195 PostMessage(COOKIE_REFRESH
);
200 CookieWindow::QuitRequested()
209 CookieWindow::_BuildDomainList()
211 // Empty the domain list (TODO should we do this when hiding instead?)
212 for (int i
= fDomains
->FullListCountItems() - 1; i
>= 1; i
--) {
213 delete fDomains
->FullListItemAt(i
);
215 fDomains
->MakeEmpty();
217 // BOutlineListView does not handle parent = NULL in many methods, so let's
218 // make sure everything always has a parent.
219 DomainItem
* rootItem
= new DomainItem("", true);
220 fDomains
->AddItem(rootItem
);
222 // Populate the domain list
223 BNetworkCookieJar::Iterator it
= fCookieJar
.GetIterator();
225 const BNetworkCookie
* cookie
;
226 while ((cookie
= it
.NextDomain()) != NULL
) {
227 _AddDomain(cookie
->Domain(), false);
231 while (i
< fDomains
->FullListCountItems())
233 DomainItem
* item
= (DomainItem
*)fDomains
->FullListItemAt(i
);
234 // Detach items from the fake root
235 item
->SetOutlineLevel(item
->OutlineLevel() - 1);
238 fDomains
->RemoveItem(rootItem
);
242 int firstNotEmpty
= i
;
243 // Collapse empty items to keep the list short
244 while (i
< fDomains
->FullListCountItems())
246 DomainItem
* item
= (DomainItem
*)fDomains
->FullListItemAt(i
);
247 if (item
->fEmpty
== true) {
248 if (fDomains
->CountItemsUnder(item
, true) == 1) {
249 // The item has no cookies, and only a single child. We can
250 // remove it and move its child one level up in the tree.
252 int count
= fDomains
->CountItemsUnder(item
, false);
253 int index
= fDomains
->FullListIndexOf(item
) + 1;
254 for (int j
= 0; j
< count
; j
++) {
255 BListItem
* child
= fDomains
->FullListItemAt(index
+ j
);
256 child
->SetOutlineLevel(child
->OutlineLevel() - 1);
259 fDomains
->RemoveItem(item
);
262 // The moved child is at the same index the removed item was.
263 // We continue the loop without incrementing i to process it.
266 // The item has no cookies, but has multiple children. Mark it
267 // as disabled so it is not selectable.
268 item
->SetEnabled(false);
269 if (i
== firstNotEmpty
)
277 fDomains
->Select(firstNotEmpty
);
282 CookieWindow::_AddDomain(BString domain
, bool fake
)
284 BStringItem
* parent
= NULL
;
285 int firstDot
= domain
.FindFirst('.');
287 BString
parentDomain(domain
);
288 parentDomain
.Remove(0, firstDot
+ 1);
289 parent
= _AddDomain(parentDomain
, true);
291 parent
= (BStringItem
*)fDomains
->FullListItemAt(0);
296 // check that we aren't already there
297 while ((existing
= fDomains
->ItemUnderAt(parent
, true, i
++)) != NULL
) {
298 DomainItem
* stringItem
= (DomainItem
*)existing
;
299 if (stringItem
->Text() == domain
) {
301 stringItem
->fEmpty
= false;
307 puts("==============================");
308 for (i
= 0; i
< fDomains
->FullListCountItems(); i
++) {
309 BStringItem
* t
= (BStringItem
*)fDomains
->FullListItemAt(i
);
310 for (unsigned j
= 0; j
< t
->OutlineLevel(); j
++)
312 printf("%s\n", t
->Text());
316 // Insert the new item, keeping the list alphabetically sorted
317 BStringItem
* domainItem
= new DomainItem(domain
, fake
);
318 domainItem
->SetOutlineLevel(parent
->OutlineLevel() + 1);
319 BStringItem
* sibling
= NULL
;
320 int siblingCount
= fDomains
->CountItemsUnder(parent
, true);
321 for (i
= 0; i
< siblingCount
; i
++) {
322 sibling
= (BStringItem
*)fDomains
->ItemUnderAt(parent
, true, i
);
323 if (strcmp(sibling
->Text(), domainItem
->Text()) > 0) {
324 fDomains
->AddItem(domainItem
, fDomains
->FullListIndexOf(sibling
));
330 // There were siblings, but all smaller than what we try to insert.
331 // Insert after the last one (and its subitems)
332 fDomains
->AddItem(domainItem
, fDomains
->FullListIndexOf(sibling
)
333 + fDomains
->CountItemsUnder(sibling
, false) + 1);
335 // There were no siblings, insert right after the parent
336 fDomains
->AddItem(domainItem
, fDomains
->FullListIndexOf(parent
) + 1);
344 CookieWindow::_ShowCookiesForDomain(BString domain
)
347 label
.SetToFormat(B_TRANSLATE("Cookies for %s"), domain
.String());
348 fHeaderView
->SetText(label
);
350 // Empty the cookie list
353 // Populate the domain list
354 BNetworkCookieJar::Iterator it
= fCookieJar
.GetIterator();
356 const BNetworkCookie
* cookie
;
357 /* FIXME A direct access to a domain would be needed in BNetworkCookieJar. */
358 while ((cookie
= it
.Next()) != NULL
) {
359 if (cookie
->Domain() == domain
)
367 new CookieRow(fCookies
, *cookie
); // Attaches itself to the list
369 } while (cookie
!= NULL
&& cookie
->Domain() == domain
);
374 CookieWindow::_DeleteCookies()
376 // TODO shall we handle multiple selection here?
377 CookieRow
* row
= (CookieRow
*)fCookies
->CurrentSelection();
379 // TODO see if a domain is selected in the domain list, and delete all
380 // cookies for that domain
384 fCookies
->RemoveRow(row
);
386 BNetworkCookie
& cookie
= row
->Cookie();
387 cookie
.SetExpirationDate(0);
388 fCookieJar
.AddCookie(cookie
);