handle btn1 in sidebar, switchable tracks
[uwplayer.git] / application / window.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 <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "window.h"
28 #include "main.h"
29 #include "player.h"
30 #include "playlist.h"
31 #include "xdnd.h"
32
33 #include "Fsb.h"
34 #include "Sidebar.h"
35
36 static MainWindow *main_window;
37
38 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *args, int nargs);
39
40 static void FileOpenCB(Widget w, void *udata, void *cdata);
41 static void FileQuitCB(Widget w, void *udata, void *cdata);
42 static void PlayRepeatCB(Widget w, void *udata, void *cdata);
43 static void PlayRepeatListCB(Widget w, void *udata, void *cdata);
44 static void PlayAutoPlayCB(Widget w, void *udata, void *cdata);
45 static void ViewFullscreenCB(Widget w, void *udata, void *cdata);
46 static void ViewSidebarCB(Widget w, void *udata, void *cdata);
47
48 static void WindowRealized(MainWindow *win);
49
50 static int blank_cursor_init = 0;
51 static Pixmap blank_cursor_pixmap;
52 static Cursor blank_cursor;
53
54 static void init_blank_cursor(Widget w) {
55     char data = 0;
56     
57     XColor c;
58     
59     blank_cursor_pixmap = XCreateBitmapFromData(XtDisplay(w), XtWindow(w), &data, 1, 1);
60     if(!blank_cursor_pixmap) return;
61     
62     blank_cursor = XCreatePixmapCursor(XtDisplay(w), blank_cursor_pixmap, blank_cursor_pixmap, &c, &c, 0, 0);
63     
64     XFreePixmap(XtDisplay(w), blank_cursor_pixmap);
65     blank_cursor_init = 1;
66 }
67
68 static void window_close_handler(Widget window, void *udata, void *cdata) {
69     FileQuitCB(window, NULL, NULL);
70 }
71
72 static unsigned int keycodeF;
73
74 static void windowKeyEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
75     MainWindow *win = data;
76     if(win->fullscreen && event->xkey.keycode == keycodeF) {
77         WindowFullscreen(main_window, FALSE);
78         *dispatch = FALSE;
79     }
80 }
81
82 static int main_window_is_realized = 0;
83
84 static void resizeEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {  
85     if(!main_window_is_realized) {
86         if(XtIsRealized(widget)) {
87             main_window_is_realized = 1;
88             WindowRealized(data);
89         }
90     }
91     WindowAdjustAspectRatio(data);
92 }
93
94 static void WindowRealized(MainWindow *win) {
95     char *open_file = GetOpenFileArg();
96     if(open_file) {
97         PlayListAddFile(win, open_file);
98         PlayListPlayNext(win, true);
99         CleanOpenFileArg();
100     }
101     
102     if(!blank_cursor_init) {
103         init_blank_cursor(win->player_widget);
104     }
105     
106     XdndEnable(win->window);
107 }
108
109 static void playerWidgetInputCB(Widget widget, XtPointer u, XtPointer c) {
110     MainWindow *win = u;
111     XmDrawingAreaCallbackStruct *cb = c;
112     
113     if(win->player && win->player->isactive) {
114         PlayerHandleInput(win, win->player, cb);
115     }
116 }
117
118 static void windowGrabButton(MainWindow *win) {
119     //printf("grab\n");
120     XtGrabButton(
121                 win->player_widget,
122                 AnyButton,
123                 AnyModifier,
124                 True,
125                 ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask,
126                 GrabModeAsync,
127                 GrabModeAsync,
128                 None,
129                 None);
130     win->buttongrab = True;
131 }
132
133 static void playerEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
134     MainWindow *win = data;
135     int etype = event->type;
136     
137     ///*
138     if(etype == EnterNotify) {
139         //printf("enter\n");
140         windowGrabButton(win);
141         return;
142     }
143     if(etype == LeaveNotify) {
144         //printf("leave\n");
145         //XtUngrabButton(win->player_widget, AnyButton, AnyModifier); 
146         //win->buttongrab = False;
147         return;
148     }
149     
150     int pass = 0;
151     if(etype == ButtonPress || etype == ButtonRelease || etype == KeyPress || etype == KeyRelease) {
152         //printf("button press\n");
153         pass = 1;
154     }
155     
156     if(!win->player || win->player->window == 0) return;
157     
158     WindowHandlePlayerEvent(win, event);
159     
160     if(pass) {
161         // redirect key events to the player window
162         //printf("redirect\n");
163         event->xkey.window = win->player->window;
164         XSendEvent(
165                 XtDisplay(win->player_widget),
166                 win->player->window,
167                 True,
168                 0,
169                 event);
170     }
171 }
172
173 #define IGNORE_MOTION_THRESHOLD_MS 1000
174 #define MOTION_POS_THRESHOLD_PIX   5
175 #define OSD_BOTTOM_THRESHOLD       0.09
176
177 #define DOUBLE_CLICK_TIME_MS       500
178
179 void WindowHandlePlayerEvent(MainWindow *win, XEvent *event) {
180     // event handler for intercepted player mouse events
181     // win->player is not NULL
182     
183     int etype = event->type;
184     
185     if(etype == MotionNotify) {
186         Time cur_motion_time = event->xmotion.time;
187         if(win->player) {
188             win->motion_playback_time = win->player->playback_time;
189         }
190         
191         int x = event->xmotion.x_root;
192         int y = event->xmotion.y_root;
193         if(win->cursorhidden && cur_motion_time - win->player_event_time < IGNORE_MOTION_THRESHOLD_MS) {
194             int diff_x = abs(x - win->mouse_x);
195             int diff_y = abs(y - win->mouse_y);
196             if(diff_x > MOTION_POS_THRESHOLD_PIX || diff_y > MOTION_POS_THRESHOLD_PIX) {
197                 WindowShowPlayerCursor(win);
198             }
199         } else {
200             win->mouse_x = x;
201             win->mouse_y = y;
202         }
203         win->player_event_time = cur_motion_time;
204         win->motion_playback_time = win->player->playback_time;
205         
206         
207         
208         if(win->pwbuttonpressed) {
209             Display *dp = XtDisplay(win->window);
210                 
211             XtUngrabPointer(win->player_widget, CurrentTime);
212
213             XEvent xev;
214             memset(&xev, 0, sizeof(xev));
215             xev.type = ClientMessage;
216             xev.xclient.message_type = XInternAtom(dp, "_NET_WM_MOVERESIZE", False);
217             xev.xclient.window = XtWindow(win->window);
218             xev.xclient.format = 32;
219             xev.xclient.data.l[0] = x;
220             xev.xclient.data.l[1] = y;
221             xev.xclient.data.l[2] = 8; // _NET_WM_MOVERESIZE_MOVE
222             xev.xclient.data.l[3] = 1; // button1
223             xev.xclient.data.l[4] = 1; // source indication
224
225             XSendEvent(dp, DefaultRootWindow(dp), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
226
227             win->pwbuttonpressed = FALSE;  
228         }
229     } else if(etype == ButtonPress) {
230         Time t = event->xbutton.time;
231         
232         int yi = win->player_widget->core.height - event->xbutton.y;
233         if((float)yi/(float)win->player_widget->core.height < OSD_BOTTOM_THRESHOLD) {
234             win->button_press_time = 0;
235         } else {
236             if(t - win->button_press_time < DOUBLE_CLICK_TIME_MS) {
237                 // double click
238                 WindowFullscreen(main_window, !win->fullscreen);
239                 win->button_press_time = 0;
240             } else {
241                 win->button_press_time = t;
242             }
243             win->pwbuttonpressed = 1;
244         }
245     } else if(etype == ButtonRelease) {
246         win->player_event_time = event->xbutton.time;
247         win->pwbuttonpressed = FALSE;
248     }
249 }
250
251
252
253 MainWindow* WindowCreate(Display *display) {
254     Arg args[32];
255     int n;
256      
257     MainWindow *window = malloc(sizeof(MainWindow));
258     memset(window, 0, sizeof(MainWindow));
259     main_window = window;
260       
261     // toplevel window
262     n = 0;
263     XtSetArg(args[n], XmNtitle, APP_NAME); n++;
264     window->window = XtAppCreateShell(
265             APP_NAME,
266             APP_CLASS,
267             applicationShellWidgetClass,
268             //vendorShellWidgetClass,
269             display,
270             args,
271             n);
272     
273     // close handler
274     Atom wm_delete_window;
275     wm_delete_window = XmInternAtom(
276             display,
277             "WM_DELETE_WINDOW",
278             0);
279     XmAddWMProtocolCallback(
280             window->window,
281             wm_delete_window,
282             window_close_handler,
283             window);
284     
285     // resize handler
286     XtAddEventHandler(window->window, StructureNotifyMask, False, resizeEH, window);
287     
288     n = 0;
289     XtSetArg(args[n], XmNwidth, 360); n++;
290     XtSetArg(args[n], XmNheight, 220); n++;
291     XtSetArg(args[n], XmNshadowThickness, 0); n++;
292     Widget container = XmCreateForm(window->window, "form", args, n);
293     XtManageChild(container);
294     XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
295     
296     n = 0;
297     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
298     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
299     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
300     WindowCreateMenu(window, container, args, n);
301     
302     n = 0;
303     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
304     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
305     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
306     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
307     XtSetArg(args[n], XmNwidth, 300); n++;
308     window->sidebar = CreateSidebar(container, "sidebar", args, n);
309     SidebarSetWindow(window->sidebar, window);
310     //XtManageChild(window->sidebar);
311        
312     n = 0;
313     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
314     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
315     XtSetArg(args[n], XmNrightWidget, window->sidebar); n++;
316     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
317     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
318     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
319     XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
320     window->player_widget = XmCreateDrawingArea(container, "player", args, n);
321     XtManageChild(window->player_widget);
322     XtAddCallback(window->player_widget, XmNinputCallback, playerWidgetInputCB, window);
323     XmProcessTraversal(window->player_widget, XmTRAVERSE_CURRENT);
324     XtAddEventHandler(window->player_widget, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
325                  EnterWindowMask | KeyPressMask | KeyReleaseMask |
326                   LeaveWindowMask, FALSE, playerEH, window);
327     
328      
329     // get F keycode
330     keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
331     
332     
333     PlayListInit(window);
334     
335     return window;
336 }
337
338 MainWindow* GetMainWindow(void) {
339     return main_window;
340 }
341
342 void WindowShow(MainWindow *win) {
343     XtRealizeWidget(win->window);
344 }
345
346 /*
347  * Creates a XmPushButton menu item
348  */
349 static Widget createMenuItem(
350         Widget menu,
351         char *name,
352         char *label,
353         char mnemonic,
354         const char *accelerator,
355         char *accelerator_text,
356         XtCallbackProc callback,
357         void *cbData)
358 {
359     Arg args[16];
360     int n = 0;
361     
362     XmString s1 = XmStringCreateSimple(label);
363     XtSetArg(args[n], XmNlabelString, s1); n++;
364     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
365     XmString at = NULL;
366     if(accelerator && accelerator_text) {
367         at = XmStringCreateSimple(accelerator_text);
368         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
369         XtSetArg(args[n], XmNacceleratorText, at); n++;
370     }
371     
372     Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
373     XtManageChild(menuItem);
374     XmStringFree(s1);
375     if(at) XmStringFree(at);
376     
377     if(callback) {
378         XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
379     }
380     
381     return menuItem;
382 }
383
384 /*
385  * Creates a XmToggleButton menu item
386  */
387 static Widget createToggleMenuItem(
388         Widget menu,
389         char *name,
390         char *label,
391         char mnemonic,
392         Boolean defaultValue,
393         const char *accelerator,
394         char *accelerator_text,
395         XtCallbackProc callback,
396         void *cbData)
397 {
398     Arg args[16];
399     int n = 0;
400     
401     XmString s1 = XmStringCreateSimple(label);
402     XtSetArg(args[n], XmNlabelString, s1); n++;
403     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
404     XtSetArg(args[n], XmNset, defaultValue); n++;
405     XmString at = NULL;
406     if(accelerator && accelerator_text) {
407         at = XmStringCreateSimple(accelerator_text);
408         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
409         XtSetArg(args[n], XmNacceleratorText, at); n++;
410     }
411     
412     Widget menuItem = XmCreateToggleButtonGadget(menu, name, args, n);
413     XtManageChild(menuItem);
414     XmStringFree(s1);
415     if(at) XmStringFree(at);
416     
417     if(callback) {
418         XtAddCallback(menuItem, XmNvalueChangedCallback, (XtCallbackProc)callback, cbData);
419     }
420     
421     return menuItem;
422 }
423
424 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
425     Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
426     XtManageChild(menubar);
427     win->menubar = menubar;
428     
429     Arg args[16];
430     int n;
431     
432     // menus
433     XmString s = XmStringCreateSimple("File");
434     XtVaCreateManagedWidget(
435             "menuitem",
436             xmCascadeButtonWidgetClass,
437             menubar,
438             XmNlabelString, s,
439             NULL);
440     XmStringFree(s);
441     Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
442     
443     s = XmStringCreateSimple("Playback");
444     XtVaCreateManagedWidget(
445             "menuitem",
446             xmCascadeButtonWidgetClass,
447             menubar,
448             XmNlabelString, s,
449             NULL);
450     XmStringFree(s);
451     Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
452     
453     s = XmStringCreateSimple("View");
454     Widget viewMenuItem = XtVaCreateManagedWidget(
455             "menuitem",
456             xmCascadeButtonWidgetClass,
457             menubar,
458             XmNlabelString, s,
459             NULL);
460     XmStringFree(s);
461     Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
462     
463     // file menu
464     createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
465     createMenuItem(fileMenu, "fileQuit", "Exit", 'E', "Ctrl<Key>Q", "Ctrl+Q", FileQuitCB, NULL);
466     
467     // play menu
468     win->playRepeatTrackButton = createToggleMenuItem(playMenu, "playRepeatTrack", "Repeat", 'R', False, NULL, NULL, PlayRepeatCB, win);
469     win->playRepeatListButton = createToggleMenuItem(playMenu, "playRepeatList", "Repeat List", 'L', False, NULL, NULL, PlayRepeatListCB, win);
470     win->playAutoPlayButton = createToggleMenuItem(playMenu, "playAutoNext", "Autoplay Folder", 'A', False, NULL, NULL, PlayAutoPlayCB, win);
471     XtVaSetValues(win->playRepeatTrackButton, XmNindicatorType, XmONE_OF_MANY, NULL);
472     XtVaSetValues(win->playRepeatListButton, XmNindicatorType, XmONE_OF_MANY, NULL);
473     XtVaSetValues(win->playAutoPlayButton, XmNindicatorType, XmONE_OF_MANY, NULL);
474     
475     // view menu
476     createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
477     win->viewSidebarButton = createToggleMenuItem(viewMenu, "viewSidebar", "View Sidebar", 'S', False, NULL, NULL, ViewSidebarCB, win);
478 }
479
480 void go_fullscreen(Display *dsp, Window win)
481 {
482   XEvent xev;
483   Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
484   Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
485   memset(&xev, 0, sizeof(xev));
486   xev.type = ClientMessage;
487   xev.xclient.window = win;
488   xev.xclient.message_type = wm_state;
489   xev.xclient.format = 32;
490   xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
491   xev.xclient.data.l[1] = fullscreen;
492   xev.xclient.data.l[2] = 0;
493   XSendEvent(dsp, DefaultRootWindow(dsp), False,
494     SubstructureNotifyMask, &xev);
495 }
496
497 static Atom net_wm_state;
498 static Atom net_wm_state_fullscreen;
499 static int net_wm_atoms_initialized = 0;
500
501 void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
502     Display *dpy = XtDisplay(win->window);
503     
504     // init net_wm_state atoms
505     if(!net_wm_atoms_initialized) {
506         net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
507         net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
508         net_wm_atoms_initialized = 1;
509     }
510     
511     WindowMenubarSetVisible(win, !enableFullscreen);
512     if(enableFullscreen && !win->fullscreen) {
513         XtUnmanageChild(main_window->menubar);
514         main_window->fullscreen = TRUE;
515     } else if(!enableFullscreen && win->fullscreen) {
516         XtManageChild(main_window->menubar);
517         main_window->fullscreen = FALSE;
518     }
519     
520     XEvent ev;
521     memset(&ev, 0, sizeof(XEvent));
522     ev.type = ClientMessage;
523     ev.xclient.window = XtWindow(win->window);
524     ev.xclient.message_type = net_wm_state;
525     ev.xclient.format = 32;
526     ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
527     ev.xclient.data.l[1] = net_wm_state_fullscreen;
528     ev.xclient.data.l[2] = 0;
529     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
530 }
531
532 void WindowMenubarSetVisible(MainWindow *win, bool visible) {
533     if(visible) {
534         XtManageChild(main_window->menubar);
535         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
536     } else {
537         XtUnmanageChild(main_window->menubar);
538         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
539     }
540 }
541
542 static void filedialog_end(
543         Widget widget,
544         MainWindow *data,
545         XmFileSelectionBoxCallbackStruct *selection)
546 {
547     XtUnmanageChild(widget);
548     XtDestroyWidget(widget);
549 }
550
551 static void filedialog_select(
552         Widget widget,
553         MainWindow *data,
554         XmFileSelectionBoxCallbackStruct *selection)
555 {
556     char *value = NULL;
557     if(selection->value) {
558         XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
559         if(value) {
560             PlayListAddFile(data, value);
561             PlayListPlayNext(data, true);
562             XtFree(value);
563         }
564     }
565     filedialog_end(widget, data, NULL);
566 }
567
568
569
570
571 static void FileOpenCB(Widget w, void *udata, void *cdata) {
572     MainWindow *win = main_window;
573     
574     Arg args[16];
575     int n = 0;
576     
577     XtSetArg(args[n], XnNshowViewMenu, 1); n++;
578     Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", args, n);
579     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
580     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
581     
582     Widget dirUp = XnFileSelectionBoxGetChild(dialog, XnFSB_DIR_UP_BUTTON);
583     XtUnmanageChild(dirUp);
584     
585     XtManageChild(dialog);
586 }
587
588 static void FileQuitCB(Widget w, void *udata, void *cdata) {
589     WindowClosePlayer(main_window);
590     ApplicationExit();
591 }
592
593 static void PlayRepeatCB(Widget w, void *udata, void *cdata) {
594     MainWindow *win = udata;
595     win->playlist.repeatTrack = XmToggleButtonGadgetGetState(w);
596     win->playlist.repeatList = 0;
597     win->playlist.autoplayFolder = 0;
598     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
599     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
600 }
601
602 static void PlayRepeatListCB(Widget w, void *udata, void *cdata) {
603     MainWindow *win = udata;
604     win->playlist.repeatList = XmToggleButtonGadgetGetState(w);
605     win->playlist.repeatTrack = 0;
606     win->playlist.autoplayFolder = 0;
607     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
608     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
609 }
610
611 static void PlayAutoPlayCB(Widget w, void *udata, void *cdata) {
612     MainWindow *win = udata;
613     win->playlist.autoplayFolder = XmToggleButtonGadgetGetState(w);
614     win->playlist.repeatTrack = 0;
615     win->playlist.repeatList = 0;
616     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
617     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
618 }
619
620 static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
621     if(main_window->fullscreen) {
622         WindowFullscreen(main_window, FALSE);
623     } else {
624         WindowFullscreen(main_window, TRUE);
625     }   
626 }
627
628 static void ViewSidebarCB(Widget w, void *udata, void *cdata) {
629     MainWindow *win = udata;
630     XmToggleButtonCallbackStruct *cb = cdata;
631     if(cb->set) {
632         WindowShowSidebar(win);
633     } else {
634         WindowHideSidebar(win);
635     }
636 }
637
638 void WindowAdjustAspectRatio(MainWindow *win) {
639     if(!win->player) return;
640     if(!win->player->isactive || win->player->width <= 0 || win->player->height <= 0) return;
641       
642     // we have a running player width video
643     // adjust window aspect ratio (the window aspect ratio is different from
644     // the video, because of window decoration, menubar and other extra controls)
645     
646     Dimension win_width, win_height;
647     XtVaGetValues(win->window, XmNwidth, &win_width, XmNheight, &win_height, NULL);
648     Dimension player_width, player_height;
649     XtVaGetValues(win->player_widget, XmNwidth, &player_width, XmNheight, &player_height, NULL);
650     
651     double r = (double)win->player->width / (double)win->player->height;
652     double p_width = player_width;
653     double p_height = p_width / r;
654     
655     Dimension new_width = p_width + win_width - player_width;
656     Dimension new_height = p_height + win_height - player_height;
657     
658     XSizeHints hints;
659     hints.flags = PAspect;
660     hints.min_aspect.x = new_width;
661     hints.min_aspect.y = new_height;
662     hints.max_aspect.x = new_width;
663     hints.max_aspect.y = new_height;
664     XSetWMNormalHints(XtDisplay(win->window), XtWindow(win->window), &hints);
665 }
666
667 void WindowClosePlayer(MainWindow *win) {
668     if(win->player) {
669         PlayerDestroy(win->player);
670     }
671     win->player = NULL;
672     WindowShowPlayerCursor(win);
673 }
674
675 void WindowHidePlayerCursor(MainWindow *win) {
676     if(!win->cursorhidden && win->player && win->player->window != 0) {
677         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), blank_cursor);
678         win->cursorhidden = True;
679         XFlush(XtDisplay(win->player_widget));
680     }
681 }
682
683 void WindowShowPlayerCursor(MainWindow *win) {
684     if(win->cursorhidden && win->player && win->player->window != 0) {
685         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), None);
686         XFlush(XtDisplay(win->player_widget));
687     }
688     win->cursorhidden = False;
689 }
690
691 void WindowHideSidebar(MainWindow *win) {
692     XtUnmanageChild(win->sidebar);
693     XtVaSetValues(win->player_widget, XmNrightAttachment, XmATTACH_FORM, NULL);
694 }
695
696 void WindowShowSidebar(MainWindow *win) {
697     XtManageChild(win->sidebar);
698     XtVaSetValues(win->player_widget, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, win->sidebar, NULL);
699 }
700
701 void WindowUpdate(MainWindow *win) {
702     SidebarRepaint(win->sidebar);
703 }