Reviewed by Richard.
[WebKit-https.git] / WebCore / kwq / KWQWidget.mm
1 /*
2  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "KWQWidget.h"
27
28 #import "KWQExceptions.h"
29 #import "KWQFoundationExtras.h"
30 #import "KWQKHTMLPart.h"
31 #import "KWQLogging.h"
32 #import "KWQView.h"
33 #import "KWQWindowWidget.h"
34 #import "WebCoreBridge.h"
35 #import "WebCoreFrameView.h"
36 #import "WebCoreView.h"
37 #import "khtmlview.h"
38 #import "render_canvas.h"
39 #import "render_replaced.h"
40 #import "render_style.h"
41
42 using khtml::RenderWidget;
43
44 /*
45     A QWidget roughly corresponds to an NSView.  In Qt a QFrame and QMainWindow inherit
46     from a QWidget.  In Cocoa a NSWindow does not inherit from NSView.  We
47     emulate most QWidgets using NSViews.
48 */
49
50 class KWQWidgetPrivate
51 {
52 public:
53     QStyle *style;
54     QFont font;
55     QPalette pal;
56     NSView *view;
57     bool visible;
58     bool mustStayInWindow;
59     bool removeFromSuperviewSoon;
60 };
61
62 QWidget::QWidget() : data(new KWQWidgetPrivate)
63 {
64     static QStyle defaultStyle;
65     data->style = &defaultStyle;
66     data->view = nil;
67     data->visible = true;
68     data->mustStayInWindow = false;
69     data->removeFromSuperviewSoon = false;
70 }
71
72 QWidget::QWidget(NSView *view) : data(new KWQWidgetPrivate)
73 {
74     static QStyle defaultStyle;
75     data->style = &defaultStyle;
76     data->view = KWQRetain(view);
77     data->visible = true;
78     data->mustStayInWindow = false;
79     data->removeFromSuperviewSoon = false;
80 }
81
82 QWidget::~QWidget() 
83 {
84     KWQ_BLOCK_EXCEPTIONS;
85     KWQRelease(data->view);
86     KWQ_UNBLOCK_EXCEPTIONS;
87
88     delete data;
89 }
90
91 QSize QWidget::sizeHint() const 
92 {
93     // May be overriden by subclasses.
94     return QSize(0,0);
95 }
96
97 void QWidget::resize(int w, int h) 
98 {
99     setFrameGeometry(QRect(pos().x(), pos().y(), w, h));
100 }
101
102 void QWidget::setActiveWindow() 
103 {
104     KWQ_BLOCK_EXCEPTIONS;
105     [KWQKHTMLPart::bridgeForWidget(this) focusWindow];
106     KWQ_UNBLOCK_EXCEPTIONS;
107 }
108
109 void QWidget::setEnabled(bool enabled)
110 {
111     id view = data->view;
112     KWQ_BLOCK_EXCEPTIONS;
113     if ([view respondsToSelector:@selector(setEnabled:)]) {
114         [view setEnabled:enabled];
115     }
116     KWQ_UNBLOCK_EXCEPTIONS;
117 }
118
119 bool QWidget::isEnabled() const
120 {
121     id view = data->view;
122
123     KWQ_BLOCK_EXCEPTIONS;
124     if ([view respondsToSelector:@selector(isEnabled)]) {
125         return [view isEnabled];
126     }
127     KWQ_UNBLOCK_EXCEPTIONS;
128
129     return true;
130 }
131
132 long QWidget::winId() const
133 {
134     return (long)this;
135 }
136
137 int QWidget::x() const
138 {
139     return frameGeometry().topLeft().x();
140 }
141
142 int QWidget::y() const 
143 {
144     return frameGeometry().topLeft().y();
145 }
146
147 int QWidget::width() const 
148
149     return frameGeometry().size().width();
150 }
151
152 int QWidget::height() const 
153 {
154     return frameGeometry().size().height();
155 }
156
157 QSize QWidget::size() const 
158 {
159     return frameGeometry().size();
160 }
161
162 void QWidget::resize(const QSize &s) 
163 {
164     resize(s.width(), s.height());
165 }
166
167 QPoint QWidget::pos() const 
168 {
169     return frameGeometry().topLeft();
170 }
171
172 void QWidget::move(int x, int y) 
173 {
174     setFrameGeometry(QRect(x, y, width(), height()));
175 }
176
177 void QWidget::move(const QPoint &p) 
178 {
179     move(p.x(), p.y());
180 }
181
182 QRect QWidget::frameGeometry() const
183 {
184     QRect rect;
185
186     KWQ_BLOCK_EXCEPTIONS;
187     rect = QRect([getOuterView() frame]);
188     KWQ_UNBLOCK_EXCEPTIONS;
189
190     return rect;
191 }
192
193 int QWidget::baselinePosition(int height) const
194 {
195     return height;
196 }
197
198 bool QWidget::hasFocus() const
199 {
200     NSView *view = [getView() _webcore_effectiveFirstResponder];
201
202     KWQ_BLOCK_EXCEPTIONS;
203     NSView *firstResponder = [KWQKHTMLPart::bridgeForWidget(this) firstResponder];
204
205     if (!firstResponder) {
206         return false;
207     }
208     if (firstResponder == view) {
209         return true;
210     }
211
212     // Some widgets, like text fields, secure text fields, text areas, and selects
213     // (when displayed using a list box) may have a descendent widget that is
214     // first responder. This checksDescendantsForFocus() check, turned on for the 
215     // four widget types listed, enables the additional check which makes this 
216     // function work correctly for the above-mentioned widget types.
217     if (checksDescendantsForFocus() && 
218         [firstResponder isKindOfClass:[NSView class]] && 
219         [(NSView *)firstResponder isDescendantOf:view]) {
220         // Return true when the first responder is a subview of this widget's view
221         return true;
222     }
223     KWQ_UNBLOCK_EXCEPTIONS;
224
225     return false;
226 }
227
228 void QWidget::setFocus()
229 {
230     if (hasFocus()) {
231         return;
232     }
233     
234     NSView *view = [getView() _webcore_effectiveFirstResponder];
235
236     KWQ_BLOCK_EXCEPTIONS;
237     if ([view acceptsFirstResponder]) {
238         WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(this);
239         NSResponder *oldFirstResponder = [bridge firstResponder];
240
241         [bridge makeFirstResponder:view];
242
243         // setting focus can actually cause a style change which might
244         // remove the view from its superview while it's being made
245         // first responder. This confuses AppKit so we must restore
246         // the old first responder.
247
248         if (![view superview]) {
249             [bridge makeFirstResponder:oldFirstResponder];
250         }
251     }
252     KWQ_UNBLOCK_EXCEPTIONS;
253 }
254
255 void QWidget::clearFocus()
256 {
257     if (!hasFocus()) {
258         return;
259     }
260     
261     KWQKHTMLPart::clearDocumentFocus(this);
262 }
263
264 bool QWidget::checksDescendantsForFocus() const
265 {
266     return false;
267 }
268
269 QWidget::FocusPolicy QWidget::focusPolicy() const
270 {
271     // This provides support for controlling the widgets that take 
272     // part in tab navigation. Widgets must:
273     // 1. not be hidden by css
274     // 2. be enabled
275     // 3. accept first responder
276
277     RenderWidget *widget = const_cast<RenderWidget *>
278         (static_cast<const RenderWidget *>(eventFilterObject()));
279     if (widget->style()->visibility() != khtml::VISIBLE)
280         return NoFocus;
281
282     if (!isEnabled())
283         return NoFocus;
284     
285     KWQ_BLOCK_EXCEPTIONS;
286     if (![getView() acceptsFirstResponder])
287         return NoFocus;
288     KWQ_UNBLOCK_EXCEPTIONS;
289     
290     return TabFocus;
291 }
292
293 const QPalette& QWidget::palette() const
294 {
295     return data->pal;
296 }
297
298 void QWidget::setPalette(const QPalette &palette)
299 {
300     data->pal = palette;
301 }
302
303 QStyle &QWidget::style() const
304 {
305     return *data->style;
306 }
307
308 void QWidget::setStyle(QStyle *style)
309 {
310     // According to the Qt implementation 
311     /*
312     Sets the widget's GUI style to \a style. Ownership of the style
313     object is not transferred.
314     */
315     data->style = style;
316 }
317
318 QFont QWidget::font() const
319 {
320     return data->font;
321 }
322
323 void QWidget::setFont(const QFont &font)
324 {
325     data->font = font;
326 }
327
328 void QWidget::constPolish() const
329 {
330 }
331
332 bool QWidget::isVisible() const
333 {
334     // FIXME - rewrite interms of top level widget?
335     
336     KWQ_BLOCK_EXCEPTIONS;
337     return [[KWQKHTMLPart::bridgeForWidget(this) window] isVisible];
338     KWQ_UNBLOCK_EXCEPTIONS;
339
340     return false;
341 }
342
343 void QWidget::setCursor(const QCursor &cur)
344 {
345     KWQ_BLOCK_EXCEPTIONS;
346     id view = data->view;
347     while (view) {
348         if ([view respondsToSelector:@selector(setDocumentCursor:)]) {
349             [view setDocumentCursor:cur.handle()];
350             break;
351         }
352         view = [view superview];
353     }
354     KWQ_UNBLOCK_EXCEPTIONS;
355 }
356
357 QCursor QWidget::cursor()
358 {
359     KWQ_BLOCK_EXCEPTIONS;
360     QCursor cursor;
361
362     id view = data->view;
363     while (view) {
364         if ([view respondsToSelector:@selector(documentCursor)]) { 
365             cursor = QCursor([view documentCursor]);
366             break;
367         }
368         view = [view superview];
369     }
370
371     return cursor;
372     KWQ_UNBLOCK_EXCEPTIONS;
373
374     return QCursor();
375 }
376
377 void QWidget::unsetCursor()
378 {
379     setCursor(QCursor());
380 }
381
382 bool QWidget::event(QEvent *)
383 {
384     return false;
385 }
386
387 void QWidget::show()
388 {
389     if (!data || data->visible)
390         return;
391
392     data->visible = true;
393
394     KWQ_BLOCK_EXCEPTIONS;
395     [getOuterView() setHidden: NO];
396     KWQ_UNBLOCK_EXCEPTIONS;
397 }
398
399 void QWidget::hide()
400 {
401     if (!data || !data->visible)
402         return;
403
404     data->visible = false;
405
406     KWQ_BLOCK_EXCEPTIONS;
407     [getOuterView() setHidden: YES];
408     KWQ_UNBLOCK_EXCEPTIONS;
409 }
410
411 void QWidget::setFrameGeometry(const QRect &rect)
412 {
413     KWQ_BLOCK_EXCEPTIONS;
414     NSView *v = getOuterView();
415     NSRect f = rect;
416     if (!NSEqualRects(f, [v frame])) {
417         [v setFrame:f];
418         [v setNeedsDisplay: NO];
419     }
420     KWQ_UNBLOCK_EXCEPTIONS;
421 }
422
423 QPoint QWidget::mapFromGlobal(const QPoint &p) const
424 {
425     NSPoint bp = {0,0};
426
427     KWQ_BLOCK_EXCEPTIONS;
428     bp = [[KWQKHTMLPart::bridgeForWidget(this) window] convertScreenToBase:[data->view convertPoint:p toView:nil]];
429     KWQ_UNBLOCK_EXCEPTIONS;
430
431     return QPoint(bp);
432 }
433
434 NSView *QWidget::getView() const
435 {
436     return data->view;
437 }
438
439 void QWidget::setView(NSView *view)
440 {
441     KWQ_BLOCK_EXCEPTIONS;
442     KWQRetain(view);
443     KWQRelease(data->view);
444     data->view = view;
445     KWQ_UNBLOCK_EXCEPTIONS;
446 }
447
448 NSView *QWidget::getOuterView() const
449 {
450     // A QScrollView is a widget normally used to represent a frame.
451     // If this widget's view is a WebCoreFrameView the we resize its containing view, a WebFrameView.
452     // The scroll view contained by the WebFrameView will be autosized.
453
454     NSView *view = data->view;
455     ASSERT(view);
456
457     if ([view conformsToProtocol:@protocol(WebCoreFrameView)]) {
458         view = [view superview];
459         ASSERT(view);
460     }
461
462     return view;
463 }
464
465 void QWidget::lockDrawingFocus()
466 {
467     KWQ_BLOCK_EXCEPTIONS;
468     [getView() lockFocus];
469     KWQ_UNBLOCK_EXCEPTIONS;
470 }
471
472 void QWidget::unlockDrawingFocus()
473 {
474     KWQ_BLOCK_EXCEPTIONS;
475     [getView() unlockFocus];
476     KWQ_UNBLOCK_EXCEPTIONS;
477 }
478
479 void QWidget::disableFlushDrawing()
480 {
481     // It's OK to use the real window here, because if the view's not
482     // in the view hierarchy, then we don't actually want to affect
483     // flushing.
484     KWQ_BLOCK_EXCEPTIONS;
485     [[getView() window] disableFlushWindow];
486     KWQ_UNBLOCK_EXCEPTIONS;
487 }
488
489 void QWidget::enableFlushDrawing()
490 {
491     // It's OK to use the real window here, because if the view's not
492     // in the view hierarchy, then we don't actually want to affect
493     // flushing.
494     KWQ_BLOCK_EXCEPTIONS;
495     NSWindow *window = [getView() window];
496     [window enableFlushWindow];
497     [window flushWindow];
498     KWQ_UNBLOCK_EXCEPTIONS;
499 }
500
501 void QWidget::setDrawingAlpha(float alpha)
502 {
503     KWQ_BLOCK_EXCEPTIONS;
504     CGContextSetAlpha((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], alpha);
505     KWQ_UNBLOCK_EXCEPTIONS;
506 }
507
508 void QWidget::paint(QPainter *p, const QRect &r)
509 {
510     if (p->paintingDisabled()) {
511         return;
512     }
513     NSView *view = getOuterView();
514     // KWQTextArea and KWQTextField both rely on the fact that we use this particular
515     // NSView display method. If you change this, be sure to update them as well.
516     KWQ_BLOCK_EXCEPTIONS;
517     [view displayRectIgnoringOpacity:[view convertRect:r fromView:[view superview]]];
518     KWQ_UNBLOCK_EXCEPTIONS;
519 }
520
521 void QWidget::sendConsumedMouseUp()
522 {
523     khtml::RenderWidget *widget = const_cast<khtml::RenderWidget *>
524         (static_cast<const khtml::RenderWidget *>(eventFilterObject()));
525
526     KWQ_BLOCK_EXCEPTIONS;
527     widget->sendConsumedMouseUp(QPoint([[NSApp currentEvent] locationInWindow]),
528                               // FIXME: should send real state and button
529                               0, 0);
530     KWQ_UNBLOCK_EXCEPTIONS;
531 }
532
533 void QWidget::setIsSelected(bool isSelected)
534 {
535     [KWQKHTMLPart::bridgeForWidget(this) setIsSelected:isSelected forView:getView()];
536 }
537
538 void QWidget::addToSuperview(NSView *superview)
539 {
540     KWQ_BLOCK_EXCEPTIONS;
541
542     ASSERT(superview);
543     NSView *subview = getOuterView();
544     ASSERT(![superview isDescendantOf:subview]);
545     if ([subview superview] != superview)
546         [superview addSubview:subview];
547     data->removeFromSuperviewSoon = false;
548
549     KWQ_UNBLOCK_EXCEPTIONS;
550 }
551
552 void QWidget::removeFromSuperview()
553 {
554     if (data->mustStayInWindow)
555         data->removeFromSuperviewSoon = true;
556     else {
557         KWQ_BLOCK_EXCEPTIONS;
558         [getOuterView() removeFromSuperview];
559         KWQ_UNBLOCK_EXCEPTIONS;
560         data->removeFromSuperviewSoon = false;
561     }
562 }
563
564 void QWidget::beforeMouseDown(NSView *view)
565 {
566     ASSERT([view conformsToProtocol:@protocol(KWQWidgetHolder)]);
567     QWidget *widget = [(NSView <KWQWidgetHolder> *)view widget];
568     if (widget) {
569         ASSERT(view == widget->getOuterView());
570         ASSERT(!widget->data->mustStayInWindow);
571         widget->data->mustStayInWindow = true;
572     }
573 }
574
575 void QWidget::afterMouseDown(NSView *view)
576 {
577     ASSERT([view conformsToProtocol:@protocol(KWQWidgetHolder)]);
578     QWidget *widget = [(NSView <KWQWidgetHolder> *)view widget];
579     if (!widget) {
580         KWQ_BLOCK_EXCEPTIONS;
581         [view removeFromSuperview];
582         KWQ_UNBLOCK_EXCEPTIONS;
583     } else {
584         ASSERT(widget->data->mustStayInWindow);
585         widget->data->mustStayInWindow = false;
586         if (widget->data->removeFromSuperviewSoon)
587             widget->removeFromSuperview();
588     }
589 }