add playlist scrollbar
[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
35
36 static void sidebar_class_init(void);
37 static void sidebar_class_part_init (WidgetClass wc);
38 static void sidebar_init(Widget request, Widget neww, ArgList args, Cardinal *num_args);
39 static void sidebar_destroy(Widget widget);
40 static void sidebar_resize(Widget widget);
41 static void sidebar_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes);
42 static void sidebar_expose(Widget widget, XEvent *event, Region region);
43 static Boolean sidebar_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args);
44 static void sidebar_insert_child(Widget child);
45 Boolean sidebar_acceptfocus(Widget widget, Time *time);
46
47 static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
48 static void SelectElmAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
49 static void PopupAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
50
51 static void sidebar_xdnd_callback(Widget w, XtPointer udata, XtPointer cdata);
52
53
54 static XtResource resources[] = {
55
56 };
57
58 static XtActionsRec actionslist[] = {
59   {"focusIn", FocusInAP},
60   {"selectElm", SelectElmAP},
61   {"popup", PopupAP}
62 };
63
64 static char defaultTranslations[] = "<FocusIn>:  focusIn()\n"
65                                     "<Btn1Down>: selectElm()\n"
66                                     "<Btn3Down>: popup()";
67
68
69 static XtResource constraints[] = {};
70
71 SidebarClassRec sidebarWidgetClassRec = {
72     // Core Class
73     {
74         (WidgetClass)&xmManagerClassRec,
75         "XnSidebar",                         // class_name
76         sizeof(SidebarRec),                  // widget_size
77         sidebar_class_init,                  // class_initialize
78         sidebar_class_part_init,             // class_part_initialize
79         FALSE,                           // class_inited
80         sidebar_init,                        // initialize
81         NULL,                            // initialize_hook
82         sidebar_realize,                     // realize
83         actionslist,                     // actions
84         XtNumber(actionslist),           // num_actions
85         resources,                       // resources
86         XtNumber(resources),             // num_resources
87         NULLQUARK,                       // xrm_class
88         True,                            // compress_motion
89         True,                            // compress_exposure
90         True,                            // compress_enterleave
91         False,                           // visible_interest
92         sidebar_destroy,                     // destroy
93         sidebar_resize,                      // resize
94         sidebar_expose,                 // expose
95         sidebar_set_values,                  // set_values
96         NULL,                            // set_values_hook
97         XtInheritSetValuesAlmost,        // set_values_almost
98         NULL,                            // get_values_hook
99         sidebar_acceptfocus,                 // accept_focus
100         XtVersion,                       // version
101         NULL,                            // callback_offsets
102         defaultTranslations,             // tm_table
103         XtInheritQueryGeometry,          // query_geometry
104         XtInheritDisplayAccelerator,     // display_accelerator
105         NULL,                            // extension
106     },
107     // Composite Class
108     {
109         XtInheritGeometryManager, // geometry_manager 
110         XtInheritChangeManaged,   // change_managed
111         sidebar_insert_child,         // insert_child 
112         XtInheritDeleteChild,     // delete_child  
113         NULL,                     // extension   
114     },
115     // Constraint Class
116     {
117         constraints,                 // resources
118         XtNumber(constraints),       // num_resources   
119         sizeof(XmFormConstraintRec), // constraint_size  
120         NULL,                        // initialize 
121         NULL,                        // destroy
122         NULL,                        // set_value
123         NULL,                        // extension 
124     },
125     // XmManager Class
126     {
127         XtInheritTranslations, // translations
128         NULL, // syn_resources
129         0,    // num_syn_resources
130         NULL, // syn_constraint_resources
131         0,    // num_syn_constraint_resources
132         XmInheritParentProcess, // parent_process
133         NULL  // extension
134     },
135     // Sidebar Class
136     {
137         0
138     }
139 };
140
141 WidgetClass xnSidebarWidgetClass = (WidgetClass)&sidebarWidgetClassRec;
142
143
144 static void sidebar_class_init(void) {
145
146 }
147
148 static void sidebar_class_part_init (WidgetClass wc) {
149     SidebarClassRec *sidebarClass = (SidebarClassRec*)wc;
150     XmManagerClassRec *managerClass = (XmManagerClassRec*)xmManagerWidgetClass;
151     
152     sidebarClass->constraint_class.initialize = managerClass->constraint_class.initialize;
153     sidebarClass->constraint_class.set_values = managerClass->constraint_class.set_values;
154 }
155
156
157
158 static void sidebar_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) {
159     Sidebar sb = (Sidebar)neww;
160     
161     // initialize everything except XftDraw or other stuff that needs a Window
162     
163     XftColor fg, bg;
164     fg.color.red = 0;
165     fg.color.green = 0;
166     fg.color.blue = 0;
167     fg.color.alpha = 0xFFFF;
168     
169     bg.color.red = 0xFFFF;
170     bg.color.green = 0xFFFF;
171     bg.color.blue = 0xFFFF;
172     bg.color.alpha = 0xFFFF;
173     
174     sb->sidebar.fg = fg;
175     sb->sidebar.bg = bg;
176     
177     sb->sidebar.font = FontFromName(XtDisplay(neww), "Sans:size=11");
178     if(!sb->sidebar.font) {
179         fprintf(stderr, "Cannot open font.\nAbort.\n");
180         exit(-1);
181     }
182     
183     XftFont *xftFont = sb->sidebar.font->fonts->font;
184     int padding = 2;
185     int fontheight = xftFont->ascent;
186     sb->sidebar.elmHeight = fontheight + 2*padding;
187 }
188
189
190
191 static void sidebar_destroy(Widget widget) {
192     
193 }
194
195 static int testresize = 1;
196 static void sidebar_resize(Widget widget) {
197     if(testresize) {
198         XtWidgetGeometry geom;
199         geom.request_mode = CWWidth | CWHeight;
200         geom.width = 200;
201         geom.height = 2000;
202         XtWidgetGeometry result;
203         //XtMakeGeometryRequest(widget, &geom, &result);
204         testresize = 0;
205     }
206     printf("resize\n");
207 }
208
209 static int xdnd_initialized = 0;
210
211 static void sidebar_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) {
212     (xmManagerClassRec.core_class.realize)(widget, mask, attributes);
213     
214     if(!xdnd_initialized) {
215         XdndInit(XtDisplay(widget), XtWidgetToApplicationContext(widget), sidebar_xdnd_callback, widget);
216     }
217     XdndEnable(widget);
218     
219     Screen *screen = widget->core.screen;
220     Visual *visual = screen->root_visual;
221     for(int i=0;i<screen->ndepths;i++) {
222         Depth d = screen->depths[i];
223         if(d.depth == widget->core.depth) {
224             visual = d.visuals;
225             break;
226         }
227     }
228     
229     Sidebar sb = (Sidebar)widget;
230     sb->sidebar.d = XftDrawCreate(
231             XtDisplay(widget),
232             XtWindow(widget),
233             visual,
234             widget->core.colormap);
235     
236     Dimension w, h;
237     
238     Widget parent = XtParent(widget);
239     XtVaSetValues(parent, XmNbackground, WhitePixelOfScreen(XtScreen(parent)), NULL);
240     
241     XtMakeResizeRequest(widget, 200, 100, &w, &h);
242     
243     
244 }
245
246 static void sidebar_expose(Widget widget, XEvent *event, Region region) {
247     //printf("expose\n");
248     Dimension w, h;
249     //XtMakeResizeRequest(widget, 200, 2000, &w, &h);
250     
251     w = widget->core.width;
252     h = widget->core.height;
253     
254     Widget parent = XtParent(widget);
255     
256     Sidebar s = (Sidebar)widget;
257     XftFont *xftFont = s->sidebar.font->fonts->font;
258     UcxList *tracks = s->sidebar.window->playlist.tracks;
259     size_t numTracks = ucx_list_size(tracks);
260     
261     int fontheight = xftFont->ascent;
262     int height = s->sidebar.elmHeight;
263     
264     int list_height = numTracks * height;
265     if((list_height > s->core.height) || (w < parent->core.width)) {
266         XtMakeResizeRequest(widget, parent->core.width, list_height, &w, &h);
267     }
268     
269     
270     XftDrawRect(s->sidebar.d, &s->sidebar.bg, 0, 0, w, h);
271     
272     
273     //printf("current track: %d\n", s->sidebar.window->playlist.current_track);
274     
275     int i = 0;
276     UCX_FOREACH(elm, s->sidebar.window->playlist.tracks) {
277         char *name = util_resource_name(elm->data);
278         XftColor *cg = &s->sidebar.fg;
279         if(i == s->sidebar.window->playlist.current_track) {
280             XftDrawRect(s->sidebar.d, &s->sidebar.fg, 0, i*height, s->core.width, height);
281             cg = &s->sidebar.bg;
282         }
283         
284         XftDrawString8(s->sidebar.d, cg, s->sidebar.font->fonts->font, 10, i*height + xftFont->ascent, (const FcChar8*)name, strlen(name));
285         
286         i++;
287     }
288 }
289
290 static Boolean sidebar_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
291     Boolean r = False;
292
293     return r;
294 }
295
296 static void sidebar_insert_child(Widget child) {
297     (xmManagerClassRec.composite_class.insert_child)(child);
298 }
299
300 Boolean sidebar_acceptfocus(Widget widget, Time *time) {
301     return 0;
302 }
303
304
305 static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
306     
307 }
308
309 static void SelectElmAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
310     //printf("btn1\n");
311     XButtonEvent *e = &event->xbutton;
312     Sidebar s = (Sidebar)w;
313     
314     int selected = e->y / s->sidebar.elmHeight;
315     PlayListPlayTrack(s->sidebar.window, selected);
316     
317     SidebarRepaint(w);
318 }
319
320 static void PopupAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
321     //printf("btn3\n");
322 }
323
324
325
326 static void sidebar_xdnd_callback(Widget w, XtPointer udata, XtPointer cdata) {
327     printf("xdnd\n");
328     fflush(stdout);
329     
330     Sidebar s = (Sidebar)cdata;
331     
332     char *urilist = udata;
333     
334     size_t len = strlen(urilist);
335     
336     size_t start = 0;
337     if(len > 7 && !memcmp(urilist, "file://", 7)) {
338         start = 7;
339     }
340     
341     int err = 0;
342     
343     // urldecode
344     char *path = malloc(len + 1);
345     int k = 0;
346     for(int i=start;i<len;i++) {
347         char c = urilist[i];
348         if(c == '%') {
349             if(i + 2 < len) {
350                 char code[3];
351                 code[0] = urilist[i+1];
352                 code[1] = urilist[i+2];
353                 code[2] = '\0';
354                 
355                 errno = 0;
356                 char *end = NULL;
357                 int ascii = (int)strtol(code, &end, 16);
358                 if(errno == 0 && end == &code[2]) {
359                     path[k] = ascii;
360                     i += 2;
361                 } else {
362                     err = 1;
363                     break;
364                 }
365             } else {
366                 err = 1;
367                 break;
368             }
369         } else if(c == '\n' || c == '\r') {
370             break;
371         } else {
372             path[k] = c;
373         }
374         
375         k++;
376     }
377     path[k] = '\0';
378     
379     PlayListAddFile(s->sidebar.window, path);
380     SidebarRepaint((Widget)s);
381     
382     free(path);
383 }
384
385
386 /* --------------------------- public --------------------------- */
387
388 Widget CreateSidebar(
389         Widget parent,
390         String name,
391         ArgList arglist,
392         Cardinal argcount)
393 {
394     return XtCreateWidget(name, xnSidebarWidgetClass, parent, arglist, argcount);
395 }
396
397 void SidebarSetWindow(Widget widget, MainWindow *win) {
398     Sidebar sb = (Sidebar)widget;
399     sb->sidebar.window = win;
400 }
401
402 void SidebarRepaint(Widget widget) {
403     XClearArea(XtDisplay(widget), XtWindow(widget), 0, 0, widget->core.width, widget->core.height, true);
404 }