summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-12-02 10:02:16 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-12-02 10:02:16 +0200
commitb9cdb34c59dc133b549deed5a4f3b9bb95197cca (patch)
tree1ed42fabfff17fe5d7d2c0ed4c8687f471df345d
parentf4942e1b4da6dc1334dcdb4f2daae670bfa1f813 (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.
-rw-r--r--CMakeLists.txt2
-rw-r--r--res/about/version.gmi2
-rw-r--r--src/ui/certlistwidget.c474
-rw-r--r--src/ui/certlistwidget.h40
-rw-r--r--src/ui/listwidget.c15
-rw-r--r--src/ui/listwidget.h20
-rw-r--r--src/ui/root.c18
-rw-r--r--src/ui/sidebarwidget.c335
8 files changed, 586 insertions, 320 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5c225c9b..4200c7c5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -168,6 +168,8 @@ set (SOURCES
168 src/ui/bindingswidget.h 168 src/ui/bindingswidget.h
169 src/ui/certimportwidget.c 169 src/ui/certimportwidget.c
170 src/ui/certimportwidget.h 170 src/ui/certimportwidget.h
171 src/ui/certlistwidget.c
172 src/ui/certlistwidget.h
171 src/ui/color.c 173 src/ui/color.c
172 src/ui/color.h 174 src/ui/color.h
173 src/ui/command.c 175 src/ui/command.c
diff --git a/res/about/version.gmi b/res/about/version.gmi
index 7a14e79b..506e3ae0 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -8,7 +8,7 @@
8 8
9## 1.10 9## 1.10
10New features: 10New features:
11* Identity toolbar menu can be used to switch between alternate identities that have been used on the site. If you have multiple accounts on a site, this makes it more convenient to switch between them. 11* Identity toolbar menu can be used to switch between alternate identities. If you have used multiple identities on one site, this makes it more convenient to switch between them.
12 12
13## 1.9.2 13## 1.9.2
14* Windows: Use the correct version number for update checks. 14* Windows: Use the correct version number for update checks.
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
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. 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
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY 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
21SOFTWARE, 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
34iDeclareType(CertItem)
35typedef iListItemClass iCertItemClass;
36
37struct 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
48void 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
59void deinit_CertItem(iCertItem *d) {
60// deinit_String(&d->url);
61 deinit_String(&d->meta);
62 deinit_String(&d->label);
63}
64
65static void draw_CertItem_(const iCertItem *d, iPaint *p, iRect itemRect, const iListWidget *list);
66
67iBeginDefineSubclass(CertItem, ListItem)
68 .draw = (iAny *) draw_CertItem_,
69iEndDefineSubclass(CertItem)
70
71iDefineObjectConstruction(CertItem)
72
73/*----------------------------------------------------------------------------------------------*/
74
75struct 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
83iDefineObjectConstruction(CertListWidget)
84
85static 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
92static 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
136static 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
155static 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
319static void draw_CertListWidget_(const iCertListWidget *d) {
320 const iWidget *w = constAs_Widget(d);
321 ((iWidgetClass *) class_Widget(w)->super)->draw(w);
322}
323
324static 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
386void 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
404void updateItemHeight_CertListWidget(iCertListWidget *d) {
405 setItemHeight_ListWidget(&d->list, 3.5f * lineHeight_Text(d->itemFonts[0]));
406}
407
408iBool 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
455void deinit_CertListWidget(iCertListWidget *d) {
456 iUnused(d);
457}
458
459const 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
467iGmIdentity *hoverIdentity_CertListWidget(const iCertListWidget *d) {
468 return iConstCast(iGmIdentity *, constHoverIdentity_CertListWidget(d));
469}
470
471iBeginDefineSubclass(CertListWidget, ListWidget)
472 .processEvent = (iAny *) processEvent_CertListWidget_,
473 .draw = (iAny *) draw_CertListWidget_,
474iEndDefineSubclass(CertListWidget)
diff --git a/src/ui/certlistwidget.h b/src/ui/certlistwidget.h
new file mode 100644
index 00000000..2e5f6247
--- /dev/null
+++ b/src/ui/certlistwidget.h
@@ -0,0 +1,40 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. 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
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY 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
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#pragma once
24
25#include "listwidget.h"
26
27iDeclareType(CertListWidget)
28
29typedef iListWidgetClass iCertListWidgetClass;
30extern iCertListWidgetClass Class_CertListWidget;
31
32iDeclareObjectConstruction(CertListWidget)
33
34iDeclareType(GmIdentity)
35
36const iGmIdentity * constHoverIdentity_CertListWidget(const iCertListWidget *);
37iGmIdentity * hoverIdentity_CertListWidget (const iCertListWidget *);
38
39iBool updateItems_CertListWidget (iCertListWidget *); /* returns False is empty */
40void updateItemHeight_CertListWidget (iCertListWidget *);
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
index c2ba5581..82e4e451 100644
--- a/src/ui/listwidget.c
+++ b/src/ui/listwidget.c
@@ -49,21 +49,6 @@ iDefineClass(ListItem)
49 49
50iDefineObjectConstruction(ListWidget) 50iDefineObjectConstruction(ListWidget)
51 51
52struct Impl_ListWidget {
53 iWidget widget;
54 iScrollWidget *scroll;
55 iSmoothScroll scrollY;
56 int itemHeight;
57 iPtrArray items;
58 size_t hoverItem;
59 size_t dragItem;
60 iInt2 dragOrigin; /* offset from mouse to drag item's top-left corner */
61 iClick click;
62 iIntSet invalidItems;
63 iVisBuf *visBuf;
64 iBool noHoverWhileScrolling;
65};
66
67static void refreshWhileScrolling_ListWidget_(iAnyObject *any) { 52static void refreshWhileScrolling_ListWidget_(iAnyObject *any) {
68 iListWidget *d = any; 53 iListWidget *d = any;
69 updateVisible_ListWidget(d); 54 updateVisible_ListWidget(d);
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h
index 8adf6ac3..7e6624a0 100644
--- a/src/ui/listwidget.h
+++ b/src/ui/listwidget.h
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25#include "scrollwidget.h" 25#include "scrollwidget.h"
26#include "paint.h" 26#include "paint.h"
27 27
28#include <the_Foundation/intset.h>
28#include <the_Foundation/ptrarray.h> 29#include <the_Foundation/ptrarray.h>
29 30
30iDeclareType(ListWidget) 31iDeclareType(ListWidget)
@@ -48,6 +49,25 @@ iDeclareObjectConstruction(ListItem)
48iDeclareWidgetClass(ListWidget) 49iDeclareWidgetClass(ListWidget)
49iDeclareObjectConstruction(ListWidget) 50iDeclareObjectConstruction(ListWidget)
50 51
52iDeclareType(VisBuf)
53
54struct Impl_ListWidget {
55 iWidget widget;
56 iScrollWidget *scroll;
57 iSmoothScroll scrollY;
58 int itemHeight;
59 iPtrArray items;
60 size_t hoverItem;
61 size_t dragItem;
62 iInt2 dragOrigin; /* offset from mouse to drag item's top-left corner */
63 iClick click;
64 iIntSet invalidItems;
65 iVisBuf *visBuf;
66 iBool noHoverWhileScrolling;
67};
68
69void init_ListWidget (iListWidget *);
70
51void setItemHeight_ListWidget (iListWidget *, int itemHeight); 71void setItemHeight_ListWidget (iListWidget *, int itemHeight);
52 72
53void invalidate_ListWidget (iListWidget *); 73void invalidate_ListWidget (iListWidget *);
diff --git a/src/ui/root.c b/src/ui/root.c
index f06ae842..5ed6b529 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -332,7 +332,8 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
332 return iTrue; 332 return iTrue;
333 } 333 }
334 else if (equal_Command(cmd, "identmenu.open")) { 334 else if (equal_Command(cmd, "identmenu.open")) {
335 iWidget *button = findWidget_Root("navbar.ident"); 335 iWidget *toolBar = findWidget_Root("toolbar");
336 iWidget *button = findWidget_Root(toolBar ? "toolbar.ident" : "navbar.ident");
336 iArray items; 337 iArray items;
337 init_Array(&items, sizeof(iMenuItem)); 338 init_Array(&items, sizeof(iMenuItem));
338 /* Current identity. */ 339 /* Current identity. */
@@ -375,16 +376,21 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
375 (iMenuItem[]){ 376 (iMenuItem[]){
376 { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, 377 { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" },
377 { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, 378 { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" },
378 { "---" }, 379 { "---" } }, 3);
380 if (deviceType_App() == desktop_AppDeviceType) {
381 pushBack_Array(&items, &(iMenuItem)
379 { isVisible_Widget(sidebar) && mode_SidebarWidget(sidebar) == identities_SidebarMode 382 { isVisible_Widget(sidebar) && mode_SidebarWidget(sidebar) == identities_SidebarMode
380 ? leftHalf_Icon " ${menu.hide.identities}" 383 ? leftHalf_Icon " ${menu.hide.identities}"
381 : leftHalf_Icon " ${menu.show.identities}", 384 : leftHalf_Icon " ${menu.show.identities}",
382 0, 385 0,
383 0, 386 0,
384 deviceType_App() == phone_AppDeviceType ? "toolbar.showident" 387 deviceType_App() == phone_AppDeviceType ? "toolbar.showident"
385 : "sidebar.mode arg:3 toggle:1" }, 388 : "sidebar.mode arg:3 toggle:1" });
386 }, 389 }
387 4); 390 else {
391 pushBack_Array(&items, &(iMenuItem){ gear_Icon " ${menu.identities}", 0, 0,
392 "toolbar.showident"});
393 }
388 iWidget *menu = 394 iWidget *menu =
389 makeMenu_Widget(button, constData_Array(&items), size_Array(&items)); 395 makeMenu_Widget(button, constData_Array(&items), size_Array(&items));
390 openMenu_Widget(menu, topLeft_Rect(bounds_Widget(button))); 396 openMenu_Widget(menu, topLeft_Rect(bounds_Widget(button)));
@@ -1475,7 +1481,7 @@ void createUserInterface_Root(iRoot *d) {
1475 frameless_WidgetFlag), 1481 frameless_WidgetFlag),
1476 "toolbar.forward"); 1482 "toolbar.forward");
1477 setId_Widget(addChildFlags_Widget(toolBar, 1483 setId_Widget(addChildFlags_Widget(toolBar,
1478 iClob(newLargeIcon_LabelWidget("\U0001f464", "toolbar.showident")), 1484 iClob(newLargeIcon_LabelWidget("\U0001f464", "identmenu.open")),
1479 frameless_WidgetFlag), 1485 frameless_WidgetFlag),
1480 "toolbar.ident"); 1486 "toolbar.ident");
1481 setId_Widget(addChildFlags_Widget(toolBar, 1487 setId_Widget(addChildFlags_Widget(toolBar,
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 9cfc507a..8440a597 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25#include "app.h" 25#include "app.h"
26#include "defs.h" 26#include "defs.h"
27#include "bookmarks.h" 27#include "bookmarks.h"
28#include "certlistwidget.h"
28#include "command.h" 29#include "command.h"
29#include "documentwidget.h" 30#include "documentwidget.h"
30#include "feeds.h" 31#include "feeds.h"
@@ -96,6 +97,7 @@ struct Impl_SidebarWidget {
96 iString cmdPrefix; 97 iString cmdPrefix;
97 iWidget * blank; 98 iWidget * blank;
98 iListWidget * list; 99 iListWidget * list;
100 iCertListWidget * certList;
99 iWidget * actions; /* below the list, area for buttons */ 101 iWidget * actions; /* below the list, area for buttons */
100 int modeScroll[max_SidebarMode]; 102 int modeScroll[max_SidebarMode];
101 iLabelWidget * modeButtons[max_SidebarMode]; 103 iLabelWidget * modeButtons[max_SidebarMode];
@@ -114,6 +116,10 @@ struct Impl_SidebarWidget {
114 116
115iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) 117iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side)
116 118
119iLocalDef iListWidget *list_SidebarWidget_(iSidebarWidget *d) {
120 return d->mode == identities_SidebarMode ? (iListWidget *) d->certList : d->list;
121}
122
117static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) { 123static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) {
118 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; 124 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0;
119} 125}
@@ -195,65 +201,6 @@ static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const cha
195 return btn; 201 return btn;
196} 202}
197 203
198static iGmIdentity *menuIdentity_SidebarWidget_(const iSidebarWidget *d) {
199 if (d->mode == identities_SidebarMode) {
200 if (d->contextItem) {
201 return identity_GmCerts(certs_App(), d->contextItem->id);
202 }
203 }
204 return NULL;
205}
206
207static void updateContextMenu_SidebarWidget_(iSidebarWidget *d) {
208 if (d->mode != identities_SidebarMode) {
209 return;
210 }
211 iArray *items = collectNew_Array(sizeof(iMenuItem));
212 pushBackN_Array(items, (iMenuItem[]){
213 { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" },
214 { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" },
215 { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" },
216 { "---", 0, 0, NULL },
217 { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" },
218 { "${ident.fingerprint}", 0, 0, "ident.fingerprint" },
219 { export_Icon " ${ident.export}", 0, 0, "ident.export" },
220 { "---", 0, 0, NULL },
221 { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" },
222 }, 9);
223 /* Used URLs. */
224 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d);
225 if (ident) {
226 size_t insertPos = 3;
227 if (!isEmpty_StringSet(ident->useUrls)) {
228 insert_Array(items, insertPos++, &(iMenuItem){ "---", 0, 0, NULL });
229 }
230 const iString *docUrl = url_DocumentWidget(document_App());
231 iBool usedOnCurrentPage = iFalse;
232 iConstForEach(StringSet, i, ident->useUrls) {
233 const iString *url = i.value;
234 usedOnCurrentPage |= equalCase_String(docUrl, url);
235 iRangecc urlStr = range_String(url);
236 if (startsWith_Rangecc(urlStr, "gemini://")) {
237 urlStr.start += 9; /* omit the default scheme */
238 }
239 if (endsWith_Rangecc(urlStr, "/")) {
240 urlStr.end--; /* looks cleaner */
241 }
242 insert_Array(items,
243 insertPos++,
244 &(iMenuItem){ format_CStr(globe_Icon " %s", cstr_Rangecc(urlStr)),
245 0,
246 0,
247 format_CStr("!open url:%s", cstr_String(url)) });
248 }
249 if (!usedOnCurrentPage) {
250 remove_Array(items, 1);
251 }
252 }
253 destroy_Widget(d->menu);
254 d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items));
255}
256
257static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBookmark *bm) { 204static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBookmark *bm) {
258 while (bm->parentId) { 205 while (bm->parentId) {
259 if (contains_IntSet(d->closedFolders, bm->parentId)) { 206 if (contains_IntSet(d->closedFolders, bm->parentId)) {
@@ -547,46 +494,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
547 break; 494 break;
548 } 495 }
549 case identities_SidebarMode: { 496 case identities_SidebarMode: {
550 const iString *tabUrl = url_DocumentWidget(document_App()); 497 isEmpty = !updateItems_CertListWidget(d->certList);
551 const iRangecc tabHost = urlHost_String(tabUrl);
552 isEmpty = iTrue;
553 iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) {
554 const iGmIdentity *ident = i.ptr;
555 iSidebarItem *item = new_SidebarItem();
556 item->id = (uint32_t) index_PtrArrayConstIterator(&i);
557 item->icon = 0x1f464; /* person */
558 set_String(&item->label, name_GmIdentity(ident));
559 iDate until;
560 validUntil_TlsCertificate(ident->cert, &until);
561 const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl);
562 format_String(&item->meta,
563 "%s",
564 isActive ? cstr_Lang("ident.using")
565 : isUsed_GmIdentity(ident)
566 ? formatCStrs_Lang("ident.usedonurls.n", size_StringSet(ident->useUrls))
567 : cstr_Lang("ident.notused"));
568 const char *expiry =
569 ident->flags & temporary_GmIdentityFlag
570 ? cstr_Lang("ident.temporary")
571 : cstrCollect_String(format_Date(&until, cstr_Lang("ident.expiry")));
572 if (isEmpty_String(&ident->notes)) {
573 appendFormat_String(&item->meta, "\n%s", expiry);
574 }
575 else {
576 appendFormat_String(&item->meta,
577 " \u2014 %s\n%s%s",
578 expiry,
579 escape_Color(uiHeading_ColorId),
580 cstr_String(&ident->notes));
581 }
582 item->listItem.isSelected = isActive;
583 if (isUsedOnDomain_GmIdentity(ident, tabHost)) {
584 item->indent = 1; /* will be highlighted */
585 }
586 addItem_ListWidget(d->list, item);
587 iRelease(item);
588 isEmpty = iFalse;
589 }
590 /* Actions. */ 498 /* Actions. */
591 if (!isEmpty) { 499 if (!isEmpty) {
592 addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); 500 addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0);
@@ -597,9 +505,11 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
597 default: 505 default:
598 break; 506 break;
599 } 507 }
600 scrollOffset_ListWidget(d->list, 0); 508 setFlags_Widget(as_Widget(d->list), hidden_WidgetFlag, d->mode == identities_SidebarMode);
601 updateVisible_ListWidget(d->list); 509 setFlags_Widget(as_Widget(d->certList), hidden_WidgetFlag, d->mode != identities_SidebarMode);
602 invalidate_ListWidget(d->list); 510 scrollOffset_ListWidget(list_SidebarWidget_(d), 0);
511 updateVisible_ListWidget(list_SidebarWidget_(d));
512 invalidate_ListWidget(list_SidebarWidget_(d));
603 /* Content for a blank tab. */ 513 /* Content for a blank tab. */
604 if (isEmpty) { 514 if (isEmpty) {
605 if (d->mode == feeds_SidebarMode) { 515 if (d->mode == feeds_SidebarMode) {
@@ -649,7 +559,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
649#endif 559#endif
650 arrange_Widget(d->actions); 560 arrange_Widget(d->actions);
651 arrange_Widget(as_Widget(d)); 561 arrange_Widget(as_Widget(d));
652 updateMouseHover_ListWidget(d->list); 562 updateMouseHover_ListWidget(list_SidebarWidget_(d));
653} 563}
654 564
655static void updateItems_SidebarWidget_(iSidebarWidget *d) { 565static void updateItems_SidebarWidget_(iSidebarWidget *d) {
@@ -668,10 +578,11 @@ static size_t findItem_SidebarWidget_(const iSidebarWidget *d, int id) {
668} 578}
669 579
670static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { 580static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) {
581 const float heights[max_SidebarMode] = { 1.333f, 2.333f, 1.333f, 3.5f, 1.2f };
671 if (d->list) { 582 if (d->list) {
672 const float heights[max_SidebarMode] = { 1.333f, 2.333f, 1.333f, 3.5f, 1.2f };
673 setItemHeight_ListWidget(d->list, heights[d->mode] * lineHeight_Text(d->itemFonts[0])); 583 setItemHeight_ListWidget(d->list, heights[d->mode] * lineHeight_Text(d->itemFonts[0]));
674 } 584 }
585 updateItemHeight_CertListWidget(d->certList);
675} 586}
676 587
677iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) { 588iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
@@ -679,18 +590,18 @@ iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
679 return iFalse; 590 return iFalse;
680 } 591 }
681 if (d->mode >= 0 && d->mode < max_SidebarMode) { 592 if (d->mode >= 0 && d->mode < max_SidebarMode) {
682 d->modeScroll[d->mode] = scrollPos_ListWidget(d->list); /* saved for later */ 593 d->modeScroll[d->mode] = scrollPos_ListWidget(list_SidebarWidget_(d)); /* saved for later */
683 } 594 }
684 d->mode = mode; 595 d->mode = mode;
685 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) { 596 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) {
686 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode); 597 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode);
687 } 598 }
688 setBackgroundColor_Widget(as_Widget(d->list), 599 setBackgroundColor_Widget(as_Widget(list_SidebarWidget_(d)),
689 d->mode == documentOutline_SidebarMode ? tmBannerBackground_ColorId 600 d->mode == documentOutline_SidebarMode ? tmBannerBackground_ColorId
690 : uiBackgroundSidebar_ColorId); 601 : uiBackgroundSidebar_ColorId);
691 updateItemHeight_SidebarWidget_(d); 602 updateItemHeight_SidebarWidget_(d);
692 /* Restore previous scroll position. */ 603 /* Restore previous scroll position. */
693 setScrollPos_ListWidget(d->list, d->modeScroll[mode]); 604 setScrollPos_ListWidget(list_SidebarWidget_(d), d->modeScroll[mode]);
694 return iTrue; 605 return iTrue;
695} 606}
696 607
@@ -787,6 +698,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
787 iZap(d->modeButtons); 698 iZap(d->modeButtons);
788 d->resizer = NULL; 699 d->resizer = NULL;
789 d->list = NULL; 700 d->list = NULL;
701 d->certList = NULL;
790 d->actions = NULL; 702 d->actions = NULL;
791 d->closedFolders = new_IntSet(); 703 d->closedFolders = new_IntSet();
792 /* On a phone, the right sidebar is used exclusively for Identities. */ 704 /* On a phone, the right sidebar is used exclusively for Identities. */
@@ -829,10 +741,16 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
829 setFlags_Widget(content, resizeChildren_WidgetFlag, iTrue); 741 setFlags_Widget(content, resizeChildren_WidgetFlag, iTrue);
830 iWidget *listAndActions = makeVDiv_Widget(); 742 iWidget *listAndActions = makeVDiv_Widget();
831 addChild_Widget(content, iClob(listAndActions)); 743 addChild_Widget(content, iClob(listAndActions));
832 d->list = new_ListWidget(); 744 iWidget *listArea = new_Widget();
745 setFlags_Widget(listArea, resizeChildren_WidgetFlag, iTrue);
746 d->list = new_ListWidget();
833 setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI); 747 setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI);
748 addChild_Widget(listArea, iClob(d->list));
749 d->certList = new_CertListWidget();
750 setPadding_Widget(as_Widget(d->certList), 0, gap_UI, 0, gap_UI);
751 addChild_Widget(listArea, iClob(d->certList));
834 addChildFlags_Widget(listAndActions, 752 addChildFlags_Widget(listAndActions,
835 iClob(d->list), 753 iClob(listArea),
836 expand_WidgetFlag); // | drawBackgroundToHorizontalSafeArea_WidgetFlag); 754 expand_WidgetFlag); // | drawBackgroundToHorizontalSafeArea_WidgetFlag);
837 setId_Widget(addChildPosFlags_Widget(listAndActions, 755 setId_Widget(addChildPosFlags_Widget(listAndActions,
838 iClob(d->actions = new_Widget()), 756 iClob(d->actions = new_Widget()),
@@ -892,16 +810,16 @@ iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) {
892 810
893static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { 811static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) {
894 if (d->mode == identities_SidebarMode) { 812 if (d->mode == identities_SidebarMode) {
895 const iSidebarItem *hoverItem = constHoverItem_ListWidget(d->list); 813 return constHoverIdentity_CertListWidget(d->certList);
896 if (hoverItem) {
897 return identity_GmCerts(certs_App(), hoverItem->id);
898 }
899 } 814 }
900 return NULL; 815 return NULL;
901} 816}
902 817
903static iGmIdentity *hoverIdentity_SidebarWidget_(const iSidebarWidget *d) { 818static iGmIdentity *hoverIdentity_SidebarWidget_(const iSidebarWidget *d) {
904 return iConstCast(iGmIdentity *, constHoverIdentity_SidebarWidget_(d)); 819 if (d->mode == identities_SidebarMode) {
820 return hoverIdentity_CertListWidget(d->certList);
821 }
822 return NULL;
905} 823}
906 824
907static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, size_t itemIndex) { 825static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, size_t itemIndex) {
@@ -944,23 +862,6 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
944 } 862 }
945 break; 863 break;
946 } 864 }
947 case identities_SidebarMode: {
948 d->contextItem = item;
949 if (d->contextIndex != iInvalidPos) {
950 invalidateItem_ListWidget(d->list, d->contextIndex);
951 }
952 d->contextIndex = itemIndex;
953 if (itemIndex < numItems_ListWidget(d->list)) {
954 updateContextMenu_SidebarWidget_(d);
955 arrange_Widget(d->menu);
956 openMenu_Widget(d->menu,
957 d->side == left_SidebarSide
958 ? topRight_Rect(itemRect_ListWidget(d->list, itemIndex))
959 : addX_I2(topLeft_Rect(itemRect_ListWidget(d->list, itemIndex)),
960 -width_Widget(d->menu)));
961 }
962 break;
963 }
964 default: 865 default:
965 break; 866 break;
966 } 867 }
@@ -1245,9 +1146,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1245 } 1146 }
1246 } 1147 }
1247 } 1148 }
1248 else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) {
1249 updateItems_SidebarWidget_(d);
1250 }
1251 else if (deviceType_App() == tablet_AppDeviceType && equal_Command(cmd, "toolbar.showident")) { 1149 else if (deviceType_App() == tablet_AppDeviceType && equal_Command(cmd, "toolbar.showident")) {
1252 postCommandf_App("sidebar.mode arg:%d toggle:1", identities_SidebarMode); 1150 postCommandf_App("sidebar.mode arg:%d toggle:1", identities_SidebarMode);
1253 return iTrue; 1151 return iTrue;
@@ -1316,9 +1214,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1316 } 1214 }
1317 return iTrue; 1215 return iTrue;
1318 } 1216 }
1319// else if (isCommand_Widget(w, ev, "menu.closed")) {
1320 // invalidateItem_ListWidget(d->list, d->contextIndex);
1321// }
1322 else if (isCommand_Widget(w, ev, "bookmark.open")) { 1217 else if (isCommand_Widget(w, ev, "bookmark.open")) {
1323 const iSidebarItem *item = d->contextItem; 1218 const iSidebarItem *item = d->contextItem;
1324 if (d->mode == bookmarks_SidebarMode && item) { 1219 if (d->mode == bookmarks_SidebarMode && item) {
@@ -1548,103 +1443,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1548 } 1443 }
1549 } 1444 }
1550 } 1445 }
1551 else if (isCommand_Widget(w, ev, "ident.use")) {
1552 iGmIdentity * ident = menuIdentity_SidebarWidget_(d);
1553 const iString *tabUrl = url_DocumentWidget(document_App());
1554 if (ident) {
1555 if (argLabel_Command(cmd, "clear")) {
1556 clearUse_GmIdentity(ident);
1557 }
1558 else if (arg_Command(cmd)) {
1559 signIn_GmCerts(certs_App(), ident, tabUrl);
1560 postCommand_App("navigate.reload");
1561 }
1562 else {
1563 signOut_GmCerts(certs_App(), tabUrl);
1564 postCommand_App("navigate.reload");
1565 }
1566 saveIdentities_GmCerts(certs_App());
1567 updateItems_SidebarWidget_(d);
1568 }
1569 return iTrue;
1570 }
1571 else if (isCommand_Widget(w, ev, "ident.edit")) {
1572 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d);
1573 if (ident) {
1574 makeValueInput_Widget(get_Root()->widget,
1575 &ident->notes,
1576 uiHeading_ColorEscape "${heading.ident.notes}",
1577 format_CStr(cstr_Lang("dlg.ident.notes"), cstr_String(name_GmIdentity(ident))),
1578 uiTextAction_ColorEscape "${dlg.default}",
1579 format_CStr("!ident.setnotes ident:%p ptr:%p", ident, d));
1580 }
1581 return iTrue;
1582 }
1583 else if (isCommand_Widget(w, ev, "ident.fingerprint")) {
1584 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d);
1585 if (ident) {
1586 const iString *fps = collect_String(
1587 hexEncode_Block(collect_Block(fingerprint_TlsCertificate(ident->cert))));
1588 SDL_SetClipboardText(cstr_String(fps));
1589 }
1590 return iTrue;
1591 }
1592 else if (isCommand_Widget(w, ev, "ident.export")) {
1593 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d);
1594 if (ident) {
1595 iString *pem = collect_String(pem_TlsCertificate(ident->cert));
1596 append_String(pem, collect_String(privateKeyPem_TlsCertificate(ident->cert)));
1597 iDocumentWidget *expTab = newTab_App(NULL, iTrue);
1598 setUrlAndSource_DocumentWidget(
1599 expTab,
1600 collectNewFormat_String("file:%s.pem", cstr_String(name_GmIdentity(ident))),
1601 collectNewCStr_String("text/plain"),
1602 utf8_String(pem));
1603 }
1604 return iTrue;
1605 }
1606 else if (isCommand_Widget(w, ev, "ident.setnotes")) {
1607 iGmIdentity *ident = pointerLabel_Command(cmd, "ident");
1608 if (ident) {
1609 setCStr_String(&ident->notes, suffixPtr_Command(cmd, "value"));
1610 updateItems_SidebarWidget_(d);
1611 }
1612 return iTrue;
1613 }
1614 else if (isCommand_Widget(w, ev, "ident.pickicon")) {
1615 return iTrue;
1616 }
1617 else if (isCommand_Widget(w, ev, "ident.reveal")) {
1618 const iGmIdentity *ident = menuIdentity_SidebarWidget_(d);
1619 if (ident) {
1620 const iString *crtPath = certificatePath_GmCerts(certs_App(), ident);
1621 if (crtPath) {
1622 revealPath_App(crtPath);
1623 }
1624 }
1625 return iTrue;
1626 }
1627 else if (isCommand_Widget(w, ev, "ident.delete")) {
1628 iSidebarItem *item = d->contextItem;
1629 if (argLabel_Command(cmd, "confirm")) {
1630 makeQuestion_Widget(
1631 uiTextCaution_ColorEscape "${heading.ident.delete}",
1632 format_CStr(cstr_Lang("dlg.confirm.ident.delete"),
1633 uiTextAction_ColorEscape,
1634 cstr_String(&item->label),
1635 uiText_ColorEscape),
1636 (iMenuItem[]){ { "${cancel}", 0, 0, NULL },
1637 { uiTextCaution_ColorEscape "${dlg.ident.delete}",
1638 0,
1639 0,
1640 format_CStr("!ident.delete confirm:0 ptr:%p", d) } },
1641 2);
1642 return iTrue;
1643 }
1644 deleteIdentity_GmCerts(certs_App(), menuIdentity_SidebarWidget_(d));
1645 postCommand_App("idents.changed");
1646 return iTrue;
1647 }
1648 else if (isCommand_Widget(w, ev, "history.delete")) { 1446 else if (isCommand_Widget(w, ev, "history.delete")) {
1649 if (d->contextItem && !isEmpty_String(&d->contextItem->url)) { 1447 if (d->contextItem && !isEmpty_String(&d->contextItem->url)) {
1650 removeUrl_Visited(visited_App(), &d->contextItem->url); 1448 removeUrl_Visited(visited_App(), &d->contextItem->url);
@@ -1698,14 +1496,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1698 /* Update cursor. */ 1496 /* Update cursor. */
1699 else if (contains_Widget(w, mouse)) { 1497 else if (contains_Widget(w, mouse)) {
1700 const iSidebarItem *item = constHoverItem_ListWidget(d->list); 1498 const iSidebarItem *item = constHoverItem_ListWidget(d->list);
1701 if (item && d->mode != identities_SidebarMode) { 1499 setCursor_Window(get_Window(),
1702 setCursor_Window(get_Window(), 1500 item ? (item->listItem.isSeparator ? SDL_SYSTEM_CURSOR_ARROW
1703 item->listItem.isSeparator ? SDL_SYSTEM_CURSOR_ARROW 1501 : SDL_SYSTEM_CURSOR_HAND)
1704 : SDL_SYSTEM_CURSOR_HAND); 1502 : SDL_SYSTEM_CURSOR_ARROW);
1705 }
1706 else {
1707 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);
1708 }
1709 } 1503 }
1710 if (d->contextIndex != iInvalidPos) { 1504 if (d->contextIndex != iInvalidPos) {
1711 invalidateItem_ListWidget(d->list, d->contextIndex); 1505 invalidateItem_ListWidget(d->list, d->contextIndex);
@@ -1713,7 +1507,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1713 } 1507 }
1714 } 1508 }
1715 /* Update context menu items. */ 1509 /* Update context menu items. */
1716 if ((d->menu || d->mode == identities_SidebarMode) && ev->type == SDL_MOUSEBUTTONDOWN) { 1510 if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) {
1717 if (ev->button.button == SDL_BUTTON_RIGHT) { 1511 if (ev->button.button == SDL_BUTTON_RIGHT) {
1718 d->contextItem = NULL; 1512 d->contextItem = NULL;
1719 if (!isVisible_Widget(d->menu)) { 1513 if (!isVisible_Widget(d->menu)) {
@@ -1726,7 +1520,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1726 invalidateItem_ListWidget(d->list, d->contextIndex); 1520 invalidateItem_ListWidget(d->list, d->contextIndex);
1727 } 1521 }
1728 d->contextIndex = hoverItemIndex_ListWidget(d->list); 1522 d->contextIndex = hoverItemIndex_ListWidget(d->list);
1729 updateContextMenu_SidebarWidget_(d);
1730 /* TODO: Some callback-based mechanism would be nice for updating menus right 1523 /* TODO: Some callback-based mechanism would be nice for updating menus right
1731 before they open? At least move these to `updateContextMenu_ */ 1524 before they open? At least move these to `updateContextMenu_ */
1732 if (d->mode == bookmarks_SidebarMode && d->contextItem) { 1525 if (d->mode == bookmarks_SidebarMode && d->contextItem) {
@@ -1756,26 +1549,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1756 isRead ? circle_Icon " ${feeds.entry.markunread}" 1549 isRead ? circle_Icon " ${feeds.entry.markunread}"
1757 : circleWhite_Icon " ${feeds.entry.markread}"); 1550 : circleWhite_Icon " ${feeds.entry.markread}");
1758 } 1551 }
1759 else if (d->mode == identities_SidebarMode) {
1760 const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d);
1761 const iString * docUrl = url_DocumentWidget(document_App());
1762 iForEach(ObjectList, i, children_Widget(d->menu)) {
1763 if (isInstance_Object(i.object, &Class_LabelWidget)) {
1764 iLabelWidget *menuItem = i.object;
1765 const char * cmdItem = cstr_String(command_LabelWidget(menuItem));
1766 if (equal_Command(cmdItem, "ident.use")) {
1767 const iBool cmdUse = arg_Command(cmdItem) != 0;
1768 const iBool cmdClear = argLabel_Command(cmdItem, "clear") != 0;
1769 setFlags_Widget(
1770 as_Widget(menuItem),
1771 disabled_WidgetFlag,
1772 (cmdClear && !isUsed_GmIdentity(ident)) ||
1773 (!cmdClear && cmdUse && isUsedOn_GmIdentity(ident, docUrl)) ||
1774 (!cmdClear && !cmdUse && !isUsedOn_GmIdentity(ident, docUrl)));
1775 }
1776 }
1777 }
1778 }
1779 } 1552 }
1780 } 1553 }
1781 } 1554 }
@@ -2077,40 +1850,6 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2077 } 1850 }
2078 iEndCollect(); 1851 iEndCollect();
2079 } 1852 }
2080 else if (sidebar->mode == identities_SidebarMode) {
2081 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
2082 : uiTextStrong_ColorId;
2083 const iBool isUsedOnDomain = (d->indent != 0);
2084 iString icon;
2085 initUnicodeN_String(&icon, &d->icon, 1);
2086 iInt2 cPos = topLeft_Rect(itemRect);
2087 const int indent = 1.4f * lineHeight_Text(font);
2088 addv_I2(&cPos,
2089 init_I2(3 * gap_UI,
2090 (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) /
2091 2));
2092 const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId
2093 : uiTextFramelessHover_ColorId)
2094 : uiTextDim_ColorId;
2095 if (!d->listItem.isSelected && !isUsedOnDomain) {
2096 drawOutline_Text(font, cPos, metaFg, none_ColorId, range_String(&icon));
2097 }
2098 drawRange_Text(font,
2099 cPos,
2100 d->listItem.isSelected ? iconColor
2101 : isUsedOnDomain ? altIconColor
2102 : uiBackgroundSidebar_ColorId,
2103 range_String(&icon));
2104 deinit_String(&icon);
2105 drawRange_Text(d->listItem.isSelected ? sidebar->itemFonts[1] : font,
2106 add_I2(cPos, init_I2(indent, 0)),
2107 fg,
2108 range_String(&d->label));
2109 drawRange_Text(uiLabel_FontId,
2110 add_I2(cPos, init_I2(indent, lineHeight_Text(font))),
2111 metaFg,
2112 range_String(&d->meta));
2113 }
2114} 1853}
2115 1854
2116iBeginDefineSubclass(SidebarWidget, Widget) 1855iBeginDefineSubclass(SidebarWidget, Widget)