diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-12-02 10:02:16 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-12-02 10:02:16 +0200 |
commit | b9cdb34c59dc133b549deed5a4f3b9bb95197cca (patch) | |
tree | 1ed42fabfff17fe5d7d2c0ed4c8687f471df345d /src/ui/certlistwidget.c | |
parent | f4942e1b4da6dc1334dcdb4f2daae670bfa1f813 (diff) |
Refactored CertListWidget out of the sidebar
The identity list is needed elsewhere outside of the sidebar, so moved it into a specialized ListWidget class.
Diffstat (limited to 'src/ui/certlistwidget.c')
-rw-r--r-- | src/ui/certlistwidget.c | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/src/ui/certlistwidget.c b/src/ui/certlistwidget.c new file mode 100644 index 00000000..4d939ae2 --- /dev/null +++ b/src/ui/certlistwidget.c | |||
@@ -0,0 +1,474 @@ | |||
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 | pushBackN_Array(items, (iMenuItem[]){ | ||
95 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | ||
96 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | ||
97 | { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" }, | ||
98 | { "---", 0, 0, NULL }, | ||
99 | { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" }, | ||
100 | { "${ident.fingerprint}", 0, 0, "ident.fingerprint" }, | ||
101 | { export_Icon " ${ident.export}", 0, 0, "ident.export" }, | ||
102 | { "---", 0, 0, NULL }, | ||
103 | { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" }, | ||
104 | }, 9); | ||
105 | /* Used URLs. */ | ||
106 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
107 | if (ident) { | ||
108 | size_t insertPos = 3; | ||
109 | if (!isEmpty_StringSet(ident->useUrls)) { | ||
110 | insert_Array(items, insertPos++, &(iMenuItem){ "---", 0, 0, NULL }); | ||
111 | } | ||
112 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
113 | iBool usedOnCurrentPage = iFalse; | ||
114 | iConstForEach(StringSet, i, ident->useUrls) { | ||
115 | const iString *url = i.value; | ||
116 | usedOnCurrentPage |= equalCase_String(docUrl, url); | ||
117 | iRangecc urlStr = range_String(url); | ||
118 | if (startsWith_Rangecc(urlStr, "gemini://")) { | ||
119 | urlStr.start += 9; /* omit the default scheme */ | ||
120 | } | ||
121 | insert_Array(items, | ||
122 | insertPos++, | ||
123 | &(iMenuItem){ format_CStr(globe_Icon " %s", cstr_Rangecc(urlStr)), | ||
124 | 0, | ||
125 | 0, | ||
126 | format_CStr("!open url:%s", cstr_String(url)) }); | ||
127 | } | ||
128 | if (!usedOnCurrentPage) { | ||
129 | remove_Array(items, 1); | ||
130 | } | ||
131 | } | ||
132 | destroy_Widget(d->menu); | ||
133 | d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); | ||
134 | } | ||
135 | |||
136 | static void itemClicked_CertListWidget_(iCertListWidget *d, iCertItem *item, size_t itemIndex) { | ||
137 | iWidget *w = as_Widget(d); | ||
138 | setFocus_Widget(NULL); | ||
139 | d->contextItem = item; | ||
140 | if (d->contextIndex != iInvalidPos) { | ||
141 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
142 | } | ||
143 | d->contextIndex = itemIndex; | ||
144 | if (itemIndex < numItems_ListWidget(&d->list)) { | ||
145 | updateContextMenu_CertListWidget_(d); | ||
146 | arrange_Widget(d->menu); | ||
147 | openMenu_Widget(d->menu, | ||
148 | bounds_Widget(w).pos.x < mid_Rect(rect_Root(w->root)).x | ||
149 | ? topRight_Rect(itemRect_ListWidget(&d->list, itemIndex)) | ||
150 | : addX_I2(topLeft_Rect(itemRect_ListWidget(&d->list, itemIndex)), | ||
151 | -width_Widget(d->menu))); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | static iBool processEvent_CertListWidget_(iCertListWidget *d, const SDL_Event *ev) { | ||
156 | iWidget *w = as_Widget(d); | ||
157 | /* Handle commands. */ | ||
158 | if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { | ||
159 | const char *cmd = command_UserEvent(ev); | ||
160 | if (equal_Command(cmd, "idents.changed")) { | ||
161 | updateItems_CertListWidget(d); | ||
162 | } | ||
163 | else if (isCommand_Widget(w, ev, "list.clicked")) { | ||
164 | itemClicked_CertListWidget_( | ||
165 | d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); | ||
166 | return iTrue; | ||
167 | } | ||
168 | else if (isCommand_Widget(w, ev, "ident.use")) { | ||
169 | iGmIdentity * ident = menuIdentity_CertListWidget_(d); | ||
170 | const iString *tabUrl = url_DocumentWidget(document_App()); | ||
171 | if (ident) { | ||
172 | if (argLabel_Command(cmd, "clear")) { | ||
173 | clearUse_GmIdentity(ident); | ||
174 | } | ||
175 | else if (arg_Command(cmd)) { | ||
176 | signIn_GmCerts(certs_App(), ident, tabUrl); | ||
177 | postCommand_App("navigate.reload"); | ||
178 | } | ||
179 | else { | ||
180 | signOut_GmCerts(certs_App(), tabUrl); | ||
181 | postCommand_App("navigate.reload"); | ||
182 | } | ||
183 | saveIdentities_GmCerts(certs_App()); | ||
184 | updateItems_CertListWidget(d); | ||
185 | } | ||
186 | return iTrue; | ||
187 | } | ||
188 | else if (isCommand_Widget(w, ev, "ident.edit")) { | ||
189 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
190 | if (ident) { | ||
191 | makeValueInput_Widget(get_Root()->widget, | ||
192 | &ident->notes, | ||
193 | uiHeading_ColorEscape "${heading.ident.notes}", | ||
194 | format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))), | ||
195 | uiTextAction_ColorEscape "${dlg.default}", | ||
196 | format_CStr("!ident.setnotes ident:%p ptr:%p", ident, d)); | ||
197 | } | ||
198 | return iTrue; | ||
199 | } | ||
200 | else if (isCommand_Widget(w, ev, "ident.fingerprint")) { | ||
201 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
202 | if (ident) { | ||
203 | const iString *fps = collect_String( | ||
204 | hexEncode_Block(collect_Block(fingerprint_TlsCertificate(ident->cert)))); | ||
205 | SDL_SetClipboardText(cstr_String(fps)); | ||
206 | } | ||
207 | return iTrue; | ||
208 | } | ||
209 | else if (isCommand_Widget(w, ev, "ident.export")) { | ||
210 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
211 | if (ident) { | ||
212 | iString *pem = collect_String(pem_TlsCertificate(ident->cert)); | ||
213 | append_String(pem, collect_String(privateKeyPem_TlsCertificate(ident->cert))); | ||
214 | iDocumentWidget *expTab = newTab_App(NULL, iTrue); | ||
215 | setUrlAndSource_DocumentWidget( | ||
216 | expTab, | ||
217 | collectNewFormat_String("file:%s.pem", cstr_String(name_GmIdentity(ident))), | ||
218 | collectNewCStr_String("text/plain"), | ||
219 | utf8_String(pem)); | ||
220 | } | ||
221 | return iTrue; | ||
222 | } | ||
223 | else if (isCommand_Widget(w, ev, "ident.setnotes")) { | ||
224 | iGmIdentity *ident = pointerLabel_Command(cmd, "ident"); | ||
225 | if (ident) { | ||
226 | setCStr_String(&ident->notes, suffixPtr_Command(cmd, "value")); | ||
227 | updateItems_CertListWidget(d); | ||
228 | } | ||
229 | return iTrue; | ||
230 | } | ||
231 | else if (isCommand_Widget(w, ev, "ident.pickicon")) { | ||
232 | return iTrue; | ||
233 | } | ||
234 | else if (isCommand_Widget(w, ev, "ident.reveal")) { | ||
235 | const iGmIdentity *ident = menuIdentity_CertListWidget_(d); | ||
236 | if (ident) { | ||
237 | const iString *crtPath = certificatePath_GmCerts(certs_App(), ident); | ||
238 | if (crtPath) { | ||
239 | revealPath_App(crtPath); | ||
240 | } | ||
241 | } | ||
242 | return iTrue; | ||
243 | } | ||
244 | else if (isCommand_Widget(w, ev, "ident.delete")) { | ||
245 | iCertItem *item = d->contextItem; | ||
246 | if (argLabel_Command(cmd, "confirm")) { | ||
247 | makeQuestion_Widget( | ||
248 | uiTextCaution_ColorEscape "${heading.ident.delete}", | ||
249 | format_CStr(cstr_Lang("dlg.confirm.ident.delete"), | ||
250 | uiTextAction_ColorEscape, | ||
251 | cstr_String(&item->label), | ||
252 | uiText_ColorEscape), | ||
253 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | ||
254 | { uiTextCaution_ColorEscape "${dlg.ident.delete}", | ||
255 | 0, | ||
256 | 0, | ||
257 | format_CStr("!ident.delete confirm:0 ptr:%p", d) } }, | ||
258 | 2); | ||
259 | return iTrue; | ||
260 | } | ||
261 | deleteIdentity_GmCerts(certs_App(), menuIdentity_CertListWidget_(d)); | ||
262 | postCommand_App("idents.changed"); | ||
263 | return iTrue; | ||
264 | } | ||
265 | } | ||
266 | if (ev->type == SDL_MOUSEMOTION && !isVisible_Widget(d->menu)) { | ||
267 | const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y); | ||
268 | /* Update cursor. */ | ||
269 | if (contains_Widget(w, mouse)) { | ||
270 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | ||
271 | } | ||
272 | else if (d->contextIndex != iInvalidPos) { | ||
273 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
274 | d->contextIndex = iInvalidPos; | ||
275 | } | ||
276 | } | ||
277 | /* Update context menu items. */ | ||
278 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT) { | ||
279 | d->contextItem = NULL; | ||
280 | if (!isVisible_Widget(d->menu)) { | ||
281 | updateMouseHover_ListWidget(&d->list); | ||
282 | } | ||
283 | if (constHoverItem_ListWidget(&d->list) || isVisible_Widget(d->menu)) { | ||
284 | d->contextItem = hoverItem_ListWidget(&d->list); | ||
285 | /* Context is drawn in hover state. */ | ||
286 | if (d->contextIndex != iInvalidPos) { | ||
287 | invalidateItem_ListWidget(&d->list, d->contextIndex); | ||
288 | } | ||
289 | d->contextIndex = hoverItemIndex_ListWidget(&d->list); | ||
290 | updateContextMenu_CertListWidget_(d); | ||
291 | /* TODO: Some callback-based mechanism would be nice for updating menus right | ||
292 | before they open? At least move these to `updateContextMenu_ */ | ||
293 | const iGmIdentity *ident = constHoverIdentity_CertListWidget(d); | ||
294 | const iString * docUrl = url_DocumentWidget(document_App()); | ||
295 | iForEach(ObjectList, i, children_Widget(d->menu)) { | ||
296 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
297 | iLabelWidget *menuItem = i.object; | ||
298 | const char * cmdItem = cstr_String(command_LabelWidget(menuItem)); | ||
299 | if (equal_Command(cmdItem, "ident.use")) { | ||
300 | const iBool cmdUse = arg_Command(cmdItem) != 0; | ||
301 | const iBool cmdClear = argLabel_Command(cmdItem, "clear") != 0; | ||
302 | setFlags_Widget( | ||
303 | as_Widget(menuItem), | ||
304 | disabled_WidgetFlag, | ||
305 | (cmdClear && !isUsed_GmIdentity(ident)) || | ||
306 | (!cmdClear && cmdUse && isUsedOn_GmIdentity(ident, docUrl)) || | ||
307 | (!cmdClear && !cmdUse && !isUsedOn_GmIdentity(ident, docUrl))); | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | } | ||
312 | if (hoverItem_ListWidget(&d->list) || isVisible_Widget(d->menu)) { | ||
313 | processContextMenuEvent_Widget(d->menu, ev, {}); | ||
314 | } | ||
315 | } | ||
316 | return ((iWidgetClass *) class_Widget(w)->super)->processEvent(w, ev); | ||
317 | } | ||
318 | |||
319 | static void draw_CertListWidget_(const iCertListWidget *d) { | ||
320 | const iWidget *w = constAs_Widget(d); | ||
321 | ((iWidgetClass *) class_Widget(w)->super)->draw(w); | ||
322 | } | ||
323 | |||
324 | static void draw_CertItem_(const iCertItem *d, iPaint *p, iRect itemRect, | ||
325 | const iListWidget *list) { | ||
326 | const iCertListWidget *certList = (const iCertListWidget *) list; | ||
327 | const iBool isMenuVisible = isVisible_Widget(certList->menu); | ||
328 | const iBool isDragging = constDragItem_ListWidget(list) == d; | ||
329 | const iBool isPressing = isMouseDown_ListWidget(list) && !isDragging; | ||
330 | const iBool isHover = | ||
331 | (!isMenuVisible && | ||
332 | isHover_Widget(constAs_Widget(list)) && | ||
333 | constHoverItem_ListWidget(list) == d) || | ||
334 | (isMenuVisible && certList->contextItem == d) || | ||
335 | isDragging; | ||
336 | const int itemHeight = height_Rect(itemRect); | ||
337 | const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) | ||
338 | : uiIcon_ColorId; | ||
339 | const int altIconColor = isPressing ? uiTextPressed_ColorId : uiTextCaution_ColorId; | ||
340 | const int font = certList->itemFonts[d->isBold ? 1 : 0]; | ||
341 | int bg = uiBackgroundSidebar_ColorId; | ||
342 | if (isHover) { | ||
343 | bg = isPressing ? uiBackgroundPressed_ColorId | ||
344 | : uiBackgroundFramelessHover_ColorId; | ||
345 | fillRect_Paint(p, itemRect, bg); | ||
346 | } | ||
347 | else if (d->listItem.isSelected) { | ||
348 | bg = uiBackgroundUnfocusedSelection_ColorId; | ||
349 | fillRect_Paint(p, itemRect, bg); | ||
350 | } | ||
351 | // iInt2 pos = itemRect.pos; | ||
352 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) | ||
353 | : uiTextStrong_ColorId; | ||
354 | const iBool isUsedOnDomain = (d->indent != 0); | ||
355 | iString icon; | ||
356 | initUnicodeN_String(&icon, &d->icon, 1); | ||
357 | iInt2 cPos = topLeft_Rect(itemRect); | ||
358 | const int indent = 1.4f * lineHeight_Text(font); | ||
359 | addv_I2(&cPos, | ||
360 | init_I2(3 * gap_UI, | ||
361 | (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) / | ||
362 | 2)); | ||
363 | const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId | ||
364 | : uiTextFramelessHover_ColorId) | ||
365 | : uiTextDim_ColorId; | ||
366 | if (!d->listItem.isSelected && !isUsedOnDomain) { | ||
367 | drawOutline_Text(font, cPos, metaFg, none_ColorId, range_String(&icon)); | ||
368 | } | ||
369 | drawRange_Text(font, | ||
370 | cPos, | ||
371 | d->listItem.isSelected ? iconColor | ||
372 | : isUsedOnDomain ? altIconColor | ||
373 | : uiBackgroundSidebar_ColorId, | ||
374 | range_String(&icon)); | ||
375 | deinit_String(&icon); | ||
376 | drawRange_Text(d->listItem.isSelected ? certList->itemFonts[1] : font, | ||
377 | add_I2(cPos, init_I2(indent, 0)), | ||
378 | fg, | ||
379 | range_String(&d->label)); | ||
380 | drawRange_Text(uiLabel_FontId, | ||
381 | add_I2(cPos, init_I2(indent, lineHeight_Text(font))), | ||
382 | metaFg, | ||
383 | range_String(&d->meta)); | ||
384 | } | ||
385 | |||
386 | void init_CertListWidget(iCertListWidget *d) { | ||
387 | iWidget *w = as_Widget(d); | ||
388 | init_ListWidget(&d->list); | ||
389 | setId_Widget(w, "certlist"); | ||
390 | setBackgroundColor_Widget(w, none_ColorId); | ||
391 | d->itemFonts[0] = uiContent_FontId; | ||
392 | d->itemFonts[1] = uiContentBold_FontId; | ||
393 | #if defined (iPlatformMobile) | ||
394 | if (deviceType_App() == phone_AppDeviceType) { | ||
395 | d->itemFonts[0] = uiLabelBig_FontId; | ||
396 | d->itemFonts[1] = uiLabelBigBold_FontId; | ||
397 | } | ||
398 | #endif | ||
399 | d->menu = NULL; | ||
400 | d->contextItem = NULL; | ||
401 | d->contextIndex = iInvalidPos; | ||
402 | } | ||
403 | |||
404 | void updateItemHeight_CertListWidget(iCertListWidget *d) { | ||
405 | setItemHeight_ListWidget(&d->list, 3.5f * lineHeight_Text(d->itemFonts[0])); | ||
406 | } | ||
407 | |||
408 | iBool updateItems_CertListWidget(iCertListWidget *d) { | ||
409 | clear_ListWidget(&d->list); | ||
410 | destroy_Widget(d->menu); | ||
411 | d->menu = NULL; | ||
412 | const iString *tabUrl = url_DocumentWidget(document_App()); | ||
413 | const iRangecc tabHost = urlHost_String(tabUrl); | ||
414 | iBool haveItems = iFalse; | ||
415 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { | ||
416 | const iGmIdentity *ident = i.ptr; | ||
417 | iCertItem *item = new_CertItem(); | ||
418 | item->id = (uint32_t) index_PtrArrayConstIterator(&i); | ||
419 | item->icon = 0x1f464; /* person */ | ||
420 | set_String(&item->label, name_GmIdentity(ident)); | ||
421 | iDate until; | ||
422 | validUntil_TlsCertificate(ident->cert, &until); | ||
423 | const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl); | ||
424 | format_String(&item->meta, | ||
425 | "%s", | ||
426 | isActive ? cstr_Lang("ident.using") | ||
427 | : isUsed_GmIdentity(ident) | ||
428 | ? formatCStrs_Lang("ident.usedonurls.n", size_StringSet(ident->useUrls)) | ||
429 | : cstr_Lang("ident.notused")); | ||
430 | const char *expiry = | ||
431 | ident->flags & temporary_GmIdentityFlag | ||
432 | ? cstr_Lang("ident.temporary") | ||
433 | : cstrCollect_String(format_Date(&until, cstr_Lang("ident.expiry"))); | ||
434 | if (isEmpty_String(&ident->notes)) { | ||
435 | appendFormat_String(&item->meta, "\n%s", expiry); | ||
436 | } | ||
437 | else { | ||
438 | appendFormat_String(&item->meta, | ||
439 | " \u2014 %s\n%s%s", | ||
440 | expiry, | ||
441 | escape_Color(uiHeading_ColorId), | ||
442 | cstr_String(&ident->notes)); | ||
443 | } | ||
444 | item->listItem.isSelected = isActive; | ||
445 | if (isUsedOnDomain_GmIdentity(ident, tabHost)) { | ||
446 | item->indent = 1; /* will be highlighted */ | ||
447 | } | ||
448 | addItem_ListWidget(&d->list, item); | ||
449 | haveItems = iTrue; | ||
450 | iRelease(item); | ||
451 | } | ||
452 | return haveItems; | ||
453 | } | ||
454 | |||
455 | void deinit_CertListWidget(iCertListWidget *d) { | ||
456 | iUnused(d); | ||
457 | } | ||
458 | |||
459 | const iGmIdentity *constHoverIdentity_CertListWidget(const iCertListWidget *d) { | ||
460 | const iCertItem *hoverItem = constHoverItem_ListWidget(&d->list); | ||
461 | if (hoverItem) { | ||
462 | return identity_GmCerts(certs_App(), hoverItem->id); | ||
463 | } | ||
464 | return NULL; | ||
465 | } | ||
466 | |||
467 | iGmIdentity *hoverIdentity_CertListWidget(const iCertListWidget *d) { | ||
468 | return iConstCast(iGmIdentity *, constHoverIdentity_CertListWidget(d)); | ||
469 | } | ||
470 | |||
471 | iBeginDefineSubclass(CertListWidget, ListWidget) | ||
472 | .processEvent = (iAny *) processEvent_CertListWidget_, | ||
473 | .draw = (iAny *) draw_CertListWidget_, | ||
474 | iEndDefineSubclass(CertListWidget) | ||