Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1431)

Side by Side Diff: webkit/port/platform/PopupMenuWin.cpp

Issue 7419: Move many files that were suffixed Win.cpp to Chromium.cpp, and place them in... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 12 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2008, Google Inc.
2 // 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 are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "config.h"
31
32 #pragma warning(push, 0)
33 #include "PopupMenu.h"
34
35 #include "CharacterNames.h"
36 #include "ChromeClientWin.h"
37 #include "Document.h"
38 #include "Font.h"
39 #include "Frame.h"
40 #include "FontSelector.h"
41 #include "FramelessScrollView.h"
42 #include "GraphicsContext.h"
43 #include "IntRect.h"
44 #include "Page.h"
45 #include "PlatformKeyboardEvent.h"
46 #include "PlatformMouseEvent.h"
47 #include "PlatformScreen.h"
48 #include "PlatformScrollbar.h"
49 #include "PlatformWheelEvent.h"
50 #include "SystemTime.h"
51 #include "RenderBlock.h"
52 #include "RenderTheme.h"
53 #include "Widget.h"
54 #include "WidgetClientWin.h"
55 #pragma warning(pop)
56
57 //#define LOG_ENABLE
58 #include "LogWin.h"
59
60 using namespace WTF;
61 using namespace Unicode;
62
63 using std::min;
64 using std::max;
65
66 namespace WebCore {
67
68 typedef unsigned long long TimeStamp;
69
70 static const int kMaxVisibleRows = 20;
71 static const int kMaxHeight = 500;
72 static const int kBorderSize = 1;
73 static const TimeStamp kTypeAheadTimeoutMs = 1000;
74
75 class PopupListBox;
76
77 // This class holds a PopupListBox. Its sole purpose is to be able to draw
78 // a border around its child. All its paint/event handling is just forwarded
79 // to the child listBox (with the appropriate transforms).
80 class PopupContainer : public FramelessScrollView {
81 public:
82 static HWND Create(PopupMenuClient* client);
83
84 // FramelessScrollView
85 virtual void paint(GraphicsContext* gc, const IntRect& rect);
86 virtual void hide();
87 virtual bool handleMouseDownEvent(const PlatformMouseEvent& event);
88 virtual bool handleMouseMoveEvent(const PlatformMouseEvent& event);
89 virtual bool handleMouseReleaseEvent(const PlatformMouseEvent& event);
90 virtual bool handleWheelEvent(const PlatformWheelEvent& event);
91 virtual bool handleKeyEvent(const PlatformKeyboardEvent& event);
92
93 // PopupContainer methods
94
95 // Show the popup
96 void showPopup(FrameView* view);
97
98 // Hide the popup. Do not call this directly: use client->hidePopup().
99 void hidePopup();
100
101 // Compute size of widget and children.
102 void layout();
103
104 PopupListBox* listBox() const { return m_listBox.get(); }
105
106 private:
107 PopupContainer(PopupMenuClient* client);
108 ~PopupContainer();
109
110 // Paint the border.
111 void paintBorder(GraphicsContext* gc, const IntRect& rect);
112
113 RefPtr<PopupListBox> m_listBox;
114 };
115
116 // This class uses WebCore code to paint and handle events for a drop-down list
117 // box ("combobox" on Windows).
118 class PopupListBox : public FramelessScrollView {
119 public:
120 // FramelessScrollView
121 virtual void paint(GraphicsContext* gc, const IntRect& rect);
122 virtual bool handleMouseDownEvent(const PlatformMouseEvent& event);
123 virtual bool handleMouseMoveEvent(const PlatformMouseEvent& event);
124 virtual bool handleMouseReleaseEvent(const PlatformMouseEvent& event);
125 virtual bool handleWheelEvent(const PlatformWheelEvent& event);
126 virtual bool handleKeyEvent(const PlatformKeyboardEvent& event);
127
128 // PopupListBox methods
129
130 // Show the popup
131 void showPopup();
132
133 // Hide the popup. Do not call this directly: use client->hidePopup().
134 void hidePopup();
135
136 // Update our internal list to match the client.
137 void updateFromElement();
138
139 // Free any allocated resources used in a particular popup session.
140 void clear();
141
142 // Set the index of the option that is displayed in the <select> widget in t he page
143 void setOriginalIndex(int index);
144
145 // Get the index of the item that the user is currently moused over or has s elected with
146 // the keyboard. This is not the same as the original index, since the user has not yet
147 // accepted this input.
148 int selectedIndex() const { return m_selectedIndex; }
149
150 // Move selection down/up the given number of items, scrolling if necessary.
151 // Positive is down. The resulting index will be clamped to the range
152 // [0, numItems), and non-option items will be skipped.
153 void adjustSelectedIndex(int delta);
154
155 // Returns the number of items in the list.
156 int numItems() const { return static_cast<int>(m_items.size()); }
157
158 void setBaseWidth(int width)
159 {
160 m_baseWidth = width;
161 }
162
163 // Compute size of widget and children.
164 void layout();
165
166 private:
167 friend class PopupContainer;
168
169 // A type of List Item
170 enum ListItemType {
171 TYPE_OPTION,
172 TYPE_GROUP,
173 TYPE_SEPARATOR
174 };
175
176 // A item (represented by <option> or <optgroup>) in the <select> widget.
177 struct ListItem {
178 ListItem(const String& label, ListItemType type)
179 : label(label.copy()), type(type), y(0) {}
180 String label;
181 ListItemType type;
182 int y; // y offset of this item, relative to the top of the popup.
183 };
184
185 PopupListBox(PopupMenuClient* client)
186 : m_originalIndex(0)
187 , m_selectedIndex(0)
188 , m_visibleRows(0)
189 , m_popupClient(client)
190 , m_repeatingChar(0)
191 , m_lastCharTime(0)
192 , m_acceptOnAbandon(false)
193 {
194 setScrollbarsMode(ScrollbarAlwaysOff);
195 }
196
197 ~PopupListBox()
198 {
199 clear();
200 }
201
202 void disconnectClient() { m_popupClient = 0; }
203
204 // Closes the popup
205 void abandon();
206 // Select an index in the list, scrolling if necessary.
207 void selectIndex(int index);
208 // Accepts the selected index as the value to be displayed in the <select> w idget on
209 // the web page, and closes the popup.
210 void acceptIndex(int index);
211
212 // Returns true if the selection can be changed to index.
213 // Disabled items, or labels cannot be selected.
214 bool isSelectableItem(int index);
215
216 // Scrolls to reveal the given index.
217 void scrollToRevealRow(int index);
218 void scrollToRevealSelection() { scrollToRevealRow(m_selectedIndex); }
219
220 // Invalidates the row at the given index.
221 void invalidateRow(int index);
222
223 // Gets the height of a row.
224 int getRowHeight(int index);
225 // Get the bounds of a row.
226 IntRect getRowBounds(int index);
227
228 // Converts a point to an index of the row the point is over
229 int pointToRowIndex(const IntPoint& point);
230
231 // Paint an individual row
232 void paintRow(GraphicsContext* gc, const IntRect& rect, int rowIndex);
233
234 // Test if the given point is within the bounds of the popup window.
235 bool isPointInBounds(const IntPoint& point);
236
237 // Called when the user presses a text key. Does a prefix-search of the ite ms.
238 void typeAheadFind(const PlatformKeyboardEvent& event);
239
240 // Returns the font to use for the given row
241 Font getRowFont(int index);
242
243 // This is the index of the item marked as "selected" - i.e. displayed in th e widget on the
244 // page.
245 int m_originalIndex;
246
247 // This is the index of the item that the user is hovered over or has select ed using the
248 // keyboard in the list. They have not confirmed this selection by clicking or pressing
249 // enter yet however.
250 int m_selectedIndex;
251
252 // True if we should accept the selectedIndex as chosen, even if the popup
253 // is "abandoned". This is used for keyboard navigation, where we want the
254 // selection to change immediately.
255 bool m_acceptOnAbandon;
256
257 // This is the number of rows visible in the popup. The maximum number visib le at a time is
258 // defined as being kMaxVisibleRows. For a scrolled popup, this can be thoug ht of as the
259 // page size in data units.
260 int m_visibleRows;
261
262 // Our suggested width, not including scrollbar.
263 int m_baseWidth;
264
265 // A list of the options contained within the <select>
266 Vector<ListItem*> m_items;
267
268 // The <select> PopupMenuClient that opened us.
269 PopupMenuClient* m_popupClient;
270
271 // The scrollbar which has mouse capture. Mouse events go straight to this
272 // if non-NULL.
273 RefPtr<PlatformScrollbar> m_capturingScrollbar;
274
275 // The last scrollbar that the mouse was over. Used for mouseover highlight s.
276 RefPtr<PlatformScrollbar> m_lastScrollbarUnderMouse;
277
278 // The string the user has typed so far into the popup. Used for typeAheadFi nd.
279 String m_typedString;
280
281 // The char the user has hit repeatedly. Used for typeAheadFind.
282 UChar m_repeatingChar;
283
284 // The last time the user hit a key. Used for typeAheadFind.
285 TimeStamp m_lastCharTime;
286 };
287
288 static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e,
289 FrameView* parent,
290 FrameView* child)
291 {
292 IntPoint pos = parent->convertSelfToChild(child, e.pos());
293
294 // FIXME(beng): This is a horrible hack since PlatformWheelEvent has no sett ers for x/y.
295 // Need to add setters and get patch back upstream to webkit so urce.
296 PlatformMouseEvent relativeEvent = e;
297 IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos());
298 relativePos.setX(pos.x());
299 relativePos.setY(pos.y());
300 return relativeEvent;
301 }
302
303 static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e,
304 FrameView* parent,
305 FrameView* child)
306 {
307 IntPoint pos = parent->convertSelfToChild(child, e.pos());
308
309 // FIXME(beng): This is a horrible hack since PlatformWheelEvent has no sett ers for x/y.
310 // Need to add setters and get patch back upstream to webkit so urce.
311 PlatformWheelEvent relativeEvent = e;
312 IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos());
313 relativePos.setX(pos.x());
314 relativePos.setY(pos.y());
315 return relativeEvent;
316 }
317
318 ///////////////////////////////////////////////////////////////////////////////
319 // PopupContainer implementation
320
321 // Get a pointer to the PopupContainer instance for the m_popup HWND, since we
322 // can't augment the PopupMenu class (above the portability layer). We store the
323 // PopupContainer in the HWND member, which is fairly hacky.
324 static PopupContainer* popupWindow(HWND popup)
325 {
326 return reinterpret_cast<PopupContainer*>(popup);
327 }
328
329 // static
330 HWND PopupContainer::Create(PopupMenuClient* client)
331 {
332 PopupContainer* container = new PopupContainer(client);
333 return reinterpret_cast<HWND>(container);
334 }
335
336 PopupContainer::PopupContainer(PopupMenuClient* client)
337 : m_listBox(new PopupListBox(client))
338 {
339 // FrameViews are created with a refcount of 1 so it needs releasing after w e
340 // assign it to a RefPtr.
341 m_listBox->deref();
342
343 setScrollbarsMode(ScrollbarAlwaysOff);
344 }
345
346 PopupContainer::~PopupContainer()
347 {
348 if (m_listBox)
349 removeChild(m_listBox.get());
350 }
351
352 void PopupContainer::showPopup(FrameView* view)
353 {
354 // Pre-layout, our size matches the <select> dropdown control.
355 int selectHeight = frameGeometry().height();
356
357 // Lay everything out to figure out our preferred size, then tell the view's
358 // WidgetClient about it. It should assign us a client.
359 layout();
360
361 WidgetClientWin* widgetClient =
362 static_cast<WidgetClientWin*>(view->client());
363 ChromeClientWin* chromeClient =
364 static_cast<ChromeClientWin*>(view->frame()->page()->chrome()->client()) ;
365 if (widgetClient && chromeClient) {
366 // If the popup would extend past the bottom of the screen, open upwards
367 // instead.
368 FloatRect screen = screenRect(view);
369 IntRect widgetRect = chromeClient->windowToScreen(frameGeometry());
370 if (widgetRect.bottom() > static_cast<int>(screen.bottom()))
371 widgetRect.move(0, -(widgetRect.height() + selectHeight));
372
373 widgetClient->popupOpened(this, widgetRect);
374 }
375
376 // Must get called after we have a client and containingWindow.
377 addChild(m_listBox.get());
378
379 // Enable scrollbars after the listbox is inserted into the hierarchy, so
380 // it has a proper WidgetClient.
381 m_listBox->setVScrollbarMode(ScrollbarAuto);
382
383 m_listBox->scrollToRevealSelection();
384
385 invalidate();
386 }
387
388 void PopupContainer::hidePopup()
389 {
390 invalidate();
391
392 m_listBox->disconnectClient();
393 removeChild(m_listBox.get());
394
395 if (client())
396 static_cast<WidgetClientWin*>(client())->popupClosed(this);
397 }
398
399 void PopupContainer::layout()
400 {
401 m_listBox->layout();
402
403 // Place the listbox within our border.
404 m_listBox->move(kBorderSize, kBorderSize);
405
406 // Size ourselves to contain listbox + border.
407 resize(m_listBox->width() + kBorderSize*2, m_listBox->height() + kBorderSize *2);
408
409 invalidate();
410 }
411
412 bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event)
413 {
414 return m_listBox->handleMouseDownEvent(
415 constructRelativeMouseEvent(event, this, m_listBox.get()));
416 }
417
418 bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event)
419 {
420 return m_listBox->handleMouseMoveEvent(
421 constructRelativeMouseEvent(event, this, m_listBox.get()));
422 }
423
424 bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event)
425 {
426 return m_listBox->handleMouseReleaseEvent(
427 constructRelativeMouseEvent(event, this, m_listBox.get()));
428 }
429
430 bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event)
431 {
432 return m_listBox->handleWheelEvent(
433 constructRelativeWheelEvent(event, this, m_listBox.get()));
434 }
435
436 bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event)
437 {
438 return m_listBox->handleKeyEvent(event);
439 }
440
441 void PopupContainer::hide() {
442 m_listBox->abandon();
443 }
444
445 void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect)
446 {
447 // adjust coords for scrolled frame
448 IntRect r = intersection(rect, frameGeometry());
449 int tx = x();
450 int ty = y();
451
452 r.move(-tx, -ty);
453
454 gc->translate(static_cast<float>(tx), static_cast<float>(ty));
455 m_listBox->paint(gc, r);
456 gc->translate(-static_cast<float>(tx), -static_cast<float>(ty));
457
458 paintBorder(gc, rect);
459 }
460
461 void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect)
462 {
463 // FIXME(mpcomplete): where do we get the border color from?
464 Color borderColor(127, 157, 185);
465
466 gc->setStrokeStyle(NoStroke);
467 gc->setFillColor(borderColor);
468
469 int tx = x();
470 int ty = y();
471
472 // top, left, bottom, right
473 gc->drawRect(IntRect(tx, ty, width(), kBorderSize));
474 gc->drawRect(IntRect(tx, ty, kBorderSize, height()));
475 gc->drawRect(IntRect(tx, ty + height() - kBorderSize, width(), kBorderSize)) ;
476 gc->drawRect(IntRect(tx + width() - kBorderSize, ty, kBorderSize, height())) ;
477 }
478
479
480 ///////////////////////////////////////////////////////////////////////////////
481 // PopupListBox implementation
482
483 bool PopupListBox::handleMouseDownEvent(const PlatformMouseEvent& event)
484 {
485 PlatformScrollbar* scrollbar = scrollbarUnderMouse(event);
486 if (scrollbar) {
487 m_capturingScrollbar = scrollbar;
488 m_capturingScrollbar->handleMousePressEvent(event);
489 return true;
490 }
491
492 if (!isPointInBounds(event.pos()))
493 abandon();
494
495 return true;
496 }
497
498 bool PopupListBox::handleMouseMoveEvent(const PlatformMouseEvent& event)
499 {
500 if (m_capturingScrollbar) {
501 m_capturingScrollbar->handleMouseMoveEvent(event);
502 return true;
503 }
504
505 PlatformScrollbar* scrollbar = scrollbarUnderMouse(event);
506 if (m_lastScrollbarUnderMouse != scrollbar) {
507 // Send mouse exited to the old scrollbar.
508 if (m_lastScrollbarUnderMouse)
509 m_lastScrollbarUnderMouse->handleMouseOutEvent(event);
510 m_lastScrollbarUnderMouse = scrollbar;
511 }
512
513 if (scrollbar) {
514 scrollbar->handleMouseMoveEvent(event);
515 return true;
516 }
517
518 if (!isPointInBounds(event.pos()))
519 return false;
520
521 selectIndex(pointToRowIndex(event.pos()));
522 return true;
523 }
524
525 bool PopupListBox::handleMouseReleaseEvent(const PlatformMouseEvent& event)
526 {
527 if (m_capturingScrollbar) {
528 m_capturingScrollbar->handleMouseReleaseEvent(event);
529 m_capturingScrollbar = 0;
530 return true;
531 }
532
533 if (!isPointInBounds(event.pos()))
534 return true;
535
536 acceptIndex(pointToRowIndex(event.pos()));
537 return true;
538 }
539
540 bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event)
541 {
542 if (!isPointInBounds(event.pos())) {
543 abandon();
544 return true;
545 }
546
547 // Pass it off to the scroll view.
548 // Sadly, WebCore devs don't understand the whole "const" thing.
549 wheelEvent(const_cast<PlatformWheelEvent&>(event));
550 return true;
551 }
552
553 bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event)
554 {
555 if (event.type() == PlatformKeyboardEvent::KeyUp)
556 return true;
557
558 if (numItems() == 0 && event.windowsVirtualKeyCode() != VK_ESCAPE)
559 return true;
560
561 int oldIndex = m_selectedIndex;
562
563 switch (event.windowsVirtualKeyCode()) {
564 case VK_ESCAPE:
565 abandon(); // may delete this
566 return true;
567 case VK_RETURN:
568 acceptIndex(m_selectedIndex); // may delete this
569 return true;
570 case VK_UP:
571 adjustSelectedIndex(-1);
572 break;
573 case VK_DOWN:
574 adjustSelectedIndex(1);
575 break;
576 case VK_PRIOR:
577 adjustSelectedIndex(-m_visibleRows);
578 break;
579 case VK_NEXT:
580 adjustSelectedIndex(m_visibleRows);
581 break;
582 case VK_HOME:
583 adjustSelectedIndex(-m_selectedIndex);
584 break;
585 case VK_END:
586 adjustSelectedIndex(m_items.size());
587 break;
588 default:
589 if (!event.ctrlKey() && !event.altKey() && !event.metaKey() &&
590 isPrintableChar(event.windowsVirtualKeyCode())) {
591 typeAheadFind(event);
592 }
593 break;
594 }
595
596 if (m_originalIndex != m_selectedIndex) {
597 // Keyboard events should update the selection immediately (but we don't
598 // want to fire the onchange event until the popup is closed, to match
599 // IE). We change the original index so we revert to that when the
600 // popup is closed.
601 m_acceptOnAbandon = true;
602 setOriginalIndex(m_selectedIndex);
603 m_popupClient->setTextFromItem(m_selectedIndex);
604 }
605
606 return true;
607 }
608
609 // From HTMLSelectElement.cpp
610 static String stripLeadingWhiteSpace(const String& string)
611 {
612 int length = string.length();
613 int i;
614 for (i = 0; i < length; ++i)
615 if (string[i] != noBreakSpace &&
616 (string[i] <= 0x7F ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
617 break;
618
619 return string.substring(i, length - i);
620 }
621
622 // From HTMLSelectElement.cpp, with modifications
623 void PopupListBox::typeAheadFind(const PlatformKeyboardEvent& event)
624 {
625 TimeStamp now = static_cast<TimeStamp>(currentTime() * 1000.0f);
626 TimeStamp delta = now - m_lastCharTime;
627
628 m_lastCharTime = now;
629
630 UChar c = event.windowsVirtualKeyCode();
631
632 String prefix;
633 int searchStartOffset = 1;
634 if (delta > kTypeAheadTimeoutMs) {
635 m_typedString = prefix = String(&c, 1);
636 m_repeatingChar = c;
637 } else {
638 m_typedString.append(c);
639
640 if (c == m_repeatingChar)
641 // The user is likely trying to cycle through all the items starting with this character, so just search on the character
642 prefix = String(&c, 1);
643 else {
644 m_repeatingChar = 0;
645 prefix = m_typedString;
646 searchStartOffset = 0;
647 }
648 }
649
650 int itemCount = numItems();
651 int index = (m_selectedIndex + searchStartOffset) % itemCount;
652 for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
653 if (!isSelectableItem(index))
654 continue;
655
656 if (stripLeadingWhiteSpace(m_items[index]->label).startsWith(prefix, fal se)) {
657 selectIndex(index);
658 return;
659 }
660 }
661 }
662
663 void PopupListBox::paint(GraphicsContext* gc, const IntRect& rect)
664 {
665 // adjust coords for scrolled frame
666 IntRect r = intersection(rect, frameGeometry());
667 int tx = x() - contentsX();
668 int ty = y() - contentsY();
669
670 r.move(-tx, -ty);
671
672 LOG(("PopupListBox::paint [%d,%d] [r: %d,%d,%d,%d]", tx, ty,
673 r.x(), r.y(), r.width(), r.height()));
674
675 // set clip rect to match revised damage rect
676 gc->save();
677 gc->translate(static_cast<float>(tx), static_cast<float>(ty));
678 gc->clip(r);
679
680 // TODO(mpcomplete): Can we optimize scrolling to not require repainting the
681 // entire window? Should we?
682 for (int i = 0; i < numItems(); ++i)
683 paintRow(gc, r, i);
684
685 // Special case for an empty popup.
686 if (numItems() == 0)
687 gc->fillRect(r, Color::white);
688
689 gc->restore();
690
691 ScrollView::paint(gc, rect);
692 }
693
694 static RenderStyle* getPopupClientStyleForRow(PopupMenuClient* client, int rowIn dex) {
695 RenderStyle* style = client->itemStyle(rowIndex);
696 if (!style)
697 style = client->clientStyle();
698 return style;
699 }
700
701 void PopupListBox::paintRow(GraphicsContext* gc, const IntRect& rect, int rowInd ex)
702 {
703 // This code is based largely on RenderListBox::paint* methods.
704
705 IntRect rowRect = getRowBounds(rowIndex);
706 if (!rowRect.intersects(rect))
707 return;
708
709 RenderStyle* style = getPopupClientStyleForRow(m_popupClient, rowIndex);
710
711 // Paint background
712 Color backColor, textColor;
713 if (rowIndex == m_selectedIndex) {
714 backColor = theme()->activeListBoxSelectionBackgroundColor();
715 textColor = theme()->activeListBoxSelectionForegroundColor();
716 } else {
717 backColor = m_popupClient->itemBackgroundColor(rowIndex);
718 textColor = style->color();
719 }
720
721 // If we have a transparent background, make sure it has a color to blend
722 // against.
723 if (backColor.hasAlpha())
724 gc->fillRect(rowRect, Color::white);
725
726 gc->fillRect(rowRect, backColor);
727 gc->setFillColor(textColor);
728
729 LOG(("paintRow %d, [%d, %d, %d, %d] %x on %x", rowIndex,
730 rowRect.x(), rowRect.y(), rowRect.width(), rowRect.height(),
731 textColor.rgb(), backColor.rgb()));
732
733 Font itemFont = getRowFont(rowIndex);
734 gc->setFont(itemFont);
735
736 // Bunch of shit to deal with RTL text...
737 String itemText = m_popupClient->itemText(rowIndex);
738 unsigned length = itemText.length();
739 const UChar* str = itemText.characters();
740
741 TextRun textRun(str, length, false, 0, 0, style->direction() == RTL, style-> unicodeBidi() == Override);
742
743 // Draw the item text
744 // TODO(ojan): http://b/1210481 We should get the padding of individual opti on elements.
745 rowRect.move(theme()->popupInternalPaddingLeft(style), itemFont.ascent());
746 if (style->direction() == RTL) {
747 // Right-justify the text for RTL style.
748 rowRect.move(rowRect.width() - itemFont.width(textRun) -
749 2 * theme()->popupInternalPaddingLeft(style), 0);
750 }
751 gc->drawBidiText(textRun, rowRect.location());
752 }
753
754 Font PopupListBox::getRowFont(int rowIndex)
755 {
756 Font itemFont = m_popupClient->itemStyle(rowIndex)->font();
757 if (m_popupClient->itemIsLabel(rowIndex)) {
758 // Bold-ify labels (ie, an <optgroup> heading).
759 FontDescription d = itemFont.fontDescription();
760 d.setWeight(FontWeightBold);
761 Font font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
762 font.update(0);
763 return font;
764 }
765
766 return itemFont;
767 }
768
769 void PopupListBox::abandon()
770 {
771 RefPtr<PopupListBox> keepAlive(this);
772
773 m_selectedIndex = m_originalIndex;
774
775 if (m_acceptOnAbandon)
776 m_popupClient->valueChanged(m_selectedIndex);
777
778 // valueChanged may have torn down the popup!
779 if (m_popupClient)
780 m_popupClient->hidePopup();
781 }
782
783 int PopupListBox::pointToRowIndex(const IntPoint& point)
784 {
785 int y = contentsY() + point.y();
786
787 // TODO(mpcomplete): binary search if perf matters.
788 for (int i = 0; i < numItems(); ++i) {
789 if (y < m_items[i]->y)
790 return i-1;
791 }
792
793 // Last item?
794 if (y < contentsHeight())
795 return m_items.size()-1;
796
797 return -1;
798 }
799
800 void PopupListBox::acceptIndex(int index)
801 {
802 ASSERT(index >= 0 && index < numItems());
803
804 if (isSelectableItem(index)) {
805 RefPtr<PopupListBox> keepAlive(this);
806
807 // Tell the <select> PopupMenuClient what index was selected, and hide o urself.
808 m_popupClient->valueChanged(index);
809
810 // valueChanged may have torn down the popup!
811 if (m_popupClient)
812 m_popupClient->hidePopup();
813 }
814 }
815
816 void PopupListBox::selectIndex(int index)
817 {
818 ASSERT(index >= 0 && index < numItems());
819
820 if (index != m_selectedIndex && isSelectableItem(index)) {
821 invalidateRow(m_selectedIndex);
822 m_selectedIndex = index;
823 invalidateRow(m_selectedIndex);
824
825 scrollToRevealSelection();
826 }
827 }
828
829 void PopupListBox::setOriginalIndex(int index)
830 {
831 m_originalIndex = m_selectedIndex = index;
832 }
833
834 int PopupListBox::getRowHeight(int index)
835 {
836 RenderStyle* style;
837 if ((index < 0) || (!(style = m_popupClient->itemStyle(index))))
838 style = m_popupClient->clientStyle();
839 return style->font().height();
840 }
841
842 IntRect PopupListBox::getRowBounds(int index)
843 {
844 if (index >= 0) {
845 return IntRect(0, m_items[index]->y, visibleWidth(), getRowHeight(index) );
846 } else {
847 return IntRect(0, 0, visibleWidth(), getRowHeight(index));
848 }
849 }
850
851 void PopupListBox::invalidateRow(int index)
852 {
853 if (index < 0)
854 return;
855
856 updateContents(getRowBounds(index));
857 }
858
859 void PopupListBox::scrollToRevealRow(int index)
860 {
861 if (index < 0)
862 return;
863
864 IntRect rowRect = getRowBounds(index);
865
866 if (rowRect.y() < contentsY()) {
867 // Row is above current scroll position, scroll up.
868 ScrollView::setContentsPos(0, rowRect.y());
869 } else if (rowRect.bottom() > contentsY() + visibleHeight()) {
870 // Row is below current scroll position, scroll down.
871 ScrollView::setContentsPos(0, rowRect.bottom() - visibleHeight());
872 }
873 }
874
875 bool PopupListBox::isSelectableItem(int index) {
876 return m_items[index]->type == TYPE_OPTION &&
877 m_popupClient->itemIsEnabled(index);
878 }
879
880 void PopupListBox::adjustSelectedIndex(int delta)
881 {
882 int targetIndex = m_selectedIndex + delta;
883 targetIndex = min(max(targetIndex, 0), numItems() - 1);
884 if (!isSelectableItem(targetIndex)) {
885 // We didn't land on an option. Try to find one.
886 // We try to select the closest index to target, prioritizing any in
887 // the range [current, target].
888
889 int dir = delta > 0 ? 1 : -1;
890 int testIndex = m_selectedIndex;
891 int bestIndex = m_selectedIndex;
892 bool passedTarget = false;
893 while (testIndex >= 0 && testIndex < numItems()) {
894 if (isSelectableItem(testIndex))
895 bestIndex = testIndex;
896 if (testIndex == targetIndex)
897 passedTarget = true;
898 if (passedTarget && bestIndex != m_selectedIndex)
899 break;
900
901 testIndex += dir;
902 }
903
904 // Pick the best index, which may mean we don't change.
905 targetIndex = bestIndex;
906 }
907
908 // Select the new index, and ensure its visible. We do this regardless of
909 // whether the selection changed to ensure keyboard events always bring the
910 // selection into view.
911 selectIndex(targetIndex);
912 scrollToRevealSelection();
913 }
914
915 void PopupListBox::updateFromElement()
916 {
917 // It happens when pressing a key to jump to an item, then use tab or
918 // mouse to get away from the select box. In that case, updateFromElement
919 // is called before abandon, which causes discarding of the select result.
920 if (m_acceptOnAbandon) {
921 m_popupClient->valueChanged(m_selectedIndex);
922 m_acceptOnAbandon = false;
923 }
924
925 clear();
926
927 int size = m_popupClient->listSize();
928 for (int i = 0; i < size; ++i) {
929 ListItemType type;
930 if (m_popupClient->itemIsSeparator(i))
931 type = PopupListBox::TYPE_SEPARATOR;
932 else if (m_popupClient->itemIsLabel(i))
933 type = PopupListBox::TYPE_GROUP;
934 else
935 type = PopupListBox::TYPE_OPTION;
936 m_items.append(new ListItem(m_popupClient->itemText(i), type));
937 }
938
939 m_selectedIndex = m_popupClient->selectedIndex();
940 setOriginalIndex(m_selectedIndex);
941
942 layout();
943 }
944
945 void PopupListBox::layout()
946 {
947 // Size our child items.
948 int baseWidth = 0;
949 int paddingWidth = 0;
950 int y = 0;
951 for (int i = 0; i < numItems(); ++i) {
952 Font itemFont = getRowFont(i);
953
954 // Place the item vertically.
955 m_items[i]->y = y;
956 y += itemFont.height();
957
958 // Ensure the popup is wide enough to fit this item.
959 String text = m_popupClient->itemText(i);
960 if (!text.isEmpty()) {
961 int width = itemFont.width(TextRun(text));
962 baseWidth = max(baseWidth, width);
963 }
964 RenderStyle* style = getPopupClientStyleForRow(m_popupClient, i);
965 // TODO(ojan): http://b/1210481 We should get the padding of individual option elements.
966 paddingWidth = max(paddingWidth,
967 theme()->popupInternalPaddingLeft(style) + theme()->popupInternalPad dingRight(style));
968 }
969
970 int windowHeight = 0;
971 m_visibleRows = min(numItems(), kMaxVisibleRows);
972 for (int i = 0; i < m_visibleRows; ++i) {
973 int rowHeight = getRowHeight(i);
974 if (windowHeight + rowHeight > kMaxHeight) {
975 m_visibleRows = i;
976 break;
977 }
978
979 windowHeight += rowHeight;
980 }
981
982 if (windowHeight == 0)
983 windowHeight = min(getRowHeight(-1), kMaxHeight);
984
985 // Set our widget and scrollable contents sizes.
986 int scrollbarWidth = 0;
987 if (m_visibleRows < numItems())
988 scrollbarWidth = PlatformScrollbar::verticalScrollbarWidth();
989
990 int windowWidth = baseWidth + scrollbarWidth + paddingWidth;
991 int contentWidth = baseWidth;
992
993 if (windowWidth < m_baseWidth) {
994 windowWidth = m_baseWidth;
995 contentWidth = m_baseWidth - scrollbarWidth - paddingWidth;
996 } else {
997 m_baseWidth = baseWidth;
998 }
999
1000 resize(windowWidth, windowHeight);
1001 resizeContents(contentWidth, getRowBounds(numItems() - 1).bottom());
1002 scrollToRevealSelection();
1003
1004 invalidate();
1005 }
1006
1007 void PopupListBox::clear()
1008 {
1009 for (Vector<ListItem*>::iterator it = m_items.begin(); it != m_items.end(); ++it)
1010 delete *it;
1011 m_items.clear();
1012 }
1013
1014 bool PopupListBox::isPointInBounds(const IntPoint& point)
1015 {
1016 return numItems() != 0 && IntRect(0, 0, width(), height()).contains(point);
1017 }
1018
1019
1020 ///////////////////////////////////////////////////////////////////////////////
1021 // PopupMenu implementation
1022 //
1023 // Note: you cannot add methods to this class, since it is defined above the
1024 // portability layer. To access methods and properties on the
1025 // popup widgets, use |popupWindow| above.
1026
1027 PopupMenu::PopupMenu(PopupMenuClient* client)
1028 : m_popupClient(client)
1029 , m_popup(0)
1030 , m_wasClicked(false)
1031 {
1032 }
1033
1034 PopupMenu::~PopupMenu()
1035 {
1036 hide();
1037 }
1038
1039 void PopupMenu::show(const IntRect& r, FrameView* v, int index)
1040 {
1041 m_popup = PopupContainer::Create(client());
1042
1043 PopupContainer* popup = popupWindow(m_popup);
1044 // The rect is the size of the select box. It's usually larger than we need.
1045 // subtract border size so that usually the container will be displayed
1046 // exactly the same width as the select box.
1047 popup->listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0));
1048
1049 updateFromElement();
1050
1051 // We set the selected item in updateFromElement(), and disregard the
1052 // index passed into this function (same as Webkit's PopupMenuWin.cpp)
1053 // TODO(ericroman): make sure this is correct, and add an assertion.
1054 // DCHECK(popupWindow(m_popup)->listBox()->selectedIndex() == index);
1055
1056 // Convert point to main window coords.
1057 IntPoint location = v->contentsToWindow(r.location());
1058
1059 // Move it below the select widget.
1060 location.move(0, r.height());
1061
1062 IntRect popupRect(location, r.size());
1063 popup->setFrameGeometry(popupRect);
1064 popup->showPopup(v);
1065 }
1066
1067 void PopupMenu::hide()
1068 {
1069 if (m_popup) {
1070 PopupContainer* popup = popupWindow(m_popup);
1071 popup->hidePopup();
1072 popup->deref();
1073 m_popup = 0;
1074 }
1075 }
1076
1077 void PopupMenu::updateFromElement()
1078 {
1079 popupWindow(m_popup)->listBox()->updateFromElement();
1080 }
1081
1082 bool PopupMenu::itemWritingDirectionIsNatural()
1083 {
1084 return false;
1085 }
1086
1087 bool PopupMenu::up(unsigned lines)
1088 {
1089 popupWindow(m_popup)->listBox()->adjustSelectedIndex(-static_cast<int>(lines ));
1090 return true;
1091 }
1092
1093 bool PopupMenu::down(unsigned lines)
1094 {
1095 popupWindow(m_popup)->listBox()->adjustSelectedIndex(static_cast<int>(lines) );
1096 return true;
1097 }
1098
1099 int PopupMenu::focusedIndex() const
1100 {
1101 return popupWindow(m_popup)->listBox()->selectedIndex();
1102 }
1103
1104 void PopupMenu::valueChanged(Scrollbar*)
1105 {
1106 // FIXME
1107 }
1108
1109 WebCore::IntRect PopupMenu::windowClipRect() const
1110 {
1111 // FIXME
1112 return WebCore::IntRect();
1113 }
1114
1115 } // namespace WebCore
OLDNEW
« no previous file with comments | « webkit/port/platform/PlatformScrollBarWin.cpp ('k') | webkit/port/platform/SSLKeyGeneratorWin.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698