f2c809177140a7dbee94e98dae881dcb0fc3d9d1
[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
31 #include "Fsb.h"
32 #include "Sidebar.h"
33
34 static MainWindow *main_window;
35
36 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *args, int nargs);
37
38 static void FileOpenCB(Widget w, void *udata, void *cdata);
39 static void FileQuitCB(Widget w, void *udata, void *cdata);
40 static void PlayRepeatCB(Widget w, void *udata, void *cdata);
41 static void PlayRepeatListCB(Widget w, void *udata, void *cdata);
42 static void PlayAutoPlayCB(Widget w, void *udata, void *cdata);
43 static void ViewFullscreenCB(Widget w, void *udata, void *cdata);
44 static void ViewSidebarCB(Widget w, void *udata, void *cdata);
45
46 static void WindowRealized(MainWindow *win);
47
48 static int blank_cursor_init = 0;
49 static Pixmap blank_cursor_pixmap;
50 static Cursor blank_cursor;
51
52 static void init_blank_cursor(Widget w) {
53     char data = 0;
54     
55     XColor c;
56     
57     blank_cursor_pixmap = XCreateBitmapFromData(XtDisplay(w), XtWindow(w), &data, 1, 1);
58     if(!blank_cursor_pixmap) return;
59     
60     blank_cursor = XCreatePixmapCursor(XtDisplay(w), blank_cursor_pixmap, blank_cursor_pixmap, &c, &c, 0, 0);
61     
62     XFreePixmap(XtDisplay(w), blank_cursor_pixmap);
63     blank_cursor_init = 1;
64 }
65
66 static void window_close_handler(Widget window, void *udata, void *cdata) {
67     FileQuitCB(window, NULL, NULL);
68 }
69
70 static unsigned int keycodeF;
71
72 static void windowKeyEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
73     MainWindow *win = data;
74     if(win->fullscreen && event->xkey.keycode == keycodeF) {
75         WindowFullscreen(main_window, FALSE);
76         *dispatch = FALSE;
77     }
78 }
79
80 static int main_window_is_realized = 0;
81
82 static void resizeEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {  
83     if(!main_window_is_realized) {
84         if(XtIsRealized(widget)) {
85             main_window_is_realized = 1;
86             WindowRealized(data);
87         }
88     }
89     WindowAdjustAspectRatio(data);
90 }
91
92 static void WindowRealized(MainWindow *win) {
93     char *open_file = GetOpenFileArg();
94     if(open_file) {
95         size_t len = strlen(open_file);
96         char *file = XtMalloc(len+1);
97         memcpy(file, open_file, len);
98         file[len] = 0;
99         WindowSetFile(win, file);
100         PlayerOpenFile(win);
101         CleanOpenFileArg();
102     }
103     
104     if(!blank_cursor_init) {
105         init_blank_cursor(win->player_widget);
106     }
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 MainWindow* WindowCreate(Display *display) {
252     Arg args[32];
253     int n;
254      
255     MainWindow *window = malloc(sizeof(MainWindow));
256     memset(window, 0, sizeof(MainWindow));
257     main_window = window;
258     
259     // toplevel window
260     n = 0;
261     XtSetArg(args[n], XmNtitle, APP_NAME); n++;
262     window->window = XtAppCreateShell(
263             APP_NAME,
264             APP_CLASS,
265             applicationShellWidgetClass,
266             //vendorShellWidgetClass,
267             display,
268             args,
269             n);
270     
271     // close handler
272     Atom wm_delete_window;
273     wm_delete_window = XmInternAtom(
274             display,
275             "WM_DELETE_WINDOW",
276             0);
277     XmAddWMProtocolCallback(
278             window->window,
279             wm_delete_window,
280             window_close_handler,
281             window);
282     
283     // resize handler
284     XtAddEventHandler(window->window, StructureNotifyMask, False, resizeEH, window);
285     
286     n = 0;
287     XtSetArg(args[n], XmNwidth, 360); n++;
288     XtSetArg(args[n], XmNheight, 220); n++;
289     XtSetArg(args[n], XmNshadowThickness, 0); n++;
290     Widget container = XmCreateForm(window->window, "form", args, n);
291     XtManageChild(container);
292     XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
293     
294     n = 0;
295     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
296     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
297     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
298     WindowCreateMenu(window, container, args, n);
299     
300     n = 0;
301     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
302     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
303     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
304     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
305     XtSetArg(args[n], XmNwidth, 300); n++;
306     window->sidebar = CreateSidebar(container, "sidebar", args, n);
307     //XtManageChild(window->sidebar);
308     
309     n = 0;
310     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
311     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
312     XtSetArg(args[n], XmNrightWidget, window->sidebar); n++;
313     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
314     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
315     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
316     XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
317     window->player_widget = XmCreateDrawingArea(container, "player", args, n);
318     XtManageChild(window->player_widget);
319     XtAddCallback(window->player_widget, XmNinputCallback, playerWidgetInputCB, window);
320     XmProcessTraversal(window->player_widget, XmTRAVERSE_CURRENT);
321     XtAddEventHandler(window->player_widget, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
322                  EnterWindowMask | KeyPressMask | KeyReleaseMask |
323                   LeaveWindowMask, FALSE, playerEH, window);
324     
325      
326     // get F keycode
327     keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
328     
329     return window;
330 }
331
332 MainWindow* GetMainWindow(void) {
333     return main_window;
334 }
335
336 void WindowShow(MainWindow *win) {
337     XtRealizeWidget(win->window);
338 }
339
340 /*
341  * Creates a XmPushButton menu item
342  */
343 static Widget createMenuItem(
344         Widget menu,
345         char *name,
346         char *label,
347         char mnemonic,
348         const char *accelerator,
349         char *accelerator_text,
350         XtCallbackProc callback,
351         void *cbData)
352 {
353     Arg args[16];
354     int n = 0;
355     
356     XmString s1 = XmStringCreateSimple(label);
357     XtSetArg(args[n], XmNlabelString, s1); n++;
358     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
359     XmString at = NULL;
360     if(accelerator && accelerator_text) {
361         at = XmStringCreateSimple(accelerator_text);
362         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
363         XtSetArg(args[n], XmNacceleratorText, at); n++;
364     }
365     
366     Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
367     XtManageChild(menuItem);
368     XmStringFree(s1);
369     if(at) XmStringFree(at);
370     
371     if(callback) {
372         XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
373     }
374     
375     return menuItem;
376 }
377
378 /*
379  * Creates a XmToggleButton menu item
380  */
381 static Widget createToggleMenuItem(
382         Widget menu,
383         char *name,
384         char *label,
385         char mnemonic,
386         Boolean defaultValue,
387         const char *accelerator,
388         char *accelerator_text,
389         XtCallbackProc callback,
390         void *cbData)
391 {
392     Arg args[16];
393     int n = 0;
394     
395     XmString s1 = XmStringCreateSimple(label);
396     XtSetArg(args[n], XmNlabelString, s1); n++;
397     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
398     XtSetArg(args[n], XmNset, defaultValue); n++;
399     XmString at = NULL;
400     if(accelerator && accelerator_text) {
401         at = XmStringCreateSimple(accelerator_text);
402         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
403         XtSetArg(args[n], XmNacceleratorText, at); n++;
404     }
405     
406     Widget menuItem = XmCreateToggleButtonGadget(menu, name, args, n);
407     XtManageChild(menuItem);
408     XmStringFree(s1);
409     if(at) XmStringFree(at);
410     
411     if(callback) {
412         XtAddCallback(menuItem, XmNvalueChangedCallback, (XtCallbackProc)callback, cbData);
413     }
414     
415     return menuItem;
416 }
417
418 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
419     Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
420     XtManageChild(menubar);
421     win->menubar = menubar;
422     
423     Arg args[16];
424     int n;
425     
426     // menus
427     XmString s = XmStringCreateSimple("File");
428     XtVaCreateManagedWidget(
429             "menuitem",
430             xmCascadeButtonWidgetClass,
431             menubar,
432             XmNlabelString, s,
433             NULL);
434     XmStringFree(s);
435     Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
436     
437     s = XmStringCreateSimple("Playback");
438     XtVaCreateManagedWidget(
439             "menuitem",
440             xmCascadeButtonWidgetClass,
441             menubar,
442             XmNlabelString, s,
443             NULL);
444     XmStringFree(s);
445     Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
446     
447     s = XmStringCreateSimple("View");
448     Widget viewMenuItem = XtVaCreateManagedWidget(
449             "menuitem",
450             xmCascadeButtonWidgetClass,
451             menubar,
452             XmNlabelString, s,
453             NULL);
454     XmStringFree(s);
455     Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
456     
457     // file menu
458     createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
459     createMenuItem(fileMenu, "fileQuit", "Exit", 'E', "Ctrl<Key>Q", "Ctrl+Q", FileQuitCB, NULL);
460     
461     // play menu
462     win->playRepeatTrackButton = createToggleMenuItem(playMenu, "playRepeatTrack", "Repeat", 'R', False, NULL, NULL, PlayRepeatCB, win);
463     win->playRepeatListButton = createToggleMenuItem(playMenu, "playRepeatList", "Repeat List", 'L', False, NULL, NULL, PlayRepeatListCB, win);
464     win->playAutoPlayButton = createToggleMenuItem(playMenu, "playAutoNext", "Autoplay Folder", 'A', False, NULL, NULL, PlayAutoPlayCB, win);
465     XtVaSetValues(win->playRepeatTrackButton, XmNindicatorType, XmONE_OF_MANY, NULL);
466     XtVaSetValues(win->playRepeatListButton, XmNindicatorType, XmONE_OF_MANY, NULL);
467     XtVaSetValues(win->playAutoPlayButton, XmNindicatorType, XmONE_OF_MANY, NULL);
468     
469     // view menu
470     createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
471     win->viewSidebarButton = createToggleMenuItem(viewMenu, "viewSidebar", "View Sidebar", 'S', False, NULL, NULL, ViewSidebarCB, win);
472 }
473
474 void go_fullscreen(Display *dsp, Window win)
475 {
476   XEvent xev;
477   Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
478   Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
479   memset(&xev, 0, sizeof(xev));
480   xev.type = ClientMessage;
481   xev.xclient.window = win;
482   xev.xclient.message_type = wm_state;
483   xev.xclient.format = 32;
484   xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
485   xev.xclient.data.l[1] = fullscreen;
486   xev.xclient.data.l[2] = 0;
487   XSendEvent(dsp, DefaultRootWindow(dsp), False,
488     SubstructureNotifyMask, &xev);
489 }
490
491 static Atom net_wm_state;
492 static Atom net_wm_state_fullscreen;
493 static int net_wm_atoms_initialized = 0;
494
495 void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
496     Display *dpy = XtDisplay(win->window);
497     
498     // init net_wm_state atoms
499     if(!net_wm_atoms_initialized) {
500         net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
501         net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
502         net_wm_atoms_initialized = 1;
503     }
504     
505     WindowMenubarSetVisible(win, !enableFullscreen);
506     if(enableFullscreen && !win->fullscreen) {
507         XtUnmanageChild(main_window->menubar);
508         main_window->fullscreen = TRUE;
509     } else if(!enableFullscreen && win->fullscreen) {
510         XtManageChild(main_window->menubar);
511         main_window->fullscreen = FALSE;
512     }
513     
514     XEvent ev;
515     memset(&ev, 0, sizeof(XEvent));
516     ev.type = ClientMessage;
517     ev.xclient.window = XtWindow(win->window);
518     ev.xclient.message_type = net_wm_state;
519     ev.xclient.format = 32;
520     ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
521     ev.xclient.data.l[1] = net_wm_state_fullscreen;
522     ev.xclient.data.l[2] = 0;
523     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
524 }
525
526 void WindowMenubarSetVisible(MainWindow *win, bool visible) {
527     if(visible) {
528         XtManageChild(main_window->menubar);
529         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
530     } else {
531         XtUnmanageChild(main_window->menubar);
532         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
533     }
534 }
535
536 void WindowSetFile(MainWindow *win, char *file) {
537     if(win->file) {
538         XtFree(win->file);
539     }
540     win->file = file;
541 }
542
543 static void filedialog_end(
544         Widget widget,
545         MainWindow *data,
546         XmFileSelectionBoxCallbackStruct *selection)
547 {
548     XtUnmanageChild(widget);
549     XtDestroyWidget(widget);
550 }
551
552 static void filedialog_select(
553         Widget widget,
554         MainWindow *data,
555         XmFileSelectionBoxCallbackStruct *selection)
556 {
557     char *value = NULL;
558     if(selection->value) {
559         XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
560         if(value) {
561             WindowSetFile(data, value);
562             // no need to free the value, because it is stored in MainWindow
563             
564             PlayerOpenFile(data);
565         }
566     }
567     filedialog_end(widget, data, NULL);
568 }
569
570
571
572
573 static void FileOpenCB(Widget w, void *udata, void *cdata) {
574     MainWindow *win = main_window;
575     
576     Arg args[16];
577     int n = 0;
578     
579     XtSetArg(args[n], XnNshowViewMenu, 1); n++;
580     Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", args, n);
581     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
582     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
583     
584     Widget dirUp = XnFileSelectionBoxGetChild(dialog, XnFSB_DIR_UP_BUTTON);
585     XtUnmanageChild(dirUp);
586     
587     XtManageChild(dialog);
588 }
589
590 static void FileQuitCB(Widget w, void *udata, void *cdata) {
591     WindowClosePlayer(main_window);
592     ApplicationExit();
593 }
594
595 static void PlayRepeatCB(Widget w, void *udata, void *cdata) {
596     MainWindow *win = udata;
597     win->repeatTrack = XmToggleButtonGadgetGetState(w);
598     win->repeatList = 0;
599     win->autoplayFolder = 0;
600     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
601     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
602 }
603
604 static void PlayRepeatListCB(Widget w, void *udata, void *cdata) {
605     MainWindow *win = udata;
606     win->repeatList = XmToggleButtonGadgetGetState(w);
607     win->repeatTrack = 0;
608     win->autoplayFolder = 0;
609     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
610     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
611 }
612
613 static void PlayAutoPlayCB(Widget w, void *udata, void *cdata) {
614     MainWindow *win = udata;
615     win->autoplayFolder = XmToggleButtonGadgetGetState(w);
616     win->repeatTrack = 0;
617     win->repeatList = 0;
618     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
619     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
620 }
621
622 static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
623     if(main_window->fullscreen) {
624         WindowFullscreen(main_window, FALSE);
625     } else {
626         WindowFullscreen(main_window, TRUE);
627     }   
628 }
629
630 static void ViewSidebarCB(Widget w, void *udata, void *cdata) {
631     MainWindow *win = udata;
632     XmToggleButtonCallbackStruct *cb = cdata;
633     if(cb->set) {
634         WindowShowSidebar(win);
635     } else {
636         WindowHideSidebar(win);
637     }
638 }
639
640 void WindowAdjustAspectRatio(MainWindow *win) {
641     if(!win->player) return;
642     if(!win->player->isactive || win->player->width <= 0 || win->player->height <= 0) return;
643       
644     // we have a running player width video
645     // adjust window aspect ratio (the window aspect ratio is different from
646     // the video, because of window decoration, menubar and other extra controls)
647     
648     Dimension win_width, win_height;
649     XtVaGetValues(win->window, XmNwidth, &win_width, XmNheight, &win_height, NULL);
650     Dimension player_width, player_height;
651     XtVaGetValues(win->player_widget, XmNwidth, &player_width, XmNheight, &player_height, NULL);
652     
653     double r = (double)win->player->width / (double)win->player->height;
654     double p_width = player_width;
655     double p_height = p_width / r;
656     
657     Dimension new_width = p_width + win_width - player_width;
658     Dimension new_height = p_height + win_height - player_height;
659     
660     XSizeHints hints;
661     hints.flags = PAspect;
662     hints.min_aspect.x = new_width;
663     hints.min_aspect.y = new_height;
664     hints.max_aspect.x = new_width;
665     hints.max_aspect.y = new_height;
666     XSetWMNormalHints(XtDisplay(win->window), XtWindow(win->window), &hints);
667 }
668
669 void WindowClosePlayer(MainWindow *win) {
670     if(win->player) {
671         PlayerDestroy(win->player);
672     }
673     win->player = NULL;
674     WindowShowPlayerCursor(win);
675 }
676
677 void WindowHidePlayerCursor(MainWindow *win) {
678     if(!win->cursorhidden && win->player && win->player->window != 0) {
679         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), blank_cursor);
680         win->cursorhidden = True;
681         XFlush(XtDisplay(win->player_widget));
682     }
683 }
684
685 void WindowShowPlayerCursor(MainWindow *win) {
686     if(win->cursorhidden && win->player && win->player->window != 0) {
687         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), None);
688         XFlush(XtDisplay(win->player_widget));
689     }
690     win->cursorhidden = False;
691 }
692
693 void WindowHideSidebar(MainWindow *win) {
694     XtUnmanageChild(win->sidebar);
695     XtVaSetValues(win->player_widget, XmNrightAttachment, XmATTACH_FORM, NULL);
696 }
697
698 void WindowShowSidebar(MainWindow *win) {
699     XtManageChild(win->sidebar);
700     XtVaSetValues(win->player_widget, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, win->sidebar, NULL);
701 }
702