KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > action > MenuManager


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jface.action;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Iterator JavaDoc;
15 import java.util.List JavaDoc;
16
17 import org.eclipse.core.runtime.ListenerList;
18 import org.eclipse.swt.SWT;
19 import org.eclipse.swt.events.MenuAdapter;
20 import org.eclipse.swt.events.MenuEvent;
21 import org.eclipse.swt.widgets.Composite;
22 import org.eclipse.swt.widgets.Control;
23 import org.eclipse.swt.widgets.CoolBar;
24 import org.eclipse.swt.widgets.Decorations;
25 import org.eclipse.swt.widgets.Menu;
26 import org.eclipse.swt.widgets.MenuItem;
27 import org.eclipse.swt.widgets.Shell;
28 import org.eclipse.swt.widgets.ToolBar;
29
30 /**
31  * A menu manager is a contribution manager which realizes itself and its items
32  * in a menu control; either as a menu bar, a sub-menu, or a context menu.
33  * <p>
34  * This class may be instantiated; it may also be subclassed.
35  * </p>
36  */

37 public class MenuManager extends ContributionManager implements IMenuManager {
38
39     /**
40      * The menu id.
41      */

42     private String JavaDoc id;
43
44     /**
45      * List of registered menu listeners (element type: <code>IMenuListener</code>).
46      */

47     private ListenerList listeners = new ListenerList();
48
49     /**
50      * The menu control; <code>null</code> before
51      * creation and after disposal.
52      */

53     private Menu menu = null;
54
55     /**
56      * The menu item widget; <code>null</code> before
57      * creation and after disposal. This field is used
58      * when this menu manager is a sub-menu.
59      */

60     private MenuItem menuItem;
61
62     /**
63      * The text for a sub-menu.
64      */

65     private String JavaDoc menuText;
66
67     /**
68      * The overrides for items of this manager
69      */

70     private IContributionManagerOverrides overrides;
71
72     /**
73      * The parent contribution manager.
74      */

75     private IContributionManager parent;
76
77     /**
78      * Indicates whether <code>removeAll</code> should be
79      * called just before the menu is displayed.
80      */

81     private boolean removeAllWhenShown = false;
82
83     /**
84      * Indicates this item is visible in its manager; <code>true</code>
85      * by default.
86      * @since 3.3
87      */

88     protected boolean visible = true;
89
90     /**
91      * Creates a menu manager. The text and id are <code>null</code>.
92      * Typically used for creating a context menu, where it doesn't need to be referred to by id.
93      */

94     public MenuManager() {
95         this(null, null);
96     }
97
98     /**
99      * Creates a menu manager with the given text. The id of the menu
100      * is <code>null</code>.
101      * Typically used for creating a sub-menu, where it doesn't need to be referred to by id.
102      *
103      * @param text the text for the menu, or <code>null</code> if none
104      */

105     public MenuManager(String JavaDoc text) {
106         this(text, null);
107     }
108
109     /**
110      * Creates a menu manager with the given text and id.
111      * Typically used for creating a sub-menu, where it needs to be referred to by id.
112      *
113      * @param text the text for the menu, or <code>null</code> if none
114      * @param id the menu id, or <code>null</code> if it is to have no id
115      */

116     public MenuManager(String JavaDoc text, String JavaDoc id) {
117         this.menuText = text;
118         this.id = id;
119     }
120
121     /* (non-Javadoc)
122      * @see org.eclipse.jface.action.IMenuManager#addMenuListener(org.eclipse.jface.action.IMenuListener)
123      */

124     public void addMenuListener(IMenuListener listener) {
125         listeners.add(listener);
126     }
127
128     /**
129      * Creates and returns an SWT context menu control for this menu,
130      * and installs all registered contributions.
131      * Does not create a new control if one already exists.
132      * <p>
133      * Note that the menu is not expected to be dynamic.
134      * </p>
135      *
136      * @param parent the parent control
137      * @return the menu control
138      */

139     public Menu createContextMenu(Control parent) {
140         if (!menuExist()) {
141             menu = new Menu(parent);
142             initializeMenu();
143         }
144         return menu;
145     }
146
147     /**
148      * Creates and returns an SWT menu bar control for this menu,
149      * for use in the given <code>Decorations</code>, and installs all registered
150      * contributions. Does not create a new control if one already exists.
151      *
152      * @param parent the parent decorations
153      * @return the menu control
154      * @since 2.1
155      */

156     public Menu createMenuBar(Decorations parent) {
157         if (!menuExist()) {
158             menu = new Menu(parent, SWT.BAR);
159             update(false);
160         }
161         return menu;
162     }
163
164     /**
165      * Creates and returns an SWT menu bar control for this menu, for use in the
166      * given <code>Shell</code>, and installs all registered contributions. Does not
167      * create a new control if one already exists. This implementation simply calls
168      * the <code>createMenuBar(Decorations)</code> method
169      *
170      * @param parent the parent decorations
171      * @return the menu control
172      * @deprecated use <code>createMenuBar(Decorations)</code> instead.
173      */

174     public Menu createMenuBar(Shell parent) {
175         return createMenuBar((Decorations) parent);
176     }
177
178     /**
179      * Disposes of this menu manager and frees all allocated SWT resources.
180      * Notifies all contribution items of the dispose. Note that this method does
181      * not clean up references between this menu manager and its associated
182      * contribution items. Use <code>removeAll</code> for that purpose.
183      */

184     public void dispose() {
185         if (menuExist()) {
186             menu.dispose();
187         }
188         menu = null;
189
190         if (menuItem != null) {
191             menuItem.dispose();
192             menuItem = null;
193         }
194
195         IContributionItem[] items = getItems();
196         for (int i = 0; i < items.length; i++) {
197             items[i].dispose();
198         }
199     }
200
201     /* (non-Javadoc)
202      * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.Composite)
203      */

204     public void fill(Composite parent) {
205     }
206
207     /* (non-Javadoc)
208      * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.CoolBar, int)
209      */

210     public void fill(CoolBar parent, int index) {
211     }
212
213     /* (non-Javadoc)
214      * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.Menu, int)
215      */

216     public void fill(Menu parent, int index) {
217         if (menuItem == null || menuItem.isDisposed()) {
218             if (index >= 0) {
219                 menuItem = new MenuItem(parent, SWT.CASCADE, index);
220             } else {
221                 menuItem = new MenuItem(parent, SWT.CASCADE);
222             }
223
224             menuItem.setText(getMenuText());
225
226             if (!menuExist()) {
227                 menu = new Menu(parent);
228             }
229
230             menuItem.setMenu(menu);
231
232             initializeMenu();
233
234             // populate the submenu, in order to enable accelerators
235
// and to set enabled state on the menuItem properly
236
update(true);
237         }
238     }
239
240     /* (non-Javadoc)
241      * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.ToolBar, int)
242      */

243     public void fill(ToolBar parent, int index) {
244     }
245
246     /* (non-Javadoc)
247      * @see org.eclipse.jface.action.IMenuManager#findMenuUsingPath(java.lang.String)
248      */

249     public IMenuManager findMenuUsingPath(String JavaDoc path) {
250         IContributionItem item = findUsingPath(path);
251         if (item instanceof IMenuManager) {
252             return (IMenuManager) item;
253         }
254         return null;
255     }
256
257     /* (non-Javadoc)
258      * @see org.eclipse.jface.action.IMenuManager#findUsingPath(java.lang.String)
259      */

260     public IContributionItem findUsingPath(String JavaDoc path) {
261         String JavaDoc id = path;
262         String JavaDoc rest = null;
263         int separator = path.indexOf('/');
264         if (separator != -1) {
265             id = path.substring(0, separator);
266             rest = path.substring(separator + 1);
267         } else {
268             return super.find(path);
269         }
270
271         IContributionItem item = super.find(id);
272         if (item instanceof IMenuManager) {
273             IMenuManager manager = (IMenuManager) item;
274             return manager.findUsingPath(rest);
275         }
276         return null;
277     }
278
279     /**
280      * Notifies any menu listeners that a menu is about to show.
281      * Only listeners registered at the time this method is called are notified.
282      *
283      * @param manager the menu manager
284      *
285      * @see IMenuListener#menuAboutToShow
286      */

287     private void fireAboutToShow(IMenuManager manager) {
288         Object JavaDoc[] listeners = this.listeners.getListeners();
289         for (int i = 0; i < listeners.length; ++i) {
290             ((IMenuListener) listeners[i]).menuAboutToShow(manager);
291         }
292     }
293
294     /**
295      * Notifies any menu listeners that a menu is about to hide.
296      * Only listeners registered at the time this method is called are notified.
297      *
298      * @param manager the menu manager
299      *
300      */

301     private void fireAboutToHide(IMenuManager manager) {
302         final Object JavaDoc[] listeners = this.listeners.getListeners();
303         for (int i = 0; i < listeners.length; ++i) {
304             final Object JavaDoc listener = listeners[i];
305             if (listener instanceof IMenuListener2) {
306                 final IMenuListener2 listener2 = (IMenuListener2) listener;
307                 listener2.menuAboutToHide(manager);
308             }
309         }
310     }
311
312     /**
313      * Returns the menu id. The menu id is used when creating a contribution
314      * item for adding this menu as a sub menu of another.
315      *
316      * @return the menu id
317      */

318     public String JavaDoc getId() {
319         return id;
320     }
321
322     /**
323      * Returns the SWT menu control for this menu manager.
324      *
325      * @return the menu control
326      */

327     public Menu getMenu() {
328         return menu;
329     }
330
331     /**
332      * Returns the text shown in the menu.
333      *
334      * @return the menu text
335      */

336     public String JavaDoc getMenuText() {
337         return menuText;
338     }
339
340     /* (non-Javadoc)
341      * @see org.eclipse.jface.action.IContributionManager#getOverrides()
342      */

343     public IContributionManagerOverrides getOverrides() {
344         if (overrides == null) {
345             if (parent == null) {
346                 overrides = new IContributionManagerOverrides() {
347                     public Integer JavaDoc getAccelerator(IContributionItem item) {
348                         return null;
349                     }
350
351                     public String JavaDoc getAcceleratorText(IContributionItem item) {
352                         return null;
353                     }
354
355                     public Boolean JavaDoc getEnabled(IContributionItem item) {
356                         return null;
357                     }
358
359                     public String JavaDoc getText(IContributionItem item) {
360                         return null;
361                     }
362                 };
363             } else {
364                 overrides = parent.getOverrides();
365             }
366             super.setOverrides(overrides);
367         }
368         return overrides;
369     }
370
371     /**
372      * Returns the parent contribution manager of this manger.
373      *
374      * @return the parent contribution manager
375      * @since 2.0
376      */

377     public IContributionManager getParent() {
378         return parent;
379     }
380
381     /* (non-Javadoc)
382      * @see org.eclipse.jface.action.IMenuManager#getRemoveAllWhenShown()
383      */

384     public boolean getRemoveAllWhenShown() {
385         return removeAllWhenShown;
386     }
387
388     /**
389      * Notifies all listeners that this menu is about to appear.
390      */

391     private void handleAboutToShow() {
392         if (removeAllWhenShown) {
393             removeAll();
394         }
395         fireAboutToShow(this);
396         update(false, true);
397     }
398
399     /**
400      * Notifies all listeners that this menu is about to disappear.
401      */

402     private void handleAboutToHide() {
403         fireAboutToHide(this);
404     }
405
406     /**
407      * Initializes the menu control.
408      */

409     private void initializeMenu() {
410         menu.addMenuListener(new MenuAdapter() {
411             public void menuHidden(MenuEvent e) {
412                 // ApplicationWindow.resetDescription(e.widget);
413
handleAboutToHide();
414             }
415
416             public void menuShown(MenuEvent e) {
417                 handleAboutToShow();
418             }
419         });
420         // Don't do an update(true) here, in case menu is never opened.
421
// Always do it lazily in handleAboutToShow().
422
}
423
424     /* (non-Javadoc)
425      * @see org.eclipse.jface.action.IContributionItem#isDynamic()
426      */

427     public boolean isDynamic() {
428         return false;
429     }
430
431     /**
432      * Returns whether this menu should be enabled or not.
433      * Used to enable the menu item containing this menu when it is realized as a sub-menu.
434      * <p>
435      * The default implementation of this framework method
436      * returns <code>true</code>. Subclasses may reimplement.
437      * </p>
438      *
439      * @return <code>true</code> if enabled, and
440      * <code>false</code> if disabled
441      */

442     public boolean isEnabled() {
443         return true;
444     }
445
446     /* (non-Javadoc)
447      * @see org.eclipse.jface.action.IContributionItem#isGroupMarker()
448      */

449     public boolean isGroupMarker() {
450         return false;
451     }
452
453     /* (non-Javadoc)
454      * @see org.eclipse.jface.action.IContributionItem#isSeparator()
455      */

456     public boolean isSeparator() {
457         return false;
458     }
459
460     /**
461      * Check if the contribution is item is a subsitute for ourselves
462      *
463      * @param item the contribution item
464      * @return <code>true</code> if give item is a substitution for ourselves
465      * @deprecated this method is no longer a part of the
466      * {@link org.eclipse.jface.action.IContributionItem} API.
467      */

468     public boolean isSubstituteFor(IContributionItem item) {
469         return this.equals(item);
470     }
471
472     /* (non-Javadoc)
473      * @see org.eclipse.jface.action.IContributionItem#isVisible()
474      */

475     public boolean isVisible() {
476         if (!visible) {
477             return false; // short circut calculations in this case
478
}
479
480         // menus arent visible if all of its children are invisible (or only contains visible separators).
481
IContributionItem[] childItems = getItems();
482         boolean visibleChildren = false;
483         for (int j = 0; j < childItems.length; j++) {
484             if (childItems[j].isVisible() && !childItems[j].isSeparator()) {
485                 visibleChildren = true;
486                 break;
487             }
488         }
489
490         return visibleChildren;
491     }
492
493     
494     /**
495      * The <code>MenuManager</code> implementation of this <code>ContributionManager</code> method
496      * also propagates the dirty flag up the parent chain.
497      *
498      * @since 3.1
499      */

500     public void markDirty() {
501         super.markDirty();
502         // Can't optimize by short-circuiting when the first dirty manager is encountered,
503
// since non-visible children are not even processed.
504
// That is, it's possible to have a dirty sub-menu under a non-dirty parent menu
505
// even after the parent menu has been updated.
506
// If items are added/removed in the sub-menu, we still need to propagate the dirty flag up,
507
// even if the sub-menu is already dirty, since the result of isVisible() may change
508
// due to the added/removed items.
509
IContributionManager parent = getParent();
510         if (parent != null) {
511             parent.markDirty();
512         }
513     }
514     
515     /**
516      * Returns whether the menu control is created
517      * and not disposed.
518      *
519      * @return <code>true</code> if the control is created
520      * and not disposed, <code>false</code> otherwise
521      */

522     private boolean menuExist() {
523         return menu != null && !menu.isDisposed();
524     }
525
526     /* (non-Javadoc)
527      * @see org.eclipse.jface.action.IMenuManager#removeMenuListener(org.eclipse.jface.action.IMenuListener)
528      */

529     public void removeMenuListener(IMenuListener listener) {
530         listeners.remove(listener);
531     }
532
533     /* (non-Javadoc)
534      * @see org.eclipse.jface.action.IContributionItem#saveWidgetState()
535      */

536     public void saveWidgetState() {
537     }
538
539     /**
540      * Sets the overrides for this contribution manager
541      *
542      * @param newOverrides the overrides for the items of this manager
543      * @since 2.0
544      */

545     public void setOverrides(IContributionManagerOverrides newOverrides) {
546         overrides = newOverrides;
547         super.setOverrides(overrides);
548     }
549
550     /* (non-Javadoc)
551      * @see org.eclipse.jface.action.IContributionItem#setParent(org.eclipse.jface.action.IContributionManager)
552      */

553     public void setParent(IContributionManager manager) {
554         parent = manager;
555     }
556
557     /* (non-Javadoc)
558      * @see org.eclipse.jface.action.IMenuManager#setRemoveAllWhenShown(boolean)
559      */

560     public void setRemoveAllWhenShown(boolean removeAll) {
561         this.removeAllWhenShown = removeAll;
562     }
563
564     /* (non-Javadoc)
565      * @see org.eclipse.jface.action.IContributionItem#setVisible(boolean)
566      */

567     public void setVisible(boolean visible) {
568         this.visible = visible;
569     }
570
571     /* (non-Javadoc)
572      * @see org.eclipse.jface.action.IContributionItem#update()
573      */

574     public void update() {
575         updateMenuItem();
576     }
577
578     /**
579      * The <code>MenuManager</code> implementation of this <code>IContributionManager</code>
580      * updates this menu, but not any of its submenus.
581      *
582      * @see #updateAll
583      */

584     public void update(boolean force) {
585         update(force, false);
586     }
587
588     /**
589      * Incrementally builds the menu from the contribution items.
590      * This method leaves out double separators and separators in the first
591      * or last position.
592      *
593      * @param force <code>true</code> means update even if not dirty,
594      * and <code>false</code> for normal incremental updating
595      * @param recursive <code>true</code> means recursively update
596      * all submenus, and <code>false</code> means just this menu
597      */

598     protected void update(boolean force, boolean recursive) {
599         if (isDirty() || force) {
600             if (menuExist()) {
601                 // clean contains all active items without double separators
602
IContributionItem[] items = getItems();
603                 List JavaDoc clean = new ArrayList JavaDoc(items.length);
604                 IContributionItem separator = null;
605                 for (int i = 0; i < items.length; ++i) {
606                     IContributionItem ci = items[i];
607                     if (!ci.isVisible()) {
608                         continue;
609                     }
610                     if (ci.isSeparator()) {
611                         // delay creation until necessary
612
// (handles both adjacent separators, and separator at end)
613
separator = ci;
614                     } else {
615                         if (separator != null) {
616                             if (clean.size() > 0) {
617                                 clean.add(separator);
618                             }
619                             separator = null;
620                         }
621                         clean.add(ci);
622                     }
623                 }
624
625                 // remove obsolete (removed or non active)
626
MenuItem[] mi = menu.getItems();
627
628                 for (int i = 0; i < mi.length; i++) {
629                     Object JavaDoc data = mi[i].getData();
630
631                     if (data == null || !clean.contains(data)) {
632                         mi[i].dispose();
633                     } else if (data instanceof IContributionItem
634                             && ((IContributionItem) data).isDynamic()
635                             && ((IContributionItem) data).isDirty()) {
636                         mi[i].dispose();
637                     }
638                 }
639
640                 // add new
641
mi = menu.getItems();
642                 int srcIx = 0;
643                 int destIx = 0;
644
645                 for (Iterator JavaDoc e = clean.iterator(); e.hasNext();) {
646                     IContributionItem src = (IContributionItem) e.next();
647                     IContributionItem dest;
648
649                     // get corresponding item in SWT widget
650
if (srcIx < mi.length) {
651                         dest = (IContributionItem) mi[srcIx].getData();
652                     } else {
653                         dest = null;
654                     }
655
656                     if (dest != null && src.equals(dest)) {
657                         srcIx++;
658                         destIx++;
659                     } else if (dest != null && dest.isSeparator()
660                             && src.isSeparator()) {
661                         mi[srcIx].setData(src);
662                         srcIx++;
663                         destIx++;
664                     } else {
665                         int start = menu.getItemCount();
666                         src.fill(menu, destIx);
667                         int newItems = menu.getItemCount() - start;
668                         for (int i = 0; i < newItems; i++) {
669                             MenuItem item = menu.getItem(destIx++);
670                             item.setData(src);
671                         }
672                     }
673
674                     // May be we can optimize this call. If the menu has just
675
// been created via the call src.fill(fMenuBar, destIx) then
676
// the menu has already been updated with update(true)
677
// (see MenuManager). So if force is true we do it again. But
678
// we can't set force to false since then information for the
679
// sub sub menus is lost.
680
if (recursive) {
681                         IContributionItem item = src;
682                         if (item instanceof SubContributionItem) {
683                             item = ((SubContributionItem) item).getInnerItem();
684                         }
685                         if (item instanceof IMenuManager) {
686                             ((IMenuManager) item).updateAll(force);
687                         }
688                     }
689
690                 }
691
692                 // remove any old menu items not accounted for
693
for (; srcIx < mi.length; srcIx++) {
694                     mi[srcIx].dispose();
695                 }
696
697                 setDirty(false);
698             }
699         } else {
700             // I am not dirty. Check if I must recursivly walk down the hierarchy.
701
if (recursive) {
702                 IContributionItem[] items = getItems();
703                 for (int i = 0; i < items.length; ++i) {
704                     IContributionItem ci = items[i];
705                     if (ci instanceof IMenuManager) {
706                         IMenuManager mm = (IMenuManager) ci;
707                         if (mm.isVisible()) {
708                             mm.updateAll(force);
709                         }
710                     }
711                 }
712             }
713         }
714         updateMenuItem();
715     }
716
717     /* (non-Javadoc)
718      * @see org.eclipse.jface.action.IContributionItem#update(java.lang.String)
719      */

720     public void update(String JavaDoc property) {
721         IContributionItem items[] = getItems();
722
723         for (int i = 0; i < items.length; i++) {
724             items[i].update(property);
725         }
726
727         if (menu != null && !menu.isDisposed() && menu.getParentItem() != null
728                 && IAction.TEXT.equals(property)) {
729             String JavaDoc text = getOverrides().getText(this);
730
731             if (text == null) {
732                 text = getMenuText();
733             }
734
735             if (text != null) {
736                 ExternalActionManager.ICallback callback = ExternalActionManager
737                         .getInstance().getCallback();
738
739                 if (callback != null) {
740                     int index = text.indexOf('&');
741
742                     if (index >= 0 && index < text.length() - 1) {
743                         char character = Character.toUpperCase(text
744                                 .charAt(index + 1));
745
746                         if (callback.isAcceleratorInUse(SWT.ALT | character)) {
747                             if (index == 0) {
748                                 text = text.substring(1);
749                             } else {
750                                 text = text.substring(0, index)
751                                         + text.substring(index + 1);
752                             }
753                         }
754                     }
755                 }
756
757                 menu.getParentItem().setText(text);
758             }
759         }
760     }
761
762     /* (non-Javadoc)
763      * @see org.eclipse.jface.action.IMenuManager#updateAll(boolean)
764      */

765     public void updateAll(boolean force) {
766         update(force, true);
767     }
768
769     /**
770      * Updates the menu item for this sub menu.
771      * The menu item is disabled if this sub menu is empty.
772      * Does nothing if this menu is not a submenu.
773      */

774     private void updateMenuItem() {
775         /*
776          * Commented out until proper solution to enablement of
777          * menu item for a sub-menu is found. See bug 30833 for
778          * more details.
779          *
780          if (menuItem != null && !menuItem.isDisposed() && menuExist()) {
781          IContributionItem items[] = getItems();
782          boolean enabled = false;
783          for (int i = 0; i < items.length; i++) {
784          IContributionItem item = items[i];
785          enabled = item.isEnabled();
786          if(enabled) break;
787          }
788          // Workaround for 1GDDCN2: SWT:Linux - MenuItem.setEnabled() always causes a redraw
789          if (menuItem.getEnabled() != enabled)
790          menuItem.setEnabled(enabled);
791          }
792          */

793         // Partial fix for bug #34969 - diable the menu item if no
794
// items in sub-menu (for context menus).
795
if (menuItem != null && !menuItem.isDisposed() && menuExist()) {
796             boolean enabled = menu.getItemCount() > 0;
797             // Workaround for 1GDDCN2: SWT:Linux - MenuItem.setEnabled() always causes a redraw
798
if (menuItem.getEnabled() != enabled) {
799                 // We only do this for context menus (for bug #34969)
800
Menu topMenu = menu;
801                 while (topMenu.getParentMenu() != null) {
802                     topMenu = topMenu.getParentMenu();
803                 }
804                 if ((topMenu.getStyle() & SWT.BAR) == 0) {
805                     menuItem.setEnabled(enabled);
806                 }
807             }
808         }
809     }
810 }
811
Popular Tags