Initial commit
[amiga/OpenBoopsi.git] / gadgets / PIPWin / PIPWin.c
1 /*
2 **      $Id: PIPWin.c,v 1.2 2000/01/12 21:01:01 bernie Exp $
3 **
4 **      Use 4 chars wide TABs to read this file.
5 **
6 **
7 **      Introduction
8 **      ============
9 **
10 **      This program opens a window showing the contents of a user selected
11 **      public screen. The PIP view can be scrolled around by dragging it
12 **      with mouse or using the cursor keys.
13 **
14 **      The source code shows how to create a resizable window with sliders
15 **      and how to write a custom `boopsi' class on top of the gadgetclass.
16 **
17 **
18 **      Usage
19 **      =====
20 **
21 **      PipWin SCREEN,PUB/K,DELAY/K,LEFT/K,TOP/K,WIDTH/K,HEIGHT/K
22 **
23 **      SCREEN
24 **              the name of the public screen to monitorize
25 **
26 **      PUBSCREEN
27 **              name of the public screen where the window should open
28 **
29 **      DELAY
30 **              delay between window refreshes, in tenth of seconds (default: 20)
31 **
32 **      LEFT, TOP, WIDTH, HEIGHT
33 **              window geometry (in pixels)
34 **
35 **
36 **      Compiling
37 **      =========
38 **
39 **      This project can be compiled with:
40 **              - SAS/C (version 6.58 or newer)
41 **              - gcc (version 2.7.2.1 or newer) and egcs (version 1.1b or newer)
42 **              - StormC (version 2.00.14 or newer)
43 **
44 **      You get the smallest executable with SAS/C. gcc will give you
45 **      quite a lot of warnings with the tag calls. Don't worry about them.
46 **
47 **
48 **      History
49 **      =======
50 **
51 **      1.0 (3.12.95) -- First version by Alfonso Caschili
52 **
53 **      1.1 (27.3.96) -- Major revamp by Bernardo Innocenti
54 **
55 **              - Source cleaned up, re-indented and commented the way I like it :)
56 **
57 **              - Fixed a bug with CTRL-C handling
58 **
59 **              - Does not keep a lock on the screen where the window opens for all the time
60 **
61 **              - Argument parsing improved a bit
62 **
63 **              - Does not need C startup code anymore (saves some KB)
64 **
65 **              - Window positioning and sizing is a bit smarter
66 **
67 **              - Does not refresh the window twice on startup
68 **
69 **
70 **      2.0 (14.4.96) -- Rewritten using a `boopsi' class by Bernardo Innocenti
71 **
72 **              - Created the PIP 'boopsi' class
73 **
74 **              - Added sliders and buttons in window borders
75 **
76 **              - Added no-op backfilling hook (makes resizing and depth arragnging
77 **                much faster and less flickering
78 **
79 **
80 **      2.1 (8.5.96)
81 **
82 **              - Fixed window positioning bug.  Window was being positioned just below
83 **                the screen title bar, but it was looking on the titlebar height of
84 **                the screen being captured instead of the screen when the window was
85 **                to be opened
86 **
87 **              - Changed PATTA_#? attributes to PIPA_#? attributes in the boopsi
88 **                interconnection diagram.  PATTA_ is the attribute prefix for the
89 **                pattedit.gadget, another boopsi class written by me
90 **
91 **              - Changed to always keep a lock on the screen where the window opens.
92 **                This is needed because we are trying to free the associated DrawInfo
93 **                strucure *after* closing the window.  Without a proper lock, our host
94 **                screen could close just after we closed our window, which would make
95 **                the parameter we pass to FreeScreenDrawInfo() an invalid pointer
96 **
97 **              - Fixed a bug that caused a call to still BltBitMapRastPort() with a
98 **                NULL BitMap pointer when the PIPA_BitMap attribute was set to NULL
99 **
100 **              - Added error report for bad arguments
101 **
102 **
103 **      2.2 (28.4.97)
104 **
105 **              - Added gcc compiler support (untested)
106 **
107 **              - Now uses a custom DoMethod() stub (faster than calling amiga.lib one)
108 **
109 **              - Fixed the arrow buttons positioning when their width/height is
110 **                not the same of the size gadget
111 **
112 **      2.3 (10.5.97)
113 **
114 **              - Fixed LEFT, TOP, WIDTH & HEIGHT command line arguments. The
115 **                OpenPIPWin() function was passing the *pointers* to the LONG
116 **                values to OpenWindowTags(). (Riccardo Torrini)
117 **
118 **              - Dragging the view to the extreme left or top does no longer send
119 **                negative numbers when notifying the sliders.
120 **
121 **              - Removed test to check if GetScreenDrawInfo() fails because this
122 **                function is always successful
123 **
124 **
125 **      2.4 (15.8.97)
126 **
127 **              - Added StormC compiler support
128 **
129 **              - gcc support tested: works fine with latest GeekGadgets snapshot
130 **
131 **
132 **      2.5 (15.9.97)
133 **
134 **              - Compiled with SAS/C 6.58
135 **
136 **              - Improved SMakefile, now PIPWin is not need to be linked with
137 **                any static libraries
138 **
139 **              - Size reduced even more!
140 **
141 **      2.6 (13.10.97)
142 **
143 **              - Fixed scroll bars positioning with non standard window
144 **                border sizes
145 **
146 **              - gcc version can now be built without linking with libnix
147 **
148 **              - Replaced call to RefreshGList() with the new PIPM_Refresh
149 **                class method, which in turn calls GM_RENDER with mode GREDRAW_UPDATE.
150 **
151 **
152 **      To Do
153 **      =====
154 **
155 **              - Add a requester to select a public screen to snoop
156 **
157 **              - Allow opening more than one window
158 **
159 **              - Workbench startup and ToolTypes parsing
160 **
161 **              - Allow zooming into the bitmap
162 **
163 **              - Make the pipclass update its imagery automatically at given
164 **                time intervals.  This would require creating a Process for each
165 **                istance the class, or perhaps one single Process for all objects.
166 **                Unfortunately, most gadgetclass methods can not be called by
167 **                soft interrupts, so it seems we really need a process :-(
168 **
169 **              - This one is very ambitious: it should be possible to forward mouse
170 **                and keyboard input to the screen being displayed.  This would allow
171 **                the user to actually USE programs without bringing their screens
172 **                to front
173 **
174 **              - Optimize display scrolling in some special cases.  When the bitmap
175 **                of the screen being displayed is not the same format of the bitmap
176 **                where the PIP gadget renders (i.e.: they arn't `friend bitmaps'),
177 **                some kind of conversion (e.g.: planar to chunky) will be done
178 **                transparently by CopyBitMapRastPort().  This operation might be
179 **                very slow, making more convenient to ScrollRaster() the existing
180 **                image and copy just the part that gets revealed by the scrolling
181 **
182 **              - Change the mouse pointer to a grabbing hand while user is dragging
183 **                the display
184 **
185 **
186 **      Copyright Notice
187 **      ================
188 **
189 **      Copyright © 1995 by Alfonso Caschili <valdus@mbox.vol.it>.
190 **      Freely Distributable.
191 **
192 **      Copyright © 1996,97 by Bernardo Innocenti <bernie@cosmos.it>.
193 **      Freely Distributable, as long as source code, documentation and
194 **      executable are kept together.  Permission is granted to release
195 **      modified versions of this program as long as all existing copyright
196 **      notices are left intact.
197 **
198 */
199
200 #define _ANSI_SOURCE
201 #define INTUI_V36_NAMES_ONLY
202 #define __USE_SYSBASE
203 #define  CLIB_ALIB_PROTOS_H     /* Avoid including this header file because of
204                                                          * conflicting definitions in BoopsiStubs.h
205                                                          */
206 #include <exec/types.h>
207 #include <exec/memory.h>
208 #include <exec/execbase.h>
209 #include <dos/rdargs.h>
210 #include <intuition/intuition.h>
211 #include <intuition/intuitionbase.h>
212 #include <intuition/screens.h>
213 #include <intuition/classes.h>
214 #include <intuition/classusr.h>
215 #include <intuition/gadgetclass.h>
216 #include <intuition/imageclass.h>
217 #include <intuition/icclass.h>
218 #include <devices/timer.h>
219
220 #include <proto/exec.h>
221 #include <proto/intuition.h>
222 #include <proto/dos.h>
223 #include <proto/utility.h>
224
225 #include <CompilerSpecific.h>
226 #include <DebugMacros.h>
227 #include <BoopsiStubs.h>
228
229 #include "PIPClass.h"
230
231
232
233 /* Local function prototypes */
234 LONG SAVEDS                              _main                  (void);
235 static struct PIPHandle *OpenPIPWin             (UBYTE *spyscreen, UBYTE *pubscreen,
236                                                                                 ULONG *left, ULONG *top, ULONG *width, ULONG *height);
237 static void                              ClosePIPWin    (struct PIPHandle *piphandle);
238 static struct Gadget    *CreateGadgets  (struct PIPHandle *piphandle);
239 static void                              DisposeGadgets (struct PIPHandle *piphandle);
240 static void                              CreateImages   (struct DrawInfo *dri);
241 static void                              FreeImages             (void);
242 static Class *                   MakeScrollButtonClass  (void);
243 static BOOL                              FreeScrollButtonClass  (Class *cl);
244 static ULONG HOOKCALL    ScrollButtonDispatcher (
245         REG(a0, Class *cl),
246         REG(a2, struct Gadget *g),
247         REG(a1, struct gpInput *gpi));
248
249
250
251 /* Definitions for argument parsing */
252
253 #define ARGS_TEMPLATE   "SCREEN,PUBSCREEN/K,DELAY/K/N,"                         \
254                                                 "LEFT/K/N,TOP/K/N,WIDTH/K/N,HEIGHT/K/N"
255
256 enum
257 {
258         ARG_SCREEN, ARG_PUBSCREEN, ARG_DELAY,
259         ARG_LEFT, ARG_TOP, ARG_WIDTH, ARG_HEIGHT, ARG_COUNT
260 };
261
262
263 /* Gadgets IDs */
264
265 enum
266 {
267         GAD_PIP, GAD_VSLIDER, GAD_HSLIDER,
268         GAD_UPBUTTON, GAD_DOWNBUTTON, GAD_LEFTBUTTON, GAD_RIGHTBUTTON,
269         GAD_COUNT
270 };
271
272
273 /* Images IDs */
274
275 enum
276 {
277         IMG_UP, IMG_DOWN, IMG_LEFT, IMG_RIGHT, IMG_COUNT
278 };
279
280
281
282 /* This structure describes an open PIP window */
283
284 struct PIPHandle
285 {
286         struct Window   *Win;
287         struct Screen   *Scr;
288         struct DrawInfo *DrawInfo;
289         struct Screen   *SpyScr;
290         struct Gadget   *Gad[GAD_COUNT];
291         APTR                     Model;
292 };
293
294
295
296 /* Version tag */
297
298 static const UBYTE versiontag[] = "$VER: PIPWin 2.6 (13.10.97) by Bernardo Innocenti & Alfonso Caschili";
299 static const UBYTE PrgName[] = "PIPWin";
300
301
302
303 /* Workaround a bug in StormC header file <proto/utility.h> */
304
305 #ifdef __STORM__
306         #define UTILITYBASETYPE struct Library
307 #else
308         #define UTILITYBASETYPE struct UtilityBase
309 #endif
310
311
312 /* Library bases */
313
314 struct ExecBase                 *SysBase;
315 struct DosLibrary               *DOSBase;
316 struct IntuitionBase    *IntuitionBase;
317 struct GfxBase                  *GfxBase;
318 UTILITYBASETYPE                 *UtilityBase;
319
320
321
322 /* Our private `boopsi' classes */
323
324 static Class                    *PIPClass;
325 static Class                    *ScrollButtonClass;
326
327
328
329 /* 'boopsi' images for all our windows
330  *
331  * These variables must be NULL at startup time. We are not
332  * going to explicitly initialize them because otherwise
333  * Storm C 2.0 would generate a constructor to do it :-)
334  * LoasSeg() will clear the BSS data section for us, so
335  * these variables are guaranteed to be NULL anyway.
336  */
337 static struct Image             *Img[IMG_COUNT];
338 static ULONG                     ImgWidth[IMG_COUNT];
339 static ULONG                     ImgHeight[IMG_COUNT];
340
341
342
343 /* Attribute translations for object interconnections */
344
345 static const ULONG MapPIPToHSlider[] =
346 {
347         PIPA_OffX,                      PGA_Top,
348         PIPA_Width,                     PGA_Total,
349         PIPA_DisplayWidth,      PGA_Visible,
350         TAG_DONE
351 };
352
353 static const ULONG MapHSliderToPIP[] =
354 {
355         PGA_Top,        PIPA_OffX,
356         TAG_DONE
357 };
358
359 static const ULONG MapPIPToVSlider[] =
360 {
361         PIPA_OffY,                      PGA_Top,
362         PIPA_Height,            PGA_Total,
363         PIPA_DisplayHeight,     PGA_Visible,
364         TAG_DONE
365 };
366
367
368 static const ULONG MapVSliderToPIP[] =
369 {
370         PGA_Top,        PIPA_OffY,
371         TAG_DONE
372 };
373
374 static const ULONG MapUpButtonToPIP[] =
375 {
376         GA_ID,          PIPA_MoveUp,
377         TAG_DONE
378 };
379
380 static const ULONG MapDownButtonToPIP[] =
381 {
382         GA_ID,          PIPA_MoveDown,
383         TAG_DONE
384 };
385
386 static const ULONG MapLeftButtonToPIP[] =
387 {
388         GA_ID,          PIPA_MoveLeft,
389         TAG_DONE
390 };
391
392 static const ULONG MapRightButtonToPIP[] =
393 {
394         GA_ID,          PIPA_MoveRight,
395         TAG_DONE
396 };
397
398
399
400 LONG SAVEDS _main(void)
401
402 /* Main program entry point.  When linking without startup code, this
403  * must be the first function in the first object module listed on the
404  * linker command line.  We also need to initialize SysBase and open
405  * all needed libraries manually.
406  */
407 {
408         struct PIPHandle        *piphandle;
409         struct RDArgs           *rdargs;
410         struct MsgPort          *TimerMsgPort;
411         struct timerequest      *TimerIO;
412         LONG                             args[ARG_COUNT] = { 0 };
413         LONG                             sigwait, sigrcvd;
414         LONG                             secs, micros;
415         LONG                             retval = RETURN_FAIL;
416         BOOL                             quit   = FALSE;
417
418
419         /* Initialize SysBase */
420         SysBase = *((struct ExecBase **)4UL);
421
422         if (DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", 37L))
423         {
424                 if (UtilityBase = (UTILITYBASETYPE *) OpenLibrary("utility.library", 37L))
425                 {
426                         /* Parse shell arguments */
427                         if (rdargs = ReadArgs(ARGS_TEMPLATE, args, NULL))
428                         {
429                                 if (args[ARG_DELAY])
430                                 {
431                                         /* We use utility.library math because it works for 68000 too */
432                                         secs    = UDivMod32(*((LONG *)args[ARG_DELAY]), 10);
433                                         micros  = UMult32((*((LONG *)args[ARG_DELAY]) - UMult32 (secs, 10)), 100000);
434                                 }
435                                 else
436                                 {
437                                         secs    = 2;
438                                         micros  = 0;
439                                 }
440
441                                 if (IntuitionBase = (struct IntuitionBase *)
442                                         OpenLibrary("intuition.library", 39))
443                                 {
444                                         if (GfxBase = (struct GfxBase *)
445                                                 OpenLibrary("graphics.library", 39))
446                                         {
447                                                 if (TimerMsgPort = CreateMsgPort())
448                                                 {
449                                                         if (TimerIO = (struct timerequest *) CreateIORequest(TimerMsgPort, sizeof (struct timerequest)))
450                                                         {
451                                                                 if (!OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)TimerIO, 0))
452                                                                 {
453                                                                         if (PIPClass = MakePIPClass())
454                                                                         {
455                                                                                 if (ScrollButtonClass = MakeScrollButtonClass())
456                                                                                 {
457                                                                                         if (piphandle = OpenPIPWin((UBYTE *)args[ARG_SCREEN], (UBYTE *)args[ARG_PUBSCREEN],
458                                                                                                 (ULONG *)args[ARG_LEFT], (ULONG *)args[ARG_TOP],
459                                                                                                 (ULONG *)args[ARG_WIDTH], (ULONG *)args[ARG_HEIGHT]))
460                                                                                         {
461                                                                                                 /* Pre-calculate the signal mask for Wait() */
462                                                                                                 sigwait = (1 << TimerMsgPort->mp_SigBit) |
463                                                                                                         (1 << piphandle->Win->UserPort->mp_SigBit) |
464                                                                                                         SIGBREAKF_CTRL_C;
465
466                                                                                                 /* Send our first IORequest to timer.device */
467                                                                                                 TimerIO->tr_node.io_Command     = TR_ADDREQUEST;
468                                                                                                 TimerIO->tr_time.tv_secs        = secs;
469                                                                                                 TimerIO->tr_time.tv_micro       = micros;
470                                                                                                 SendIO ((struct IORequest *)TimerIO);
471
472                                                                                                 /* Now for the main loop.  As you can see, it is really
473                                                                                                  * very compact.  That's the magic of boopsi! :-)
474                                                                                                  */
475                                                                                                 while (!quit)
476                                                                                                 {
477                                                                                                         /* Sleep until something interesting occurs */
478                                                                                                         sigrcvd = Wait(sigwait);
479
480                                                                                                         /* Now handle received signals */
481
482                                                                                                         /* Break signal? */
483                                                                                                         if (sigrcvd & SIGBREAKF_CTRL_C)
484                                                                                                                 quit = TRUE;
485
486                                                                                                         /* timer.device? */
487                                                                                                         if (sigrcvd & (1 << TimerMsgPort->mp_SigBit))
488                                                                                                         {
489                                                                                                                 /* Update the PIP gadget and send another
490                                                                                                                  * request to the timer.device
491                                                                                                                  */
492                                                                                                                 DoGadgetMethod(piphandle->Gad[GAD_PIP], piphandle->Win, NULL,
493                                                                                                                         PIPM_REFRESH, NULL);
494
495                                                                                                                 TimerIO->tr_node.io_Command     = TR_ADDREQUEST;
496                                                                                                                 TimerIO->tr_time.tv_secs        = secs;
497                                                                                                                 TimerIO->tr_time.tv_micro       = micros;
498                                                                                                                 SendIO((struct IORequest *)TimerIO);
499                                                                                                         }
500
501                                                                                                         /* IDCMP message? */
502                                                                                                         if (sigrcvd & (1 << piphandle->Win->UserPort->mp_SigBit))
503                                                                                                         {
504                                                                                                                 struct IntuiMessage     *msg;
505
506                                                                                                                 while (msg = (struct IntuiMessage *) GetMsg(piphandle->Win->UserPort))
507                                                                                                                 {
508                                                                                                                         switch (msg->Class)
509                                                                                                                         {
510                                                                                                                                 case IDCMP_CLOSEWINDOW:
511                                                                                                                                         quit = TRUE;
512                                                                                                                                         break;
513
514                                                                                                                                 default:
515                                                                                                                                         break;
516                                                                                                                         }
517                                                                                                                         ReplyMsg ((struct Message *) msg);
518                                                                                                                 }
519                                                                                                         }
520                                                                                                 } /* End while (!quit) */
521
522                                                                                                 retval = RETURN_OK;
523
524                                                                                                 /* Abort the IORequest sent to the timer.device */
525                                                                                                 AbortIO((struct IORequest *)TimerIO);
526                                                                                                 WaitIO((struct IORequest *)TimerIO);
527
528                                                                                                 ClosePIPWin(piphandle);
529                                                                                         }
530
531                                                                                         FreeImages();
532                                                                                         /* This one cannot fail */
533                                                                                         FreeScrollButtonClass(ScrollButtonClass);
534                                                                                 }
535                                                                                 /* This one cannot fail */
536                                                                                 FreePIPClass(PIPClass);
537                                                                         }
538                                                                         CloseDevice((struct IORequest *)TimerIO);
539                                                                 }
540                                                                 DeleteIORequest((struct IORequest *)TimerIO);
541                                                         }
542                                                         DeleteMsgPort(TimerMsgPort);
543                                                 }
544                                                 CloseLibrary((struct Library *)GfxBase);
545                                         }
546                                         CloseLibrary((struct Library *)IntuitionBase);
547                                 }
548                                 FreeArgs(rdargs);
549                         }
550                         else PrintFault(IoErr(), (STRPTR)PrgName);
551
552                         CloseLibrary((struct Library *)UtilityBase);
553                 }
554                 CloseLibrary((struct Library *)DOSBase);
555         }
556
557         return retval;
558 }
559
560
561
562 static struct PIPHandle *OpenPIPWin(UBYTE *screen, UBYTE *pubscreen,
563         ULONG *left, ULONG *top, ULONG *width, ULONG *height)
564 {
565         struct PIPHandle        *piphandle;
566         struct Gadget           *glist;
567
568
569         if (piphandle = AllocMem(sizeof (struct PIPHandle), MEMF_ANY | MEMF_CLEAR))
570         {
571                 if (piphandle->SpyScr = LockPubScreen(screen))
572                 {
573                         if (piphandle->Scr = LockPubScreen(pubscreen))
574                         {
575                                 if (glist = CreateGadgets(piphandle))
576                                 {
577                                         SetAttrs(glist,
578                                                 PIPA_Screen,    piphandle->SpyScr,
579                                                 TAG_DONE);
580
581                                         if (piphandle->Win = OpenWindowTags(NULL,
582                                                 WA_Left,                        left ? *left : 0,
583                                                 WA_Top,                         top ? *top : piphandle->Scr->BarHeight + 1,
584                                                 WA_InnerWidth,          width ? *width : piphandle->SpyScr->Width,
585                                                 WA_InnerHeight,         height ? *height : piphandle->SpyScr->Height,
586                                                 WA_MinWidth,            64,
587                                                 WA_MinHeight,           64,
588                                                 WA_MaxWidth,            piphandle->SpyScr->Width,
589                                                 WA_MaxHeight,           piphandle->SpyScr->Height,
590                                                 WA_PubScreen,           piphandle->Scr,
591                                                 WA_PubScreenFallBack, TRUE,
592                                                 WA_AutoAdjust,          TRUE,
593                                                 WA_IDCMP,                       IDCMP_CLOSEWINDOW,
594                                                 WA_Flags,                       WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET |
595                                                                                         WFLG_SIZEGADGET | WFLG_SIZEBRIGHT | WFLG_SIZEBBOTTOM |
596                                                                                         WFLG_SIMPLE_REFRESH | WFLG_NOCAREREFRESH,
597                                                 WA_Gadgets,                     glist,
598                                                 WA_BackFill,            LAYERS_NOBACKFILL,
599                                                 WA_Title,                       piphandle->SpyScr->DefaultTitle,
600                                                 WA_ScreenTitle,         versiontag + 6,
601                                                 TAG_DONE))
602
603                                                 /* We need to keep our screen locked all the time
604                                                  * because we want to free the associated DrawInfo
605                                                  * *after* the window has been closed and
606                                                  * FreeScreenDrawInfo() wants a pointer to a *valid*
607                                                  * Screen.
608                                                  */
609                                                 return piphandle;
610                                         DisposeGadgets(piphandle);
611                                 }
612                                 UnlockPubScreen(NULL, piphandle->Scr);
613                         }
614                         UnlockPubScreen(NULL, piphandle->SpyScr);
615                 }
616                 FreeMem(piphandle, sizeof (struct PIPHandle));
617         }
618         return NULL;
619 }
620
621
622
623 static void ClosePIPWin(struct PIPHandle *piphandle)
624 {
625         /* Close our window. No need to reply queued messages,
626          * Intuition is clever enough to care about this for us.
627          */
628         CloseWindow(piphandle->Win);
629         DisposeGadgets(piphandle);
630         UnlockPubScreen(NULL, piphandle->Scr);
631         UnlockPubScreen(NULL, piphandle->SpyScr);
632         FreeMem(piphandle, sizeof (struct PIPHandle));
633 }
634
635
636
637 /*
638  *      Diagram of object interconnections
639  *      ==================================
640  *
641  *
642  *                     ScrollButtonClass objects
643  *      +----------+ +------------+ +------------+ +-------------+
644  *      | UpButton | | DownButton | | LeftButton | | RightButton |
645  *      +----------+ +------------+ +------------+ +-------------+
646  *       | GA_ID =     | GA_ID =       | GA_ID =       | GA_ID =
647  *       | PIPA_MoveUp | PIPA_MoveDown | PIPA_MoveLeft | PIPA_MoveRight
648  *       |             |               |               |
649  *       |  +----------+               |               |
650  *       |  |  +-----------------------+               |
651  *       |  |  |  +------------------------------------+
652  *       |  |  |  |        propgclass object     icclass object
653  *       |  |  |  |          +-----------+      +--------------+
654  *       |  |  |  |          |  HSlider  |<-----| PIPToHSlider |
655  *       |  |  |  |          +-----------+      +--------------+
656  *       |  |  |  |      PGA_Top = |                 ^ PIPA_OffX  = PGA_Top
657  *       |  |  |  |      PIPA_OffX |                 | PIPA_Width = PGA_Visible
658  *       |  |  |  |                |                 | PIPA_DisplayWidth =
659  *       V  V  V  V                V                 |              PGA_Total
660  *      +-----------+         ***********            |
661  *      |           |-------->*         *------------+
662  *      |    PIP    |         *  Model  *
663  *      |           |<--------*         *------------+
664  *      +-----------+         ***********            |
665  *                                 ^                 |
666  *                       PGA_Top = |                 |
667  *                       PIPA_OffY |                 V  icclass object
668  *                           +-----------+      +--------------+
669  *                           |  VSlider  |<-----| PIPToVSlider |
670  *                           +-----------+      +--------------+
671  *                         propgclass object     PIPA_OffY   = PGA_Top
672  *                                               PIPA_Height = PGA_Visible
673  *                                               PIPA_DisplayHeight = PGA_Total
674  */
675
676 static struct Gadget *CreateGadgets(struct PIPHandle *piphandle)
677 {
678         struct DrawInfo *dri;
679         struct Screen   *scr = piphandle->Scr;
680         struct Image    *SizeImage;
681         ULONG SizeWidth = 18, SizeHeight = 11;  /* Default values */
682
683         /* GetScreenDrawInfo() never fails */
684         dri = piphandle->DrawInfo = GetScreenDrawInfo (scr);
685
686         /* Create a new size image to get its size */
687         if (SizeImage = NewObject(NULL, SYSICLASS,
688                 SYSIA_Which,    SIZEIMAGE,
689                 SYSIA_DrawInfo, dri,
690                 TAG_DONE))
691         {
692                 /* Get size gadget geometry */
693                 GetAttr(IA_Width, SizeImage, &SizeWidth);
694                 GetAttr(IA_Height, SizeImage, &SizeHeight);
695
696                 /* And then get rid of it... */
697                 DisposeObject(SizeImage);
698         }
699
700         /* No need to check this: in case of failure we would just
701          * get no images in the scroll buttons, but we can still try
702          * to open our window.
703          */
704         CreateImages(dri);
705
706         if (piphandle->Model = NewObjectA(NULL, MODELCLASS, NULL))
707                 if (piphandle->Gad[GAD_PIP] = NewObject (PIPClass, NULL,
708                         GA_ID,                  GAD_PIP,
709                         GA_Left,                scr->WBorLeft,
710                         GA_Top,                 scr->WBorTop + scr->Font->ta_YSize + 1,
711                         GA_RelWidth,    - SizeWidth - scr->WBorLeft,
712                         GA_RelHeight,   - (scr->WBorTop + scr->Font->ta_YSize + SizeHeight + 1),
713                         GA_DrawInfo,    dri,
714                         ICA_TARGET,             piphandle->Model,
715                         TAG_DONE))
716                         if (piphandle->Gad[GAD_VSLIDER] = NewObject(NULL, PROPGCLASS,
717                                 GA_ID,                  GAD_VSLIDER,
718                                 GA_Previous,    piphandle->Gad[GAD_PIP],
719                                 GA_RelRight,    - SizeWidth + 5,
720                                 GA_Top,                 scr->WBorTop + scr->Font->ta_YSize + 2,
721                                 GA_Width,               SizeWidth - 8,
722                                 GA_RelHeight,   - (scr->WBorTop + scr->Font->ta_YSize +
723                                                                 SizeHeight + ImgHeight[IMG_DOWN] + ImgHeight[IMG_UP] + 4),
724                                 GA_RightBorder, TRUE,
725                                 GA_DrawInfo,    dri,
726                                 PGA_Freedom,    FREEVERT,
727                                 PGA_Borderless, ((dri->dri_Flags & DRIF_NEWLOOK) && (dri->dri_Depth != 1)),
728                                 PGA_NewLook,    TRUE,
729                                 ICA_TARGET,             piphandle->Model,
730                                 ICA_MAP,                MapVSliderToPIP,
731                                 TAG_DONE))
732                                 if (piphandle->Gad[GAD_HSLIDER] = NewObject(NULL, PROPGCLASS,
733                                         GA_ID,                  GAD_HSLIDER,
734                                         GA_Previous,    piphandle->Gad[GAD_VSLIDER],
735                                         GA_RelBottom,   - SizeHeight + ((SizeHeight > 15) ? 4 : 3),
736                                         GA_Left,                scr->WBorLeft,
737                                         GA_Height,              SizeHeight - ((SizeHeight > 15)  ? 6 : 4),
738                                         GA_RelWidth,    - (SizeWidth + ImgWidth[IMG_RIGHT] + ImgWidth[IMG_LEFT] + scr->WBorLeft + 2),
739                                         GA_BottomBorder,TRUE,
740                                         GA_DrawInfo,    dri,
741                                         PGA_Freedom,    FREEHORIZ,
742                                         PGA_Borderless, ((dri->dri_Flags & DRIF_NEWLOOK) && (dri->dri_Depth != 1)),
743                                         PGA_NewLook,    TRUE,
744                                         ICA_TARGET,             piphandle->Model,
745                                         ICA_MAP,                MapHSliderToPIP,
746                                         TAG_DONE))
747                                         if (piphandle->Gad[GAD_UPBUTTON] = NewObject(ScrollButtonClass, NULL,
748                                                 GA_ID,                  GAD_UPBUTTON,
749                                                 GA_Previous,    piphandle->Gad[GAD_HSLIDER],
750                                                 GA_RelBottom,   - SizeHeight - ImgHeight[IMG_DOWN] - ImgHeight[IMG_UP] + 1,
751                                                 GA_RelRight,    - ImgWidth[IMG_DOWN] + 1,
752                                                 GA_RightBorder, TRUE,
753                                                 GA_DrawInfo,    dri,
754                                                 GA_Image,               Img[IMG_UP],
755                                                 ICA_TARGET,             piphandle->Gad[GAD_PIP],
756                                                 ICA_MAP,                MapUpButtonToPIP,
757                                                 TAG_DONE))
758                                                 if (piphandle->Gad[GAD_DOWNBUTTON] = NewObject(ScrollButtonClass, NULL,
759                                                         GA_ID,                  GAD_DOWNBUTTON,
760                                                         GA_Previous,    piphandle->Gad[GAD_UPBUTTON],
761                                                         GA_RelBottom,   - SizeHeight - ImgHeight[IMG_DOWN] + 1,
762                                                         GA_RelRight,    - ImgWidth[IMG_DOWN] + 1,
763                                                         GA_RightBorder, TRUE,
764                                                         GA_DrawInfo,    dri,
765                                                         GA_Image,               Img[IMG_DOWN],
766                                                         ICA_TARGET,             piphandle->Gad[GAD_PIP],
767                                                         ICA_MAP,                MapDownButtonToPIP,
768                                                         TAG_DONE))
769                                                         if (piphandle->Gad[GAD_LEFTBUTTON] = NewObject(ScrollButtonClass, NULL,
770                                                                 GA_ID,                  GAD_LEFTBUTTON,
771                                                                 GA_Previous,    piphandle->Gad[GAD_DOWNBUTTON],
772                                                                 GA_RelBottom,   - ImgHeight[IMG_LEFT] + 1,
773                                                                 GA_RelRight,    - SizeWidth - ImgWidth[IMG_RIGHT] - ImgWidth[IMG_LEFT] + 1,
774                                                                 GA_BottomBorder,TRUE,
775                                                                 GA_DrawInfo,    dri,
776                                                                 GA_Image,               Img[IMG_LEFT],
777                                                                 ICA_TARGET,             piphandle->Gad[GAD_PIP],
778                                                                 ICA_MAP,                MapLeftButtonToPIP,
779                                                                 TAG_DONE))
780                                                                 if (piphandle->Gad[GAD_RIGHTBUTTON] = NewObject(ScrollButtonClass, NULL,
781                                                                         GA_ID,                  GAD_RIGHTBUTTON,
782                                                                         GA_Previous,    piphandle->Gad[GAD_LEFTBUTTON],
783                                                                         GA_RelBottom,   - ImgHeight[IMG_RIGHT] + 1,
784                                                                         GA_RelRight,    - SizeWidth - ImgWidth[IMG_RIGHT] + 1,
785                                                                         GA_BottomBorder,TRUE,
786                                                                         GA_DrawInfo,    dri,
787                                                                         GA_Image,               Img[IMG_RIGHT],
788                                                                         ICA_TARGET,             piphandle->Gad[GAD_PIP],
789                                                                         ICA_MAP,                MapRightButtonToPIP,
790                                                                         TAG_DONE))
791                                                                         {
792                                                                                 APTR icobject;
793
794                                                                                 /* Connect VSlider to Model */
795
796                                                                                 if (icobject = NewObject(NULL, ICCLASS,
797                                                                                         ICA_TARGET,     piphandle->Gad[GAD_VSLIDER],
798                                                                                         ICA_MAP,        MapPIPToVSlider,
799                                                                                         TAG_DONE))
800                                                                                         if (!DoMethod (piphandle->Model, OM_ADDMEMBER, icobject))
801                                                                                                 DisposeObject (icobject);
802
803                                                                                 /* Connect HSlider to Model */
804
805                                                                                 if (icobject = NewObject(NULL, ICCLASS,
806                                                                                         ICA_TARGET,     piphandle->Gad[GAD_HSLIDER],
807                                                                                         ICA_MAP,        MapPIPToHSlider,
808                                                                                         TAG_DONE))
809                                                                                         if (!DoMethod (piphandle->Model, OM_ADDMEMBER, icobject))
810                                                                                                 DisposeObject (icobject);
811
812                                                                                 /* Connect Model to PIP */
813
814                                                                                 SetAttrs(piphandle->Model,
815                                                                                         ICA_TARGET, piphandle->Gad[GAD_PIP],
816                                                                                         TAG_DONE);
817
818                                                                                 return piphandle->Gad[GAD_PIP];
819                                                                         }
820         DisposeGadgets(piphandle);
821
822         return NULL;
823 }
824
825
826
827 static void DisposeGadgets(struct PIPHandle *piphandle)
828 {
829         ULONG i;
830
831         for (i = 0; i < GAD_COUNT; i++)
832         {
833                 DisposeObject(piphandle->Gad[i]);
834                 /* piphandle->Gad[i] = NULL; */
835         }
836
837         /* Freeing the Model will also free its two targets */
838         DisposeObject(piphandle->Model);
839         /* piphandle->Model = NULL */
840
841         FreeScreenDrawInfo(piphandle->Scr, piphandle->DrawInfo);
842         /* piphandle->DrawInfo = NULL */
843 }
844
845
846
847 static void CreateImages(struct DrawInfo *dri)
848
849 /* Create 4 arrow images for the window scrolling buttons.
850  *
851  * Why bother checking for failure? The arrow images are not
852  * life critical in our program...
853  */
854 {
855         static ULONG imagetypes[IMG_COUNT] = { UPIMAGE, DOWNIMAGE, LEFTIMAGE, RIGHTIMAGE };
856         ULONG i;
857
858         for (i = 0; i < IMG_COUNT; i++)
859                 if (!Img[i])
860                         if (Img[i] = (struct Image *)NewObject(NULL, SYSICLASS,
861                                 SYSIA_Which,    imagetypes[i],
862                                 SYSIA_DrawInfo, dri,
863                                 TAG_DONE))
864                         {
865                                 /* Ask image width and height */
866                                 GetAttr(IA_Width, Img[i], &ImgWidth[i]);
867                                 GetAttr(IA_Height, Img[i], &ImgHeight[i]);
868                         }
869 }
870
871
872
873 static void FreeImages(void)
874 {
875         ULONG i;
876
877         for (i = 0; i < IMG_COUNT; i++)
878                 DisposeObject((APTR)Img[i]);    /* DisposeObject(NULL) is safe */
879 }
880
881
882
883 /*
884 **      ScrollButtonClass
885 **
886 **      This code has been taken from ScrollerWindow 0.3
887 **      Copyright © 1994 Christoph Feck, TowerSystems.
888 **
889 **      Subclass of buttongclass.  The ROM class has two problems, which make
890 **      it not quite usable for scrollarrows.  The first problem is the missing
891 **      delay.  Once the next INTUITICK gets send by input.device, the ROM
892 **      class already sends a notification.  The other problem is that it also
893 **      notifies us, when the button finally gets released (which is necessary
894 **      for command buttons).
895 **
896 **      We define a new class with the GM_GOACTIVE and GM_HANDLEINPUT method
897 **      overloaded to work around these problems.
898 */
899
900
901
902 /* Function prototypes */
903
904 static ULONG HOOKCALL ScrollButtonDispatcher(
905         REG(a0, Class *cl),
906         REG(a2, struct Gadget *g),
907         REG(a1, struct gpInput *gpi));
908
909
910 /* Per object instance data */
911 struct ScrollButtonData
912 {
913         /* The number of ticks we still have to wait
914          * before sending any notification.
915          */
916         ULONG TickCounter;
917 };
918
919
920
921 static ULONG HOOKCALL ScrollButtonDispatcher(
922         REG(a0, Class *cl),
923         REG(a2, struct Gadget *g),
924         REG(a1, struct gpInput *gpi))
925
926 /* ScrollButton Class Dispatcher entrypoint.
927  * Handle BOOPSI messages.
928  */
929 {
930         struct ScrollButtonData *bd = (struct ScrollButtonData *) INST_DATA(cl, g);
931
932         switch (gpi->MethodID)
933         {
934                 case GM_GOACTIVE:
935                         /* May define an attribute to make delay configurable */
936                         bd->TickCounter = 3;
937
938                         /* Notify our target that we have initially hit. */
939                         NotifyAttrs((Object *)g, gpi->gpi_GInfo, 0,
940                                 GA_ID,  g->GadgetID,
941                                 TAG_DONE);
942
943                         /* Send more input */
944                         return GMR_MEACTIVE;
945
946                 case GM_HANDLEINPUT:
947                 {
948                         ULONG retval = GMR_MEACTIVE;
949                         UWORD selected = 0;
950
951                         /* This also works with classic (non-boopsi) images. */
952                         if (PointInImage((gpi->gpi_Mouse.X << 16) + (gpi->gpi_Mouse.Y), g->GadgetRender))
953                         {
954                                 /* We are hit */
955                                 selected = GFLG_SELECTED;
956                         }
957
958                         if (gpi->gpi_IEvent->ie_Class == IECLASS_RAWMOUSE && gpi->gpi_IEvent->ie_Code == SELECTUP)
959                         {
960                                 /* Gadgetup, time to go */
961                                 retval = GMR_NOREUSE;
962                                 /* Unselect the gadget on our way out... */
963                                 selected = 0;
964                         }
965                         else if (gpi->gpi_IEvent->ie_Class == IECLASS_TIMER)
966                         {
967                                 /* We got a tick: decrement counter, and if 0, send notify. */
968
969                                 if (bd->TickCounter) bd->TickCounter--;
970                                 else if (selected)
971                                         /* Notify our target that we are still being hit */
972                                         NotifyAttrs((Object *)g, gpi->gpi_GInfo, 0,
973                                                 GA_ID,  g->GadgetID,
974                                                 TAG_DONE);
975                         }
976
977                         if ((g->Flags & GFLG_SELECTED) != selected)
978                         {
979                                 struct RastPort *rp;
980
981                                 /* Update changes in gadget render */
982                                 g->Flags ^= GFLG_SELECTED;
983                                 if (rp = ObtainGIRPort(gpi->gpi_GInfo))
984                                 {
985                                         DoMethod((Object *) g, GM_RENDER, gpi->gpi_GInfo, rp, GREDRAW_UPDATE);
986                                         ReleaseGIRPort (rp);
987                                 }
988                         }
989                         return retval;
990                 }
991
992                 default:
993                         /* Super class handles everything else */
994                         return (DoSuperMethodA(cl, (Object *)g, (Msg) gpi));
995         }
996 }
997
998
999
1000 static Class *MakeScrollButtonClass(void)
1001 {
1002         Class *class;
1003
1004         if (class = MakeClass(NULL, BUTTONGCLASS, NULL, sizeof(struct ScrollButtonData), 0))
1005                 class->cl_Dispatcher.h_Entry = (ULONG (*)()) ScrollButtonDispatcher;
1006
1007         return class;
1008 }
1009
1010
1011
1012 static BOOL FreeScrollButtonClass(Class *cl)
1013 {
1014         return (FreeClass(cl));
1015 }