b3f24d77db44e68aea5a380774737e8ef74f7ddf
[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
176 #define DOUBLE_CLICK_TIME_MS       500
177
178 void WindowHandlePlayerEvent(MainWindow *win, XEvent *event) {
179     // event handler for intercepted player mouse events
180     // win->player is not NULL
181     
182     int etype = event->type;
183     
184     if(etype == MotionNotify) {
185         Time cur_motion_time = event->xmotion.time;
186         if(win->player) {
187             win->motion_playback_time = win->player->playback_time;
188         }
189         
190         int x = event->xmotion.x_root;
191         int y = event->xmotion.y_root;
192         if(win->cursorhidden && cur_motion_time - win->player_event_time < IGNORE_MOTION_THRESHOLD_MS) {
193             int diff_x = abs(x - win->mouse_x);
194             int diff_y = abs(y - win->mouse_y);
195             if(diff_x > MOTION_POS_THRESHOLD_PIX || diff_y > MOTION_POS_THRESHOLD_PIX) {
196                 WindowShowPlayerCursor(win);
197             }
198         } else {
199             win->mouse_x = x;
200             win->mouse_y = y;
201         }
202         win->player_event_time = cur_motion_time;
203         win->motion_playback_time = win->player->playback_time;
204         
205         
206         
207         if(win->pwbuttonpressed) {
208             Display *dp = XtDisplay(win->window);
209                 
210             XtUngrabPointer(win->player_widget, CurrentTime);
211
212             XEvent xev;
213             memset(&xev, 0, sizeof(xev));
214             xev.type = ClientMessage;
215             xev.xclient.message_type = XInternAtom(dp, "_NET_WM_MOVERESIZE", False);
216             xev.xclient.window = XtWindow(win->window);
217             xev.xclient.format = 32;
218             xev.xclient.data.l[0] = x;
219             xev.xclient.data.l[1] = y;
220             xev.xclient.data.l[2] = 8; // _NET_WM_MOVERESIZE_MOVE
221             xev.xclient.data.l[3] = 1; // button1
222             xev.xclient.data.l[4] = 1; // source indication
223
224             XSendEvent(dp, DefaultRootWindow(dp), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
225
226             win->pwbuttonpressed = FALSE;  
227         }
228     } else if(etype == ButtonPress) {
229         Time t = event->xbutton.time;
230         if(t - win->button_press_time < DOUBLE_CLICK_TIME_MS) {
231             // double click
232             WindowFullscreen(main_window, !win->fullscreen);
233             win->button_press_time = 0;
234         } else {
235             win->button_press_time = t;
236         }
237         win->pwbuttonpressed = 1;
238     } else if(etype == ButtonRelease) {
239         win->player_event_time = event->xbutton.time;
240         win->pwbuttonpressed = FALSE;
241     }
242 }
243
244 MainWindow* WindowCreate(Display *display) {
245     Arg args[32];
246     int n;
247      
248     MainWindow *window = malloc(sizeof(MainWindow));
249     memset(window, 0, sizeof(MainWindow));
250     main_window = window;
251     
252     // toplevel window
253     n = 0;
254     XtSetArg(args[n], XmNtitle, APP_NAME); n++;
255     window->window = XtAppCreateShell(
256             APP_NAME,
257             APP_CLASS,
258             applicationShellWidgetClass,
259             //vendorShellWidgetClass,
260             display,
261             args,
262             n);
263     
264     // close handler
265     Atom wm_delete_window;
266     wm_delete_window = XmInternAtom(
267             display,
268             "WM_DELETE_WINDOW",
269             0);
270     XmAddWMProtocolCallback(
271             window->window,
272             wm_delete_window,
273             window_close_handler,
274             window);
275     
276     // resize handler
277     XtAddEventHandler(window->window, StructureNotifyMask, False, resizeEH, window);
278     
279     n = 0;
280     XtSetArg(args[n], XmNwidth, 360); n++;
281     XtSetArg(args[n], XmNheight, 220); n++;
282     XtSetArg(args[n], XmNshadowThickness, 0); n++;
283     Widget container = XmCreateForm(window->window, "form", args, n);
284     XtManageChild(container);
285     XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
286     
287     n = 0;
288     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
289     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
290     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
291     WindowCreateMenu(window, container, args, n);
292     
293     n = 0;
294     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
295     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
296     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
297     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
298     XtSetArg(args[n], XmNwidth, 300); n++;
299     window->sidebar = CreateSidebar(container, "sidebar", args, n);
300     //XtManageChild(window->sidebar);
301     
302     n = 0;
303     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
304     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
305     XtSetArg(args[n], XmNrightWidget, window->sidebar); n++;
306     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
307     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
308     XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
309     XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
310     window->player_widget = XmCreateDrawingArea(container, "player", args, n);
311     XtManageChild(window->player_widget);
312     XtAddCallback(window->player_widget, XmNinputCallback, playerWidgetInputCB, window);
313     XmProcessTraversal(window->player_widget, XmTRAVERSE_CURRENT);
314     XtAddEventHandler(window->player_widget, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
315                  EnterWindowMask | KeyPressMask | KeyReleaseMask |
316                   LeaveWindowMask, FALSE, playerEH, window);
317     
318      
319     // get F keycode
320     keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
321     
322     return window;
323 }
324
325 MainWindow* GetMainWindow(void) {
326     return main_window;
327 }
328
329 void WindowShow(MainWindow *win) {
330     XtRealizeWidget(win->window);
331 }
332
333 /*
334  * Creates a XmPushButton menu item
335  */
336 static Widget createMenuItem(
337         Widget menu,
338         char *name,
339         char *label,
340         char mnemonic,
341         const char *accelerator,
342         char *accelerator_text,
343         XtCallbackProc callback,
344         void *cbData)
345 {
346     Arg args[16];
347     int n = 0;
348     
349     XmString s1 = XmStringCreateSimple(label);
350     XtSetArg(args[n], XmNlabelString, s1); n++;
351     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
352     XmString at = NULL;
353     if(accelerator && accelerator_text) {
354         at = XmStringCreateSimple(accelerator_text);
355         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
356         XtSetArg(args[n], XmNacceleratorText, at); n++;
357     }
358     
359     Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
360     XtManageChild(menuItem);
361     XmStringFree(s1);
362     if(at) XmStringFree(at);
363     
364     if(callback) {
365         XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
366     }
367     
368     return menuItem;
369 }
370
371 /*
372  * Creates a XmToggleButton menu item
373  */
374 static Widget createToggleMenuItem(
375         Widget menu,
376         char *name,
377         char *label,
378         char mnemonic,
379         Boolean defaultValue,
380         const char *accelerator,
381         char *accelerator_text,
382         XtCallbackProc callback,
383         void *cbData)
384 {
385     Arg args[16];
386     int n = 0;
387     
388     XmString s1 = XmStringCreateSimple(label);
389     XtSetArg(args[n], XmNlabelString, s1); n++;
390     XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
391     XtSetArg(args[n], XmNset, defaultValue); n++;
392     XmString at = NULL;
393     if(accelerator && accelerator_text) {
394         at = XmStringCreateSimple(accelerator_text);
395         XtSetArg(args[n], XmNaccelerator, accelerator); n++;
396         XtSetArg(args[n], XmNacceleratorText, at); n++;
397     }
398     
399     Widget menuItem = XmCreateToggleButtonGadget(menu, name, args, n);
400     XtManageChild(menuItem);
401     XmStringFree(s1);
402     if(at) XmStringFree(at);
403     
404     if(callback) {
405         XtAddCallback(menuItem, XmNvalueChangedCallback, (XtCallbackProc)callback, cbData);
406     }
407     
408     return menuItem;
409 }
410
411 static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
412     Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
413     XtManageChild(menubar);
414     win->menubar = menubar;
415     
416     Arg args[16];
417     int n;
418     
419     // menus
420     XmString s = XmStringCreateSimple("File");
421     XtVaCreateManagedWidget(
422             "menuitem",
423             xmCascadeButtonWidgetClass,
424             menubar,
425             XmNlabelString, s,
426             NULL);
427     XmStringFree(s);
428     Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
429     
430     s = XmStringCreateSimple("Playback");
431     XtVaCreateManagedWidget(
432             "menuitem",
433             xmCascadeButtonWidgetClass,
434             menubar,
435             XmNlabelString, s,
436             NULL);
437     XmStringFree(s);
438     Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
439     
440     s = XmStringCreateSimple("View");
441     Widget viewMenuItem = XtVaCreateManagedWidget(
442             "menuitem",
443             xmCascadeButtonWidgetClass,
444             menubar,
445             XmNlabelString, s,
446             NULL);
447     XmStringFree(s);
448     Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
449     
450     // file menu
451     createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
452     createMenuItem(fileMenu, "fileQuit", "Exit", 'E', "Ctrl<Key>Q", "Ctrl+Q", FileQuitCB, NULL);
453     
454     // play menu
455     win->playRepeatTrackButton = createToggleMenuItem(playMenu, "playRepeatTrack", "Repeat", 'R', False, NULL, NULL, PlayRepeatCB, win);
456     win->playRepeatListButton = createToggleMenuItem(playMenu, "playRepeatList", "Repeat List", 'L', False, NULL, NULL, PlayRepeatListCB, win);
457     win->playAutoPlayButton = createToggleMenuItem(playMenu, "playAutoNext", "Autoplay Folder", 'A', False, NULL, NULL, PlayAutoPlayCB, win);
458     XtVaSetValues(win->playRepeatTrackButton, XmNindicatorType, XmONE_OF_MANY, NULL);
459     XtVaSetValues(win->playRepeatListButton, XmNindicatorType, XmONE_OF_MANY, NULL);
460     XtVaSetValues(win->playAutoPlayButton, XmNindicatorType, XmONE_OF_MANY, NULL);
461     
462     // view menu
463     createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
464     win->viewSidebarButton = createToggleMenuItem(viewMenu, "viewSidebar", "View Sidebar", 'S', False, NULL, NULL, ViewSidebarCB, win);
465 }
466
467 void go_fullscreen(Display *dsp, Window win)
468 {
469   XEvent xev;
470   Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
471   Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
472   memset(&xev, 0, sizeof(xev));
473   xev.type = ClientMessage;
474   xev.xclient.window = win;
475   xev.xclient.message_type = wm_state;
476   xev.xclient.format = 32;
477   xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
478   xev.xclient.data.l[1] = fullscreen;
479   xev.xclient.data.l[2] = 0;
480   XSendEvent(dsp, DefaultRootWindow(dsp), False,
481     SubstructureNotifyMask, &xev);
482 }
483
484 static Atom net_wm_state;
485 static Atom net_wm_state_fullscreen;
486 static int net_wm_atoms_initialized = 0;
487
488 void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
489     Display *dpy = XtDisplay(win->window);
490     
491     // init net_wm_state atoms
492     if(!net_wm_atoms_initialized) {
493         net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
494         net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
495         net_wm_atoms_initialized = 1;
496     }
497     
498     WindowMenubarSetVisible(win, !enableFullscreen);
499     if(enableFullscreen && !win->fullscreen) {
500         XtUnmanageChild(main_window->menubar);
501         main_window->fullscreen = TRUE;
502     } else if(!enableFullscreen && win->fullscreen) {
503         XtManageChild(main_window->menubar);
504         main_window->fullscreen = FALSE;
505     }
506     
507     XEvent ev;
508     memset(&ev, 0, sizeof(XEvent));
509     ev.type = ClientMessage;
510     ev.xclient.window = XtWindow(win->window);
511     ev.xclient.message_type = net_wm_state;
512     ev.xclient.format = 32;
513     ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
514     ev.xclient.data.l[1] = net_wm_state_fullscreen;
515     ev.xclient.data.l[2] = 0;
516     XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
517 }
518
519 void WindowMenubarSetVisible(MainWindow *win, bool visible) {
520     if(visible) {
521         XtManageChild(main_window->menubar);
522         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
523     } else {
524         XtUnmanageChild(main_window->menubar);
525         XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
526     }
527 }
528
529 void WindowSetFile(MainWindow *win, char *file) {
530     if(win->file) {
531         XtFree(win->file);
532     }
533     win->file = file;
534 }
535
536 static void filedialog_end(
537         Widget widget,
538         MainWindow *data,
539         XmFileSelectionBoxCallbackStruct *selection)
540 {
541     XtUnmanageChild(widget);
542     XtDestroyWidget(widget);
543 }
544
545 static void filedialog_select(
546         Widget widget,
547         MainWindow *data,
548         XmFileSelectionBoxCallbackStruct *selection)
549 {
550     char *value = NULL;
551     if(selection->value) {
552         XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
553         if(value) {
554             WindowSetFile(data, value);
555             // no need to free the value, because it is stored in MainWindow
556             
557             PlayerOpenFile(data);
558         }
559     }
560     filedialog_end(widget, data, NULL);
561 }
562
563
564
565
566 static void FileOpenCB(Widget w, void *udata, void *cdata) {
567     MainWindow *win = main_window;
568     
569     Arg args[16];
570     int n = 0;
571     
572     XtSetArg(args[n], XnNshowViewMenu, 1); n++;
573     Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", args, n);
574     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
575     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
576     
577     Widget dirUp = XnFileSelectionBoxGetChild(dialog, XnFSB_DIR_UP_BUTTON);
578     XtUnmanageChild(dirUp);
579     
580     XtManageChild(dialog);
581 }
582
583 static void FileQuitCB(Widget w, void *udata, void *cdata) {
584     WindowClosePlayer(main_window);
585     ApplicationExit();
586 }
587
588 static void PlayRepeatCB(Widget w, void *udata, void *cdata) {
589     MainWindow *win = udata;
590     win->repeatTrack = XmToggleButtonGadgetGetState(w);
591     win->repeatList = 0;
592     win->autoplayFolder = 0;
593     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
594     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
595 }
596
597 static void PlayRepeatListCB(Widget w, void *udata, void *cdata) {
598     MainWindow *win = udata;
599     win->repeatList = XmToggleButtonGadgetGetState(w);
600     win->repeatTrack = 0;
601     win->autoplayFolder = 0;
602     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
603     XtVaSetValues(win->playAutoPlayButton, XmNset, 0, NULL);
604 }
605
606 static void PlayAutoPlayCB(Widget w, void *udata, void *cdata) {
607     MainWindow *win = udata;
608     win->autoplayFolder = XmToggleButtonGadgetGetState(w);
609     win->repeatTrack = 0;
610     win->repeatList = 0;
611     XtVaSetValues(win->playRepeatTrackButton, XmNset, 0, NULL);
612     XtVaSetValues(win->playRepeatListButton, XmNset, 0, NULL);
613 }
614
615 static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
616     if(main_window->fullscreen) {
617         WindowFullscreen(main_window, FALSE);
618     } else {
619         WindowFullscreen(main_window, TRUE);
620     }   
621 }
622
623 static void ViewSidebarCB(Widget w, void *udata, void *cdata) {
624     MainWindow *win = udata;
625     XmToggleButtonCallbackStruct *cb = cdata;
626     if(cb->set) {
627         WindowShowSidebar(win);
628     } else {
629         WindowHideSidebar(win);
630     }
631 }
632
633 void WindowAdjustAspectRatio(MainWindow *win) {
634     if(!win->player) return;
635     if(!win->player->isactive || win->player->width <= 0 || win->player->height <= 0) return;
636       
637     // we have a running player width video
638     // adjust window aspect ratio (the window aspect ratio is different from
639     // the video, because of window decoration, menubar and other extra controls)
640     
641     Dimension win_width, win_height;
642     XtVaGetValues(win->window, XmNwidth, &win_width, XmNheight, &win_height, NULL);
643     Dimension player_width, player_height;
644     XtVaGetValues(win->player_widget, XmNwidth, &player_width, XmNheight, &player_height, NULL);
645     
646     double r = (double)win->player->width / (double)win->player->height;
647     double p_width = player_width;
648     double p_height = p_width / r;
649     
650     Dimension new_width = p_width + win_width - player_width;
651     Dimension new_height = p_height + win_height - player_height;
652     
653     XSizeHints hints;
654     hints.flags = PAspect;
655     hints.min_aspect.x = new_width;
656     hints.min_aspect.y = new_height;
657     hints.max_aspect.x = new_width;
658     hints.max_aspect.y = new_height;
659     XSetWMNormalHints(XtDisplay(win->window), XtWindow(win->window), &hints);
660 }
661
662 void WindowClosePlayer(MainWindow *win) {
663     if(win->player) {
664         PlayerDestroy(win->player);
665     }
666     win->player = NULL;
667     WindowShowPlayerCursor(win);
668 }
669
670 void WindowHidePlayerCursor(MainWindow *win) {
671     if(!win->cursorhidden && win->player && win->player->window != 0) {
672         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), blank_cursor);
673         win->cursorhidden = True;
674         XFlush(XtDisplay(win->player_widget));
675     }
676 }
677
678 void WindowShowPlayerCursor(MainWindow *win) {
679     if(win->cursorhidden && win->player && win->player->window != 0) {
680         XDefineCursor(XtDisplay(win->player_widget), XtWindow(win->player_widget), None);
681         XFlush(XtDisplay(win->player_widget));
682     }
683     win->cursorhidden = False;
684 }
685
686 void WindowHideSidebar(MainWindow *win) {
687     XtUnmanageChild(win->sidebar);
688     XtVaSetValues(win->player_widget, XmNrightAttachment, XmATTACH_FORM, NULL);
689 }
690
691 void WindowShowSidebar(MainWindow *win) {
692     XtManageChild(win->sidebar);
693     XtVaSetValues(win->player_widget, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, win->sidebar, NULL);
694 }
695