| |
1 /* |
| |
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| |
3 * |
| |
4 * Copyright 2025 Olaf Wintermann. All rights reserved. |
| |
5 * |
| |
6 * Redistribution and use in source and binary forms, with or without |
| |
7 * modification, are permitted provided that the following conditions are met: |
| |
8 * |
| |
9 * 1. Redistributions of source code must retain the above copyright |
| |
10 * notice, this list of conditions and the following disclaimer. |
| |
11 * |
| |
12 * 2. Redistributions in binary form must reproduce the above copyright |
| |
13 * notice, this list of conditions and the following disclaimer in the |
| |
14 * documentation and/or other materials provided with the distribution. |
| |
15 * |
| |
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| |
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| |
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| |
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| |
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| |
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| |
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| |
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| |
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| |
26 * POSSIBILITY OF SUCH DAMAGE. |
| |
27 */ |
| |
28 |
| |
29 #include "pathbar.h" |
| |
30 |
| |
31 #include <unistd.h> |
| |
32 #include <cx/string.h> |
| |
33 |
| |
34 |
| |
35 |
| |
36 void pathbar_resize(Widget w, PathBar *p, XtPointer d) |
| |
37 { |
| |
38 Dimension width, height; |
| |
39 XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); |
| |
40 |
| |
41 Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); |
| |
42 |
| |
43 Dimension maxHeight = 0; |
| |
44 |
| |
45 /* get width/height from all widgets */ |
| |
46 Dimension pathWidth = 0; |
| |
47 for(int i=0;i<p->numSegments;i++) { |
| |
48 Dimension segWidth; |
| |
49 Dimension segHeight; |
| |
50 XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); |
| |
51 segW[i] = segWidth; |
| |
52 pathWidth += segWidth; |
| |
53 if(segHeight > maxHeight) { |
| |
54 maxHeight = segHeight; |
| |
55 } |
| |
56 } |
| |
57 Dimension tfHeight; |
| |
58 XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); |
| |
59 if(tfHeight > maxHeight) { |
| |
60 maxHeight = tfHeight; |
| |
61 } |
| |
62 |
| |
63 Boolean arrows = False; |
| |
64 if(pathWidth + 10 > width) { |
| |
65 arrows = True; |
| |
66 pathWidth += p->lw + p->rw; |
| |
67 } |
| |
68 |
| |
69 /* calc max visible widgets */ |
| |
70 int start = 0; |
| |
71 if(arrows) { |
| |
72 Dimension vis = p->lw+p->rw; |
| |
73 for(int i=p->numSegments;i>0;i--) { |
| |
74 Dimension segWidth = segW[i-1]; |
| |
75 if(vis + segWidth + 10 > width) { |
| |
76 start = i; |
| |
77 arrows = True; |
| |
78 break; |
| |
79 } |
| |
80 vis += segWidth; |
| |
81 } |
| |
82 } else { |
| |
83 p->shift = 0; |
| |
84 } |
| |
85 |
| |
86 int leftShift = 0; |
| |
87 if(p->shift < 0) { |
| |
88 if(start + p->shift < 0) { |
| |
89 leftShift = start; |
| |
90 start = 0; |
| |
91 p->shift = -leftShift; |
| |
92 } else { |
| |
93 leftShift = -p->shift; /* negative shift */ |
| |
94 start += p->shift; |
| |
95 } |
| |
96 } |
| |
97 |
| |
98 int x = 0; |
| |
99 if(arrows) { |
| |
100 XtManageChild(p->left); |
| |
101 XtManageChild(p->right); |
| |
102 x = p->lw; |
| |
103 } else { |
| |
104 XtUnmanageChild(p->left); |
| |
105 XtUnmanageChild(p->right); |
| |
106 } |
| |
107 |
| |
108 for(int i=0;i<p->numSegments;i++) { |
| |
109 if(i >= start && i < p->numSegments - leftShift && !p->input) { |
| |
110 XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); |
| |
111 x += segW[i]; |
| |
112 XtManageChild(p->pathSegments[i]); |
| |
113 } else { |
| |
114 XtUnmanageChild(p->pathSegments[i]); |
| |
115 } |
| |
116 } |
| |
117 |
| |
118 if(arrows) { |
| |
119 XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); |
| |
120 XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); |
| |
121 } |
| |
122 |
| |
123 free(segW); |
| |
124 |
| |
125 Dimension rw, rh; |
| |
126 XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); |
| |
127 |
| |
128 XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); |
| |
129 } |
| |
130 |
| |
131 static void pathbarActivateTF(PathBar *p) |
| |
132 { |
| |
133 XtUnmanageChild(p->left); |
| |
134 XtUnmanageChild(p->right); |
| |
135 XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); |
| |
136 XtManageChild(p->textfield); |
| |
137 p->input = 1; |
| |
138 |
| |
139 XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); |
| |
140 |
| |
141 pathbar_resize(p->widget, p, NULL); |
| |
142 } |
| |
143 |
| |
144 void PathBarActivateTextfield(PathBar *p) |
| |
145 { |
| |
146 p->focus = 1; |
| |
147 pathbarActivateTF(p); |
| |
148 } |
| |
149 |
| |
150 void pathbar_input(Widget w, PathBar *p, XtPointer c) |
| |
151 { |
| |
152 XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; |
| |
153 XEvent *xevent = cbs->event; |
| |
154 |
| |
155 if (cbs->reason == XmCR_INPUT) { |
| |
156 if (xevent->xany.type == ButtonPress) { |
| |
157 p->focus = 0; |
| |
158 pathbarActivateTF(p); |
| |
159 } |
| |
160 } |
| |
161 } |
| |
162 |
| |
163 void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) |
| |
164 { |
| |
165 if(--p->focus < 0) { |
| |
166 p->input = False; |
| |
167 XtUnmanageChild(p->textfield); |
| |
168 } |
| |
169 } |
| |
170 |
| |
171 static cxmutstr concat_path_s(cxstring base, cxstring path) { |
| |
172 if(!path.ptr) { |
| |
173 path = CX_STR(""); |
| |
174 } |
| |
175 |
| |
176 int add_separator = 0; |
| |
177 if(base.length != 0 && base.ptr[base.length-1] == '/') { |
| |
178 if(path.ptr[0] == '/') { |
| |
179 base.length--; |
| |
180 } |
| |
181 } else { |
| |
182 if(path.length == 0 || path.ptr[0] != '/') { |
| |
183 add_separator = 1; |
| |
184 } |
| |
185 } |
| |
186 |
| |
187 cxmutstr url; |
| |
188 if(add_separator) { |
| |
189 url = cx_strcat(3, base, CX_STR("/"), path); |
| |
190 } else { |
| |
191 url = cx_strcat(2, base, path); |
| |
192 } |
| |
193 |
| |
194 return url; |
| |
195 } |
| |
196 |
| |
197 static char* ConcatPath(const char *path1, const char *path2) { |
| |
198 return concat_path_s(cx_str(path1), cx_str(path2)).ptr; |
| |
199 } |
| |
200 |
| |
201 void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) |
| |
202 { |
| |
203 char *newpath = XNETextGetString(p->textfield); |
| |
204 if(newpath) { |
| |
205 if(newpath[0] == '~') { |
| |
206 char *p = newpath+1; |
| |
207 char *home = getenv("HOME"); |
| |
208 char *cp = ConcatPath(home, p); |
| |
209 XtFree(newpath); |
| |
210 newpath = cp; |
| |
211 } else if(newpath[0] != '/') { |
| |
212 char curdir[2048]; |
| |
213 curdir[0] = 0; |
| |
214 getcwd(curdir, 2048); |
| |
215 char *cp = ConcatPath(curdir, newpath); |
| |
216 XtFree(newpath); |
| |
217 newpath = cp; |
| |
218 } |
| |
219 |
| |
220 /* update path */ |
| |
221 PathBarSetPath(p, newpath); |
| |
222 if(p->updateDir) { |
| |
223 p->updateDir(p->updateDirData, newpath, -1); |
| |
224 } |
| |
225 XtFree(newpath); |
| |
226 |
| |
227 /* hide textfield and show path as buttons */ |
| |
228 XtUnmanageChild(p->textfield); |
| |
229 pathbar_resize(p->widget, p, NULL); |
| |
230 |
| |
231 if(p->focus_widget) { |
| |
232 XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); |
| |
233 } |
| |
234 } |
| |
235 } |
| |
236 |
| |
237 void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) |
| |
238 { |
| |
239 p->shift--; |
| |
240 pathbar_resize(p->widget, p, NULL); |
| |
241 } |
| |
242 |
| |
243 void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) |
| |
244 { |
| |
245 if(p->shift < 0) { |
| |
246 p->shift++; |
| |
247 } |
| |
248 pathbar_resize(p->widget, p, NULL); |
| |
249 } |
| |
250 |
| |
251 static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { |
| |
252 PathBar *pb = data; |
| |
253 if(event->type == KeyReleaseMask) { |
| |
254 if(event->xkey.keycode == 9) { |
| |
255 XtUnmanageChild(pb->textfield); |
| |
256 pathbar_resize(pb->widget, pb, NULL); |
| |
257 *dispatch = False; |
| |
258 } else if(event->xkey.keycode == 36) { |
| |
259 pathbar_pathinput(pb->textfield, pb, NULL); |
| |
260 *dispatch = False; |
| |
261 } |
| |
262 } |
| |
263 } |
| |
264 |
| |
265 PathBar* CreatePathBar(Widget parent, ArgList args, int n) |
| |
266 { |
| |
267 PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); |
| |
268 bar->path = NULL; |
| |
269 bar->updateDir = NULL; |
| |
270 bar->updateDirData = NULL; |
| |
271 |
| |
272 bar->focus_widget = NULL; |
| |
273 |
| |
274 bar->getpathelm = NULL; |
| |
275 bar->getpathelmdata = NULL; |
| |
276 bar->current_pathelms = NULL; |
| |
277 |
| |
278 bar->shift = 0; |
| |
279 |
| |
280 XtSetArg(args[n], XmNmarginWidth, 0); n++; |
| |
281 XtSetArg(args[n], XmNmarginHeight, 0); n++; |
| |
282 bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); |
| |
283 XtAddCallback( |
| |
284 bar->widget, |
| |
285 XmNresizeCallback, |
| |
286 (XtCallbackProc)pathbar_resize, |
| |
287 bar); |
| |
288 XtAddCallback( |
| |
289 bar->widget, |
| |
290 XmNinputCallback, |
| |
291 (XtCallbackProc)pathbar_input, |
| |
292 bar); |
| |
293 |
| |
294 Arg a[4]; |
| |
295 XtSetArg(a[0], XmNshadowThickness, 0); |
| |
296 XtSetArg(a[1], XmNx, 0); |
| |
297 XtSetArg(a[2], XmNy, 0); |
| |
298 bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); |
| |
299 bar->input = 0; |
| |
300 XtAddCallback( |
| |
301 bar->textfield, |
| |
302 XmNlosingFocusCallback, |
| |
303 (XtCallbackProc)pathbar_losingfocus, |
| |
304 bar); |
| |
305 XtAddCallback(bar->textfield, XmNactivateCallback, |
| |
306 (XtCallbackProc)pathbar_pathinput, bar); |
| |
307 XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); |
| |
308 |
| |
309 XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); |
| |
310 bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); |
| |
311 XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); |
| |
312 bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); |
| |
313 XtAddCallback( |
| |
314 bar->left, |
| |
315 XmNactivateCallback, |
| |
316 (XtCallbackProc)pathbar_shift_left, |
| |
317 bar); |
| |
318 XtAddCallback( |
| |
319 bar->right, |
| |
320 XmNactivateCallback, |
| |
321 (XtCallbackProc)pathbar_shift_right, |
| |
322 bar); |
| |
323 |
| |
324 Pixel bg; |
| |
325 XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); |
| |
326 XtVaSetValues(bar->widget, XmNbackground, bg, NULL); |
| |
327 |
| |
328 XtManageChild(bar->left); |
| |
329 XtManageChild(bar->right); |
| |
330 |
| |
331 XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); |
| |
332 XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); |
| |
333 |
| |
334 bar->segmentAlloc = 16; |
| |
335 bar->numSegments = 0; |
| |
336 bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); |
| |
337 |
| |
338 bar->selection = 0; |
| |
339 |
| |
340 return bar; |
| |
341 } |
| |
342 |
| |
343 void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) |
| |
344 { |
| |
345 XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); |
| |
346 |
| |
347 int i; |
| |
348 for(i=0;i<bar->numSegments;i++) { |
| |
349 if(bar->pathSegments[i] == w) { |
| |
350 bar->selection = i; |
| |
351 XmToggleButtonSetState(w, True, False); |
| |
352 break; |
| |
353 } |
| |
354 } |
| |
355 |
| |
356 UiPathElm elm = bar->current_pathelms[i]; |
| |
357 cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len)); |
| |
358 if(bar->updateDir) { |
| |
359 XNETextSetString(bar->textfield, path.ptr); |
| |
360 bar->updateDir(bar->updateDirData, path.ptr, i); |
| |
361 } |
| |
362 free(path.ptr); |
| |
363 } |
| |
364 |
| |
365 static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { |
| |
366 for(int i=0;i<nelm;i++) { |
| |
367 free(elms[i].name); |
| |
368 free(elms[i].path); |
| |
369 } |
| |
370 free(elms); |
| |
371 } |
| |
372 |
| |
373 void PathBarSetPath(PathBar *bar, const char *path) |
| |
374 { |
| |
375 if(bar->path) { |
| |
376 free(bar->path); |
| |
377 } |
| |
378 bar->path = strdup(path); |
| |
379 |
| |
380 for(int i=0;i<bar->numSegments;i++) { |
| |
381 XtDestroyWidget(bar->pathSegments[i]); |
| |
382 } |
| |
383 XtUnmanageChild(bar->textfield); |
| |
384 XtManageChild(bar->left); |
| |
385 XtManageChild(bar->right); |
| |
386 bar->input = False; |
| |
387 |
| |
388 Arg args[4]; |
| |
389 XmString str; |
| |
390 |
| |
391 bar->numSegments = 0; |
| |
392 |
| |
393 ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); |
| |
394 size_t nelm = 0; |
| |
395 UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); |
| |
396 if (!path_elm) { |
| |
397 return; |
| |
398 } |
| |
399 bar->current_pathelms = path_elm; |
| |
400 bar->numSegments = nelm; |
| |
401 bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); |
| |
402 |
| |
403 for(int i=0;i<nelm;i++) { |
| |
404 UiPathElm elm = path_elm[i]; |
| |
405 |
| |
406 cxmutstr name = cx_strdup(cx_strn(elm.name, elm.name_len)); |
| |
407 str = XmStringCreateLocalized(elm.name); |
| |
408 free(name.ptr); |
| |
409 |
| |
410 XtSetArg(args[0], XmNlabelString, str); |
| |
411 XtSetArg(args[1], XmNfillOnSelect, True); |
| |
412 XtSetArg(args[2], XmNindicatorOn, False); |
| |
413 Widget button = XmCreateToggleButton(bar->widget, "pbbutton", args, 3); |
| |
414 XtAddCallback( |
| |
415 button, |
| |
416 XmNvalueChangedCallback, |
| |
417 (XtCallbackProc)PathBarChangeDir, |
| |
418 bar); |
| |
419 XmStringFree(str); |
| |
420 |
| |
421 bar->pathSegments[i] = button; |
| |
422 } |
| |
423 |
| |
424 bar->selection = bar->numSegments-1; |
| |
425 XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); |
| |
426 |
| |
427 XNETextSetString(bar->textfield, (char*)path); |
| |
428 XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); |
| |
429 |
| |
430 pathbar_resize(bar->widget, bar, NULL); |
| |
431 } |
| |
432 |
| |
433 void PathBarDestroy(PathBar *pathbar) { |
| |
434 if(pathbar->path) { |
| |
435 XtFree(pathbar->path); |
| |
436 } |
| |
437 XtFree((void*)pathbar->pathSegments); |
| |
438 XtFree((void*)pathbar); |
| |
439 } |