1 // Container.cc
2 // Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
3 //                and Simon Bowden    (rathnor at users.sourceforge.net)
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a
6 // copy of this software and associated documentation files (the "Software"),
7 // to deal in the Software without restriction, including without limitation
8 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 // and/or sell copies of the Software, and to permit persons to whom the
10 // Software is furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 // DEALINGS IN THE SOFTWARE.
22 
23 #include "Container.hh"
24 
25 #include "Button.hh"
26 #include "TextUtils.hh"
27 #include "EventManager.hh"
28 #include "CompareEqual.hh"
29 #include "STLUtil.hh"
30 
31 #include <algorithm>
32 
33 namespace FbTk {
34 
35 typedef CompareEqual_base<FbWindow, Window> CompareWindow;
36 
Container(const FbWindow & parent,bool auto_resize)37 Container::Container(const FbWindow &parent, bool auto_resize):
38     FbWindow(parent, 0, 0, 1, 1, ExposureMask),
39     m_orientation(ROT0),
40     m_align(RELATIVE),
41     m_max_size_per_client(60),
42     m_max_total_size(0),
43     m_update_lock(false),
44     m_auto_resize(auto_resize) {
45     EventManager::instance()->add(*this, *this);
46 }
47 
~Container()48 Container::~Container() {
49     // ~FbWindow cleans event manager
50 }
51 
resize(unsigned int width,unsigned int height)52 void Container::resize(unsigned int width, unsigned int height) {
53     // do we need to resize?
54     if (FbWindow::width() == width &&
55         FbWindow::height() == height)
56         return;
57 
58     FbWindow::resize(width, height);
59     repositionItems();
60 }
61 
moveResize(int x,int y,unsigned int width,unsigned int height)62 void Container::moveResize(int x, int y,
63                            unsigned int width, unsigned int height) {
64     FbWindow::moveResize(x, y, width, height);
65     repositionItems();
66 }
67 
insertItem(Item item,int pos)68 void Container::insertItem(Item item, int pos) {
69     if (find(item) != -1)
70         return;
71 
72     // it must be a child of this window
73     if (item->parent() != this)
74         return;
75 
76     item->setOrientation(m_orientation);
77     if (pos >= size() || pos < 0) {
78         m_item_list.push_back(item);
79     } else if (pos == 0) {
80         m_item_list.push_front(item);
81     } else {
82         ItemList::iterator it = begin();
83         for (; pos != 0; ++it, --pos)
84             continue;
85 
86         m_item_list.insert(it, item);
87     }
88 
89     // make sure we dont have duplicate items
90     m_item_list.unique();
91 
92     repositionItems();
93 }
94 
moveItem(Item item,int movement)95 void Container::moveItem(Item item, int movement) {
96 
97     if (m_item_list.empty()) {
98         return;
99     }
100 
101     int index = find(item);
102     const size_t size = m_item_list.size();
103 
104     if (index < 0 || (movement % static_cast<signed>(size)) == 0) {
105         return;
106     }
107 
108     int newindex = (index + movement) % static_cast<signed>(size);
109     if (newindex < 0) // neg wrap
110         newindex += size;
111 
112     ItemList::iterator it = std::find(begin(), end(), item);
113     m_item_list.erase(it);
114 
115     for (it = begin(); newindex >= 0; ++it, --newindex) {
116         if (newindex == 0) {
117             break;
118         }
119     }
120 
121     m_item_list.insert(it, item);
122     repositionItems();
123 }
124 
125 // returns true if something was done
moveItemTo(Item item,int x,int y)126 bool Container::moveItemTo(Item item, int x, int y) {
127     Window parent_return=0,
128         root_return=0,
129         *children_return = NULL;
130 
131     unsigned int nchildren_return;
132 
133     // get the root window
134     if (!XQueryTree(display(), window(),
135                     &root_return, &parent_return, &children_return, &nchildren_return))
136         return false;
137 
138     if (children_return != NULL)
139         XFree(children_return);
140 
141     int dest_x = 0, dest_y = 0;
142     Window itemwin = 0;
143     if (!XTranslateCoordinates(display(),
144                                root_return, window(),
145                                x, y, &dest_x, &dest_y,
146                                &itemwin))
147         return false;
148 
149     ItemList::iterator it = find_if(begin(), end(),
150                                     CompareWindow(&FbWindow::window,
151                                                   itemwin));
152     // not found :(
153     if (it == end())
154         return false;
155 
156     Window child_return = 0;
157     //make x and y relative to our item
158     if (!XTranslateCoordinates(display(),
159                                window(), itemwin,
160                                dest_x, dest_y, &x, &y,
161                                &child_return))
162         return false;
163     return true;
164 }
165 
removeItem(Item item)166 bool Container::removeItem(Item item) {
167     ItemList::iterator it = begin();
168     ItemList::iterator it_end = end();
169     for (; it != it_end && (*it) != item; ++it);
170 
171     if (it == it_end)
172         return false;
173 
174     m_item_list.erase(it);
175     repositionItems();
176     return true;
177 }
178 
removeItem(int index)179 bool Container::removeItem(int index) {
180     if (index < 0 || index > size())
181         return false;
182 
183     ItemList::iterator it = begin();
184     for (; index != 0; ++it, --index)
185         continue;
186 
187     m_item_list.erase(it);
188 
189     repositionItems();
190     return true;
191 }
192 
removeAll()193 void Container::removeAll() {
194     m_item_list.clear();
195     if (!m_update_lock) {
196         clear();
197     }
198 
199 }
200 
find(ConstItem item)201 int Container::find(ConstItem item) {
202     ItemList::iterator it = m_item_list.begin();
203     ItemList::iterator it_end = m_item_list.end();
204     int index = 0;
205     for (; it != it_end; ++it, ++index) {
206         if ((*it) == item)
207             break;
208     }
209 
210     if (it == it_end)
211         return -1;
212 
213     return index;
214 }
215 
setMaxSizePerClient(unsigned int size)216 void Container::setMaxSizePerClient(unsigned int size) {
217     if (size != m_max_size_per_client) {
218         m_max_size_per_client = size;
219         repositionItems();
220     }
221 }
222 
setMaxTotalSize(unsigned int size)223 void Container::setMaxTotalSize(unsigned int size) {
224     if (m_max_total_size == size)
225         return;
226 
227     m_max_total_size = size;
228 
229     repositionItems();
230     return;
231 }
232 
setAlignment(Container::Alignment a)233 void Container::setAlignment(Container::Alignment a) {
234     if (m_align != a) {
235         m_align = a;
236         repositionItems();
237     }
238 }
239 
exposeEvent(XExposeEvent & event)240 void Container::exposeEvent(XExposeEvent &event) {
241     if (!m_update_lock) {
242         clearArea(event.x, event.y, event.width, event.height);
243     }
244 }
245 
tryExposeEvent(XExposeEvent & event)246 bool Container::tryExposeEvent(XExposeEvent &event) {
247     if (event.window == window()) {
248         exposeEvent(event);
249         return true;
250     }
251 
252     ItemList::iterator it = find_if(begin(), end(),
253                                     CompareWindow(&FbWindow::window,
254                                                   event.window));
255     // not found :(
256     if (it == end())
257         return false;
258 
259     (*it)->exposeEvent(event);
260     return true;
261 }
262 
tryButtonPressEvent(XButtonEvent & event)263 bool Container::tryButtonPressEvent(XButtonEvent &event) {
264     if (event.window == window()) {
265         // we don't have a buttonrelease event atm
266         return true;
267     }
268 
269     ItemList::iterator it = find_if(begin(), end(),
270                                     CompareWindow(&FbWindow::window,
271                                                   event.window));
272     // not found :(
273     if (it == end())
274         return false;
275 
276     (*it)->buttonPressEvent(event);
277     return true;
278 }
279 
tryButtonReleaseEvent(XButtonEvent & event)280 bool Container::tryButtonReleaseEvent(XButtonEvent &event) {
281     if (event.window == window()) {
282         // we don't have a buttonrelease event atm
283         return true;
284     }
285 
286     ItemList::iterator it = find_if(begin(), end(),
287                                     CompareWindow(&FbWindow::window,
288                                                   event.window));
289     // not found :(
290     if (it == end())
291         return false;
292 
293     (*it)->buttonReleaseEvent(event);
294     return true;
295 }
296 
repositionItems()297 void Container::repositionItems() {
298     if (empty() || m_update_lock)
299         return;
300 
301     /**
302        NOTE: all calculations here are done in non-rotated space
303      */
304 
305     unsigned int max_width_per_client = maxWidthPerClient();
306     unsigned int borderW = m_item_list.front()->borderWidth();
307     size_t num_items = m_item_list.size();
308 
309     unsigned int total_width;
310     unsigned int cur_width;
311     unsigned int height;
312 
313     // unrotate
314     if (m_orientation == ROT0 || m_orientation == ROT180) {
315         total_width = cur_width = width();
316         height = this->height();
317     } else {
318         total_width = cur_width = this->height();
319         height = width();
320     }
321 
322     // if we have a max total size, then we must also resize ourself
323     // within that bound
324     Alignment align = alignment();
325     if (m_max_total_size && align != RELATIVE) {
326         total_width = (max_width_per_client + borderW) * num_items - borderW;
327         if (total_width > m_max_total_size) {
328             total_width = m_max_total_size;
329             if (m_max_total_size > ((num_items - 1)*borderW)) { // don't go negative with unsigned nums
330                 max_width_per_client = ( m_max_total_size - (num_items - 1)*borderW ) / num_items;
331             } else
332                 max_width_per_client = 1;
333         }
334         if (m_auto_resize && total_width != cur_width) {
335             // calling Container::resize here risks infinite loops
336             unsigned int neww = total_width, newh = height;
337             translateSize(m_orientation, neww, newh);
338             if (!(align == LEFT && (m_orientation == ROT0 ||
339                                      m_orientation == ROT90)) &&
340                 !(align == RIGHT && (m_orientation == ROT180 ||
341                                      m_orientation == ROT270))) {
342                 int deltax = 0;
343                 int deltay = 0;
344                 if (m_orientation == ROT0 || m_orientation == ROT180)
345                     deltax = - (total_width - cur_width);
346                 else
347                     deltay = - (total_width - cur_width);
348                 // TODO: rounding errors could accumulate in this process
349                 if (align == CENTER) {
350                     deltax = deltax/2;
351                     deltay = deltay/2;
352                 }
353 
354                 FbWindow::moveResize(x() + deltax, y() + deltay, neww, newh);
355             } else {
356                 FbWindow::resize(neww, newh);
357             }
358         }
359     }
360 
361 
362     ItemList::iterator it = begin();
363     const ItemList::iterator it_end = end();
364 
365     int rounding_error = 0;
366 
367     if (align == RELATIVE || total_width == m_max_total_size) {
368         rounding_error = total_width - ((max_width_per_client + borderW)* num_items - borderW);
369     }
370 
371     int next_x = -borderW; // zero so the border of the first shows
372     int extra = 0;
373     int direction = 1;
374     if (align == RIGHT) {
375         direction = -1;
376         next_x = total_width - max_width_per_client - borderW;
377     }
378 
379     int tmpx, tmpy;
380     unsigned int tmpw, tmph;
381     for (; it != it_end; ++it, next_x += direction*(max_width_per_client + borderW + extra)) {
382         // we only need to do error stuff with alignment RELATIVE
383         // OR with max_total_size triggered
384         if (rounding_error) {
385             --rounding_error;
386             extra = 1;
387             //counter for different direction
388             if (align == RIGHT)
389                 --next_x;
390         } else {
391             if (extra && align == RIGHT) // last extra
392                 ++next_x;
393             extra = 0;
394         }
395         // rotate the x and y coords
396         tmpx = next_x;
397         tmpy = -borderW;
398         tmpw = max_width_per_client + extra;
399         tmph = height;
400 
401         translateCoords(m_orientation, tmpx, tmpy, total_width, height);
402         translatePosition(m_orientation, tmpx, tmpy, tmpw, tmph, borderW);
403         translateSize(m_orientation, tmpw, tmph);
404 
405         // resize each clients including border in size
406         (*it)->moveResize(tmpx, tmpy,
407                           tmpw, tmph);
408 
409         // moveresize does a clear
410     }
411 
412 }
413 
414 
maxWidthPerClient() const415 unsigned int Container::maxWidthPerClient() const {
416     switch (alignment()) {
417     case RIGHT:
418     case CENTER:
419     case LEFT:
420         return m_max_size_per_client;
421         break;
422     case RELATIVE:
423         if (size() == 0)
424             return width();
425         else {
426             unsigned int borderW = m_item_list.front()->borderWidth();
427             // there're count-1 borders to fit in with the windows
428             // -> 1 per window plus end
429             unsigned int w = width(), h = height();
430             translateSize(m_orientation, w, h);
431             if (w < (size()-1)*borderW)
432                 return 1;
433             else
434                 return (w - (size() - 1) * borderW) / size();
435         }
436         break;
437     }
438 
439     // this will never happen anyway
440     return 1;
441 }
442 
for_each(std::mem_fun_t<void,FbWindow> function)443 void Container::for_each(std::mem_fun_t<void, FbWindow> function) {
444     std::for_each(begin(), end(), function);
445 }
446 
setAlpha(int alpha)447 void Container::setAlpha(int alpha) {
448     FbWindow::setAlpha(alpha);
449     STLUtil::forAll(m_item_list, std::bind2nd(std::mem_fun(&Button::setAlpha), alpha));
450 }
451 
parentMoved()452 void Container::parentMoved() {
453     FbWindow::parentMoved();
454     STLUtil::forAll(m_item_list, std::mem_fun(&Button::parentMoved));
455 }
456 
invalidateBackground()457 void Container::invalidateBackground() {
458     FbWindow::invalidateBackground();
459     STLUtil::forAll(m_item_list, std::mem_fun(&Button::invalidateBackground));
460 }
461 
clear()462 void Container::clear() {
463     STLUtil::forAll(m_item_list, std::mem_fun(&Button::clear));
464 }
465 
setOrientation(Orientation orient)466 void Container::setOrientation(Orientation orient) {
467     if (m_orientation == orient)
468         return;
469 
470     FbWindow::invalidateBackground();
471     STLUtil::forAll(m_item_list, std::bind2nd(std::mem_fun(&Button::setOrientation), orient));
472 
473     if (((m_orientation == ROT0 || m_orientation == ROT180) &&
474         (orient == ROT90 || orient == ROT270)) ||
475         ((m_orientation == ROT90 || m_orientation == ROT270) &&
476         (orient == ROT0 || orient == ROT180))) {
477         // flip width and height
478         m_orientation = orient;
479         resize(height(), width());
480     } else {
481         m_orientation = orient;
482         repositionItems();
483     }
484 
485 }
486 
487 } // end namespace FbTk
488