diff options
Diffstat (limited to 'src/ui/certlistwidget.c')
-rw-r--r-- | src/ui/certlistwidget.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/src/ui/certlistwidget.c b/src/ui/certlistwidget.c new file mode 100644 index 00000000..2a7562d8 --- /dev/null +++ b/src/ui/certlistwidget.c | |||
@@ -0,0 +1,490 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "certlistwidget.h" | ||
24 | |||
25 | #include "documentwidget.h" | ||
26 | #include "command.h" | ||
27 | #include "labelwidget.h" | ||
28 | #include "listwidget.h" | ||
29 | #include "../gmcerts.h" | ||
30 | #include "../app.h" | ||
31 | |||
32 | #include <SDL_clipboard.h> | ||
33 | |||
34 | iDeclareType(CertItem) | ||
35 | typedef iListItemClass iCertItemClass; | ||
36 | |||
37 | struct Impl_CertItem { | ||
38 | iListItem listItem; | ||
39 | uint32_t id; | ||
40 | int indent; | ||
41 | iChar icon; | ||
42 | iBool isBold; | ||
43 | iString label; | ||
44 | iString meta; | ||
45 | // iString url; | ||
46 | }; | ||
47 | |||
48 | void init_CertItem(iCertItem *d) { | ||
49 | init_ListItem(&d->listItem); | ||
50 | d->id = 0; | ||
51 | d->indent = 0; | ||
52 | d->icon = 0; | ||
53 | d->isBold = iFalse; | ||
54 | init_String(&d->label); | ||
55 | init_String(&d->meta); | ||
56 | // init_String(&d->url); | ||
57 | } | ||
58 | |||
59 | void deinit_CertItem(iCertItem *d) { | ||
60 | // deinit_String(&d->url); | ||
61 | deinit_String(&d->meta); | ||
62 | deinit_String(&d->label); | ||
63 | } | ||
64 | |||
65 | static void draw_CertItem_(const iCertItem *d, iPaint *p, iRect itemRect, const iListWidget *list); | ||
66 | |||
67 | iBeginDefineSubclass(CertItem, ListItem) | ||
68 | .draw = (iAny *) draw_CertItem_, | ||
69 | iEndDefineSubclass(CertItem) | ||
70 | |||
71 | iDefineObjectConstruction(CertItem) | ||
72 | |||
73 | /*----------------------------------------------------------------------------------------------*/ | ||
74 | |||
75 | struct Impl_CertListWidget { | ||
76 | iListWidget list; | ||
77 | int itemFonts[2]; | ||
78 | iWidget *menu; /* context menu for an item */ | ||
79 | iCertItem *contextItem; /* list item accessed in the context menu */ | ||
80 | size_t contextIndex; /* index of list item accessed in the context menu */ | ||
81 | }; | ||
82 | |||
83 | iDefineObjectConstruction(CertListWidget) | ||
84 | |||
85 | static iGmIdentity *menuIdentity_CertListWidget_(const iCertListWidget *d) { | ||
86 | if (d->contextItem) { | ||
87 | return identity_GmCerts(certs_App(), d->contextItem->id); | ||
88 | } | ||
89 | return NULL; | ||
90 | } | ||
91 | |||
92 | static void updateContextMenu_CertListWidget_(iCertListWidget *d) { | ||
93 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
94 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
95 | size_t firstIndex = 0; | ||
96 | if (deviceType_App() != desktop_AppDeviceType && !isEmpty_String(docUrl)) { | ||
97 | pushBack_Array(items, &(iMenuItem){ format_CStr("```%s", cstr_String(docUrl)) }); | ||
98 | firstIndex = 1; | ||
99 | } | ||
100 | const iMenuItem ctxItems[] = { | ||
101 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | ||
102 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | ||
103 | { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" }, | ||
104 | { "---", 0, 0, NULL }, | ||
105 | { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" }, | ||
106 | { "${ident.fingerprint}", 0, 0, "ident.fingerprint" }, | ||
107 | #if defined (iPlatformAppleDesktop) | ||
108 | { magnifyingGlass_Icon " ${menu.reveal.macos}", 0, 0, "ident.reveal" }, | ||
109 | #endif | ||
110 | #if defined (iPlatformLinux) | ||
111 | { magnifyingGlass_Icon " ${menu.reveal.filemgr}", 0, 0, "ident.reveal" }, | ||
112 | #endif | ||
113 | { export_Icon " ${ident.export}", 0, 0, "ident.export" }, | ||
114 | { "---", 0, 0, NULL }, | ||
115 | { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" }, | ||
116 | }; | ||
117 | pushBackN_Array(items, ctxItems, iElemCount(ctxItems)); | ||
118 | /* Used URLs. */ | ||
119 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
120 | if (ident) { | ||
121 | size_t insertPos = firstIndex + 3; | ||
122 | if (!isEmpty_StringSet(ident->useUrls)) { | ||
123 | insert_Array(items, insertPos++, &(iMenuItem){ "---", 0, 0, NULL }); | ||
124 | } | ||
125 | iBool usedOnCurrentPage = iFalse; | ||
126 | iConstForEach(StringSet, i, ident->useUrls) { | ||
127 | const iString *url = i.value; | ||
128 | usedOnCurrentPage |= startsWithCase_String(docUrl, cstr_String(url)); | ||
129 | iRangecc urlStr = range_String(url); | ||
130 | if (startsWith_Rangecc(urlStr, "gemini://")) { | ||
131 | urlStr.start += 9; /* omit the default scheme */ | ||
132 | } | ||
133 | insert_Array(items, | ||
134 | insertPos++, | ||
135 | &(iMenuItem){ format_CStr(globe_Icon " %s", cstr_Rangecc(urlStr)), | ||
136 | 0, | ||
137 | 0, | ||
138 | format_CStr("!open url:%s", cstr_String(url)) }); | ||
139 | } | ||
140 | if (!usedOnCurrentPage) { | ||
141 | remove_Array(items, firstIndex + 1); | ||
142 | } | ||
143 | else { | ||
144 | remove_Array(items, firstIndex); | ||
145 | } | ||
146 | } | ||
147 | destroy_Widget(d->menu); | ||
148 | d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); | ||
149 | } | ||
150 | |||
151 | static void itemClicked_CertListWidget_(iCertListWidget *d, iCertItem *item, size_t itemIndex) { | ||
152 | iWidget *w = as_Widget(d); | ||
153 | setFocus_Widget(NULL); | ||
154 | d->contextItem = item; | ||
155 | if (d->contextIndex != iInvalidPos) { | ||
156 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
157 | } | ||
158 | d->contextIndex = itemIndex; | ||
159 | if (itemIndex < numItems_ListWidget(&d->list)) { | ||
160 | updateContextMenu_CertListWidget_(d); | ||
161 | arrange_Widget(d->menu); | ||
162 | openMenu_Widget(d->menu, | ||
163 | bounds_Widget(w).pos.x < mid_Rect(rect_Root(w->root)).x | ||
164 | ? topRight_Rect(itemRect_ListWidget(&d->list, itemIndex)) | ||
165 | : addX_I2(topLeft_Rect(itemRect_ListWidget(&d->list, itemIndex)), | ||
166 | -width_Widget(d->menu))); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | static iBool processEvent_CertListWidget_(iCertListWidget *d, const SDL_Event *ev) { | ||
171 | iWidget *w = as_Widget(d); | ||
172 | /* Handle commands. */ | ||
173 | if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { | ||
174 | const char *cmd = command_UserEvent(ev); | ||
175 | if (equal_Command(cmd, "idents.changed")) { | ||
176 | updateItems_CertListWidget(d); | ||
177 | } | ||
178 | else if (isCommand_Widget(w, ev, "list.clicked")) { | ||
179 | itemClicked_CertListWidget_( | ||
180 | d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); | ||
181 | return iTrue; | ||
182 | } | ||
183 | else if (isCommand_Widget(w, ev, "ident.use")) { | ||
184 | iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
185 | const iString *tabUrl = urlQueryStripped_String(url_DocumentWidget(document_App())); | ||
186 | if (ident) { | ||
187 | if (argLabel_Command(cmd, "clear")) { | ||
188 | clearUse_GmIdentity(ident); | ||
189 | } | ||
190 | else if (arg_Command(cmd)) { | ||
191 | signIn_GmCerts(certs_App(), ident, tabUrl); | ||
192 | postCommand_App("navigate.reload"); | ||
193 | } | ||
194 | else { | ||
195 | signOut_GmCerts(certs_App(), tabUrl); | ||
196 | postCommand_App("navigate.reload"); | ||
197 | } | ||
198 | saveIdentities_GmCerts(certs_App()); | ||
199 | updateItems_CertListWidget(d); | ||
200 | } | ||
201 | return iTrue; | ||
202 | } | ||
203 | else if (isCommand_Widget(w, ev, "ident.edit")) { | ||
204 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
205 | if (ident) { | ||
206 | makeValueInput_Widget(get_Root()->widget, | ||
207 | &ident->notes, | ||
208 | uiHeading_ColorEscape "${heading.ident.notes}", | ||
209 | format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))), | ||
210 | uiTextAction_ColorEscape "${dlg.default}", | ||
211 | format_CStr("!ident.setnotes ident:%p ptr:%p", ident, d)); | ||
212 | } | ||
213 | return iTrue; | ||
214 | } | ||
215 | else if (isCommand_Widget(w, ev, "ident.fingerprint")) { | ||
216 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
217 | if (ident) { | ||
218 | const iString *fps = collect_String( | ||
219 | hexEncode_Block(collect_Block(fingerprint_TlsCertificate(ident->cert)))); | ||
220 | SDL_SetClipboardText(cstr_String(fps)); | ||
221 | } | ||
222 | return iTrue; | ||
223 | } | ||
224 | else if (isCommand_Widget(w, ev, "ident.export")) { | ||
225 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
226 | if (ident) { | ||
227 | iString *pem = collect_String(pem_TlsCertificate(ident->cert)); | ||
228 | append_String(pem, collect_String(privateKeyPem_TlsCertificate(ident->cert))); | ||
229 | iDocumentWidget *expTab = newTab_App(NULL, iTrue); | ||
230 | setUrlAndSource_DocumentWidget( | ||
231 | expTab, | ||
232 | collectNewFormat_String("file:%s.pem", cstr_String(name_GmIdentity(ident))), | ||
233 | collectNewCStr_String("text/plain"), | ||
234 | utf8_String(pem)); | ||
235 | } | ||
236 | return iTrue; | ||
237 | } | ||
238 | else if (isCommand_Widget(w, ev, "ident.setnotes")) { | ||
239 | iGmIdentity *ident = pointerLabel_Command(cmd, "ident"); | ||
240 | if (ident) { | ||
241 | setCStr_String(&ident->notes, suffixPtr_Command(cmd, "value")); | ||
242 | updateItems_CertListWidget(d); | ||
243 | } | ||
244 | return iTrue; | ||
245 | } | ||
246 | else if (isCommand_Widget(w, ev, "ident.pickicon")) { | ||
247 | return iTrue; | ||
248 | } | ||
249 | else if (isCommand_Widget(w, ev, "ident.reveal")) { | ||
250 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
251 | if (ident) { | ||
252 | const iString *crtPath = certificatePath_GmCerts(certs_App(), ident); | ||
253 | if (crtPath) { | ||
254 | postCommandf_App("reveal path:%s", cstr_String(crtPath)); | ||
255 | } | ||
256 | } | ||
257 | return iTrue; | ||
258 | } | ||
259 | else if (isCommand_Widget(w, ev, "ident.delete")) { | ||
260 | iCertItem *item = d->contextItem; | ||
261 | if (argLabel_Command(cmd, "confirm")) { | ||
262 | makeQuestion_Widget( | ||
263 | uiTextCaution_ColorEscape "${heading.ident.delete}", | ||
264 | format_CStr(cstr_Lang("dlg.confirm.ident.delete"), | ||
265 | uiTextAction_ColorEscape, | ||
266 | cstr_String(&item->label), | ||
267 | uiText_ColorEscape), | ||
268 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | ||
269 | { uiTextCaution_ColorEscape "${dlg.ident.delete}", | ||
270 | 0, | ||
271 | 0, | ||
272 | format_CStr("!ident.delete confirm:0 ptr:%p", d) } }, | ||
273 | 2); | ||
274 | return iTrue; | ||
275 | } | ||
276 | deleteIdentity_GmCerts(certs_App(), menuIdentity_CertListWidget_(d)); | ||
277 | postCommand_App("idents.changed"); | ||
278 | return iTrue; | ||
279 | } | ||
280 | } | ||
281 | if (ev->type == SDL_MOUSEMOTION && !isVisible_Widget(d->menu)) { | ||
282 | const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y); | ||
283 | /* Update cursor. */ | ||
284 | if (contains_Widget(w, mouse)) { | ||
285 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | ||
286 | } | ||
287 | else if (d->contextIndex != iInvalidPos) { | ||
288 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
289 | d->contextIndex = iInvalidPos; | ||
290 | } | ||
291 | } | ||
292 | /* Update context menu items. */ | ||
293 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT) { | ||
294 | d->contextItem = NULL; | ||
295 | if (!isVisible_Widget(d->menu)) { | ||
296 | updateMouseHover_ListWidget(&d->list); | ||
297 | } | ||
298 | if (constHoverItem_ListWidget(&d->list) || isVisible_Widget(d->menu)) { | ||
299 | d->contextItem = hoverItem_ListWidget(&d->list); | ||
300 | /* Context is drawn in hover state. */ | ||
301 | if (d->contextIndex != iInvalidPos) { | ||
302 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
303 | } | ||
304 | d->contextIndex = hoverItemIndex_ListWidget(&d->list); | ||
305 | updateContextMenu_CertListWidget_(d); | ||
306 | /* TODO: Some callback-based mechanism would be nice for updating menus right | ||
307 | before they open? At least move these to `updateContextMenu_ */ | ||
308 | const iGmIdentity *ident = constHoverIdentity_CertListWidget(d); | ||
309 | const iString * docUrl = url_DocumentWidget(document_App()); | ||
310 | iForEach(ObjectList, i, children_Widget(d->menu)) { | ||
311 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
312 | iLabelWidget *menuItem = i.object; | ||
313 | const char * cmdItem = cstr_String(command_LabelWidget(menuItem)); | ||
314 | if (equal_Command(cmdItem, "ident.use")) { | ||
315 | const iBool cmdUse = arg_Command(cmdItem) != 0; | ||
316 | const iBool cmdClear = argLabel_Command(cmdItem, "clear") != 0; | ||
317 | setFlags_Widget( | ||
318 | as_Widget(menuItem), | ||
319 | disabled_WidgetFlag, | ||
320 | (cmdClear && !isUsed_GmIdentity(ident)) || | ||
321 | (!cmdClear && cmdUse && isUsedOn_GmIdentity(ident, docUrl)) || | ||
322 | (!cmdClear && !cmdUse && !isUsedOn_GmIdentity(ident, docUrl))); | ||
323 | } | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | if (hoverItem_ListWidget(&d->list) || isVisible_Widget(d->menu)) { | ||
328 | processContextMenuEvent_Widget(d->menu, ev, {}); | ||
329 | } | ||
330 | } | ||
331 | return ((iWidgetClass *) class_Widget(w)->super)->processEvent(w, ev); | ||
332 | } | ||
333 | |||
334 | static void draw_CertListWidget_(const iCertListWidget *d) { | ||
335 | const iWidget *w = constAs_Widget(d); | ||
336 | ((iWidgetClass *) class_Widget(w)->super)->draw(w); | ||
337 | } | ||
338 | |||
339 | static void draw_CertItem_(const iCertItem *d, iPaint *p, iRect itemRect, | ||
340 | const iListWidget *list) { | ||
341 | const iCertListWidget *certList = (const iCertListWidget *) list; | ||
342 | const iBool isMenuVisible = isVisible_Widget(certList->menu); | ||
343 | const iBool isDragging = constDragItem_ListWidget(list) == d; | ||
344 | const iBool isPressing = isMouseDown_ListWidget(list) && !isDragging; | ||
345 | const iBool isHover = | ||
346 | (!isMenuVisible && | ||
347 | isHover_Widget(constAs_Widget(list)) && | ||
348 | constHoverItem_ListWidget(list) == d) || | ||
349 | (isMenuVisible && certList->contextItem == d) || | ||
350 | isDragging; | ||
351 | const int itemHeight = height_Rect(itemRect); | ||
352 | const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) | ||
353 | : uiIcon_ColorId; | ||
354 | const int altIconColor = isPressing ? uiTextPressed_ColorId : uiTextCaution_ColorId; | ||
355 | const int font = certList->itemFonts[d->isBold ? 1 : 0]; | ||
356 | int bg = uiBackgroundSidebar_ColorId; | ||
357 | if (isHover) { | ||
358 | bg = isPressing ? uiBackgroundPressed_ColorId | ||
359 | : uiBackgroundFramelessHover_ColorId; | ||
360 | fillRect_Paint(p, itemRect, bg); | ||
361 | } | ||
362 | else if (d->listItem.isSelected) { | ||
363 | bg = uiBackgroundUnfocusedSelection_ColorId; | ||
364 | fillRect_Paint(p, itemRect, bg); | ||
365 | } | ||
366 | // iInt2 pos = itemRect.pos; | ||
367 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) | ||
368 | : uiTextStrong_ColorId; | ||
369 | const iBool isUsedOnDomain = (d->indent != 0); | ||
370 | iString icon; | ||
371 | initUnicodeN_String(&icon, &d->icon, 1); | ||
372 | iInt2 cPos = topLeft_Rect(itemRect); | ||
373 | const int indent = 1.4f * lineHeight_Text(font); | ||
374 | addv_I2(&cPos, | ||
375 | init_I2(3 * gap_UI, | ||
376 | (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) / | ||
377 | 2)); | ||
378 | const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId | ||
379 | : uiTextFramelessHover_ColorId) | ||
380 | : uiTextDim_ColorId; | ||
381 | if (!d->listItem.isSelected && !isUsedOnDomain) { | ||
382 | drawOutline_Text(font, cPos, metaFg, none_ColorId, range_String(&icon)); | ||
383 | } | ||
384 | drawRange_Text(font, | ||
385 | cPos, | ||
386 | d->listItem.isSelected ? iconColor | ||
387 | : isUsedOnDomain ? altIconColor | ||
388 | : uiBackgroundSidebar_ColorId, | ||
389 | range_String(&icon)); | ||
390 | deinit_String(&icon); | ||
391 | drawRange_Text(d->listItem.isSelected ? certList->itemFonts[1] : font, | ||
392 | add_I2(cPos, init_I2(indent, 0)), | ||
393 | fg, | ||
394 | range_String(&d->label)); | ||
395 | drawRange_Text(uiLabel_FontId, | ||
396 | add_I2(cPos, init_I2(indent, lineHeight_Text(font))), | ||
397 | metaFg, | ||
398 | range_String(&d->meta)); | ||
399 | } | ||
400 | |||
401 | void init_CertListWidget(iCertListWidget *d) { | ||
402 | iWidget *w = as_Widget(d); | ||
403 | init_ListWidget(&d->list); | ||
404 | setId_Widget(w, "certlist"); | ||
405 | setBackgroundColor_Widget(w, none_ColorId); | ||
406 | d->itemFonts[0] = uiContent_FontId; | ||
407 | d->itemFonts[1] = uiContentBold_FontId; | ||
408 | #if defined (iPlatformMobile) | ||
409 | if (deviceType_App() == phone_AppDeviceType) { | ||
410 | d->itemFonts[0] = uiLabelBig_FontId; | ||
411 | d->itemFonts[1] = uiLabelBigBold_FontId; | ||
412 | } | ||
413 | #endif | ||
414 | updateItemHeight_CertListWidget(d); | ||
415 | d->menu = NULL; | ||
416 | d->contextItem = NULL; | ||
417 | d->contextIndex = iInvalidPos; | ||
418 | } | ||
419 | |||
420 | void updateItemHeight_CertListWidget(iCertListWidget *d) { | ||
421 | setItemHeight_ListWidget(&d->list, 3.5f * lineHeight_Text(d->itemFonts[0])); | ||
422 | } | ||
423 | |||
424 | iBool updateItems_CertListWidget(iCertListWidget *d) { | ||
425 | clear_ListWidget(&d->list); | ||
426 | destroy_Widget(d->menu); | ||
427 | d->menu = NULL; | ||
428 | const iString *tabUrl = url_DocumentWidget(document_App()); | ||
429 | const iRangecc tabHost = urlHost_String(tabUrl); | ||
430 | iBool haveItems = iFalse; | ||
431 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { | ||
432 | const iGmIdentity *ident = i.ptr; | ||
433 | iCertItem *item = new_CertItem(); | ||
434 | item->id = (uint32_t) index_PtrArrayConstIterator(&i); | ||
435 | item->icon = 0x1f464; /* person */ | ||
436 | set_String(&item->label, name_GmIdentity(ident)); | ||
437 | iDate until; | ||
438 | validUntil_TlsCertificate(ident->cert, &until); | ||
439 | const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl); | ||
440 | format_String(&item->meta, | ||
441 | "%s", | ||
442 | isActive ? cstr_Lang("ident.using") | ||
443 | : isUsed_GmIdentity(ident) | ||
444 | ? formatCStrs_Lang("ident.usedonurls.n", size_StringSet(ident->useUrls)) | ||
445 | : cstr_Lang("ident.notused")); | ||
446 | const char *expiry = | ||
447 | ident->flags & temporary_GmIdentityFlag | ||
448 | ? cstr_Lang("ident.temporary") | ||
449 | : cstrCollect_String(format_Date(&until, cstr_Lang("ident.expiry"))); | ||
450 | if (isEmpty_String(&ident->notes)) { | ||
451 | appendFormat_String(&item->meta, "\n%s", expiry); | ||
452 | } | ||
453 | else { | ||
454 | appendFormat_String(&item->meta, | ||
455 | " \u2014 %s\n%s%s", | ||
456 | expiry, | ||
457 | escape_Color(uiHeading_ColorId), | ||
458 | cstr_String(&ident->notes)); | ||
459 | } | ||
460 | item->listItem.isSelected = isActive; | ||
461 | if (!isActive && isUsedOnDomain_GmIdentity(ident, tabHost)) { | ||
462 | item->indent = 1; /* will be highlighted */ | ||
463 | } | ||
464 | addItem_ListWidget(&d->list, item); | ||
465 | haveItems = iTrue; | ||
466 | iRelease(item); | ||
467 | } | ||
468 | return haveItems; | ||
469 | } | ||
470 | |||
471 | void deinit_CertListWidget(iCertListWidget *d) { | ||
472 | iUnused(d); | ||
473 | } | ||
474 | |||
475 | const iGmIdentity *constHoverIdentity_CertListWidget(const iCertListWidget *d) { | ||
476 | const iCertItem *hoverItem = constHoverItem_ListWidget(&d->list); | ||
477 | if (hoverItem) { | ||
478 | return identity_GmCerts(certs_App(), hoverItem->id); | ||
479 | } | ||
480 | return NULL; | ||
481 | } | ||
482 | |||
483 | iGmIdentity *hoverIdentity_CertListWidget(const iCertListWidget *d) { | ||
484 | return iConstCast(iGmIdentity *, constHoverIdentity_CertListWidget(d)); | ||
485 | } | ||
486 | |||
487 | iBeginDefineSubclass(CertListWidget, ListWidget) | ||
488 | .processEvent = (iAny *) processEvent_CertListWidget_, | ||
489 | .draw = (iAny *) draw_CertListWidget_, | ||
490 | iEndDefineSubclass(CertListWidget) | ||