void JucerDocumentEditor::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
{
    ComponentLayout* const currentLayout = getCurrentLayout();
    PaintRoutine* const currentPaintRoutine = getCurrentPaintRoutine();

    const int cmd = ModifierKeys::commandModifier;
    const int shift = ModifierKeys::shiftModifier;

    if (commandID >= JucerCommandIDs::newComponentBase
         && commandID < JucerCommandIDs::newComponentBase + ObjectTypes::numComponentTypes)
    {
        const int index = commandID - JucerCommandIDs::newComponentBase;

        result.setInfo ("New " + ObjectTypes::componentTypeHandlers [index]->getTypeName(),
                        "Creates a new " + ObjectTypes::componentTypeHandlers [index]->getTypeName(),
                        CommandCategories::editing, 0);
        return;
    }

    if (commandID >= JucerCommandIDs::newElementBase
         && commandID < JucerCommandIDs::newElementBase + ObjectTypes::numElementTypes)
    {
        const int index = commandID - JucerCommandIDs::newElementBase;

        result.setInfo (String ("New ") + ObjectTypes::elementTypeNames [index],
                        String ("Adds a new ") + ObjectTypes::elementTypeNames [index],
                        CommandCategories::editing, 0);

        result.setActive (currentPaintRoutine != nullptr);
        return;
    }

    switch (commandID)
    {
    case JucerCommandIDs::toFront:
        result.setInfo (TRANS("Bring to front"), TRANS("Brings the currently selected component to the front."), CommandCategories::editing, 0);
        result.setActive (isSomethingSelected());
        result.defaultKeypresses.add (KeyPress ('f', cmd, 0));
        break;

    case JucerCommandIDs::toBack:
        result.setInfo (TRANS("Send to back"), TRANS("Sends the currently selected component to the back."), CommandCategories::editing, 0);
        result.setActive (isSomethingSelected());
        result.defaultKeypresses.add (KeyPress ('b', cmd, 0));
        break;

    case JucerCommandIDs::group:
        result.setInfo (TRANS("Group selected items"), TRANS("Turns the currently selected elements into a single group object."), CommandCategories::editing, 0);
        result.setActive (currentPaintRoutine != nullptr && currentPaintRoutine->getSelectedElements().getNumSelected() > 1);
        result.defaultKeypresses.add (KeyPress ('k', cmd, 0));
        break;

    case JucerCommandIDs::ungroup:
        result.setInfo (TRANS("Ungroup selected items"), TRANS("Turns the currently selected elements into a single group object."), CommandCategories::editing, 0);
        result.setActive (currentPaintRoutine != nullptr
                           && currentPaintRoutine->getSelectedElements().getNumSelected() == 1
                           && currentPaintRoutine->getSelectedElements().getSelectedItem (0)->getTypeName() == "Group");
        result.defaultKeypresses.add (KeyPress ('k', cmd | shift, 0));
        break;

    case JucerCommandIDs::test:
        result.setInfo (TRANS("Test component..."), TRANS("Runs the current component interactively."), CommandCategories::view, 0);
        result.defaultKeypresses.add (KeyPress ('t', cmd, 0));
        break;

    case JucerCommandIDs::enableSnapToGrid:
        result.setInfo (TRANS("Enable snap-to-grid"), TRANS("Toggles whether components' positions are aligned to a grid."), CommandCategories::view, 0);
        result.setTicked (document != nullptr && document->isSnapActive (false));
        result.defaultKeypresses.add (KeyPress ('g', cmd, 0));
        break;

    case JucerCommandIDs::showGrid:
        result.setInfo (TRANS("Show snap-to-grid"), TRANS("Toggles whether the snapping grid is displayed on-screen."), CommandCategories::view, 0);
        result.setTicked (document != nullptr && document->isSnapShown());
        result.defaultKeypresses.add (KeyPress ('g', cmd | shift, 0));
        break;

    case JucerCommandIDs::editCompLayout:
        result.setInfo (TRANS("Edit sub-component layout"), TRANS("Switches to the sub-component editor view."), CommandCategories::view, 0);
        result.setTicked (currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress ('n', cmd, 0));
        break;

    case JucerCommandIDs::editCompGraphics:
        result.setInfo (TRANS("Edit background graphics"), TRANS("Switches to the background graphics editor view."), CommandCategories::view, 0);
        result.setTicked (currentPaintRoutine != nullptr);
        result.defaultKeypresses.add (KeyPress ('m', cmd, 0));
        break;

    case JucerCommandIDs::bringBackLostItems:
        result.setInfo (TRANS("Retrieve offscreen items"), TRANS("Moves any items that are lost beyond the edges of the screen back to the centre."), CommandCategories::editing, 0);
        result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress ('m', cmd, 0));
        break;

    case JucerCommandIDs::zoomIn:
        result.setInfo (TRANS("Zoom in"), TRANS("Zooms in on the current component."), CommandCategories::editing, 0);
        result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress (']', cmd, 0));
        break;

    case JucerCommandIDs::zoomOut:
        result.setInfo (TRANS("Zoom out"), TRANS("Zooms out on the current component."), CommandCategories::editing, 0);
        result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress ('[', cmd, 0));
        break;

    case JucerCommandIDs::zoomNormal:
        result.setInfo (TRANS("Zoom to 100%"), TRANS("Restores the zoom level to normal."), CommandCategories::editing, 0);
        result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress ('1', cmd, 0));
        break;

    case JucerCommandIDs::spaceBarDrag:
        result.setInfo (TRANS("Scroll while dragging mouse"), TRANS("When held down, this key lets you scroll around by dragging with the mouse."),
                        CommandCategories::view, ApplicationCommandInfo::wantsKeyUpDownCallbacks);
        result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress (KeyPress::spaceKey, 0, 0));
        break;

    case JucerCommandIDs::compOverlay0:
    case JucerCommandIDs::compOverlay33:
    case JucerCommandIDs::compOverlay66:
    case JucerCommandIDs::compOverlay100:
        {
            int amount = 0, num = 0;

            if (commandID == JucerCommandIDs::compOverlay33)
            {
                amount = 33;
                num = 1;
            }
            else if (commandID == JucerCommandIDs::compOverlay66)
            {
                amount = 66;
                num = 2;
            }
            else if (commandID == JucerCommandIDs::compOverlay100)
            {
                amount = 100;
                num = 3;
            }

            result.defaultKeypresses.add (KeyPress ('2' + num, cmd, 0));

            int currentAmount = 0;
            if (document != nullptr && document->getComponentOverlayOpacity() > 0.9f)
                currentAmount = 100;
            else if (document != nullptr && document->getComponentOverlayOpacity() > 0.6f)
                currentAmount = 66;
            else if (document != nullptr && document->getComponentOverlayOpacity() > 0.3f)
                currentAmount = 33;

            result.setInfo (commandID == JucerCommandIDs::compOverlay0
                                ? TRANS("No component overlay")
                                : TRANS("Overlay with opacity of 123%").replace ("123", String (amount)),
                            TRANS("Changes the opacity of the components that are shown over the top of the graphics editor."),
                            CommandCategories::view, 0);
            result.setActive (currentPaintRoutine != nullptr && document->getComponentLayout() != nullptr);
            result.setTicked (amount == currentAmount);
        }
        break;

    case StandardApplicationCommandIDs::undo:
        result.setInfo (TRANS ("Undo"), TRANS ("Undo"), "Editing", 0);
        result.setActive (document != nullptr && document->getUndoManager().canUndo());
        result.defaultKeypresses.add (KeyPress ('z', cmd, 0));
        break;

    case StandardApplicationCommandIDs::redo:
        result.setInfo (TRANS ("Redo"), TRANS ("Redo"), "Editing", 0);
        result.setActive (document != nullptr && document->getUndoManager().canRedo());
        result.defaultKeypresses.add (KeyPress ('z', cmd | shift, 0));
        break;

    case StandardApplicationCommandIDs::cut:
        result.setInfo (TRANS ("Cut"), String::empty, "Editing", 0);
        result.setActive (isSomethingSelected());
        result.defaultKeypresses.add (KeyPress ('x', cmd, 0));
        break;

    case StandardApplicationCommandIDs::copy:
        result.setInfo (TRANS ("Copy"), String::empty, "Editing", 0);
        result.setActive (isSomethingSelected());
        result.defaultKeypresses.add (KeyPress ('c', cmd, 0));
        break;

    case StandardApplicationCommandIDs::paste:
        {
            result.setInfo (TRANS ("Paste"), String::empty, "Editing", 0);
            result.defaultKeypresses.add (KeyPress ('v', cmd, 0));

            bool canPaste = false;

            ScopedPointer<XmlElement> doc (XmlDocument::parse (SystemClipboard::getTextFromClipboard()));

            if (doc != nullptr)
            {
                if (doc->hasTagName (ComponentLayout::clipboardXmlTag))
                    canPaste = (currentLayout != nullptr);
                else if (doc->hasTagName (PaintRoutine::clipboardXmlTag))
                    canPaste = (currentPaintRoutine != nullptr);
            }

            result.setActive (canPaste);
        }

        break;

    case StandardApplicationCommandIDs::del:
        result.setInfo (TRANS ("Delete"), String::empty, "Editing", 0);
        result.setActive (isSomethingSelected());
        break;

    case StandardApplicationCommandIDs::selectAll:
        result.setInfo (TRANS ("Select All"), String::empty, "Editing", 0);
        result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress ('a', cmd, 0));
        break;

    case StandardApplicationCommandIDs::deselectAll:
        result.setInfo (TRANS ("Deselect All"), String::empty, "Editing", 0);
        result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
        result.defaultKeypresses.add (KeyPress ('d', cmd, 0));
        break;

    default:
        break;
    }
}
bool JucerDocumentEditor::perform (const InvocationInfo& info)
{
    ComponentLayout* const currentLayout = getCurrentLayout();
    PaintRoutine* const currentPaintRoutine = getCurrentPaintRoutine();

    document->beginTransaction();

    if (info.commandID >= JucerCommandIDs::newComponentBase
         && info.commandID < JucerCommandIDs::newComponentBase + ObjectTypes::numComponentTypes)
    {
        addComponent (info.commandID - JucerCommandIDs::newComponentBase);
        return true;
    }

    if (info.commandID >= JucerCommandIDs::newElementBase
         && info.commandID < JucerCommandIDs::newElementBase + ObjectTypes::numElementTypes)
    {
        addElement (info.commandID - JucerCommandIDs::newElementBase);
        return true;
    }

    switch (info.commandID)
    {
        case StandardApplicationCommandIDs::undo:
            document->getUndoManager().undo();
            document->dispatchPendingMessages();
            break;

        case StandardApplicationCommandIDs::redo:
            document->getUndoManager().redo();
            document->dispatchPendingMessages();
            break;

        case JucerCommandIDs::test:
            TestComponent::showInDialogBox (*document);
            break;

        case JucerCommandIDs::enableSnapToGrid:
            document->setSnappingGrid (document->getSnappingGridSize(),
                                       ! document->isSnapActive (false),
                                       document->isSnapShown());
            break;

        case JucerCommandIDs::showGrid:
            document->setSnappingGrid (document->getSnappingGridSize(),
                                       document->isSnapActive (false),
                                       ! document->isSnapShown());
            break;

        case JucerCommandIDs::editCompLayout:
            showLayout();
            break;

        case JucerCommandIDs::editCompGraphics:
            showGraphics (0);
            break;

        case JucerCommandIDs::zoomIn:      setZoom (snapToIntegerZoom (getZoom() * 2.0)); break;
        case JucerCommandIDs::zoomOut:     setZoom (snapToIntegerZoom (getZoom() / 2.0)); break;
        case JucerCommandIDs::zoomNormal:  setZoom (1.0); break;

        case JucerCommandIDs::spaceBarDrag:
            if (EditingPanelBase* panel = dynamic_cast<EditingPanelBase*> (tabbedComponent.getCurrentContentComponent()))
                panel->dragKeyHeldDown (info.isKeyDown);

            break;

        case JucerCommandIDs::compOverlay0:
        case JucerCommandIDs::compOverlay33:
        case JucerCommandIDs::compOverlay66:
        case JucerCommandIDs::compOverlay100:
            {
                int amount = 0;

                if (info.commandID == JucerCommandIDs::compOverlay33)
                    amount = 33;
                else if (info.commandID == JucerCommandIDs::compOverlay66)
                    amount = 66;
                else if (info.commandID == JucerCommandIDs::compOverlay100)
                    amount = 100;

                document->setComponentOverlayOpacity (amount * 0.01f);
            }
            break;

        case JucerCommandIDs::bringBackLostItems:
            if (EditingPanelBase* panel = dynamic_cast<EditingPanelBase*> (tabbedComponent.getCurrentContentComponent()))
            {
                int w = panel->getComponentArea().getWidth();
                int h = panel->getComponentArea().getHeight();

                if (currentPaintRoutine != nullptr)
                    currentPaintRoutine->bringLostItemsBackOnScreen (panel->getComponentArea());
                else if (currentLayout != nullptr)
                    currentLayout->bringLostItemsBackOnScreen (w, h);
            }

            break;

        case JucerCommandIDs::toFront:
            if (currentLayout != nullptr)
                currentLayout->selectedToFront();
            else if (currentPaintRoutine != nullptr)
                currentPaintRoutine->selectedToFront();

            break;

        case JucerCommandIDs::toBack:
            if (currentLayout != nullptr)
                currentLayout->selectedToBack();
            else if (currentPaintRoutine != nullptr)
                currentPaintRoutine->selectedToBack();

            break;

        case JucerCommandIDs::group:
            if (currentPaintRoutine != nullptr)
                currentPaintRoutine->groupSelected();
            break;

        case JucerCommandIDs::ungroup:
            if (currentPaintRoutine != nullptr)
                currentPaintRoutine->ungroupSelected();
            break;

        case StandardApplicationCommandIDs::cut:
            if (currentLayout != nullptr)
            {
                currentLayout->copySelectedToClipboard();
                currentLayout->deleteSelected();
            }
            else if (currentPaintRoutine != nullptr)
            {
                currentPaintRoutine->copySelectedToClipboard();
                currentPaintRoutine->deleteSelected();
            }

            break;

        case StandardApplicationCommandIDs::copy:
            if (currentLayout != nullptr)
                currentLayout->copySelectedToClipboard();
            else if (currentPaintRoutine != nullptr)
                currentPaintRoutine->copySelectedToClipboard();

            break;

        case StandardApplicationCommandIDs::paste:
            {
                ScopedPointer<XmlElement> doc (XmlDocument::parse (SystemClipboard::getTextFromClipboard()));

                if (doc != nullptr)
                {
                    if (doc->hasTagName (ComponentLayout::clipboardXmlTag))
                    {
                        if (currentLayout != nullptr)
                            currentLayout->paste();
                    }
                    else if (doc->hasTagName (PaintRoutine::clipboardXmlTag))
                    {
                        if (currentPaintRoutine != nullptr)
                            currentPaintRoutine->paste();
                    }
                }
            }
            break;

        case StandardApplicationCommandIDs::del:
            if (currentLayout != nullptr)
                currentLayout->deleteSelected();
            else if (currentPaintRoutine != nullptr)
                currentPaintRoutine->deleteSelected();
            break;

        case StandardApplicationCommandIDs::selectAll:
            if (currentLayout != nullptr)
                currentLayout->selectAll();
            else if (currentPaintRoutine != nullptr)
                currentPaintRoutine->selectAll();
            break;

        case StandardApplicationCommandIDs::deselectAll:
            if (currentLayout != nullptr)
            {
                currentLayout->getSelectedSet().deselectAll();
            }
            else if (currentPaintRoutine != nullptr)
            {
                currentPaintRoutine->getSelectedElements().deselectAll();
                currentPaintRoutine->getSelectedPoints().deselectAll();
            }

            break;

        default:
            return false;
    }

    document->beginTransaction();
    return true;
}