2 * Copyright 2022 Olaf Wintermann
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
34 #include <cx/string.h>
37 static void sidebar_class_init(void);
38 static void sidebar_class_part_init (WidgetClass wc);
39 static void sidebar_init(Widget request, Widget neww, ArgList args, Cardinal *num_args);
40 static void sidebar_destroy(Widget widget);
41 static void sidebar_resize(Widget widget);
42 static void sidebar_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes);
43 static void sidebar_expose(Widget widget, XEvent *event, Region region);
44 static Boolean sidebar_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args);
45 static void sidebar_insert_child(Widget child);
46 Boolean sidebar_acceptfocus(Widget widget, Time *time);
48 static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
49 static void SelectElmAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
50 static void PopupAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
52 static void sidebar_xdnd_callback(Widget w, XtPointer udata, XtPointer cdata);
55 static XtResource resources[] = {
59 static XtActionsRec actionslist[] = {
60 {"focusIn", FocusInAP},
61 {"selectElm", SelectElmAP},
65 static char defaultTranslations[] = "<FocusIn>: focusIn()\n"
66 "<Btn1Down>: selectElm()\n"
67 "<Btn3Down>: popup()";
70 static XtResource constraints[] = {};
72 SidebarClassRec sidebarWidgetClassRec = {
75 (WidgetClass)&xmManagerClassRec,
76 "XnSidebar", // class_name
77 sizeof(SidebarRec), // widget_size
78 sidebar_class_init, // class_initialize
79 sidebar_class_part_init, // class_part_initialize
80 FALSE, // class_inited
81 sidebar_init, // initialize
82 NULL, // initialize_hook
83 sidebar_realize, // realize
84 actionslist, // actions
85 XtNumber(actionslist), // num_actions
86 resources, // resources
87 XtNumber(resources), // num_resources
88 NULLQUARK, // xrm_class
89 True, // compress_motion
90 True, // compress_exposure
91 True, // compress_enterleave
92 False, // visible_interest
93 sidebar_destroy, // destroy
94 sidebar_resize, // resize
95 sidebar_expose, // expose
96 sidebar_set_values, // set_values
97 NULL, // set_values_hook
98 XtInheritSetValuesAlmost, // set_values_almost
99 NULL, // get_values_hook
100 sidebar_acceptfocus, // accept_focus
101 XtVersion, // version
102 NULL, // callback_offsets
103 defaultTranslations, // tm_table
104 XtInheritQueryGeometry, // query_geometry
105 XtInheritDisplayAccelerator, // display_accelerator
110 XtInheritGeometryManager, // geometry_manager
111 XtInheritChangeManaged, // change_managed
112 sidebar_insert_child, // insert_child
113 XtInheritDeleteChild, // delete_child
118 constraints, // resources
119 XtNumber(constraints), // num_resources
120 sizeof(XmFormConstraintRec), // constraint_size
128 XtInheritTranslations, // translations
129 NULL, // syn_resources
130 0, // num_syn_resources
131 NULL, // syn_constraint_resources
132 0, // num_syn_constraint_resources
133 XmInheritParentProcess, // parent_process
142 WidgetClass xnSidebarWidgetClass = (WidgetClass)&sidebarWidgetClassRec;
145 static void sidebar_class_init(void) {
149 static void sidebar_class_part_init (WidgetClass wc) {
150 SidebarClassRec *sidebarClass = (SidebarClassRec*)wc;
151 XmManagerClassRec *managerClass = (XmManagerClassRec*)xmManagerWidgetClass;
153 sidebarClass->constraint_class.initialize = managerClass->constraint_class.initialize;
154 sidebarClass->constraint_class.set_values = managerClass->constraint_class.set_values;
159 static void sidebar_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) {
160 Sidebar sb = (Sidebar)neww;
162 // initialize everything except XftDraw or other stuff that needs a Window
164 XftColor fg, bg, sel2;
168 fg.color.alpha = 0xFFFF;
170 bg.color.red = 0xFFFF;
171 bg.color.green = 0xFFFF;
172 bg.color.blue = 0xFFFF;
173 bg.color.alpha = 0xFFFF;
175 sel2.color.red = 0xFFFF;
176 sel2.color.green = 0xFFFF;
178 sel2.color.alpha = 0xFFFF;
182 sb->sidebar.select2_bg = sel2;
184 sb->sidebar.select2 = -1;
186 sb->sidebar.font = FontFromName(XtDisplay(neww), "Sans:size=11");
187 if(!sb->sidebar.font) {
188 fprintf(stderr, "Cannot open font.\nAbort.\n");
192 XftFont *xftFont = sb->sidebar.font->fonts->font;
194 int fontheight = xftFont->ascent;
195 sb->sidebar.elmHeight = fontheight + 2*padding;
200 static void sidebar_destroy(Widget widget) {
204 static int testresize = 1;
205 static void sidebar_resize(Widget widget) {
207 XtWidgetGeometry geom;
208 geom.request_mode = CWWidth | CWHeight;
211 XtWidgetGeometry result;
212 //XtMakeGeometryRequest(widget, &geom, &result);
219 static void remove_cb(Widget item, XtPointer index, XtPointer cd) {
220 Widget parent = XtParent(item);
222 XtVaGetValues(parent, XmNuserData, &sb, NULL);
224 int currentTrack = sb->sidebar.window->playlist.current_track;
225 cxListRemove(sb->sidebar.window->playlist.tracks, sb->sidebar.select2);
226 if(sb->sidebar.select2 == currentTrack) {
227 PlayListPlayNext(sb->sidebar.window, true);
229 sb->sidebar.select2 = -1;
230 SidebarRepaint((Widget)sb);
234 static int xdnd_initialized = 0;
236 static void sidebar_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) {
237 (xmManagerClassRec.core_class.realize)(widget, mask, attributes);
239 if(!xdnd_initialized) {
240 XdndInit(XtDisplay(widget), XtWidgetToApplicationContext(widget), sidebar_xdnd_callback, widget);
244 Screen *screen = widget->core.screen;
245 Visual *visual = screen->root_visual;
246 for(int i=0;i<screen->ndepths;i++) {
247 Depth d = screen->depths[i];
248 if(d.depth == widget->core.depth) {
254 Sidebar sb = (Sidebar)widget;
255 sb->sidebar.d = XftDrawCreate(
259 widget->core.colormap);
263 Widget parent = XtParent(widget);
264 XtVaSetValues(parent, XmNbackground, WhitePixelOfScreen(XtScreen(parent)), NULL);
266 XtMakeResizeRequest(widget, 200, 100, &w, &h);
269 XmString s1 = XmStringCreateSimple("Remove");
270 sb->sidebar.popupMenu = XmVaCreateSimplePopupMenu(
271 widget, "popup", remove_cb,
272 XmVaPUSHBUTTON, s1, 'R', NULL, NULL,
274 XtVaSetValues(sb->sidebar.popupMenu, XmNuserData, sb, NULL);
278 static void sidebar_expose(Widget widget, XEvent *event, Region region) {
279 //printf("expose\n");
281 //XtMakeResizeRequest(widget, 200, 2000, &w, &h);
283 w = widget->core.width;
284 h = widget->core.height;
286 Widget parent = XtParent(widget);
288 Sidebar s = (Sidebar)widget;
289 XftFont *xftFont = s->sidebar.font->fonts->font;
290 CxList *tracks = s->sidebar.window->playlist.tracks;
291 size_t numTracks = tracks->size;
293 int fontheight = xftFont->ascent;
294 int height = s->sidebar.elmHeight;
297 int list_height = numTracks * height;
298 if((list_height > s->core.height) || (w < parent->core.width)) {
299 XtMakeResizeRequest(widget, parent->core.width, list_height + 5, &w, &h);
303 XftDrawRect(s->sidebar.d, &s->sidebar.bg, 0, 0, w, h);
306 //printf("current track: %d\n", s->sidebar.window->playlist.current_track);
308 CxIterator i = cxListIterator(s->sidebar.window->playlist.tracks);
309 cx_foreach(const char *, elm, i) {
310 const char *name = util_resource_name(elm);
311 XftColor *cg = &s->sidebar.fg;
312 if(i.index == s->sidebar.window->playlist.current_track) {
313 XftDrawRect(s->sidebar.d, &s->sidebar.fg, 0, i.index*height, s->core.width, height);
315 } else if(i.index == s->sidebar.select2) {
316 XftDrawRect(s->sidebar.d, &s->sidebar.select2_bg, 0, i.index*height, s->core.width, height);
319 XftDrawString8(s->sidebar.d, cg, s->sidebar.font->fonts->font, 10, i.index*height + xftFont->ascent, (const FcChar8*)name, strlen(name));
323 static Boolean sidebar_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
329 static void sidebar_insert_child(Widget child) {
330 (xmManagerClassRec.composite_class.insert_child)(child);
333 Boolean sidebar_acceptfocus(Widget widget, Time *time) {
338 static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
342 static void SelectElmAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
344 XButtonEvent *e = &event->xbutton;
345 Sidebar s = (Sidebar)w;
347 int selected = e->y / s->sidebar.elmHeight;
348 PlayListPlayTrack(s->sidebar.window, selected);
349 s->sidebar.select2 = -1;
354 static void PopupAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
355 XButtonEvent *e = &event->xbutton;
356 Sidebar s = (Sidebar)w;
358 int selected = e->y / s->sidebar.elmHeight;
359 s->sidebar.select2 = selected;
365 XmMenuPosition(s->sidebar.popupMenu, &event->xbutton);
366 XtManageChild(s->sidebar.popupMenu);
370 static void open_uri(Sidebar s, cxstring uri) {
371 if(uri.length == 0) {
375 const char *urilist = uri.ptr;
376 size_t len = uri.length;
379 if(len > 7 && !memcmp(urilist, "file://", 7)) {
386 char *path = malloc(len + 1);
388 for(int i=start;i<len;i++) {
393 code[0] = urilist[i+1];
394 code[1] = urilist[i+2];
399 int ascii = (int)strtol(code, &end, 16);
400 if(errno == 0 && end == &code[2]) {
411 } else if(c == '\n' || c == '\r') {
421 PlayListAddFile(s->sidebar.window, path);
426 static void sidebar_xdnd_callback(Widget w, XtPointer udata, XtPointer cdata) {
430 Sidebar s = (Sidebar)cdata;
432 cxstring urllist = cx_str(udata);
434 CxStrtokCtx tk = cx_strtok(urllist, cx_str("\r\n"), INT_MAX);
436 while(cx_strtok_next(&tk, &uri)) {
440 SidebarRepaint((Widget)s);
444 /* --------------------------- public --------------------------- */
446 Widget CreateSidebar(
452 return XtCreateWidget(name, xnSidebarWidgetClass, parent, arglist, argcount);
455 void SidebarSetWindow(Widget widget, MainWindow *win) {
456 Sidebar sb = (Sidebar)widget;
457 sb->sidebar.window = win;
460 void SidebarRepaint(Widget widget) {
461 XClearArea(XtDisplay(widget), XtWindow(widget), 0, 0, widget->core.width, widget->core.height, true);