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