1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 focus.c for the Openbox window manager
4 Copyright (c) 2003 Ben Jansens
6 This program 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.
11 This program 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.
16 See the COPYING file for a copy of the GNU General Public License.
23 #include "framerender.h"
33 #include "render/render.h"
39 ObClient
*focus_client
, *focus_hilite
;
40 GList
**focus_order
; /* these lists are created when screen_startup
41 sets the number of desktops */
42 ObClient
*focus_cycle_target
;
48 InternalWindow bottom
;
51 RrAppearance
*a_focus_indicator
;
54 static ObIconPopup
*focus_cycle_popup
;
56 static void focus_cycle_destructor(ObClient
*client
, gpointer data
)
58 /* end cycling if the target disappears */
59 if (focus_cycle_target
== client
)
60 focus_cycle(TRUE
, TRUE
, TRUE
, TRUE
, TRUE
);
63 static Window
createWindow(Window parent
, unsigned long mask
,
64 XSetWindowAttributes
*attrib
)
66 return XCreateWindow(ob_display
, parent
, 0, 0, 1, 1, 0,
67 RrDepth(ob_rr_inst
), InputOutput
,
68 RrVisual(ob_rr_inst
), mask
, attrib
);
72 void focus_startup(gboolean reconfig
)
74 focus_cycle_popup
= icon_popup_new(TRUE
);
77 XSetWindowAttributes attr
;
79 client_add_destructor(focus_cycle_destructor
, NULL
);
81 /* start with nothing focused */
82 focus_set_client(NULL
);
84 focus_indicator
.top
.obwin
.type
= Window_Internal
;
85 focus_indicator
.left
.obwin
.type
= Window_Internal
;
86 focus_indicator
.right
.obwin
.type
= Window_Internal
;
87 focus_indicator
.bottom
.obwin
.type
= Window_Internal
;
89 attr
.override_redirect
= True
;
90 attr
.background_pixel
= BlackPixel(ob_display
, ob_screen
);
91 focus_indicator
.top
.win
=
92 createWindow(RootWindow(ob_display
, ob_screen
),
93 CWOverrideRedirect
| CWBackPixel
, &attr
);
94 focus_indicator
.left
.win
=
95 createWindow(RootWindow(ob_display
, ob_screen
),
96 CWOverrideRedirect
| CWBackPixel
, &attr
);
97 focus_indicator
.right
.win
=
98 createWindow(RootWindow(ob_display
, ob_screen
),
99 CWOverrideRedirect
| CWBackPixel
, &attr
);
100 focus_indicator
.bottom
.win
=
101 createWindow(RootWindow(ob_display
, ob_screen
),
102 CWOverrideRedirect
| CWBackPixel
, &attr
);
104 stacking_add(INTERNAL_AS_WINDOW(&focus_indicator
.top
));
105 stacking_add(INTERNAL_AS_WINDOW(&focus_indicator
.left
));
106 stacking_add(INTERNAL_AS_WINDOW(&focus_indicator
.right
));
107 stacking_add(INTERNAL_AS_WINDOW(&focus_indicator
.bottom
));
109 color_white
= RrColorNew(ob_rr_inst
, 0xff, 0xff, 0xff);
111 a_focus_indicator
= RrAppearanceNew(ob_rr_inst
, 4);
112 a_focus_indicator
->surface
.grad
= RR_SURFACE_SOLID
;
113 a_focus_indicator
->surface
.relief
= RR_RELIEF_FLAT
;
114 a_focus_indicator
->surface
.primary
= RrColorNew(ob_rr_inst
,
116 a_focus_indicator
->texture
[0].type
= RR_TEXTURE_LINE_ART
;
117 a_focus_indicator
->texture
[0].data
.lineart
.color
= color_white
;
118 a_focus_indicator
->texture
[1].type
= RR_TEXTURE_LINE_ART
;
119 a_focus_indicator
->texture
[1].data
.lineart
.color
= color_white
;
120 a_focus_indicator
->texture
[2].type
= RR_TEXTURE_LINE_ART
;
121 a_focus_indicator
->texture
[2].data
.lineart
.color
= color_white
;
122 a_focus_indicator
->texture
[3].type
= RR_TEXTURE_LINE_ART
;
123 a_focus_indicator
->texture
[3].data
.lineart
.color
= color_white
;
127 void focus_shutdown(gboolean reconfig
)
131 icon_popup_free(focus_cycle_popup
);
134 client_remove_destructor(focus_cycle_destructor
);
136 for (i
= 0; i
< screen_num_desktops
; ++i
)
137 g_list_free(focus_order
[i
]);
140 /* reset focus to root */
141 XSetInputFocus(ob_display
, PointerRoot
, RevertToNone
, event_lasttime
);
143 RrColorFree(color_white
);
145 RrAppearanceFree(a_focus_indicator
);
147 XDestroyWindow(ob_display
, focus_indicator
.top
.win
);
148 XDestroyWindow(ob_display
, focus_indicator
.left
.win
);
149 XDestroyWindow(ob_display
, focus_indicator
.right
.win
);
150 XDestroyWindow(ob_display
, focus_indicator
.bottom
.win
);
154 static void push_to_top(ObClient
*client
)
158 desktop
= client
->desktop
;
159 if (desktop
== DESKTOP_ALL
) desktop
= screen_desktop
;
160 focus_order
[desktop
] = g_list_remove(focus_order
[desktop
], client
);
161 focus_order
[desktop
] = g_list_prepend(focus_order
[desktop
], client
);
164 void focus_set_client(ObClient
*client
)
170 ob_debug("focus_set_client 0x%lx\n", client
? client
->window
: 0);
173 /* uninstall the old colormap, and install the new one */
174 screen_install_colormap(focus_client
, FALSE
);
175 screen_install_colormap(client
, TRUE
);
177 if (client
== NULL
) {
179 ob_debug("actively focusing NONWINDOW\n");
181 /* when nothing will be focused, send focus to the backup target */
182 XSetInputFocus(ob_display
, screen_support_win
, RevertToNone
,
184 XSync(ob_display
, FALSE
);
187 /* in the middle of cycling..? kill it. */
188 if (focus_cycle_target
)
189 focus_cycle(TRUE
, TRUE
, TRUE
, TRUE
, TRUE
);
192 focus_client
= client
;
194 /* move to the top of the list */
198 /* set the NET_ACTIVE_WINDOW hint, but preserve it on shutdown */
199 if (ob_state() != OB_STATE_EXITING
) {
200 active
= client
? client
->window
: None
;
201 PROP_SET32(RootWindow(ob_display
, ob_screen
),
202 net_active_window
, window
, active
);
206 /* finds the first transient that isn't 'skip' and ensure's that client_normal
208 static ObClient
*find_transient_recursive(ObClient
*c
, ObClient
*top
, ObClient
*skip
)
213 for (it
= c
->transients
; it
; it
= it
->next
) {
214 if (it
->data
== top
) return NULL
;
215 ret
= find_transient_recursive(it
->data
, top
, skip
);
216 if (ret
&& ret
!= skip
&& client_normal(ret
)) return ret
;
217 if (it
->data
!= skip
&& client_normal(it
->data
)) return it
->data
;
222 static ObClient
* focus_fallback_transient(ObClient
*top
, ObClient
*old
)
224 ObClient
*target
= find_transient_recursive(top
, top
, old
);
226 /* make sure client_normal is true always */
227 if (!client_normal(top
))
229 target
= top
; /* no transient, keep the top */
231 if (client_can_focus(target
))
237 ObClient
* focus_fallback_target(ObFocusFallbackType type
)
240 ObClient
*old
= NULL
;
241 ObClient
*target
= NULL
;
245 if (type
== OB_FOCUS_FALLBACK_UNFOCUSING
&& old
) {
246 if (old
->transient_for
) {
247 gboolean trans
= FALSE
;
249 if (!config_focus_follow
)
252 if ((target
= client_under_pointer()) &&
253 client_search_transient
254 (client_search_top_transient(target
), old
))
260 /* try for transient relations */
262 if (old
->transient_for
== OB_TRAN_GROUP
) {
263 for (it
= focus_order
[screen_desktop
]; it
; it
= it
->next
) {
266 for (sit
= old
->group
->members
; sit
; sit
= sit
->next
)
267 if (sit
->data
== it
->data
)
269 focus_fallback_transient(sit
->data
, old
)))
274 focus_fallback_transient(old
->transient_for
, old
)))
281 if (config_focus_follow
) {
282 if ((target
= client_under_pointer()))
283 if (client_normal(target
) && client_can_focus(target
))
288 /* try for group relations */
292 for (it
= focus_order
[screen_desktop
]; it
!= NULL
; it
= it
->next
)
293 for (sit
= old
->group
->members
; sit
; sit
= sit
->next
)
294 if (sit
->data
== it
->data
)
295 if (sit
->data
!= old
&& client_normal(sit
->data
))
296 if (client_can_focus(sit
->data
))
301 for (it
= focus_order
[screen_desktop
]; it
!= NULL
; it
= it
->next
)
302 if (type
!= OB_FOCUS_FALLBACK_UNFOCUSING
|| it
->data
!= old
)
303 if (client_normal(it
->data
) && client_can_focus(it
->data
))
309 void focus_fallback(ObFocusFallbackType type
)
313 /* unfocus any focused clients.. they can be focused by Pointer events
314 and such, and then when I try focus them, I won't get a FocusIn event
317 focus_set_client(NULL
);
319 if ((new = focus_fallback_target(type
)))
323 static void popup_cycle(ObClient
*c
, gboolean show
)
326 icon_popup_hide(focus_cycle_popup
);
332 a
= screen_physical_area_monitor(0);
333 icon_popup_position(focus_cycle_popup
, CenterGravity
,
334 a
->x
+ a
->width
/ 2, a
->y
+ a
->height
/ 2);
335 /* icon_popup_size(focus_cycle_popup, a->height/2, a->height/16);
336 icon_popup_show(focus_cycle_popup, c->title,
337 client_icon(c, a->height/16, a->height/16));
339 /* XXX the size and the font extents need to be related on some level
341 icon_popup_size(focus_cycle_popup
, POPUP_WIDTH
, POPUP_HEIGHT
);
343 /* use the transient's parent's title/icon */
344 while (p
->transient_for
&& p
->transient_for
!= OB_TRAN_GROUP
)
345 p
= p
->transient_for
;
350 title
= g_strconcat((c
->iconic
? c
->icon_title
: c
->title
),
352 (p
->iconic
? p
->icon_title
: p
->title
),
355 icon_popup_show(focus_cycle_popup
,
357 (c
->iconic
? c
->icon_title
: c
->title
)),
358 client_icon(p
, 48, 48));
363 void focus_cycle_draw_indicator()
365 if (!focus_cycle_target
) {
366 XUnmapWindow(ob_display
, focus_indicator
.top
.win
);
367 XUnmapWindow(ob_display
, focus_indicator
.left
.win
);
368 XUnmapWindow(ob_display
, focus_indicator
.right
.win
);
369 XUnmapWindow(ob_display
, focus_indicator
.bottom
.win
);
372 if (focus_cycle_target)
373 frame_adjust_focus(focus_cycle_target->frame, FALSE);
374 frame_adjust_focus(focus_cycle_target->frame, TRUE);
379 wt
= wl
= wr
= wb
= MAX(3,
380 ob_rr_theme
->handle_height
+
381 ob_rr_theme
->bwidth
* 2);
383 x
= focus_cycle_target
->frame
->area
.x
;
384 y
= focus_cycle_target
->frame
->area
.y
;
385 w
= focus_cycle_target
->frame
->area
.width
;
388 XMoveResizeWindow(ob_display
, focus_indicator
.top
.win
,
390 a_focus_indicator
->texture
[0].data
.lineart
.x1
= 0;
391 a_focus_indicator
->texture
[0].data
.lineart
.y1
= h
-1;
392 a_focus_indicator
->texture
[0].data
.lineart
.x2
= 0;
393 a_focus_indicator
->texture
[0].data
.lineart
.y2
= 0;
394 a_focus_indicator
->texture
[1].data
.lineart
.x1
= 0;
395 a_focus_indicator
->texture
[1].data
.lineart
.y1
= 0;
396 a_focus_indicator
->texture
[1].data
.lineart
.x2
= w
-1;
397 a_focus_indicator
->texture
[1].data
.lineart
.y2
= 0;
398 a_focus_indicator
->texture
[2].data
.lineart
.x1
= w
-1;
399 a_focus_indicator
->texture
[2].data
.lineart
.y1
= 0;
400 a_focus_indicator
->texture
[2].data
.lineart
.x2
= w
-1;
401 a_focus_indicator
->texture
[2].data
.lineart
.y2
= h
-1;
402 a_focus_indicator
->texture
[3].data
.lineart
.x1
= (wl
-1);
403 a_focus_indicator
->texture
[3].data
.lineart
.y1
= h
-1;
404 a_focus_indicator
->texture
[3].data
.lineart
.x2
= w
- wr
;
405 a_focus_indicator
->texture
[3].data
.lineart
.y2
= h
-1;
406 RrPaint(a_focus_indicator
, focus_indicator
.top
.win
,
409 x
= focus_cycle_target
->frame
->area
.x
;
410 y
= focus_cycle_target
->frame
->area
.y
;
412 h
= focus_cycle_target
->frame
->area
.height
;
414 XMoveResizeWindow(ob_display
, focus_indicator
.left
.win
,
416 a_focus_indicator
->texture
[0].data
.lineart
.x1
= w
-1;
417 a_focus_indicator
->texture
[0].data
.lineart
.y1
= 0;
418 a_focus_indicator
->texture
[0].data
.lineart
.x2
= 0;
419 a_focus_indicator
->texture
[0].data
.lineart
.y2
= 0;
420 a_focus_indicator
->texture
[1].data
.lineart
.x1
= 0;
421 a_focus_indicator
->texture
[1].data
.lineart
.y1
= 0;
422 a_focus_indicator
->texture
[1].data
.lineart
.x2
= 0;
423 a_focus_indicator
->texture
[1].data
.lineart
.y2
= h
-1;
424 a_focus_indicator
->texture
[2].data
.lineart
.x1
= 0;
425 a_focus_indicator
->texture
[2].data
.lineart
.y1
= h
-1;
426 a_focus_indicator
->texture
[2].data
.lineart
.x2
= w
-1;
427 a_focus_indicator
->texture
[2].data
.lineart
.y2
= h
-1;
428 a_focus_indicator
->texture
[3].data
.lineart
.x1
= w
-1;
429 a_focus_indicator
->texture
[3].data
.lineart
.y1
= wt
-1;
430 a_focus_indicator
->texture
[3].data
.lineart
.x2
= w
-1;
431 a_focus_indicator
->texture
[3].data
.lineart
.y2
= h
- wb
;
432 RrPaint(a_focus_indicator
, focus_indicator
.left
.win
,
435 x
= focus_cycle_target
->frame
->area
.x
+
436 focus_cycle_target
->frame
->area
.width
- wr
;
437 y
= focus_cycle_target
->frame
->area
.y
;
439 h
= focus_cycle_target
->frame
->area
.height
;
441 XMoveResizeWindow(ob_display
, focus_indicator
.right
.win
,
443 a_focus_indicator
->texture
[0].data
.lineart
.x1
= 0;
444 a_focus_indicator
->texture
[0].data
.lineart
.y1
= 0;
445 a_focus_indicator
->texture
[0].data
.lineart
.x2
= w
-1;
446 a_focus_indicator
->texture
[0].data
.lineart
.y2
= 0;
447 a_focus_indicator
->texture
[1].data
.lineart
.x1
= w
-1;
448 a_focus_indicator
->texture
[1].data
.lineart
.y1
= 0;
449 a_focus_indicator
->texture
[1].data
.lineart
.x2
= w
-1;
450 a_focus_indicator
->texture
[1].data
.lineart
.y2
= h
-1;
451 a_focus_indicator
->texture
[2].data
.lineart
.x1
= w
-1;
452 a_focus_indicator
->texture
[2].data
.lineart
.y1
= h
-1;
453 a_focus_indicator
->texture
[2].data
.lineart
.x2
= 0;
454 a_focus_indicator
->texture
[2].data
.lineart
.y2
= h
-1;
455 a_focus_indicator
->texture
[3].data
.lineart
.x1
= 0;
456 a_focus_indicator
->texture
[3].data
.lineart
.y1
= wt
-1;
457 a_focus_indicator
->texture
[3].data
.lineart
.x2
= 0;
458 a_focus_indicator
->texture
[3].data
.lineart
.y2
= h
- wb
;
459 RrPaint(a_focus_indicator
, focus_indicator
.right
.win
,
462 x
= focus_cycle_target
->frame
->area
.x
;
463 y
= focus_cycle_target
->frame
->area
.y
+
464 focus_cycle_target
->frame
->area
.height
- wb
;
465 w
= focus_cycle_target
->frame
->area
.width
;
468 XMoveResizeWindow(ob_display
, focus_indicator
.bottom
.win
,
470 a_focus_indicator
->texture
[0].data
.lineart
.x1
= 0;
471 a_focus_indicator
->texture
[0].data
.lineart
.y1
= 0;
472 a_focus_indicator
->texture
[0].data
.lineart
.x2
= 0;
473 a_focus_indicator
->texture
[0].data
.lineart
.y2
= h
-1;
474 a_focus_indicator
->texture
[1].data
.lineart
.x1
= 0;
475 a_focus_indicator
->texture
[1].data
.lineart
.y1
= h
-1;
476 a_focus_indicator
->texture
[1].data
.lineart
.x2
= w
-1;
477 a_focus_indicator
->texture
[1].data
.lineart
.y2
= h
-1;
478 a_focus_indicator
->texture
[2].data
.lineart
.x1
= w
-1;
479 a_focus_indicator
->texture
[2].data
.lineart
.y1
= h
-1;
480 a_focus_indicator
->texture
[2].data
.lineart
.x2
= w
-1;
481 a_focus_indicator
->texture
[2].data
.lineart
.y2
= 0;
482 a_focus_indicator
->texture
[3].data
.lineart
.x1
= wl
-1;
483 a_focus_indicator
->texture
[3].data
.lineart
.y1
= 0;
484 a_focus_indicator
->texture
[3].data
.lineart
.x2
= w
- wr
;
485 a_focus_indicator
->texture
[3].data
.lineart
.y2
= 0;
486 RrPaint(a_focus_indicator
, focus_indicator
.bottom
.win
,
489 XMapWindow(ob_display
, focus_indicator
.top
.win
);
490 XMapWindow(ob_display
, focus_indicator
.left
.win
);
491 XMapWindow(ob_display
, focus_indicator
.right
.win
);
492 XMapWindow(ob_display
, focus_indicator
.bottom
.win
);
496 static gboolean
valid_focus_target(ObClient
*ft
)
498 /* we don't use client_can_focus here, because that doesn't let you
499 focus an iconic window, but we want to be able to, so we just check
500 if the focus flags on the window allow it, and its on the current
502 return ((ft
->type
== OB_CLIENT_TYPE_NORMAL
||
503 ft
->type
== OB_CLIENT_TYPE_DIALOG
||
504 (!client_has_group_siblings(ft
) &&
505 (ft
->type
== OB_CLIENT_TYPE_TOOLBAR
||
506 ft
->type
== OB_CLIENT_TYPE_MENU
||
507 ft
->type
== OB_CLIENT_TYPE_UTILITY
))) &&
509 ((ft
->can_focus
|| ft
->focus_notify
) &&
511 (ft
->desktop
== screen_desktop
|| ft
->desktop
== DESKTOP_ALL
)));
514 void focus_cycle(gboolean forward
, gboolean linear
,
515 gboolean dialog
, gboolean done
, gboolean cancel
)
517 static ObClient
*first
= NULL
;
518 static ObClient
*t
= NULL
;
519 static GList
*order
= NULL
;
520 GList
*it
, *start
, *list
;
524 focus_cycle_target
= NULL
;
529 if (!focus_order
[screen_desktop
])
532 if (!first
) first
= focus_client
;
533 if (!focus_cycle_target
) focus_cycle_target
= focus_client
;
535 if (linear
) list
= client_list
;
536 else list
= focus_order
[screen_desktop
];
538 start
= it
= g_list_find(list
, focus_cycle_target
);
539 if (!start
) /* switched desktops or something? */
540 start
= it
= forward
? g_list_last(list
) : g_list_first(list
);
541 if (!start
) goto done_cycle
;
546 if (it
== NULL
) it
= g_list_first(list
);
549 if (it
== NULL
) it
= g_list_last(list
);
552 if (valid_focus_target(ft
)) {
553 if (ft
!= focus_cycle_target
) { /* prevents flicker */
554 focus_cycle_target
= ft
;
555 focus_cycle_draw_indicator();
557 popup_cycle(ft
, dialog
);
560 } while (it
!= start
);
563 if (done
&& focus_cycle_target
)
564 client_activate(focus_cycle_target
, FALSE
);
568 focus_cycle_target
= NULL
;
572 focus_cycle_draw_indicator();
573 popup_cycle(ft
, FALSE
);
578 void focus_directional_cycle(ObDirection dir
,
579 gboolean dialog
, gboolean done
, gboolean cancel
)
581 static ObClient
*first
= NULL
;
585 focus_cycle_target
= NULL
;
590 if (!focus_order
[screen_desktop
])
593 if (!first
) first
= focus_client
;
594 if (!focus_cycle_target
) focus_cycle_target
= focus_client
;
596 if (focus_cycle_target
)
597 ft
= client_find_directional(focus_cycle_target
, dir
);
601 for (it
= focus_order
[screen_desktop
]; it
; it
= g_list_next(it
))
602 if (valid_focus_target(it
->data
))
607 if (ft
!= focus_cycle_target
) {/* prevents flicker */
608 focus_cycle_target
= ft
;
609 focus_cycle_draw_indicator();
612 if (focus_cycle_target
) {
613 popup_cycle(focus_cycle_target
, dialog
);
620 if (done
&& focus_cycle_target
)
621 client_activate(focus_cycle_target
, FALSE
);
624 focus_cycle_target
= NULL
;
626 focus_cycle_draw_indicator();
627 popup_cycle(ft
, FALSE
);
632 void focus_order_add_new(ObClient
*c
)
637 focus_order_to_top(c
);
640 if (d
== DESKTOP_ALL
) {
641 for (i
= 0; i
< screen_num_desktops
; ++i
) {
642 if (focus_order
[i
] && ((ObClient
*)focus_order
[i
]->data
)->iconic
)
643 focus_order
[i
] = g_list_insert(focus_order
[i
], c
, 0);
645 focus_order
[i
] = g_list_insert(focus_order
[i
], c
, 1);
648 if (focus_order
[d
] && ((ObClient
*)focus_order
[d
]->data
)->iconic
)
649 focus_order
[d
] = g_list_insert(focus_order
[d
], c
, 0);
651 focus_order
[d
] = g_list_insert(focus_order
[d
], c
, 1);
655 void focus_order_remove(ObClient
*c
)
660 if (d
== DESKTOP_ALL
) {
661 for (i
= 0; i
< screen_num_desktops
; ++i
)
662 focus_order
[i
] = g_list_remove(focus_order
[i
], c
);
664 focus_order
[d
] = g_list_remove(focus_order
[d
], c
);
667 static void to_top(ObClient
*c
, guint d
)
669 focus_order
[d
] = g_list_remove(focus_order
[d
], c
);
671 focus_order
[d
] = g_list_prepend(focus_order
[d
], c
);
675 /* insert before first iconic window */
676 for (it
= focus_order
[d
];
677 it
&& !((ObClient
*)it
->data
)->iconic
; it
= it
->next
);
678 focus_order
[d
] = g_list_insert_before(focus_order
[d
], it
, c
);
682 void focus_order_to_top(ObClient
*c
)
687 if (d
== DESKTOP_ALL
) {
688 for (i
= 0; i
< screen_num_desktops
; ++i
)
694 static void to_bottom(ObClient
*c
, guint d
)
696 focus_order
[d
] = g_list_remove(focus_order
[d
], c
);
698 focus_order
[d
] = g_list_append(focus_order
[d
], c
);
702 /* insert before first iconic window */
703 for (it
= focus_order
[d
];
704 it
&& !((ObClient
*)it
->data
)->iconic
; it
= it
->next
);
705 g_list_insert_before(focus_order
[d
], it
, c
);
709 void focus_order_to_bottom(ObClient
*c
)
714 if (d
== DESKTOP_ALL
) {
715 for (i
= 0; i
< screen_num_desktops
; ++i
)