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