1 /*****************************************************************************/
2 // ImageView
3 // Written by Michael Wilber, Haiku Translation Kit Team
4 //
5 // ImageView.cpp
6 //
7 // BView class for showing images. Images can be dropped on this view
8 // from the tracker and images viewed in this view can be dragged
9 // to the tracker to be saved.
10 //
11 //
12 // Copyright (c) 2003 Haiku Project
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining a
15 // copy of this software and associated documentation files (the "Software"),
16 // to deal in the Software without restriction, including without limitation
17 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
18 // and/or sell copies of the Software, and to permit persons to whom the
19 // Software is furnished to do so, subject to the following conditions:
20 //
21 // The above copyright notice and this permission notice shall be included
22 // in all copies or substantial portions of the Software.
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
25 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30 // DEALINGS IN THE SOFTWARE.
31 /*****************************************************************************/
32
33
34 #include "ImageView.h"
35 #include "Constants.h"
36 #include "StatusCheck.h"
37 #include "InspectorApp.h"
38 #include "TranslatorItem.h"
39 #include <Application.h>
40 #include <Catalog.h>
41 #include <Message.h>
42 #include <Locale.h>
43 #include <List.h>
44 #include <String.h>
45 #include <TranslationUtils.h>
46 #include <TranslatorRoster.h>
47 #include <BitmapStream.h>
48 #include <Entry.h>
49 #include <Path.h>
50 #include <Directory.h>
51 #include <File.h>
52 #include <MenuBar.h>
53 #include <Screen.h>
54 #include <ScrollBar.h>
55 #include <Alert.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <string.h>
59
60 #define BORDER_WIDTH 16
61 #define BORDER_HEIGHT 16
62 #define PEN_SIZE 1.0f
63
64 #undef B_TRANSLATION_CONTEXT
65 #define B_TRANSLATION_CONTEXT "ImageView"
66
67
ImageView(BRect rect,const char * name)68 ImageView::ImageView(BRect rect, const char *name)
69 : BView(rect, name, B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS)
70 {
71 fpbitmap = NULL;
72 fdocumentIndex = 1;
73 fdocumentCount = 1;
74
75 SetViewColor(192, 192, 192);
76 SetHighColor(0, 0, 0);
77 SetPenSize(PEN_SIZE);
78 }
79
80
~ImageView()81 ImageView::~ImageView()
82 {
83 delete fpbitmap;
84 fpbitmap = NULL;
85 }
86
87
88 void
AttachedToWindow()89 ImageView::AttachedToWindow()
90 {
91 AdjustScrollBars();
92 }
93
94
95 void
Draw(BRect rect)96 ImageView::Draw(BRect rect)
97 {
98 if (HasImage()) {
99 // Draw black rectangle around image
100 StrokeRect(
101 BRect(BORDER_WIDTH - PEN_SIZE,
102 BORDER_HEIGHT - PEN_SIZE,
103 fpbitmap->Bounds().Width() + BORDER_WIDTH + PEN_SIZE,
104 fpbitmap->Bounds().Height() + BORDER_HEIGHT + PEN_SIZE));
105
106 DrawBitmap(fpbitmap, BPoint(BORDER_WIDTH, BORDER_HEIGHT));
107 }
108 }
109
110
111 void
ReDraw()112 ImageView::ReDraw()
113 {
114 Draw(Bounds());
115 }
116
117
118 void
FrameResized(float width,float height)119 ImageView::FrameResized(float width, float height)
120 {
121 AdjustScrollBars();
122
123 if (!HasImage())
124 Invalidate();
125 }
126
127
128 void
MouseDown(BPoint point)129 ImageView::MouseDown(BPoint point)
130 {
131 if (!HasImage())
132 return;
133
134 // Only accept left button clicks
135 BMessage *pmsg = Window()->CurrentMessage();
136 int32 button = pmsg->FindInt32("buttons");
137 if (button != B_PRIMARY_MOUSE_BUTTON)
138 return;
139
140 // Tell BeOS to setup a Drag/Drop operation
141 //
142 // (When the image is dropped, BeOS sends
143 // the following message to ImageWindow,
144 // which causes it to call ImageView::SetImage())
145 BMessage msg(B_SIMPLE_DATA);
146 msg.AddInt32("be:actions", B_COPY_TARGET);
147 msg.AddString("be:filetypes", "application/octet-stream");
148 msg.AddString("be:types", "application/octet-stream");
149 msg.AddString("be:clip_name", "Bitmap");
150
151 DragMessage(&msg, Bounds());
152 }
153
154
155 void
MouseMoved(BPoint point,uint32 state,const BMessage * pmsg)156 ImageView::MouseMoved(BPoint point, uint32 state, const BMessage *pmsg)
157 {
158 }
159
160
161 void
MouseUp(BPoint point)162 ImageView::MouseUp(BPoint point)
163 {
164 }
165
166
167 void
MessageReceived(BMessage * pmsg)168 ImageView::MessageReceived(BMessage *pmsg)
169 {
170 switch (pmsg->what) {
171 case B_COPY_TARGET:
172 SaveImageAtDropLocation(pmsg);
173 break;
174
175 default:
176 BView::MessageReceived(pmsg);
177 break;
178 }
179 }
180
181
182 void
SaveImageAtDropLocation(BMessage * pmsg)183 ImageView::SaveImageAtDropLocation(BMessage *pmsg)
184 {
185 // Find the location and name of the drop and
186 // write the image file there
187 BBitmapStream stream(fpbitmap);
188
189 StatusCheck chk;
190 // throw an exception if this is assigned
191 // anything other than B_OK
192
193 try {
194 entry_ref dirref;
195 chk = pmsg->FindRef("directory", &dirref);
196 const char *filename;
197 chk = pmsg->FindString("name", &filename);
198
199 BDirectory dir(&dirref);
200 BFile file(&dir, filename, B_WRITE_ONLY | B_CREATE_FILE);
201 chk = file.InitCheck();
202
203 BTranslatorRoster *proster = BTranslatorRoster::Default();
204 chk = proster->Translate(&stream, NULL, NULL, &file, B_TGA_FORMAT);
205
206 } catch (StatusNotOKException) {
207 BAlert *palert = new BAlert(NULL,
208 B_TRANSLATE("Sorry, unable to write the image file."),
209 B_TRANSLATE("OK"));
210 palert->Go();
211 }
212
213 stream.DetachBitmap(&fpbitmap);
214 }
215
216
217 void
AdjustScrollBars()218 ImageView::AdjustScrollBars()
219 {
220 BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0);
221 if (HasImage())
222 rctbitmap = fpbitmap->Bounds();
223
224 float prop, range;
225 BScrollBar *psb = ScrollBar(B_HORIZONTAL);
226 if (psb) {
227 range = rctbitmap.Width() + (BORDER_WIDTH * 2) - rctview.Width();
228 if (range < 0) range = 0;
229 prop = rctview.Width() / (rctbitmap.Width() + (BORDER_WIDTH * 2));
230 if (prop > 1.0f) prop = 1.0f;
231 psb->SetRange(0, range);
232 psb->SetProportion(prop);
233 psb->SetSteps(10, 100);
234 }
235
236 psb = ScrollBar(B_VERTICAL);
237 if (psb) {
238 range = rctbitmap.Height() + (BORDER_HEIGHT * 2) - rctview.Height();
239 if (range < 0) range = 0;
240 prop = rctview.Height() / (rctbitmap.Height() + (BORDER_HEIGHT * 2));
241 if (prop > 1.0f) prop = 1.0f;
242 psb->SetRange(0, range);
243 psb->SetProportion(prop);
244 psb->SetSteps(10, 100);
245 }
246 }
247
248
249 struct ColorSpaceName {
250 color_space id;
251 const char *name;
252 };
253 #define COLORSPACENAME(id) {id, #id}
254
255
256 // convert colorspace numerical value to
257 // a string value
258 const char *
get_color_space_name(color_space colors)259 get_color_space_name(color_space colors)
260 {
261 // print out colorspace if it matches an item in the list
262 const ColorSpaceName kcolorspaces[] = {
263 COLORSPACENAME(B_NO_COLOR_SPACE),
264 COLORSPACENAME(B_RGB32),
265 COLORSPACENAME(B_RGBA32),
266 COLORSPACENAME(B_RGB24),
267 COLORSPACENAME(B_RGB16),
268 COLORSPACENAME(B_RGB15),
269 COLORSPACENAME(B_RGBA15),
270 COLORSPACENAME(B_CMAP8),
271 COLORSPACENAME(B_GRAY8),
272 COLORSPACENAME(B_GRAY1),
273 COLORSPACENAME(B_RGB32_BIG),
274 COLORSPACENAME(B_RGBA32_BIG),
275 COLORSPACENAME(B_RGB24_BIG),
276 COLORSPACENAME(B_RGB16_BIG),
277 COLORSPACENAME(B_RGB15_BIG),
278 COLORSPACENAME(B_RGBA15_BIG),
279 COLORSPACENAME(B_YCbCr422),
280 COLORSPACENAME(B_YCbCr411),
281 COLORSPACENAME(B_YCbCr444),
282 COLORSPACENAME(B_YCbCr420),
283 COLORSPACENAME(B_YUV422),
284 COLORSPACENAME(B_YUV411),
285 COLORSPACENAME(B_YUV444),
286 COLORSPACENAME(B_YUV420),
287 COLORSPACENAME(B_YUV9),
288 COLORSPACENAME(B_YUV12),
289 COLORSPACENAME(B_UVL24),
290 COLORSPACENAME(B_UVL32),
291 COLORSPACENAME(B_UVLA32),
292 COLORSPACENAME(B_LAB24),
293 COLORSPACENAME(B_LAB32),
294 COLORSPACENAME(B_LABA32),
295 COLORSPACENAME(B_HSI24),
296 COLORSPACENAME(B_HSI32),
297 COLORSPACENAME(B_HSIA32),
298 COLORSPACENAME(B_HSV24),
299 COLORSPACENAME(B_HSV32),
300 COLORSPACENAME(B_HSVA32),
301 COLORSPACENAME(B_HLS24),
302 COLORSPACENAME(B_HLS32),
303 COLORSPACENAME(B_HLSA32),
304 COLORSPACENAME(B_CMY24),
305 COLORSPACENAME(B_CMY32),
306 COLORSPACENAME(B_CMYA32),
307 COLORSPACENAME(B_CMYK32)
308 };
309 const int32 kncolorspaces = sizeof(kcolorspaces) /
310 sizeof(ColorSpaceName);
311 for (int32 i = 0; i < kncolorspaces; i++) {
312 if (colors == kcolorspaces[i].id)
313 return kcolorspaces[i].name;
314 }
315
316 return B_TRANSLATE("Unknown");
317 }
318
319
320 // return a string of the passed number formated
321 // as a hexadecimal number in lowercase with a leading "0x"
322 const char *
hex_format(uint32 num)323 hex_format(uint32 num)
324 {
325 static char str[11] = { 0 };
326 sprintf(str, "0x%.8lx", num);
327
328 return str;
329 }
330
331
332 // convert passed number to a string of 4 characters
333 // and return that string
334 const char *
char_format(uint32 num)335 char_format(uint32 num)
336 {
337 static char str[5] = { 0 };
338 uint32 bnum = B_HOST_TO_BENDIAN_INT32(num);
339 memcpy(str, &bnum, 4);
340
341 return str;
342 }
343
344
345 void
dump_translation_formats(BString & bstr,const translation_format * pfmts,int32 nfmts)346 dump_translation_formats(BString &bstr, const translation_format *pfmts,
347 int32 nfmts)
348 {
349 BString *str1 = NULL;
350 for (int i = 0; i < nfmts; i++) {
351 BString string = B_TRANSLATE("\nType: '%1' (%2)\n"
352 "Group: '%3' (%4)\n"
353 "Quality: %5\n"
354 "Capability: %6\n"
355 "MIME Type: %7\n"
356 "Name: %8\n");
357 string.ReplaceFirst("%1", char_format(pfmts[i].type));
358 string.ReplaceFirst("%2", hex_format(pfmts[i].type));
359 string.ReplaceFirst("%3", char_format(pfmts[i].group));
360 string.ReplaceFirst("%4", hex_format(pfmts[i].group));
361 char str2[127] = { 0 };
362 sprintf(str2, "%f", pfmts[i].quality);
363 string.ReplaceFirst("%5", str2 );
364 str2[0] = '\0';
365 sprintf(str2, "%f", pfmts[i].capability);
366 string.ReplaceFirst("%6", str2 );
367 string.ReplaceFirst("%7", pfmts[i].MIME);
368 string.ReplaceFirst("%8", pfmts[i].name);
369 if (i == 0)
370 str1 = new BString(string);
371 else
372 str1->Append(string);
373 }
374 bstr = str1->String();
375 }
376
377
378 // Send information about the currently open image to the
379 // BApplication object so it can send it to the InfoWindow
380 void
UpdateInfoWindow(const BPath & path,BMessage & ioExtension,const translator_info & tinfo,BTranslatorRoster * proster)381 ImageView::UpdateInfoWindow(const BPath &path, BMessage &ioExtension,
382 const translator_info &tinfo, BTranslatorRoster *proster)
383 {
384 BMessage msg(M_INFO_WINDOW_TEXT);
385 BString bstr;
386
387 bstr = B_TRANSLATE("Image: %1\n"
388 "Color Space: %2 (%3)\n"
389 "Dimensions: %4 x %5\n"
390 "Bytes per Row: %6\n"
391 "Total Bytes: %7\n"
392 "\nIdentify Info:\n"
393 "ID String: %8\n"
394 "MIME Type: %9\n"
395 "Type: '%10' (%11)\n"
396 "Translator ID: %12\n"
397 "Group: '%13' (%14)\n"
398 "Quality: %15\n"
399 "Capability: %16\n"
400 "\nExtension Info:\n");
401 bstr.ReplaceFirst("%1", path.Path());
402 color_space cs = fpbitmap->ColorSpace();
403 bstr.ReplaceFirst("%2", get_color_space_name(cs));
404 bstr.ReplaceFirst("%3", hex_format(static_cast<uint32>(cs)));
405 char str2[127] = { 0 };
406 sprintf(str2, "%ld", fpbitmap->Bounds().IntegerWidth() + 1);
407 bstr.ReplaceFirst("%4", str2);
408 str2[0] = '\0';
409 sprintf(str2, "%ld", fpbitmap->Bounds().IntegerHeight() + 1);
410 bstr.ReplaceFirst("%5", str2);
411 str2[0] = '\0';
412 sprintf(str2, "%ld", fpbitmap->BytesPerRow());
413 bstr.ReplaceFirst("%6", str2);
414 str2[0] = '\0';
415 sprintf(str2, "%ld", fpbitmap->BitsLength());
416 bstr.ReplaceFirst("%7", str2);
417 bstr.ReplaceFirst("%8", tinfo.name);
418 bstr.ReplaceFirst("%9", tinfo.MIME);
419 bstr.ReplaceFirst("%10", char_format(tinfo.type));
420 bstr.ReplaceFirst("%11", hex_format(tinfo.type));
421 str2[0] = '\0';
422 sprintf(str2, "%ld", tinfo.translator);
423 bstr.ReplaceFirst("%12", str2);
424 bstr.ReplaceFirst("%13", char_format(tinfo.group));
425 bstr.ReplaceFirst("%14", hex_format(tinfo.group));
426 str2[0] = '\0';
427 sprintf(str2, "%f", tinfo.quality);
428 bstr.ReplaceFirst("%15", str2);
429 str2[0] = '\0';
430 sprintf(str2, "%f", tinfo.capability);
431 bstr.ReplaceFirst("%16", str2);
432
433 int32 document_count = 0, document_index = 0;
434 // Translator Info
435 const char *tranname = NULL, *traninfo = NULL;
436 int32 tranversion = 0;
437
438 if (ioExtension.FindInt32("/documentCount", &document_count) == B_OK) {
439 BString str = B_TRANSLATE("Number of Documents: %1\n"
440 "\nTranslator Used:\n"
441 "Name: %2\n"
442 "Info: %3\n"
443 "Version: %4\n");
444 char str2[127] = { 0 };
445 sprintf(str2, "%ld", document_count);
446 str.ReplaceFirst("%1", str2);
447 str.ReplaceFirst("%2", tranname);
448 str.ReplaceFirst("%3", traninfo);
449 str2[0] = '\0';
450 sprintf(str2, "%d", (int)tranversion);
451 str.ReplaceFirst("%4", str2);
452 bstr.Append(str.String());
453 }
454 else
455 if (ioExtension.FindInt32("/documentIndex", &document_index) == B_OK) {
456 BString str = B_TRANSLATE("Selected Document: %1\n"
457 "\nTranslator Used:\n"
458 "Name: %2\n"
459 "Info: %3\n"
460 "Version: %4\n");
461 char str2[127] = { 0 };
462 sprintf(str2, "%ld", document_index);
463 str.ReplaceFirst("%1", str2);
464 str.ReplaceFirst("%2", tranname);
465 str.ReplaceFirst("%3", traninfo);
466 str2[0] = '\0';
467 sprintf(str2, "%d", (int)tranversion);
468 str.ReplaceFirst("%4", str2);
469 bstr.Append(str.String());
470 }
471 else
472 if (proster->GetTranslatorInfo(tinfo.translator, &tranname,
473 &traninfo, &tranversion) == B_OK) {
474 BString str = B_TRANSLATE("\nTranslator Used:\n"
475 "Name: %1\n"
476 "Info: %2\n"
477 "Version: %3\n");
478 str.ReplaceFirst("%1", tranname);
479 str.ReplaceFirst("%2", traninfo);
480 char str2[127] = { 0 };
481 sprintf(str2, "%d", (int)tranversion);
482 str.ReplaceFirst("%3", str2);
483 bstr.Append(str.String());
484 }
485
486 // Translator Input / Output Formats
487 int32 nins = 0, nouts = 0;
488 const translation_format *pins = NULL, *pouts = NULL;
489 if (proster->GetInputFormats(tinfo.translator, &pins, &nins) == B_OK) {
490 bstr << B_TRANSLATE("\nInput Formats:");
491 dump_translation_formats(bstr, pins, nins);
492 pins = NULL;
493 }
494 if (proster->GetOutputFormats(tinfo.translator, &pouts, &nouts) == B_OK) {
495 bstr << B_TRANSLATE("\nOutput Formats:");
496 dump_translation_formats(bstr, pouts, nouts);
497 pouts = NULL;
498 }
499 msg.AddString("text", bstr);
500 be_app->PostMessage(&msg);
501 }
502
503
504 BTranslatorRoster *
SelectTranslatorRoster(BTranslatorRoster & roster)505 ImageView::SelectTranslatorRoster(BTranslatorRoster &roster)
506 {
507 bool bNoneSelected = true;
508
509 InspectorApp *papp;
510 papp = static_cast<InspectorApp *>(be_app);
511 if (papp) {
512 BList *plist = papp->GetTranslatorsList();
513 if (plist) {
514 for (int32 i = 0; i < plist->CountItems(); i++) {
515 BTranslatorItem *pitem =
516 static_cast<BTranslatorItem *>(plist->ItemAt(i));
517 if (pitem->IsSelected()) {
518 bNoneSelected = false;
519 roster.AddTranslators(pitem->Path());
520 }
521 }
522 }
523 }
524
525 if (bNoneSelected)
526 return BTranslatorRoster::Default();
527 else
528 return &roster;
529 }
530
531
532 void
SetImage(BMessage * pmsg)533 ImageView::SetImage(BMessage *pmsg)
534 {
535 // Replace current image with the image
536 // specified in the given BMessage
537
538 entry_ref ref;
539 if (!pmsg)
540 ref = fcurrentRef;
541 else if (pmsg->FindRef("refs", &ref) != B_OK)
542 // If refs not found, just ignore the message
543 return;
544
545 StatusCheck chk;
546
547 try {
548 BFile file(&ref, B_READ_ONLY);
549 chk = file.InitCheck();
550
551 BTranslatorRoster roster, *proster;
552 proster = SelectTranslatorRoster(roster);
553 if (!proster)
554 // throw exception
555 chk = B_ERROR;
556 // determine what type the image is
557 translator_info tinfo;
558 BMessage ioExtension;
559 if (ref != fcurrentRef)
560 // if new image, reset to first document
561 fdocumentIndex = 1;
562 chk = ioExtension.AddInt32("/documentIndex", fdocumentIndex);
563 chk = proster->Identify(&file, &ioExtension, &tinfo, 0, NULL,
564 B_TRANSLATOR_BITMAP);
565
566 // perform the actual translation
567 BBitmapStream outstream;
568 chk = proster->Translate(&file, &tinfo, &ioExtension, &outstream,
569 B_TRANSLATOR_BITMAP);
570 BBitmap *pbitmap = NULL;
571 chk = outstream.DetachBitmap(&pbitmap);
572 delete fpbitmap;
573 fpbitmap = pbitmap;
574 pbitmap = NULL;
575 fcurrentRef = ref;
576 // need to keep the ref around if user wants to switch pages
577 int32 documentCount = 0;
578 if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK &&
579 documentCount > 0)
580 fdocumentCount = documentCount;
581 else
582 fdocumentCount = 1;
583
584 // Set the name of the Window to reflect the file name
585 BWindow *pwin = Window();
586 BEntry entry(&ref);
587 BPath path;
588 if (entry.InitCheck() == B_OK) {
589 if (path.SetTo(&entry) == B_OK)
590 pwin->SetTitle(path.Leaf());
591 else
592 pwin->SetTitle(IMAGEWINDOW_TITLE);
593 } else
594 pwin->SetTitle(IMAGEWINDOW_TITLE);
595 UpdateInfoWindow(path, ioExtension, tinfo, proster);
596
597 // Resize parent window and set size limits to
598 // reflect the size of the new bitmap
599 float width, height;
600 BMenuBar *pbar = pwin->KeyMenuBar();
601 width = fpbitmap->Bounds().Width() + B_V_SCROLL_BAR_WIDTH + (BORDER_WIDTH * 2);
602 height = fpbitmap->Bounds().Height() +
603 pbar->Bounds().Height() + B_H_SCROLL_BAR_HEIGHT + (BORDER_HEIGHT * 2) + 1;
604 BScreen *pscreen = new BScreen(pwin);
605 BRect rctscreen = pscreen->Frame();
606 if (width > rctscreen.Width())
607 width = rctscreen.Width();
608 if (height > rctscreen.Height())
609 height = rctscreen.Height();
610 pwin->SetSizeLimits(B_V_SCROLL_BAR_WIDTH * 4, width,
611 pbar->Bounds().Height() + (B_H_SCROLL_BAR_HEIGHT * 4) + 1, height);
612 pwin->SetZoomLimits(width, height);
613 AdjustScrollBars();
614
615 //pwin->Zoom();
616 // Perform all of the hard work of resizing the
617 // window while taking into account the size of
618 // the screen, the tab and borders of the window
619 //
620 // HACK: Need to fix case where window un-zooms
621 // when the window is already the correct size
622 // for the current image
623
624 // repaint view
625 Invalidate();
626
627 } catch (StatusNotOKException) {
628 BAlert *palert = new BAlert(NULL,
629 B_TRANSLATE("Sorry, unable to load the image."),
630 B_TRANSLATE("OK"));
631 palert->Go();
632 }
633 }
634
635
636 void
FirstPage()637 ImageView::FirstPage()
638 {
639 if (fdocumentIndex != 1) {
640 fdocumentIndex = 1;
641 SetImage(NULL);
642 }
643 }
644
645
646 void
LastPage()647 ImageView::LastPage()
648 {
649 if (fdocumentIndex != fdocumentCount) {
650 fdocumentIndex = fdocumentCount;
651 SetImage(NULL);
652 }
653 }
654
655
656 void
NextPage()657 ImageView::NextPage()
658 {
659 if (fdocumentIndex < fdocumentCount) {
660 fdocumentIndex++;
661 SetImage(NULL);
662 }
663 }
664
665
666 void
PrevPage()667 ImageView::PrevPage()
668 {
669 if (fdocumentIndex > 1) {
670 fdocumentIndex--;
671 SetImage(NULL);
672 }
673 }
674
675