More-or-less finished reasonably cool wxToolBar class with tooltips.
[wxWidgets.git] / src / motif / toolbar.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: toolbar.cpp
3 // Purpose: wxToolBar
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #ifdef __GNUG__
13 #pragma implementation "toolbar.h"
14 #endif
15
16 #include "wx/wx.h"
17 #include "wx/app.h"
18 #include "wx/timer.h"
19 #include "wx/motif/toolbar.h"
20
21 #include <Xm/Xm.h>
22 #include <Xm/PushBG.h>
23 #include <Xm/PushB.h>
24 #include <Xm/Label.h>
25 #include <Xm/ToggleB.h>
26 #include <Xm/ToggleBG.h>
27 #include <Xm/Form.h>
28
29 #include "wx/motif/private.h"
30
31 #if !USE_SHARED_LIBRARY
32 IMPLEMENT_DYNAMIC_CLASS(wxToolBar, wxToolBarBase)
33
34 BEGIN_EVENT_TABLE(wxToolBar, wxToolBarBase)
35 END_EVENT_TABLE()
36 #endif
37
38 static void wxToolButtonCallback (Widget w, XtPointer clientData,
39 XtPointer ptr);
40 static void wxToolButtonPopupCallback (Widget w, XtPointer client_data,
41 XEvent *event, Boolean *continue_to_dispatch);
42
43 wxBitmap wxCreateMaskedBitmap(wxBitmap& bitmap, wxColour& colour);
44
45 class wxToolBarTimer: public wxTimer
46 {
47 public:
48 wxToolBarTimer() { }
49 virtual void Notify();
50
51 static Widget help_popup;
52 static Widget buttonWidget;
53 static wxString helpString;
54 };
55
56 static wxToolBarTimer* wxTheToolBarTimer = (wxToolBarTimer*) NULL;
57
58 Widget wxToolBarTimer::help_popup = (Widget) 0;
59 Widget wxToolBarTimer::buttonWidget = (Widget) 0;
60 wxString wxToolBarTimer::helpString = "";
61
62 wxToolBar::wxToolBar():
63 m_widgets(wxKEY_INTEGER)
64 {
65 m_maxWidth = -1;
66 m_maxHeight = -1;
67 m_defaultWidth = 24;
68 m_defaultHeight = 22;
69 }
70
71 bool wxToolBar::Create(wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size,
72 long style, const wxString& name)
73 {
74 m_maxWidth = -1;
75 m_maxHeight = -1;
76
77 m_defaultWidth = 24;
78 m_defaultHeight = 22;
79 SetName(name);
80 m_backgroundColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DFACE);
81 m_foregroundColour = parent->GetForegroundColour();
82 m_windowStyle = style;
83
84 SetParent(parent);
85
86 if (parent) parent->AddChild(this);
87
88 Widget parentWidget = (Widget) parent->GetClientWidget();
89
90 Widget toolbar = XtVaCreateManagedWidget("toolbar",
91 xmFormWidgetClass, parentWidget,
92 XmNtraversalOn, False,
93 XmNhorizontalSpacing, 0,
94 XmNverticalSpacing, 0,
95 XmNleftOffset, 0,
96 XmNrightOffset, 0,
97 XmNmarginWidth, 0,
98 XmNmarginHeight, 0,
99 NULL);
100
101 m_mainWidget = (WXWidget) toolbar;
102
103 SetCanAddEventHandler(TRUE);
104 AttachWidget (parent, m_mainWidget, (WXWidget) NULL, pos.x, pos.y, size.x, size.y);
105
106 SetFont(* parent->GetFont());
107 ChangeBackgroundColour();
108
109 return TRUE;
110 }
111
112 wxToolBar::~wxToolBar()
113 {
114 delete wxTheToolBarTimer;
115 wxTheToolBarTimer = NULL;
116 ClearTools();
117 DestroyPixmaps();
118 }
119
120 bool wxToolBar::CreateTools()
121 {
122 if (m_tools.Number() == 0)
123 return FALSE;
124
125 // Separator spacing
126 const int separatorSize = GetToolSeparation(); // 8;
127
128 int currentSpacing = 0;
129
130 m_widgets.Clear();
131 Widget prevButton = (Widget) 0;
132 wxNode* node = m_tools.First();
133 while (node)
134 {
135 wxToolBarTool *tool = (wxToolBarTool *)node->Data();
136
137 if (tool->m_toolStyle == wxTOOL_STYLE_SEPARATOR)
138 currentSpacing = separatorSize;
139 else if (tool->m_bitmap1.Ok())
140 {
141 Widget button = (Widget) 0;
142
143 if (tool->m_isToggle)
144 {
145 button = XtVaCreateManagedWidget("toggleButton",
146 xmToggleButtonWidgetClass, (Widget) m_mainWidget,
147 XmNleftAttachment, (prevButton == (Widget) 0) ? XmATTACH_FORM : XmATTACH_WIDGET,
148 XmNleftWidget, (prevButton == (Widget) 0) ? NULL : prevButton,
149 XmNleftOffset, currentSpacing,
150 XmNtopAttachment, XmATTACH_FORM,
151 // XmNpushButtonEnabled, True,
152 XmNmultiClick, XmMULTICLICK_KEEP,
153 XmNlabelType, XmPIXMAP,
154 NULL);
155 XtAddCallback ((Widget) button, XmNvalueChangedCallback, (XtCallbackProc) wxToolButtonCallback,
156 (XtPointer) this);
157 }
158 else
159 {
160 button = XtVaCreateManagedWidget("button",
161 xmPushButtonWidgetClass, (Widget) m_mainWidget,
162 XmNleftAttachment, (prevButton == (Widget) 0) ? XmATTACH_FORM : XmATTACH_WIDGET,
163 XmNleftWidget, (prevButton == (Widget) 0) ? NULL : prevButton,
164 XmNleftOffset, currentSpacing,
165 XmNtopAttachment, XmATTACH_FORM,
166 XmNpushButtonEnabled, True,
167 XmNmultiClick, XmMULTICLICK_KEEP,
168 XmNlabelType, XmPIXMAP,
169 NULL);
170 XtAddCallback (button,
171 XmNactivateCallback, (XtCallbackProc) wxToolButtonCallback,
172 (XtPointer) this);
173 }
174
175 // For each button, if there is a mask, we must create
176 // a new wxBitmap that has the correct background colour
177 // for the button. Otherwise the background will just be
178 // e.g. black if a transparent XPM has been loaded.
179 wxBitmap originalBitmap = tool->m_bitmap1;
180
181 if (tool->m_bitmap1.GetMask())
182 {
183 int backgroundPixel;
184 XtVaGetValues(button, XmNbackground, &backgroundPixel,
185 NULL);
186
187
188 wxColour col;
189 col.SetPixel(backgroundPixel);
190
191 wxBitmap newBitmap = wxCreateMaskedBitmap(tool->m_bitmap1, col);
192
193 tool->m_bitmap1 = newBitmap;
194 }
195
196 // Create a selected/toggled bitmap. If there isn't a m_bitmap2,
197 // we need to create it (with a darker, selected background)
198 int backgroundPixel;
199 if (tool->m_isToggle)
200 XtVaGetValues(button, XmNselectColor, &backgroundPixel,
201 NULL);
202 else
203 XtVaGetValues(button, XmNarmColor, &backgroundPixel,
204 NULL);
205
206 wxColour col;
207 col.SetPixel(backgroundPixel);
208
209 if (tool->m_bitmap2.Ok() && tool->m_bitmap2.GetMask())
210 {
211 // Use what's there
212 wxBitmap newBitmap = wxCreateMaskedBitmap(tool->m_bitmap2, col);
213 tool->m_bitmap2 = newBitmap;
214 }
215 else
216 {
217 // Use unselected bitmap
218 if (originalBitmap.GetMask())
219 {
220 wxBitmap newBitmap = wxCreateMaskedBitmap(originalBitmap, col);
221 tool->m_bitmap2 = newBitmap;
222 }
223 else
224 tool->m_bitmap2 = tool->m_bitmap1;
225 }
226
227 Pixmap pixmap = (Pixmap) tool->m_bitmap1.GetPixmap();
228 Pixmap insensPixmap = (Pixmap) tool->m_bitmap1.GetInsensPixmap();
229
230 if (tool->m_isToggle)
231 {
232 // Toggle button
233 Pixmap pixmap2 = (Pixmap) 0;
234 Pixmap insensPixmap2 = (Pixmap) 0;
235
236 // If there's a bitmap for the toggled state, use it,
237 // otherwise generate one.
238 if (tool->m_bitmap2.Ok())
239 {
240 pixmap2 = (Pixmap) tool->m_bitmap2.GetPixmap();
241 insensPixmap2 = (Pixmap) tool->m_bitmap2.GetInsensPixmap();
242 }
243 else
244 {
245 pixmap2 = (Pixmap) tool->m_bitmap1.GetArmPixmap(button);
246 insensPixmap2 = XCreateInsensitivePixmap((Display*) wxGetDisplay(), pixmap2);
247 m_pixmaps.Append((wxObject*) insensPixmap2); // Store for later deletion
248 }
249 XtVaSetValues (button,
250 XmNindicatorOn, False,
251 XmNshadowThickness, 2,
252 // XmNborderWidth, 0,
253 // XmNspacing, 0,
254 XmNmarginWidth, 0,
255 XmNmarginHeight, 0,
256 XmNfillOnSelect, True,
257 XmNlabelPixmap, pixmap,
258 XmNselectPixmap, pixmap2,
259 XmNlabelInsensitivePixmap, insensPixmap,
260 XmNselectInsensitivePixmap, insensPixmap2,
261 XmNlabelType, XmPIXMAP,
262 NULL);
263 }
264 else
265 {
266 Pixmap pixmap2 = (Pixmap) 0;
267
268 // If there's a bitmap for the armed state, use it,
269 // otherwise generate one.
270 if (tool->m_bitmap2.Ok())
271 {
272 pixmap2 = (Pixmap) tool->m_bitmap2.GetPixmap();
273 }
274 else
275 {
276 pixmap2 = (Pixmap) tool->m_bitmap1.GetArmPixmap(button);
277
278 }
279 // Normal button
280 XtVaSetValues(button,
281 XmNlabelPixmap, pixmap,
282 XmNlabelInsensitivePixmap, insensPixmap,
283 XmNarmPixmap, pixmap2,
284 NULL);
285 }
286
287 XtAddEventHandler (button, EnterWindowMask | LeaveWindowMask,
288 False, wxToolButtonPopupCallback, (XtPointer) this);
289 m_widgets.Append(tool->m_index, (wxObject*) button);
290
291 prevButton = button;
292 currentSpacing = 0;
293 }
294 node = node->Next();
295 }
296
297 return TRUE;
298 }
299
300 void wxToolBar::SetToolBitmapSize(const wxSize& size)
301 {
302 m_defaultWidth = size.x; m_defaultHeight = size.y;
303 // TODO
304 }
305
306 wxSize wxToolBar::GetMaxSize() const
307 {
308 // TODO
309 return wxSize(0, 0);
310 }
311
312 // The button size is bigger than the bitmap size
313 wxSize wxToolBar::GetToolSize() const
314 {
315 // TODO
316 return wxSize(m_defaultWidth + 8, m_defaultHeight + 7);
317 }
318
319 void wxToolBar::EnableTool(int toolIndex, bool enable)
320 {
321 wxNode *node = m_tools.Find((long)toolIndex);
322 if (node)
323 {
324 wxToolBarTool *tool = (wxToolBarTool *)node->Data();
325 tool->m_enabled = enable;
326
327 WXWidget widget = FindWidgetForIndex(tool->m_index);
328 if (widget == (WXWidget) 0)
329 return;
330
331 XtSetSensitive((Widget) widget, (Boolean) enable);
332 }
333 }
334
335 void wxToolBar::ToggleTool(int toolIndex, bool toggle)
336 {
337 wxNode *node = m_tools.Find((long)toolIndex);
338 if (node)
339 {
340 wxToolBarTool *tool = (wxToolBarTool *)node->Data();
341 if (tool->m_isToggle)
342 {
343 tool->m_toggleState = toggle;
344
345 WXWidget widget = FindWidgetForIndex(tool->m_index);
346 if (widget == (WXWidget) 0)
347 return;
348
349 XmToggleButtonSetState((Widget) widget, (Boolean) toggle, False);
350 }
351 }
352 }
353
354 void wxToolBar::ClearTools()
355 {
356 wxNode* node = m_widgets.First();
357 while (node)
358 {
359 Widget button = (Widget) node->Data();
360 XtDestroyWidget(button);
361 node = node->Next();
362 }
363 m_widgets.Clear();
364 DestroyPixmaps();
365
366 wxToolBarBase::ClearTools();
367 }
368
369 void wxToolBar::DestroyPixmaps()
370 {
371 wxNode* node = m_pixmaps.First();
372 while (node)
373 {
374 Pixmap pixmap = (Pixmap) node->Data();
375 XmDestroyPixmap (DefaultScreenOfDisplay ((Display*) GetXDisplay()), pixmap);
376 node = node->Next();
377 }
378 m_pixmaps.Clear();
379 }
380
381 // If pushedBitmap is NULL, a reversed version of bitmap is
382 // created and used as the pushed/toggled image.
383 // If toggle is TRUE, the button toggles between the two states.
384
385 wxToolBarTool *wxToolBar::AddTool(int index, const wxBitmap& bitmap, const wxBitmap& pushedBitmap,
386 bool toggle, long xPos, long yPos, wxObject *clientData, const wxString& helpString1, const wxString& helpString2)
387 {
388 wxToolBarTool *tool = new wxToolBarTool(index, bitmap, (wxBitmap *)NULL, toggle, xPos, yPos, helpString1, helpString2);
389 tool->m_clientData = clientData;
390
391 if (xPos > -1)
392 tool->m_x = xPos;
393 else
394 tool->m_x = m_xMargin;
395
396 if (yPos > -1)
397 tool->m_y = yPos;
398 else
399 tool->m_y = m_yMargin;
400
401 tool->SetSize(GetDefaultButtonWidth(), GetDefaultButtonHeight());
402
403 m_tools.Append((long)index, tool);
404 return tool;
405 }
406
407 int wxToolBar::FindIndexForWidget(WXWidget w)
408 {
409 wxNode* node = m_widgets.First();
410 while (node)
411 {
412 WXWidget widget = (WXWidget) node->Data();
413 if (widget == w)
414 return (int) node->key.integer;
415 node = node->Next();
416 }
417 return -1;
418 }
419
420 WXWidget wxToolBar::FindWidgetForIndex(int index)
421 {
422 wxNode* node = m_widgets.Find((long) index);
423 if (!node)
424 return (WXWidget) 0;
425 else
426 return (WXWidget) node->Data();
427 }
428
429 void wxToolButtonCallback (Widget w, XtPointer clientData,
430 XtPointer ptr)
431 {
432 wxToolBar *toolBar = (wxToolBar *) clientData;
433 int index = toolBar->FindIndexForWidget((WXWidget) w);
434
435 if (index != -1)
436 {
437 wxNode *node = toolBar->GetTools().Find((long)index);
438 if (!node)
439 return;
440 wxToolBarTool *tool = (wxToolBarTool *)node->Data();
441 if (tool->m_isToggle)
442 tool->m_toggleState = toolBar->GetToolState(index);
443
444 (void) toolBar->OnLeftClick(index, tool->m_toggleState);
445 }
446
447 }
448
449 // Creates a bitmap with transparent areas drawn in
450 // the given colour.
451 wxBitmap wxCreateMaskedBitmap(wxBitmap& bitmap, wxColour& colour)
452 {
453 wxBitmap newBitmap(bitmap.GetWidth(),
454 bitmap.GetHeight(),
455 bitmap.GetDepth());
456 wxMemoryDC destDC;
457 wxMemoryDC srcDC;
458 srcDC.SelectObject(bitmap);
459 destDC.SelectObject(newBitmap);
460
461 wxBrush brush(colour, wxSOLID);
462 destDC.SetOptimization(FALSE);
463 destDC.SetBackground(brush);
464 destDC.Clear();
465 destDC.Blit(0, 0, bitmap.GetWidth(), bitmap.GetHeight(), & srcDC, 0, 0, wxCOPY, TRUE);
466
467 return newBitmap;
468 }
469
470
471 static void wxToolButtonPopupCallback (Widget w, XtPointer client_data,
472 XEvent *event, Boolean *continue_to_dispatch)
473 {
474 // TODO: retrieve delay before popping up tooltip from wxSystemSettings.
475 int delayMilli = 800;
476 wxToolBar* toolBar = (wxToolBar*) client_data;
477
478 int index = toolBar->FindIndexForWidget((WXWidget) w);
479
480 if (index != -1)
481 {
482 wxNode *node = toolBar->GetTools().Find((long)index);
483 if (!node)
484 return;
485 wxToolBarTool *tool = (wxToolBarTool *)node->Data();
486 wxString str(toolBar->GetToolShortHelp(index));
487 if (str.IsNull() || str == "")
488 return;
489
490 if (!wxTheToolBarTimer)
491 wxTheToolBarTimer = new wxToolBarTimer;
492
493 wxToolBarTimer::buttonWidget = w;
494 wxToolBarTimer::helpString = str;
495
496
497 /************************************************************/
498 /* Popup help label */
499 /************************************************************/
500 if (event->type == EnterNotify)
501 {
502 if (wxToolBarTimer::help_popup != (Widget) 0)
503 {
504 XtDestroyWidget (wxToolBarTimer::help_popup);
505 XtPopdown (wxToolBarTimer::help_popup);
506 }
507 wxToolBarTimer::help_popup = (Widget) 0;
508
509 // One shot
510 wxTheToolBarTimer->Start(delayMilli, TRUE);
511
512 }
513 /************************************************************/
514 /* Popdown help label */
515 /************************************************************/
516 else if (event->type == LeaveNotify)
517 {
518 if (wxTheToolBarTimer)
519 wxTheToolBarTimer->Stop();
520 if (wxToolBarTimer::help_popup != (Widget) 0)
521 {
522 XtDestroyWidget (wxToolBarTimer::help_popup);
523 XtPopdown (wxToolBarTimer::help_popup);
524 }
525 wxToolBarTimer::help_popup = (Widget) 0;
526 }
527 }
528 }
529
530 void wxToolBarTimer::Notify()
531 {
532 Position x, y;
533
534 /************************************************************/
535 /* Create shell without window decorations */
536 /************************************************************/
537 help_popup = XtVaCreatePopupShell ("shell",
538 overrideShellWidgetClass, (Widget) wxTheApp->GetTopLevelWidget(),
539 NULL);
540
541 /************************************************************/
542 /* Get absolute position on display of toolbar button */
543 /************************************************************/
544 XtTranslateCoords (buttonWidget,
545 (Position) 0,
546 (Position) 0,
547 &x, &y);
548
549 // Move the tooltip more or less above the button
550 int yOffset = 20; // TODO: What should be really?
551 y -= yOffset;
552 if (y < yOffset) y = 0;
553
554 /************************************************************/
555 /* Set the position of the help popup */
556 /************************************************************/
557 XtVaSetValues (help_popup,
558 XmNx, (Position) x,
559 XmNy, (Position) y,
560 NULL);
561
562 /************************************************************/
563 /* Create help label */
564 /************************************************************/
565 XmString text = XmStringCreateSimple ((char*) (const char*) helpString);
566 XtVaCreateManagedWidget ("help_label",
567 xmLabelWidgetClass, help_popup,
568 XmNlabelString, text,
569 XtVaTypedArg,
570 XmNforeground, XtRString, "black",
571 strlen("black")+1,
572 XtVaTypedArg,
573 XmNbackground, XtRString, "LightGoldenrod",
574 strlen("LightGoldenrod")+1,
575 NULL);
576 XmStringFree (text);
577
578 /************************************************************/
579 /* Popup help label */
580 /************************************************************/
581 XtPopup (help_popup, XtGrabNone);
582 }
583