]> git.vomp.tv Git - vompclient.git/blob - boxstack.cc
43 CWFs
[vompclient.git] / boxstack.cc
1 /*
2     Copyright 2004-2020 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "boxstack.h"
21
22 #include "messagequeue.h"
23 #include "input.h"
24 #include "log.h"
25
26 static const char* TAG = "BoxStack";
27
28 BoxStack* BoxStack::instance = NULL;
29
30 BoxStack::BoxStack()
31 {
32   if (instance) return;
33   instance = this;
34   osd = Osd::getInstance();
35 }
36
37 BoxStack::~BoxStack()
38 {
39   instance = NULL;
40 }
41
42 BoxStack* BoxStack::getInstance()
43 {
44   return instance;
45 }
46
47 int BoxStack::init()
48 {
49   if (initted) return 0;
50   initted = 1;
51   
52   return 1;
53 }
54
55 int BoxStack::shutdown()
56 {
57   if (!initted) return 0;
58   removeAll();
59   initted = 0;
60   return 1;
61 }
62
63 void BoxStack::removeAll()
64 {
65   removeAllExceptWallpaper();
66   if (numBoxes == 1) remove(boxes[0]);
67 }
68
69 int BoxStack::addVideoDisplay(Boxx* box,VideoDisplay vd)
70 {
71           boxLock.lock();
72           videoStack.push(std::pair<Boxx*,VideoDisplay>(box,vd));
73           boxLock.unlock();
74           Video::getInstance()->setVideoDisplay(vd);
75           return 1;
76 }
77
78 int BoxStack::add(Boxx* v)
79 {
80   if (!initted) return 0;
81   LogNT::getInstance()->debug(TAG, "add called");
82   boxLock.lock();
83   LogNT::getInstance()->debug(TAG, "Locked for add");
84   
85   if (numBoxes == 16)
86   { //Error
87     LogNT::getInstance()->error(TAG, "More than 16 boxes! Unlocked for add");
88     boxLock.unlock();
89     return 0;
90   }
91   boxes[numBoxes++] = v;
92   boxLock.unlock();
93   VideoDisplay vd;
94   if (v->getVideoDisplay(vd)) {
95           LogNT::getInstance()->debug(TAG, "Add video display");
96           addVideoDisplay(v,vd);
97   }
98
99
100
101   LogNT::getInstance()->debug(TAG, "Unlocked for add");
102   
103   return 1;
104 }
105
106 // ---------------------------------------------------- REMOVE CODE
107
108 int BoxStack::remove(Boxx* toDelete)
109 {
110   if (!initted) return 0;
111   VideoDisplay *display = NULL;
112
113   boxLock.lock();
114   LogNT::getInstance()->debug(TAG, "Locked for remove");
115   
116   if (numBoxes == 0)
117   {
118     boxLock.unlock();
119     LogNT::getInstance()->error(TAG, "Unlocked for remove numBoxes == 0");
120     return 0;
121   }
122
123 //  LogNT::getInstance()->debug(TAG, "entering remove, numBoxes={}", numBoxes);
124
125   int i;
126
127   if (toDelete == NULL)
128   {
129     toDelete = boxes[numBoxes-1];
130     i = numBoxes - 1;
131   }
132   else
133   {
134     // to be deleted box is more likely to be at the top
135     for (i = numBoxes-1; i >= 0; i--)
136     {
137 //      LogNT::getInstance()->debug(TAG, "todel: {}, i={}, boxes[i]={}", (void*)toDelete, i, (void*)boxes[i]);
138       if (boxes[i] == toDelete) break;
139     }
140
141     if (i == -1)
142     {
143       // not a Box we have!
144       // FIXME
145       boxLock.unlock();
146       LogNT::getInstance()->error(TAG, "Unlocked for remove - no boxx deleted");
147       return 0;
148     }
149   }
150
151   boxLock.unlock();
152
153   toDelete->preDelete();
154
155   boxLock.lock();
156
157   //LogNT::getInstance()->debug(TAG, "Starting repaintRevealed loop");
158   RegionList rl;
159   boxSplit(boxes[i]->area, i + 1, numBoxes, 1, rl);
160   while(!rl.empty())
161   {
162     repaintRevealed(i, rl.front());
163     rl.pop_front();
164   }
165   //LogNT::getInstance()->debug(TAG, "Done repaintRevealed loop");
166
167   // Shift the boxes on top down one
168   --numBoxes;
169   for(int j = i; j < numBoxes; j++) boxes[j] = boxes[j+1];
170
171   // If there is only the wallpaper left signal command
172   if (numBoxes == 1)
173   {
174     Message* m = new Message();
175     m->p_to = Message::CONTROL;
176     m->message = Message::LAST_VIEW_CLOSE;
177     MessageQueue::getInstance()->postMessage(m);
178   }
179
180   if (!videoStack.empty() && videoStack.top().first==toDelete) {
181         videoStack.pop();
182     if (!videoStack.empty()) display=&videoStack.top().second;
183   }
184   boxLock.unlock();
185   LogNT::getInstance()->debug(TAG, "Unlocked for remove");
186  
187   // Delete the box
188   //AVO: do this delete outside the lock to allow for recursive calls within the destructor
189   //     as this box is not in the stack any more, there is no chance for a second delete
190   LogNT::getInstance()->debug(TAG, "remove: going to delete boxx {}, num {}", static_cast<void*>(toDelete), numBoxes);
191   delete toDelete;
192
193   osd->doRender();
194
195   if  (display) {
196           LogNT::getInstance()->debug(TAG, "setVideoDisplay {} {} {} {} {} {}", display->mode, display->fallbackMode,
197                           display->x, display->y, display->width, display->height);
198           Video::getInstance()->setVideoDisplay(*display);
199   }
200
201   return 1;
202 }
203
204 void BoxStack::redrawAllBoxes()
205 {
206   boxLock.lock();
207
208   for (int z = 0; z < numBoxes; z++)
209   {
210     boxes[z]->draw();
211   }
212
213   boxLock.unlock();
214   update(NULL,NULL); // should blt all
215 }
216
217 void BoxStack::update(Boxx* toUpdate, const Region* regionToUpdate)
218 {
219 //  LogNT::getInstance()->debug(TAG, "Update called");
220   if (!initted) return; // it is allowed to call this before init
221   boxLock.lock();
222 //  LogNT::getInstance()->debug(TAG, "Locked for update");
223
224   // Get the z index of the box
225   int z = 0;
226   if (toUpdate)
227   {
228     for (z = 0; z < numBoxes; z++)
229     {
230       if (boxes[z] == toUpdate) break;
231     }
232
233     if (z == numBoxes)
234     {
235       // not a Box we have!
236       boxLock.unlock();
237       LogNT::getInstance()->error(TAG, "Unlocked for update! box not inside boxstack");
238       return;
239     }
240   }
241   else
242   {
243     toUpdate = boxes[0];
244   }
245
246   if (!toUpdate) {
247       boxLock.unlock();
248           LogNT::getInstance()->debug(TAG, "Unlocked for update, no box present");
249           return ;
250   }
251
252   // get the region for the whole box, could be less than that
253   // for smaller updates
254
255   Region r = toUpdate->area;
256
257   if (regionToUpdate)
258   {
259     // Can be null if the whole box should be updated
260     // If this is given the numbers are relative to the size of the box, not the screen
261
262     r.x += regionToUpdate->x;
263     r.y += regionToUpdate->y;
264     r.w = regionToUpdate->w;
265     r.h = regionToUpdate->h;
266   }
267
268   RegionList rl;
269
270   Region r2;
271   boxSplit(r, z+1, numBoxes, 1, rl);
272   while(!rl.empty())
273   {
274     r2 = rl.front();
275     r2.z = z;
276     boxes[z]->blt(r2);
277     rl.pop_front();
278   }
279
280   boxLock.unlock();
281 //  LogNT::getInstance()->debug(TAG, "Unlocked for update");
282 }
283
284 void BoxStack::repaintRevealed(int x, Region r)
285 {
286   RegionList rl;
287   boxSplit(r, x - 1, -1, -1, rl);
288
289   Region r2;
290   while(!rl.empty())
291   {
292     r2 = rl.front();
293     boxes[r2.z]->blt(r2);
294     rl.pop_front();
295   }
296 }
297
298 void BoxStack::boxSplit(Region r, int start, int end, int direction, RegionList& rl)
299 {
300 //  printf("Y=  S=%i E=%i D=%i: Boxsplit: %i %i %i %i\n", start, end, direction, r.x, r.y, r.w, r.h);
301
302   for(int z = start; z != end; z += direction)
303   {
304     if (r.overlappedBy(boxes[z]->area))
305     {
306 //      printf("Z=%i S=%i E=%i D=%i: %i overlaps\n", z, start, end, direction, z);
307
308       int top = r.y;
309       int btm = r.y2();
310
311       if (boxes[z]->area.y > r.y)
312       {
313 //        printf("Z=%i S=%i E=%i D=%i: Case 1 for %i %i %i %i split by %i: %i %i %i %i\n", z, start, end, direction, r.x, r.y, r.w, r.h, z, boxes[z]->area.x, boxes[z]->area.y, boxes[z]->area.w, boxes[z]->area.h);
314         top = boxes[z]->area.y;
315         Region newR;
316         newR.x = r.x;
317         newR.y = r.y;
318         newR.w = r.w;
319         newR.h = boxes[z]->area.y - r.y;
320         boxSplit(newR, z + direction, end, direction, rl);
321
322         if (direction == -1)
323         {
324           Region newR2;
325           newR2.x = r.x;
326           newR2.y = boxes[z]->area.y;
327           newR2.w = r.w;
328           newR2.h = r.h - newR.h;
329           boxSplit(newR2, z, end, -1, rl);
330           return;
331         }
332       }
333
334       if (boxes[z]->area.y2() < r.y2())
335       {
336 //        printf("Z=%i S=%i E=%i D=%i: Case 2 for %i %i %i %i split by %i: %i %i %i %i\n", z, start, end, direction, r.x, r.y, r.w, r.h, z, boxes[z]->area.x, boxes[z]->area.y, boxes[z]->area.w, boxes[z]->area.h);
337         btm = boxes[z]->area.y2();
338         Region newR;
339         newR.x = r.x;
340         newR.y = boxes[z]->area.y2() + 1;
341         newR.w = r.w;
342         newR.h = r.y2() - newR.y + 1;
343         boxSplit(newR, z + direction, end, direction, rl);
344
345         if (direction == -1)
346         {
347           Region newR2;
348           newR2.x = r.x;
349           newR2.y = r.y;
350           newR2.w = r.w;
351           newR2.h = r.h - newR.h;
352           boxSplit(newR2, z, end, -1, rl);
353           return;
354         }
355       }
356
357       if (boxes[z]->area.x > r.x)
358       {
359 //        printf("Z=%i S=%i E=%i D=%i: Case 3 for %i %i %i %i split by %i: %i %i %i %i\n", z, start, end, direction, r.x, r.y, r.w, r.h, z, boxes[z]->area.x, boxes[z]->area.y, boxes[z]->area.w, boxes[z]->area.h);
360         Region newR;
361         newR.x = r.x;
362         newR.y = top;
363         newR.w = boxes[z]->area.x - r.x;
364         newR.h = btm - top + 1;
365         boxSplit(newR, z + direction, end, direction, rl);
366
367         if (direction == -1)
368         {
369           Region newR2;
370           newR2.x = r.x + newR.w;
371           newR2.y = r.y;
372           newR2.w = r.w - newR.w;
373           newR2.h = r.h;
374           boxSplit(newR2, z, end, -1, rl);
375           return;
376         }
377       }
378
379       if (boxes[z]->area.x2() < r.x2())
380       {
381 //        printf("Z=%i S=%i E=%i D=%i: Case 4 for %i %i %i %i split by %i: %i %i %i %i\n", z, start, end, direction, r.x, r.y, r.w, r.h, z, boxes[z]->area.x, boxes[z]->area.y, boxes[z]->area.w, boxes[z]->area.h);
382         Region newR;
383         newR.x = boxes[z]->area.x2() + 1;
384         newR.y = top;
385         newR.w = r.x2() - newR.x + 1;
386         newR.h = btm - top + 1;
387         boxSplit(newR, z + direction, end, direction, rl);
388
389         if (direction == -1)
390         {
391           Region newR2;
392           newR2.x = r.x;
393           newR2.y = r.y;
394           newR2.w = r.w - newR.w;
395           newR2.h = r.h;
396           boxSplit(newR2, z, end, -1, rl);
397           return;
398         }
399       }
400
401       if (direction == -1)
402       {
403         // we are going down the stack
404         // r is underlapped by boxes[z]
405         // but we have not split
406         // Therefore this region under test is
407         // completely covering boxes[z]
408
409         // don't go any further down, generate a region and quit
410
411 //        printf("Repaint region: %i %i %i %i\n", r.x, r.y, r.w, r.h);
412         r.z = z;
413         rl.push_front(r);
414       }
415
416 //      printf("Returning from Z=%i\n", z);
417       return;
418     }
419     else
420     {
421 //      printf("Z=%i S=%i E=%i D=%i: %i does not overlap\n", z, start, end, direction, z);
422     }
423   }
424
425   // if direction = 1 then we have come to a region that is
426   // entirely clear of higher boxes and needs to be redrawn
427
428   // if direction = -1 then we have come to a region that is on
429   // the very bottom with nothing below it to repaint.
430   // do nothing. stale window data will be left on screen?
431
432   if (direction == 1)
433   {
434     rl.push_front(r);
435   }
436 }
437
438 void BoxStack::removeAllExceptWallpaper()
439 {
440   // 1.. Don't delete wallpaper. No point.
441
442   // Need locking on this one??
443   
444   // This is pretty silly now that preDelete needs mutex unlocked
445   
446   Boxx* toDel = NULL;
447   VideoDisplay *display = NULL;
448   
449   while(numBoxes > 1)
450   {
451     boxLock.lock();
452
453     if (numBoxes == 1)
454     {
455       boxLock.unlock();
456       break;
457     }    
458   
459     toDel = boxes[numBoxes - 1];
460
461     boxLock.unlock();
462
463     toDel->preDelete();
464     
465     boxLock.lock();
466
467     // If boxes[numBoxes - 1] isn't toDel then there's a problem
468     if (boxes[numBoxes - 1] == toDel)
469     {
470       --numBoxes;
471     }
472     else
473     {
474       LogNT::getInstance()->error(TAG, "Can this actually happen? Why?");
475       toDel = NULL;
476     }
477
478     // FIXME what is this?
479     if (!videoStack.empty() && videoStack.top().first==toDel) {
480         videoStack.pop();
481         if (!videoStack.empty()) display=&videoStack.top().second;
482     }
483     boxLock.unlock();
484
485     //AVO: do the delete outside the lock to allow for recursive deletes
486     LogNT::getInstance()->debug(TAG, "removeall: going to delete boxx {}, num={}", static_cast<void*>(toDel), numBoxes);
487     if (display) Video::getInstance()->setVideoDisplay(*display);
488
489     if (toDel) delete toDel;
490   }
491 }
492
493 int BoxStack::handleCommand(int command)
494 {
495   int retVal;
496   int retVal2 = 0;
497   int i;
498
499   LogNT::getInstance()->debug(TAG, "handle command {}", command);
500
501   if (command != Input::NA_NONE)
502   {
503     // handle command return values
504     // 0 - drop through to next box
505     // 1 - dont drop to next box, but not handled
506     // 2 - handled - stop command here
507     // 4 - handled - delete this box
508
509     for (i=numBoxes-1; i>=0; i--)
510     {
511       LogNT::getInstance()->debug(TAG, "Giving command to i={}", i);
512       retVal = boxes[i]->handleCommand(command);
513       if (retVal == ABANDON_COMMAND)
514       {
515         // not handled but don't give to any more boxes
516         return 0;
517       }
518
519       if (retVal == COMMAND_HANDLED)
520       {
521         retVal2 = 1;
522         break;
523       }
524       else if (retVal == DELETE_ME)
525       {
526 //        LogNT::getInstance()->debug(TAG, "Return 4: i={}, boxes[i]={}", i, (void*)boxes[i]);
527         remove(boxes[i]);
528         retVal2 = 1;
529         break;
530       }
531     }
532   }
533   else
534   {
535     // fake the return code
536     retVal2 = 2;
537   }
538
539   return retVal2;
540 }
541
542 void BoxStack::processMessage(Message* m)
543 {
544   if (m->p_to == Message::MOUSE_RECEIVER)
545   {
546     // Handle mouse events
547     // They just need to be delivered to top box
548     if (numBoxes > 1) boxes[numBoxes-1]->processMessage(m);
549     return;
550   }
551   else if (m->p_to == Message::BOXSTACK)
552   {
553     switch(m->message)
554     {
555       case Message::CLOSE_ME:
556       {
557         remove(static_cast<Boxx*>(m->from));
558         break;
559       }
560       case Message::ADD_VIEW:
561       {
562         Boxx* toAdd = reinterpret_cast<Boxx*>(m->data);
563         add(toAdd);
564         toAdd->draw();
565         update(toAdd);
566         break;
567       }
568     }
569   }
570 }