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