ui/motif/pathbar.c

changeset 925
df27741d02b5
child 926
32b16cbca280
equal deleted inserted replaced
924:6c6e97e06009 925:df27741d02b5
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 }

mercurial