1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * gwin.c: gtk window management routines for pho, an image viewer.
4  *
5  * Copyright 2004,2009 by Akkana Peck.
6  * You are free to use or modify this code under the Gnu Public License.
7  */
8 
9 #include "pho.h"
10 #include "dialogs.h"
11 #include "exif/phoexif.h"
12 
13 #include <stdio.h>
14 #include <stdlib.h>       /* for exit() */
15 #include <string.h>
16 #include <unistd.h>
17 
18 #include <gdk/gdk.h>
19 
20 /* Some window managers don't deal well with windows that resize,
21  * or don't retain focus if a resized window no longer contains
22  * the mouse pointer.
23  * Offer an option to make new windows instead.
24  */
25 int gMakeNewWindows = 0;
26 
27 /* The size our window frame adds on the top left of the image */
28 gint sFrameWidth = 10;
29 gint sFrameHeight = 28;
30 int sHaveFrameSize = 0;
31 
32 gint gPhysMonitorWidth = 0;
33 gint gPhysMonitorHeight = 0;
34 
35 int sDragOffsetX = 0;
36 int sDragOffsetY = 0;
37 int sDragStartX = 0;
38 int sDragStartY = 0;
39 
40 /* Initialize the "black" color */
41 static GdkColor sBlack = { 0x0000, 0x0000, 0x0000 };
42 
43 /* gtk related window attributes */
44 GtkWidget *gWin = 0;
45 static GtkWidget *sDrawingArea = 0;
46 
47 /* This is so gross. GTK has no way to tell if a window has been exposed.
48  * GTK_WIDGET_MAPPED and GTK_WIDGET_VISIBLE are both true before the
49  * main loop has run or an expose event has happened.
50  */
51 static int sExposed = 0;
52 
53 static void NewWindow(); /* forward */
54 
hide_cursor(GtkWidget * w)55 static void hide_cursor(GtkWidget* w)
56 {
57     static char invisible_cursor_bits[] = { 0x0 };
58     static GdkBitmap *empty_bitmap = 0;
59     static GdkCursor* cursor = 0;
60 
61     if (empty_bitmap == 0 || cursor == 0) {
62         empty_bitmap = gdk_bitmap_create_from_data(NULL,
63                                                    invisible_cursor_bits,
64                                                    1, 1);
65         cursor = gdk_cursor_new_from_pixmap (empty_bitmap, empty_bitmap,
66                                              &sBlack, &sBlack, 1, 1);
67     }
68 
69     if (w->window != NULL)
70         gdk_window_set_cursor(w->window, cursor);
71 
72     /* If we need to free this, do it this way:
73     gdk_cursor_unref(cursor);
74     g_object_unref(empty_bitmap);
75      */
76 }
77 
show_cursor(GtkWidget * w)78 static void show_cursor(GtkWidget* w)
79 {
80     gdk_window_set_cursor(w->window, NULL);
81 }
82 
83 /*
84  * Adjust gMonitorWidth/Height and sFrameWidth/Height
85  * according to the display mode and current frames,
86  * then resize the image accordingly.
87  * Call this when the image size changes --
88  * zoom, shift in/out of presentation mode, etc.
89  */
AdjustScreenSize()90 static void AdjustScreenSize()
91 {
92     if (gDisplayMode == PHO_DISPLAY_PRESENTATION) {
93         gMonitorWidth = gPhysMonitorWidth;
94         gMonitorHeight = gPhysMonitorHeight;
95     }
96     else {
97         if (!sHaveFrameSize && GTK_WIDGET_MAPPED(gWin)) {
98             gint width, height;
99             GdkRectangle rect;
100 
101             if (gDebug)
102                 printf("AdjustScreenSize: Requesting frame size\n");
103             gdk_drawable_get_size(gWin->window, &width, &height);
104             gdk_window_get_frame_extents(gWin->window, &rect);
105             sFrameWidth = rect.width - width;
106             sFrameHeight = rect.height - height;
107             sHaveFrameSize = 1;
108         }
109 
110         if (gDebug)
111             printf("AdjustScreenSize: Frame size: %d, %d\n",
112                    sFrameWidth, sFrameHeight);
113 
114         gMonitorWidth = gPhysMonitorWidth - sFrameWidth;
115         gMonitorHeight = gPhysMonitorHeight - sFrameHeight;
116     }
117 
118     ScaleAndRotate(gCurImage, 0);
119 }
120 
CenterWindow(GtkWidget * win)121 static void CenterWindow(GtkWidget* win)
122 {
123     gint w, h;
124     if (gDebug) printf("CenterWindow\n");
125     gtk_window_get_size(GTK_WINDOW(gWin), &w, &h);
126     gtk_window_set_gravity(GTK_WINDOW(win), GDK_GRAVITY_CENTER);
127     gtk_window_move(GTK_WINDOW(win),
128                     (gMonitorWidth - gCurImage->curWidth)/2,
129                     (gMonitorHeight - gCurImage->curHeight)/2);
130     gtk_window_set_gravity(GTK_WINDOW(win), GDK_GRAVITY_NORTH_WEST);
131 }
132 
133 /* If we  resized, see if we might move off the screen.
134  * Make sure we don't do that, assuming the image is still
135  * small enough to fit. Then request focus from the window manager.
136  * Try to move the window as little as possible.
137  */
MaybeMove()138 static void MaybeMove()
139 {
140     gint x, y, w, h, nx, ny;
141 
142     if (gDebug) printf("MaybeMove\n");
143     if (gCurImage->curWidth > gPhysMonitorWidth
144         || gCurImage->curHeight > gPhysMonitorHeight)
145         return;
146 
147     /* If we don't have a window yet, don't move it */
148     if (!gWin || !GTK_WIDGET_MAPPED(gWin))
149         return;
150 
151     /* If we're in presentation or keywords mode, never move the window */
152     if (gDisplayMode == PHO_DISPLAY_PRESENTATION
153         || gDisplayMode == PHO_DISPLAY_KEYWORDS)
154         return;
155 
156     gtk_window_get_position(GTK_WINDOW(gWin), &x, &y);
157     nx = x;  ny = y;
158     gtk_window_get_size(GTK_WINDOW(gWin), &w, &h);
159     /* printf("Currently (%d, %d) %d x %d\n", x, y, w, h); */
160 
161     /* See if it would overflow off the screen */
162     if (x + gCurImage->curWidth > gMonitorWidth)
163         nx = gMonitorWidth - gCurImage->curWidth;
164     if (nx < 0)
165         nx = 0;
166     if (y + gCurImage->curHeight > gMonitorHeight)
167         ny = gMonitorHeight - gCurImage->curHeight;
168     if (ny < 0)
169         ny = 0;
170 
171     if (x != nx || y != ny) {
172         gtk_window_move(GTK_WINDOW(gWin), nx, ny);
173     }
174 
175     /* Request focus from the window manager.
176      * This is pretty much a no-op, but what the heck.
177      * However, it also messes with NewWindow's ability
178      * to get the frame size because it maps the window before
179      * we call gtk_widget_show().
180     gtk_window_present(GTK_WINDOW(gWin));
181      */
182 }
183 
FracOfScreenSize()184 double FracOfScreenSize() {
185     if (gMonitorWidth == 0 || gMonitorHeight == 0)
186         return 0.0;    /* Signal to re-call this function later */
187 
188     if (gMonitorWidth > gMonitorHeight)
189         return gMonitorHeight * .95;
190     return gMonitorWidth * .95;
191 }
192 
193 /* Change the view (display and scale) modes,
194  * and re-display the image if necessary.
195  */
196 /* XXX Pho really needs a state table defining which commands
197  * take which state to which new state! What a mess.
198  */
SetViewModes(int dispmode,int scalemode,double scalefactor)199 int SetViewModes(int dispmode, int scalemode, double scalefactor)
200 {
201     sDragOffsetX = sDragOffsetY = 0;
202 
203     if (dispmode == gDisplayMode && scalemode == gScaleMode
204         && scalefactor == gScaleRatio)
205         return 0;
206     if (gDebug)
207         printf("SetViewModes(%d, %d, %f (was %d, %d, %f)\n",
208                dispmode, scalemode, scalefactor,
209                gDisplayMode, gScaleMode, gScaleRatio);
210 
211     if (dispmode == PHO_DISPLAY_KEYWORDS) {
212         if (gDisplayMode != PHO_DISPLAY_KEYWORDS) {
213             /* switching to keyword mode from some other mode */
214             gScaleMode = PHO_SCALE_FIXED;
215             gScaleRatio = FracOfScreenSize();
216             /*
217             if (gDebug)
218                 printf("Showing keywords dialog from SetViewModes\n");
219             ShowKeywordsDialog();
220              */
221         }
222         else {
223             /* staying in keywords mode but changing some other factor:
224              * don't force anything.
225              */
226             gScaleMode = scalemode;
227             gScaleRatio = scalefactor;
228         }
229     }
230     else {
231         if (gDisplayMode == PHO_DISPLAY_KEYWORDS)  /* leaving keywords mode */
232             HideKeywordsDialog();
233         gScaleMode = scalemode;
234         gScaleRatio = scalefactor;
235     }
236 
237     if (dispmode != gDisplayMode) {
238         gDisplayMode = dispmode;
239         if (gWin && sDrawingArea && GTK_WIDGET_MAPPED(gWin))
240             /* Changing an existing window */
241         {
242             if (dispmode == PHO_DISPLAY_PRESENTATION) {
243                 if (gDebug)
244                     printf("\nSetViewModes: changing to presentation mode\n");
245                 hide_cursor(sDrawingArea);
246                 gtk_window_fullscreen(GTK_WINDOW(gWin));
247                 gtk_window_move(GTK_WINDOW(gWin), 0, 0);
248                 /* Is it still necessary to move a fullscreen window? */
249 
250                 AdjustScreenSize();
251             }
252             else {
253                 if (gDebug)
254                     printf("\nSetViewModes: changing to normal mode\n");
255                 show_cursor(sDrawingArea);
256                 gtk_window_unfullscreen(GTK_WINDOW(gWin));
257                 AdjustScreenSize();
258                 if (gDebug)
259                     printf("Monitor size after AdjustScreenSize: %dx%d\n",
260                            gMonitorWidth, gMonitorHeight);
261                 CenterWindow(gWin);
262             }
263         }
264     }
265     else {
266         /* If we're not changing the display mode, then we're changing
267          * the scale mode or scale factor, and may need to move to
268          * keep the window under the mouse so as not to lose focus
269          * or move off the screen.
270          * XXX unfortunately this doesn't work when changing from keywords
271          * to fullscreen/normal.
272          */
273         int ret = ScaleAndRotate(gCurImage, 0);
274         if (ret != 0) return ret;
275         MaybeMove();
276     }
277 
278     if (gDisplayMode == PHO_DISPLAY_KEYWORDS) {
279         ShowKeywordsDialog();
280     }
281 
282     return 0;
283 }
284 
285 /* DrawImage is called from the expose callback.
286  * It assumes we already have the image in gImage.
287  */
DrawImage()288 void DrawImage()
289 {
290     int dstX = 0, dstY = 0;
291     char title[BUFSIZ];
292 #   define TITLELEN ((sizeof title) / (sizeof *title))
293 
294     if (gDebug) {
295         printf("DrawImage %s, %dx%d\n", gCurImage->filename,
296                gCurImage->curWidth, gCurImage->curHeight);
297     }
298 
299     if (gImage == 0 || gWin == 0 || sDrawingArea == 0) return;
300     if (!sExposed) return;
301     if (!GTK_WIDGET_MAPPED(gWin)) return;
302 
303     if (gDisplayMode == PHO_DISPLAY_PRESENTATION) {
304         gint width, height;
305         gdk_window_clear(sDrawingArea->window);
306 
307         /* Center the image. This has to be done according to
308          * the current window size, not the phys monitor size,
309          * because in the xinerama case, gtk_window_fullscreen()
310          * only fullscreens the current monitor, not all of them.
311          */
312         gtk_window_get_size(GTK_WINDOW(gWin), &width, &height);
313 
314         /* If we have a presentation screen size set (e.g. for a projector
315          * that has a different resolution from our native monitor),
316          * Fudge the screen size and center based on a virtual screen
317          * starting in the upper left corner of our current screen.
318          * That way, it will center on the projector or other device.
319          */
320         if (gPresentationWidth > 0)
321             width = gPresentationWidth;
322         if (gPresentationHeight > 0)
323             height = gPresentationHeight;
324 
325         dstX = (width - gCurImage->curWidth) / 2 + sDragOffsetX;
326         dstY = (height - gCurImage->curHeight) / 2 + sDragOffsetY;
327 
328         /* But we probably shouldn't allow dragging the image
329          * completely off the screen -- just drag to the point where
330          * a corner is visible.
331          */
332         /* Left edge */
333         if (gCurImage->curWidth > gMonitorWidth
334             && dstX < gMonitorWidth - gCurImage->curWidth)
335             dstX = gMonitorWidth - gCurImage->curWidth;
336         else if (gCurImage->curWidth <= gMonitorWidth && dstX <= 0)
337             dstX = 0;
338 
339         /* Top edge */
340         if (gCurImage->curHeight > gMonitorHeight
341             && dstY < gMonitorHeight - gCurImage->curHeight)
342             dstY = gMonitorHeight - gCurImage->curHeight;
343         else if (gCurImage->curHeight <= gMonitorHeight && dstY <= 0)
344             dstY = 0;
345 
346         /* Right edge */
347         if (gCurImage->curWidth < gMonitorWidth
348             && dstX > gMonitorWidth - gCurImage->curWidth)
349             dstX = gMonitorWidth - gCurImage->curWidth;
350         else if (gCurImage->curWidth >= gMonitorWidth
351                  && dstX > 0)
352             dstX = 0;
353 
354         /* Bottom edge */
355         if (gCurImage->curHeight < gMonitorHeight
356             && dstY > gMonitorHeight - gCurImage->curHeight)
357             dstY = gMonitorHeight - gCurImage->curHeight;
358         else if (gCurImage->curHeight >= gMonitorHeight
359                  && dstY > 0)
360             dstY = 0;
361 
362         /* XXX Would be good to reset sDragOffsetX and sDragOffsetY
363          * in these cases so they don't get crazily out of kilter.
364          */
365     }
366     else {
367         /* Update the titlebar */
368         sprintf(title, "pho: %s (%d x %d)", gCurImage->filename,
369                 gCurImage->trueWidth, gCurImage->trueHeight);
370         if (HasExif())
371         {
372             const char* date = ExifGetString(ExifDate);
373             if (date && date[0]) {
374                 /* Make sure there's room */
375                 if (strlen(title) + strlen(date) + 3 < TITLELEN)
376                     strcat(title, " (");
377                 strcat(title, date);
378                 strcat(title, ")");
379             }
380         }
381         /* XXX replace these strcats with safer strncat */
382         if (gScaleMode == PHO_SCALE_FULLSIZE)
383             strcat(title, " (fullsize)");
384         else if (gScaleMode == PHO_SCALE_FULLSCREEN)
385             strcat(title, " (fullscreen)");
386         else if (gScaleMode == PHO_SCALE_IMG_RATIO ||
387                  gScaleMode == PHO_SCALE_SCREEN_RATIO) {
388             char* titleEnd = title + strlen(title);
389             if (gScaleRatio < 1)
390                 sprintf(titleEnd, " [%s/ %d]",
391                         (gScaleMode == PHO_SCALE_IMG_RATIO ? "fullsize " : ""),
392                         (int)(1. / gScaleRatio));
393             else
394                 sprintf(titleEnd, " [%s* %d]",
395                         (gScaleMode == PHO_SCALE_IMG_RATIO ? "fullsize " : ""),
396                         (int)gScaleRatio);
397         }
398         else if (gScaleMode == PHO_SCALE_FIXED)
399             strcat(title, " (fixed)");
400         gtk_window_set_title(GTK_WINDOW(gWin), title);
401 
402         if (gDisplayMode == PHO_DISPLAY_KEYWORDS) {
403             if (gDebug)
404                 printf("Showing keywords dialog from DrawImage\n");
405             ShowKeywordsDialog(gCurImage);
406         }
407     }
408 
409     gdk_pixbuf_render_to_drawable(gImage, sDrawingArea->window,
410                    sDrawingArea->style->fg_gc[GTK_WIDGET_STATE(sDrawingArea)],
411                                   0, 0, dstX, dstY,
412                                   gCurImage->curWidth, gCurImage->curHeight,
413                                   GDK_RGB_DITHER_NONE, 0, 0);
414 
415     UpdateInfoDialog(gCurImage);
416 }
417 
418 static gboolean
HandlePress(GtkWidget * widget,GdkEventButton * event)419 HandlePress(GtkWidget *widget, GdkEventButton *event)
420 {
421     if (event->button != 2 )
422         return TRUE;
423 
424     /*  grab with owner_events == TRUE so the popup's widgets can
425      *  receive events. we filter away events outside this toplevel
426      *  away in button_press()
427      */
428     if (gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
429                           GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
430                           | GDK_POINTER_MOTION_MASK
431                           | GDK_POINTER_MOTION_HINT_MASK,
432                           NULL, NULL, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
433         printf("Couldn't grab!\n");
434     sDragStartX = event->x;
435     sDragStartY = event->y;
436 
437     return TRUE;
438 }
439 
440 static gboolean
HandleRelease(GtkWidget * widget,GdkEventButton * event)441 HandleRelease(GtkWidget *widget, GdkEventButton *event)
442 {
443     if (event->button != 2 )
444         return TRUE;
445 
446     /* Ungrab, stop listening for motion */
447     gdk_display_pointer_ungrab (gtk_widget_get_display(widget),
448                                 GDK_CURRENT_TIME);
449     return TRUE;
450 }
451 
452 static gboolean
HandleMotionNotify(GtkWidget * widget,GdkEventMotion * event)453 HandleMotionNotify(GtkWidget *widget, GdkEventMotion *event)
454 {
455 #define DRAGRESTART 3
456     int x, y;
457     GdkModifierType state;
458     gdk_window_get_pointer (widget->window, &x, &y, &state);
459 
460     if (state & GDK_BUTTON2_MASK) {
461         sDragOffsetX += x - sDragStartX;
462         sDragOffsetY += y - sDragStartY;
463         /* Drag offsets will get sanity checked when we show the image,
464          * to disallow dragging the image entirely off the screen.
465          */
466 
467         /* We've handled this drag, so the next motion event
468          * should start from here.
469          */
470         sDragStartX = x;
471         sDragStartY = y;
472 
473         /* Assuming we're definitely in presentation mode,
474          * the user probably can't see the pointer, and it's confusing
475          * and frustrating when the mouse hits the edge of the screen
476          * and it's not obvious what to do about it.
477          * So when that happens, warp it to the opposite side of the screen.
478          */
479         if (gDisplayMode == PHO_DISPLAY_PRESENTATION) {
480             int warpToX = -1;
481             int warpToY = -1;
482             if (x >= gMonitorWidth-1) {
483                 warpToX = DRAGRESTART;
484                 warpToY = y;
485             }
486             else if (x <= 1) {
487                 warpToX = gMonitorWidth-DRAGRESTART;
488                 warpToY = y;
489             }
490             if (y >= gMonitorHeight-1) {
491                 warpToX = x;
492                 warpToY = DRAGRESTART;
493             }
494             else if (y <= 1) {
495                 warpToX = x;
496                 warpToY = gMonitorHeight-DRAGRESTART;
497             }
498             /* If we've hit an edge of the screen, warp to the opposite edge */
499             if (warpToX >= 0 && warpToY >= 0) {
500                 gdk_display_warp_pointer(gtk_widget_get_display(widget),
501                                          gdk_drawable_get_screen(sDrawingArea->window),
502                                          warpToX, warpToY);
503                 sDragStartX = warpToX;
504                 sDragStartY = warpToY;
505             }
506         }
507 
508         /* This flickers: would be nice to collapse events more */
509         DrawImage();
510     }
511 
512     return TRUE;
513 }
514 
515 /* An expose event has a GdkRectangle area and a GdkRegion *region
516  * as well as gint count of subsequent expose events.
517  * Unfortunately count is always 0.
518  */
HandleExpose(GtkWidget * widget,GdkEventExpose * event)519 static gint HandleExpose(GtkWidget* widget, GdkEventExpose* event)
520 {
521     gint width, height;
522 
523     sExposed = 1;
524     gdk_drawable_get_size(widget->window, &width, &height);
525     if (gDebug) {
526         printf("HandleExpose: area %dx%d +%d+%d in window %dx%d\n",
527                event->area.width, event->area.height,
528                event->area.x, event->area.y,
529                width, height);
530         if ((gDisplayMode != PHO_DISPLAY_PRESENTATION) &&
531             (event->area.x != 0 || event->area.y != 0)) {
532             if (event->area.width != width || event->area.height != height)
533                 printf("*** Expose different from window size!\n");
534             if (event->area.width != gCurImage->curWidth ||
535                 event->area.height != gCurImage->curHeight)
536                 printf("** Expose different from actual image size of %dx%d!\n",
537                        gCurImage->curWidth, gCurImage->curHeight);
538         }
539     }
540 
541     /* Make sure the window can resize smaller */
542     if (!gMakeNewWindows)
543         gtk_widget_set_size_request(GTK_WIDGET(gWin), 1, 1);
544 
545     if (!sHaveFrameSize && gDisplayMode != PHO_DISPLAY_PRESENTATION) {
546         int oldw = sFrameWidth;
547         int oldh = sFrameHeight;
548         AdjustScreenSize();
549         if (sFrameWidth != oldw || sFrameHeight != oldh) {
550             gtk_window_resize(GTK_WINDOW(gWin),
551                               gCurImage->curWidth, gCurImage->curHeight);
552             /* Since we resized, we might no longer be over the cursor
553              * and may have lost focus:
554              */
555             MaybeMove();
556         }
557     }
558 
559     DrawImage();
560 
561     return TRUE;
562 }
563 
EndSession()564 void EndSession()
565 {
566     gCurImage = 0;
567     UpdateInfoDialog();
568     RememberKeywords();
569     PrintNotes();
570     gtk_main_quit();
571     /* This doesn't always quit!  So make sure: */
572     exit(0);
573 }
574 
HandleDelete(GtkWidget * widget,GdkEventKey * event,gpointer data)575 static gint HandleDelete(GtkWidget* widget, GdkEventKey* event, gpointer data)
576 {
577     /* If you return FALSE in the "delete_event" signal handler,
578      * GTK will emit the "destroy" signal. Returning TRUE means
579      * you don't want the window to be destroyed.
580      * This is useful for popping up 'are you sure you want to quit?'
581      * type dialogs. */
582     EndSession();
583     return TRUE; /* Never called -- just here to keep gcc happy. */
584 }
585 
586 /* Make a new window, destroying the old one. */
NewWindow()587 static void NewWindow()
588 {
589     gint root_x = -1;
590     gint root_y = -1;
591 
592     sExposed = 0;    /* reset the exposed flag */
593 
594     if (gDebug)
595         printf("NewWindow()\n");
596 
597     if (gWin) {
598         gdk_window_get_position(gWin->window, &root_x, &root_y);
599         gtk_object_destroy(GTK_OBJECT(gWin));
600     }
601 
602     gWin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
603 
604     gtk_window_set_wmclass(GTK_WINDOW(gWin), "pho", "Pho");
605 
606     /* Window manager delete */
607     gtk_signal_connect(GTK_OBJECT(gWin), "delete_event",
608                        (GtkSignalFunc)HandleDelete, 0);
609 
610     /* This event occurs when we call gtk_widget_destroy() on the window,
611      * or if we return FALSE in the "delete_event" callback.
612     gtk_signal_connect(GTK_OBJECT(gWin), "destroy",
613                        (GtkSignalFunc)HandleDestroy, 0);
614      */
615 
616     /* KeyPress events on the drawing area don't come through --
617      * they have to be on the window.
618      */
619     gtk_signal_connect(GTK_OBJECT(gWin), "key_press_event",
620                        (GtkSignalFunc)HandleGlobalKeys, 0);
621 
622     sDrawingArea = gtk_drawing_area_new();
623     gtk_container_add(GTK_CONTAINER(gWin), sDrawingArea);
624     gtk_widget_show(sDrawingArea);
625 
626     /* This can't be done in expose: it causes one of those extra
627      * spurious expose events that gtk so loves.
628      */
629     gtk_widget_modify_bg(sDrawingArea, GTK_STATE_NORMAL, &sBlack);
630 
631     /* We hope this has already been done: LoadImageAndRotate
632      * should have called ScaleAndRotate when the image was loaded.
633     AdjustScreenSize();
634      */
635     if (gDisplayMode == PHO_DISPLAY_PRESENTATION) {
636         gtk_drawing_area_size(GTK_DRAWING_AREA(sDrawingArea),
637                               gPhysMonitorWidth, gPhysMonitorHeight);
638         gtk_window_fullscreen(GTK_WINDOW(gWin));
639 
640         /* Listen for middle clicks to drag position: */
641         gtk_widget_set_events(sDrawingArea, GDK_BUTTON_PRESS_MASK);
642         gtk_signal_connect(GTK_OBJECT(sDrawingArea), "button_press_event",
643                            (GtkSignalFunc)HandlePress, 0);
644         gtk_signal_connect(GTK_OBJECT(sDrawingArea), "button_release_event",
645                            (GtkSignalFunc)HandleRelease, 0);
646         gtk_signal_connect(GTK_OBJECT(sDrawingArea), "motion_notify_event",
647                            (GtkSignalFunc)HandleMotionNotify, 0);
648     }
649     else {
650         gtk_drawing_area_size(GTK_DRAWING_AREA(sDrawingArea),
651                               gCurImage->curWidth, gCurImage->curHeight);
652         gtk_window_unfullscreen(GTK_WINDOW(gWin));
653     }
654 
655     gtk_signal_connect(GTK_OBJECT(sDrawingArea), "expose_event",
656                        (GtkSignalFunc)HandleExpose, 0);
657     /* To track in/out of fullscreen mode, use configure_event
658      * or window_state_event.
659      */
660 
661     gtk_widget_show(gWin);
662 
663     /* Must come after show(), hide_cursor needs a window */
664     if (gDisplayMode == PHO_DISPLAY_PRESENTATION)
665         hide_cursor(sDrawingArea);
666 
667 }
668 
669 /**
670  * gdk_window_focus:
671  * @window: a #GdkWindow
672  * @timestamp: timestamp of the event triggering the window focus
673  *
674  * Sets keyboard focus to @window. If @window is not onscreen this
675  * will not work. In most cases, gtk_window_present() should be used on
676  * a #GtkWindow, rather than calling this function.
677  *
678  * For Pho: this is a replacement for gdk_window_focus
679  * due to the issue in http://bugzilla.gnome.org/show_bug.cgi?id=150668
680  *
681  **/
682 #define GDK_WINDOW_DISPLAY(win)       gdk_drawable_get_display(win)
683 #define GDK_WINDOW_SCREEN(win)	      gdk_drawable_get_screen(win)
684 #define GDK_WINDOW_XROOTWIN(win)      GDK_ROOT_WINDOW()
685 
686 /* PrepareWindow is responsible for making the window the right
687  * size and position, so the user doesn't see flickering.
688  * It may actually make a new window, or it may just resize and/or
689  * reposition the existing window.
690  */
PrepareWindow()691 void PrepareWindow()
692 {
693     if (gMakeNewWindows || gWin == 0) {
694         NewWindow();
695         return;
696     }
697 
698     sDragOffsetX = sDragOffsetY = 0;
699 
700     /* If the window is new but hasn't been mapped yet,
701      * there's nothing we can do from here.
702      */
703     if (!GTK_WIDGET_MAPPED(gWin))
704         return;
705 
706     /* Otherwise, resize and reposition the current window. */
707 
708     if (gDisplayMode == PHO_DISPLAY_PRESENTATION) {
709         /* XXX shouldn't have to do this every time */
710         gtk_drawing_area_size(GTK_DRAWING_AREA(sDrawingArea),
711                               gPhysMonitorWidth, gPhysMonitorHeight);
712     }
713     else {
714         gint winwidth, winheight;
715 
716         gdk_drawable_get_size(gWin->window, &winwidth, &winheight);
717         gdk_drawable_get_size(sDrawingArea->window, &winwidth, &winheight);
718 
719         /* We need to size the actual window, not just the drawing area.
720          * Resizing the drawing area will resize the window for many
721          * window managers, but not for metacity.
722          *
723          * Worse, metacity maximizes a window if the initial size is
724          * bigger in either dimension than the screen size.
725          * Since we can't be sure about the size of the wm decorations,
726          * we will probably hit this and get unintentionally maximized,
727          * after which metacity refuses to resize the window any smaller.
728          * (Mac OS X apparently does this too.)
729          * So force non-maximal mode.  (Users who want a maximized
730          * window will probably prefer fullscreen mode anyway.)
731          */
732         gtk_window_unfullscreen(GTK_WINDOW(gWin));
733         gtk_window_unmaximize(GTK_WINDOW(gWin));
734 
735         /* XXX Without the next line, we may get no expose events!
736          * Likewise, if the next line doesn't actually resize anything
737          * we may not get an expose event.
738          */
739         if (gCurImage->curWidth != winwidth
740             || gCurImage->curHeight != winheight) {
741             gtk_window_resize(GTK_WINDOW(gWin),
742                               gCurImage->curWidth, gCurImage->curHeight);
743 
744             /* Unfortunately, on OS X this resize may not work,
745              * if it puts part ofthe window off-screen; in which case
746              * we won't get an Expose event. So if that happened,
747              * force a redraw:
748              */
749             /* Just as unfortunately, get_size probably isn't reliable
750              * on Linux either, until after the window manager has had
751              * a chance to act on the resize request
752              * (at which time we'll get another Configure notify).
753              * So the dilemma is: we need to call DrawImage now in the
754              * case where there will be no further events. But if there
755              * are further events, we want to wait for them and not
756              */
757             gdk_drawable_get_size(sDrawingArea->window, &winwidth, &winheight);
758             if (gCurImage->curWidth != winwidth
759                 || gCurImage->curHeight != winheight) {
760                 if (gDebug)
761                     printf("Resize didn't work! Forcing redraw\n");
762                 DrawImage();
763             }
764         }
765 
766         /* If we didn't resize the window, then we won't get an expose
767          * event, and hence DrawImage won't be called. So call it explicitly
768          * -- but not if we haven't displayed a window yet.
769          * If we do that, we get duplicate calls to DrawImage
770          * plus in keywords mode, the keywords dialog gets set as
771          * transient too early. (I hate window management.)
772          */
773         else if (GTK_WIDGET_VISIBLE(gWin)) {
774             DrawImage();
775         }
776 
777         /* Try to ensure that the window will be over the cursor
778          * (so it will still have focus -- some window managers will
779          * lose focus otherwise). But not in Keywords mode, where the
780          * mouse should be over the Keywords dialog, not necessarily
781          * the image window.
782          */
783         MaybeMove();
784     }
785 
786     /* Want to request the focus here, but
787      * neither gtk_window_present nor gdk_window_focus seem to work.
788      */
789 }
790 
791