733e21a5a9ffbaf2f24c4ef38de7b1996d2bfb3d
[uwplayer.git] / application / Sidebar.c
1 /*
2  * Copyright 2022 Olaf Wintermann
3  *
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:
10  * 
11  * The above copyright notice and this permission notice shall be included in 
12  * all copies or substantial portions of the Software.
13  *
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.
21  */
22
23 #include "Sidebar.h"
24 #include "SidebarP.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30
31 #include "xdnd.h"
32 #include "utils.h"
33 #include "playlist.h"
34 #include <cx/string.h>
35
36
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);
47
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);
51
52 static void sidebar_xdnd_callback(Widget w, XtPointer udata, XtPointer cdata);
53
54
55 static XtResource resources[] = {
56
57 };
58
59 static XtActionsRec actionslist[] = {
60   {"focusIn", FocusInAP},
61   {"selectElm", SelectElmAP},
62   {"popup", PopupAP}
63 };
64
65 static char defaultTranslations[] = "<FocusIn>:  focusIn()\n"
66                                     "<Btn1Down>: selectElm()\n"
67                                     "<Btn3Down>: popup()";
68
69
70 static XtResource constraints[] = {};
71
72 SidebarClassRec sidebarWidgetClassRec = {
73     // Core Class
74     {
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
106         NULL,                            // extension
107     },
108     // Composite Class
109     {
110         XtInheritGeometryManager, // geometry_manager 
111         XtInheritChangeManaged,   // change_managed
112         sidebar_insert_child,         // insert_child 
113         XtInheritDeleteChild,     // delete_child  
114         NULL,                     // extension   
115     },
116     // Constraint Class
117     {
118         constraints,                 // resources
119         XtNumber(constraints),       // num_resources   
120         sizeof(XmFormConstraintRec), // constraint_size  
121         NULL,                        // initialize 
122         NULL,                        // destroy
123         NULL,                        // set_value
124         NULL,                        // extension 
125     },
126     // XmManager Class
127     {
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
134         NULL  // extension
135     },
136     // Sidebar Class
137     {
138         0
139     }
140 };
141
142 WidgetClass xnSidebarWidgetClass = (WidgetClass)&sidebarWidgetClassRec;
143
144
145 static void sidebar_class_init(void) {
146
147 }
148
149 static void sidebar_class_part_init (WidgetClass wc) {
150     SidebarClassRec *sidebarClass = (SidebarClassRec*)wc;
151     XmManagerClassRec *managerClass = (XmManagerClassRec*)xmManagerWidgetClass;
152     
153     sidebarClass->constraint_class.initialize = managerClass->constraint_class.initialize;
154     sidebarClass->constraint_class.set_values = managerClass->constraint_class.set_values;
155 }
156
157
158
159 static void sidebar_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) {
160     Sidebar sb = (Sidebar)neww;
161     
162     // initialize everything except XftDraw or other stuff that needs a Window
163     
164     XftColor fg, bg, sel2;
165     fg.color.red = 0;
166     fg.color.green = 0;
167     fg.color.blue = 0;
168     fg.color.alpha = 0xFFFF;
169     
170     bg.color.red = 0xFFFF;
171     bg.color.green = 0xFFFF;
172     bg.color.blue = 0xFFFF;
173     bg.color.alpha = 0xFFFF;
174     
175     sel2.color.red = 0xFFFF;
176     sel2.color.green = 0xFFFF;
177     sel2.color.blue = 0;
178     sel2.color.alpha = 0xFFFF;
179     
180     sb->sidebar.fg = fg;
181     sb->sidebar.bg = bg;
182     sb->sidebar.select2_bg = sel2;
183     
184     sb->sidebar.select2 = -1;
185     
186     sb->sidebar.font = FontFromName(XtDisplay(neww), "Sans:size=11");
187     if(!sb->sidebar.font) {
188         fprintf(stderr, "Cannot open font.\nAbort.\n");
189         exit(-1);
190     }
191     
192     XftFont *xftFont = sb->sidebar.font->fonts->font;
193     int padding = 2;
194     int fontheight = xftFont->ascent;
195     sb->sidebar.elmHeight = fontheight + 2*padding;
196 }
197
198
199
200 static void sidebar_destroy(Widget widget) {
201     
202 }
203
204 static int testresize = 1;
205 static void sidebar_resize(Widget widget) {
206     if(testresize) {
207         XtWidgetGeometry geom;
208         geom.request_mode = CWWidth | CWHeight;
209         geom.width = 200;
210         geom.height = 2000;
211         XtWidgetGeometry result;
212         //XtMakeGeometryRequest(widget, &geom, &result);
213         testresize = 0;
214     }
215     printf("resize\n");
216 }
217
218
219 static void remove_cb(Widget item, XtPointer index, XtPointer cd) {
220     printf("test_cb\n");
221     fflush(stdout);
222 }
223
224
225 static int xdnd_initialized = 0;
226
227 static void sidebar_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) {
228     (xmManagerClassRec.core_class.realize)(widget, mask, attributes);
229     
230     if(!xdnd_initialized) {
231         XdndInit(XtDisplay(widget), XtWidgetToApplicationContext(widget), sidebar_xdnd_callback, widget);
232     }
233     XdndEnable(widget);
234     
235     Screen *screen = widget->core.screen;
236     Visual *visual = screen->root_visual;
237     for(int i=0;i<screen->ndepths;i++) {
238         Depth d = screen->depths[i];
239         if(d.depth == widget->core.depth) {
240             visual = d.visuals;
241             break;
242         }
243     }
244     
245     Sidebar sb = (Sidebar)widget;
246     sb->sidebar.d = XftDrawCreate(
247             XtDisplay(widget),
248             XtWindow(widget),
249             visual,
250             widget->core.colormap);
251     
252     Dimension w, h;
253     
254     Widget parent = XtParent(widget);
255     XtVaSetValues(parent, XmNbackground, WhitePixelOfScreen(XtScreen(parent)), NULL);
256     
257     XtMakeResizeRequest(widget, 200, 100, &w, &h);
258     
259     
260     XmString s1 = XmStringCreateSimple("Remove");
261     sb->sidebar.popupMenu = XmVaCreateSimplePopupMenu(
262             widget, "popup", remove_cb,
263             //XmNuserData, widget,
264             XmVaPUSHBUTTON, s1, 'R', NULL, NULL,
265             NULL);
266     XmStringFree(s1);
267 }
268
269 static void sidebar_expose(Widget widget, XEvent *event, Region region) {
270     //printf("expose\n");
271     Dimension w, h;
272     //XtMakeResizeRequest(widget, 200, 2000, &w, &h);
273     
274     w = widget->core.width;
275     h = widget->core.height;
276     
277     Widget parent = XtParent(widget);
278     
279     Sidebar s = (Sidebar)widget;
280     XftFont *xftFont = s->sidebar.font->fonts->font;
281     CxList *tracks = s->sidebar.window->playlist.tracks;
282     size_t numTracks = tracks->size;
283     
284     int fontheight = xftFont->ascent;
285     int height = s->sidebar.elmHeight;
286     
287     
288     int list_height = numTracks * height;
289     if((list_height > s->core.height) || (w < parent->core.width)) {
290         XtMakeResizeRequest(widget, parent->core.width, list_height + 5, &w, &h);
291     }
292     
293     
294     XftDrawRect(s->sidebar.d, &s->sidebar.bg, 0, 0, w, h);
295     
296     
297     //printf("current track: %d\n", s->sidebar.window->playlist.current_track);
298     
299     CxIterator i = cxListIterator(s->sidebar.window->playlist.tracks);
300     cx_foreach(const char *, elm, i) {
301         const char *name = util_resource_name(elm);
302         XftColor *cg = &s->sidebar.fg;
303         if(i.index == s->sidebar.window->playlist.current_track) {
304             XftDrawRect(s->sidebar.d, &s->sidebar.fg, 0, i.index*height, s->core.width, height);
305             cg = &s->sidebar.bg;
306         } else if(i.index == s->sidebar.select2) {
307             XftDrawRect(s->sidebar.d, &s->sidebar.select2_bg, 0, i.index*height, s->core.width, height);
308         }
309         
310         XftDrawString8(s->sidebar.d, cg, s->sidebar.font->fonts->font, 10, i.index*height + xftFont->ascent, (const FcChar8*)name, strlen(name));
311     }
312 }
313
314 static Boolean sidebar_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
315     Boolean r = False;
316
317     return r;
318 }
319
320 static void sidebar_insert_child(Widget child) {
321     (xmManagerClassRec.composite_class.insert_child)(child);
322 }
323
324 Boolean sidebar_acceptfocus(Widget widget, Time *time) {
325     return 0;
326 }
327
328
329 static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
330     
331 }
332
333 static void SelectElmAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
334     //printf("btn1\n");
335     XButtonEvent *e = &event->xbutton;
336     Sidebar s = (Sidebar)w;
337     
338     int selected = e->y / s->sidebar.elmHeight;
339     PlayListPlayTrack(s->sidebar.window, selected);
340     s->sidebar.select2 = -1;
341     
342     SidebarRepaint(w);
343 }
344
345 static void PopupAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
346     XButtonEvent *e = &event->xbutton;
347     Sidebar s = (Sidebar)w;
348     
349     int selected = e->y / s->sidebar.elmHeight;
350     s->sidebar.select2 = selected;
351     SidebarRepaint(w);
352     
353     //printf("btn3\n");
354     //fflush(stdout);
355     
356     XmMenuPosition(s->sidebar.popupMenu, &event->xbutton);
357     XtManageChild(s->sidebar.popupMenu);
358 }
359
360
361 static void open_uri(Sidebar s, cxstring uri) {
362     if(uri.length == 0) {
363         return;
364     }
365     
366     const char *urilist = uri.ptr;
367     size_t len = uri.length;
368     
369     size_t start = 0;
370     if(len > 7 && !memcmp(urilist, "file://", 7)) {
371         start = 7;
372     }
373     
374     int err = 0;
375     
376     // urldecode
377     char *path = malloc(len + 1);
378     int k = 0;
379     for(int i=start;i<len;i++) {
380         char c = urilist[i];
381         if(c == '%') {
382             if(i + 2 < len) {
383                 char code[3];
384                 code[0] = urilist[i+1];
385                 code[1] = urilist[i+2];
386                 code[2] = '\0';
387                 
388                 errno = 0;
389                 char *end = NULL;
390                 int ascii = (int)strtol(code, &end, 16);
391                 if(errno == 0 && end == &code[2]) {
392                     path[k] = ascii;
393                     i += 2;
394                 } else {
395                     err = 1;
396                     break;
397                 }
398             } else {
399                 err = 1;
400                 break;
401             }
402         } else if(c == '\n' || c == '\r') {
403             break;
404         } else {
405             path[k] = c;
406         }
407         
408         k++;
409     }
410     path[k] = '\0';
411     
412     PlayListAddFile(s->sidebar.window, path);
413     
414     free(path);
415 }
416
417 static void sidebar_xdnd_callback(Widget w, XtPointer udata, XtPointer cdata) {
418     printf("xdnd\n");
419     fflush(stdout);
420     
421     Sidebar s = (Sidebar)cdata;
422     
423     cxstring urllist = cx_str(udata);
424     
425     CxStrtokCtx tk = cx_strtok(urllist, cx_str("\r\n"), INT_MAX);
426     cxstring uri;
427     while(cx_strtok_next(&tk, &uri)) {
428         open_uri(s, uri);
429     }
430     
431     SidebarRepaint((Widget)s);
432 }
433
434
435 /* --------------------------- public --------------------------- */
436
437 Widget CreateSidebar(
438         Widget parent,
439         String name,
440         ArgList arglist,
441         Cardinal argcount)
442 {
443     return XtCreateWidget(name, xnSidebarWidgetClass, parent, arglist, argcount);
444 }
445
446 void SidebarSetWindow(Widget widget, MainWindow *win) {
447     Sidebar sb = (Sidebar)widget;
448     sb->sidebar.window = win;
449 }
450
451 void SidebarRepaint(Widget widget) {
452     XClearArea(XtDisplay(widget), XtWindow(widget), 0, 0, widget->core.width, widget->core.height, true);
453 }