Home » Modeling » TMF (Xtext) » Embed Editor
Embed Editor [message #1698082] |
Wed, 10 June 2015 22:56 |
Luis De Bello Messages: 95 Registered: January 2015 |
Member |
|
|
Hi guys,
I am trying to use the embed editor functionality, I was able to include the editor as the widget but the editor does not have the line column ruler. I am using xtext 2.8.3.
It seems to me that the builder is not able to create the editor with the line column ruler, also I didn't see any logic to supoort folding functionality. Is there any reason to avoid those features? Or maybe they are there and I am doing something wrong.
Regards,
Luis
[Updated on: Thu, 11 June 2015 03:14] Report message to a moderator
|
|
|
Re: Embed Editor [message #1698085 is a reply to message #1698082] |
Thu, 11 June 2015 04:28 |
|
embeddedEditor = embeddedEditorFactory.newEditor(editedResourceProvider)......showErrorAndWarningAnnotations().........withParent(composite);
...
XtextSourceViewer xtextSourceViewer = editor.getViewer()
...
LineNumberRulerColumn lineNumberRulerColumn = new LineNumberRulerColumn();
xtextSourceViewer.addVerticalRulerColumn(lineNumberRulerColumn);
Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
[Updated on: Thu, 11 June 2015 05:00] Report message to a moderator
|
|
|
Re: Embed Editor [message #1698571 is a reply to message #1698085] |
Tue, 16 June 2015 12:53 |
Luis De Bello Messages: 95 Registered: January 2015 |
Member |
|
|
Hi guys,
I was working on embedding a xtext editor in my app and I was adding folding, line ruler, setting jface font, find and replace option, and format action , in order to use the action you need to search from them and execute it.
I share the code here, it can be useful for someone else
public class MyDslUiModule extends org.xtext.example.mydsl.ui.AbstractMyDslUiModule {
public MyDslUiModule(AbstractUIPlugin plugin) {
super(plugin);
}
public Class<? extends Builder> bindBuilder() {
return CustomEmbeddedEditorBuilder.class;
}
public Class<? extends Factory> bindEmbeddedEditorActions$Factory() {
return CustomEmbeddedEditorActionsFactory.class;
}
}
public final class CustomEditorHelper {
private CustomEditorHelper() {
}
public static EmbeddedEditor createEditor(Composite parent) throws CoreException {
Composite top = new Composite(parent, SWT.NONE);
top.setLayout(new GridLayout());
Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
CustomEmbeddedEditorResourceProvider resourceProvider = injector.getInstance(CustomEmbeddedEditorResourceProvider.class);
EmbeddedEditorFactory factory = injector.getInstance(EmbeddedEditorFactory.class);
EmbeddedEditor editor = factory.newEditor(resourceProvider).showErrorAndWarningAnnotations().withParent(top);
String content = "My mapping here!!!";
EmbeddedEditorModelAccess editorModelAccess = editor.createPartialEditor("", content, "", false);
return editor;
}
}
public abstract class CustomEmbeddedEditor extends EmbeddedEditor {
public CustomEmbeddedEditor(XtextDocument document, XtextSourceViewer viewer, XtextSourceViewerConfiguration configuration, IEditedResourceProvider resourceProvider,
Runnable afterSetDocumet) {
super(document, viewer, configuration, resourceProvider, afterSetDocumet);
}
public abstract void cleanDisposeListener();
public abstract Optional<IAction> getAction(String actionId);
}
public class CustomEmbeddedEditorActions extends EmbeddedEditorActions {
private Shell shell;
private DisposeListener disposeListener;
public CustomEmbeddedEditorActions(ISourceViewer viewer, IWorkbench workbench) {
super(viewer, workbench);
}
@Override
protected void initialize() {
final List<IHandlerActivation> handlerActivations = Lists.newArrayListWithExpectedSize(3);
final IHandlerService handlerService = (IHandlerService) workbench.getAdapter(IHandlerService.class);
final IContextService contextService = (IContextService) workbench.getAdapter(IContextService.class);
shell = viewer.getTextWidget().getShell();
final ActiveShellExpression expression = new ActiveShellExpression(shell);
final IContextActivation contextActivation = contextService.activateContext(EMBEDDED_TEXT_EDITOR_SCOPE, expression);
disposeListener = new ShellDisposeListener();
shell.addDisposeListener(disposeListener);
viewer.getTextWidget().addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
handlerService.deactivateHandlers(handlerActivations);
handlerActivations.clear();
}
@Override
public void focusGained(FocusEvent e) {
for (final IAction action : allActions.values()) {
handlerActivations.add(handlerService.activateHandler(action.getActionDefinitionId(), new ActionHandler(action), expression, true));
}
}
});
createActions();
// create context menu
MenuManager manager = new MenuManager(null, null);
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager mgr) {
fillContextMenu(mgr);
}
});
StyledText text = viewer.getTextWidget();
Menu menu = manager.createContextMenu(text);
text.setMenu(menu);
List<ActionActivationCode> activationCodes = Lists.newArrayList();
setActionActivationCode(activationCodes, ITextEditorActionConstants.SHIFT_RIGHT_TAB, '\t', -1, SWT.NONE);
setActionActivationCode(activationCodes, ITextEditorActionConstants.SHIFT_LEFT, '\t', -1, SWT.SHIFT);
viewer.getTextWidget().addVerifyKeyListener(new ActivationCodeTrigger(allActions, activationCodes));
}
@Override
protected void createActions() {
super.createActions();
ResourceAction action = new FindReplaceAction(EmbeddedEditorMessages.getBundleForConstructedKeys(), "Editor.FindReplace.", Display.getDefault().getActiveShell(),
viewer.getFindReplaceTarget());
action.setHelpContextId(IAbstractTextEditorHelpContextIds.FIND_ACTION);
action.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_FIND_AND_REPLACE);
setAction(ITextEditorActionConstants.FIND, action);
// TODO (Define your own bundle
action = new TextViewerOperationAction(EmbeddedEditorMessages.getBundleForConstructedKeys(), "Format.", viewer, ISourceViewer.FORMAT); //$NON-NLS-1$
action.setActionDefinitionId(IJavaEditorActionDefinitionIds.FORMAT);
setAction("Format", action);
}
@Override
protected void fillContextMenu(IMenuManager menu) {
menu.add(new GroupMarker(ITextEditorActionConstants.GROUP_UNDO));
menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, allActions.get(ITextEditorActionConstants.UNDO));
menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, allActions.get(ITextEditorActionConstants.REDO));
menu.add(new Separator(ITextEditorActionConstants.GROUP_EDIT));
menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, allActions.get(ITextEditorActionConstants.CUT));
menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, allActions.get(ITextEditorActionConstants.COPY));
menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, allActions.get(ITextEditorActionConstants.PASTE));
menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, allActions.get(ITextEditorActionConstants.SELECT_ALL));
menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, allActions.get("Format"));
menu.add(new Separator(ITextEditorActionConstants.GROUP_GENERATE));
menu.appendToGroup(ITextEditorActionConstants.GROUP_GENERATE, allActions.get("ContentAssistProposal")); //$NON-NLS-1$
}
public IAction getAction(String actionId) {
return get(actionId);
}
public void cleanDisposeListener() {
shell.removeDisposeListener(disposeListener);
shell = null;
}
private static final class ShellDisposeListener implements DisposeListener {
private IHandlerService handlerService;
private IContextService contextService;
private IContextActivation contextActivation;
private List<IHandlerActivation> handlerActivations;
@Override
public void widgetDisposed(DisposeEvent e) {
handlerService.deactivateHandlers(handlerActivations);
contextService.deactivateContext(contextActivation);
}
}
}
public class CustomEmbeddedEditorActionsFactory extends Factory {
@Override
protected EmbeddedEditorActions createActions(ISourceViewer viewer) {
return new CustomEmbeddedEditorActions(viewer, workbench);
}
}
public class CustomEmbeddedEditorBuilder extends Builder {
private static final String FG_COLOR_KEY = AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR;
private static final String BG_COLOR_KEY = AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND;
private static final String USE_DEFAULT_BG_KEY = AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT;
@Inject(optional = true)
private AnnotationPainter.IDrawingStrategy projectionAnnotationDrawingStrategy;
@Inject
private CustomEmbeddedEditorFoldingStructureProvider foldingStructureProvider;
private IAnnotationAccess fAnnotationAccess;
protected IAnnotationAccess createAnnotationAccess() {
return new DefaultMarkerAnnotationAccess() {
@Override
public int getLayer(Annotation annotation) {
if (annotation.isMarkedDeleted()) {
return IAnnotationAccessExtension.DEFAULT_LAYER;
}
return super.getLayer(annotation);
}
};
}
protected IAnnotationAccess getAnnotationAccess() {
if (fAnnotationAccess == null) {
fAnnotationAccess = createAnnotationAccess();
}
return fAnnotationAccess;
}
@Override
protected ISharedTextColors getSharedColors() {
return EditorsPlugin.getDefault().getSharedTextColors();
}
protected ProjectionSupport installProjectionSupport(EmbeddedEditor e, ProjectionViewer projectionViewer) {
ProjectionSupport projectionSupport = new ProjectionSupport(projectionViewer, getAnnotationAccess(), getSharedColors());
projectionSupport.setAnnotationPainterDrawingStrategy(projectionAnnotationDrawingStrategy);
projectionSupport.install();
return projectionSupport;
}
@Override
public EmbeddedEditor withParent(final Composite parent) {
if (editorBuild) {
throw new IllegalStateException();
}
editorBuild = true;
// /*fProjectionSupport =*/installProjectionSupport(this.fSourceViewer);
final CompositeRuler annotationRuler;
if (annotationTypes != null && annotationTypes.length != 0) {
annotationRuler = new CompositeRuler();
} else {
annotationRuler = null;
}
final XtextSourceViewer viewer = this.sourceViewerFactory.createSourceViewer(parent, annotationRuler, null, // overviewRuler
false, // showAnnotationOverview
SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
final XtextSourceViewerConfiguration viewerConfiguration = this.sourceViewerConfigurationProvider.get();
viewer.configure(viewerConfiguration);
// Configuring default font
StyledText textWidget = viewer.getTextWidget();
textWidget.setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
// squiggles for markers and other decorations
final SourceViewerDecorationSupport viewerDecorationSupport = new SourceViewerDecorationSupport(viewer, null, // overviewRuler
getAnnotationAccess(), getSharedColors());
MarkerAnnotationPreferences annotationPreferences = new MarkerAnnotationPreferences();
Iterator<AnnotationPreference> e = Iterators.filter(annotationPreferences.getAnnotationPreferences().iterator(), AnnotationPreference.class);
while (e.hasNext()) {
viewerDecorationSupport.setAnnotationPreference(e.next());
}
if (characterPairMatcher != null) {
viewerDecorationSupport.setCharacterPairMatcher(characterPairMatcher);
viewerDecorationSupport.setMatchingCharacterPainterPreferenceKeys(BracketMatchingPreferencesInitializer.IS_ACTIVE_KEY, BracketMatchingPreferencesInitializer.COLOR_KEY);
}
viewerDecorationSupport.install(this.preferenceStoreAccess.getPreferenceStore());
final XtextDocument document = this.documentProvider.get();
IDocumentPartitioner partitioner = this.documentPartitionerProvider.get();
partitioner.connect(document);
document.setDocumentPartitioner(partitioner);
final EmbeddedEditorActions actions = initializeActions(viewer);
parent.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
viewerDecorationSupport.dispose();
highlightingHelper.uninstall();
}
});
viewer.setEditable(!Boolean.TRUE.equals(readonly));
viewer.getContentAssistantFacade().addCompletionListener(new ICompletionListener() {
private Button defaultButton;
@Override
public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
}
@Override
public void assistSessionStarted(ContentAssistEvent event) {
defaultButton = parent.getShell().getDefaultButton();
parent.getShell().setDefaultButton(null);
}
@Override
public void assistSessionEnded(ContentAssistEvent event) {
parent.getShell().setDefaultButton(defaultButton);
defaultButton = null;
}
});
final ValidationJob job = new ValidationJob(this.resourceValidator, document, new IValidationIssueProcessor() {
private AnnotationIssueProcessor annotationIssueProcessor;
@Override
public void processIssues(List<Issue> issues, IProgressMonitor monitor) {
IValidationIssueProcessor issueProcessor = CustomEmbeddedEditorBuilder.this.issueProcessor;
if (issueProcessor != null) {
issueProcessor.processIssues(issues, monitor);
}
IAnnotationModel annotationModel = viewer.getAnnotationModel();
if (annotationModel != null) {
if (this.annotationIssueProcessor == null) {
this.annotationIssueProcessor = new AnnotationIssueProcessor(document, annotationModel, new IssueResolutionProvider() {
@Override
public boolean hasResolutionFor(String issueCode) {
return issueResolutionProvider.hasResolutionFor(issueCode);
}
@Override
public List<IssueResolution> getResolutions(Issue issue) {
List<IssueResolution> resolutions = issueResolutionProvider.getResolutions(issue);
List<IssueResolution> result = Lists.transform(resolutions, new Function<IssueResolution, IssueResolution>() {
@Override
public IssueResolution apply(final IssueResolution input) {
IssueResolution result = new IssueResolution(input.getLabel(), input.getDescription(), input.getImage(), new IModificationContext() {
@Override
public IXtextDocument getXtextDocument(URI uri) {
if (uri.trimFragment().equals(document.getResourceURI())) {
return document;
}
return input.getModificationContext().getXtextDocument(uri);
}
@Override
public IXtextDocument getXtextDocument() {
IModificationContext original = input.getModificationContext();
if (original instanceof IssueModificationContext) {
URI uri = ((IssueModificationContext) original).getIssue().getUriToProblem();
return getXtextDocument(uri);
}
return original.getXtextDocument();
}
}, input.getModification());
return result;
}
});
return result;
}
});
}
if (this.annotationIssueProcessor != null) {
this.annotationIssueProcessor.processIssues(issues, monitor);
}
}
}
}, CheckMode.FAST_ONLY);
document.setValidationJob(job);
final EmbeddedEditor result = new CustomEmbeddedEditor(document, viewer, viewerConfiguration, resourceProvider, new Runnable() {
@Override
public void run() {
afterCreatePartialEditor(viewer, document, annotationRuler, actions);
highlightingHelper.install(viewerConfiguration, viewer);
// Adding line number
LineNumberRulerColumn lineNumberColumn = new LineNumberRulerColumn();
updateForegroundColor(preferenceStoreAccess.getPreferenceStore(), lineNumberColumn);
updateBackgroundColor(preferenceStoreAccess.getPreferenceStore(), lineNumberColumn);
annotationRuler.addDecorator(2, lineNumberColumn);
// Changing color background for error/information marker
Iterator decoratorIterator = annotationRuler.getDecoratorIterator();
while (decoratorIterator.hasNext()) {
Object next = decoratorIterator.next();
if (next instanceof AnnotationRulerColumn) {
AnnotationRulerColumn annotationRulerColumn = (AnnotationRulerColumn) next;
annotationRulerColumn.getControl().setBackground(getSharedColors().getColor(new RGB(236, 237, 236)));
}
}
viewer.doOperation(ProjectionViewer.TOGGLE);
job.schedule();
}
}) {
@Override
public Optional<IAction> getAction(String actionId) {
if (actions instanceof CustomEmbeddedEditorActions) {
return Optional.fromNullable(((CustomEmbeddedEditorActions) actions).getAction(ITextEditorActionConstants.FIND));
}
return Optional.absent();
}
@Override
public void cleanDisposeListener() {
if (actions instanceof CustomEmbeddedEditorActions) {
((CustomEmbeddedEditorActions) actions).cleanDisposeListener();
}
}
};
Control control = viewer.getControl();
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
control.setLayoutData(data);
installProjectionSupport(result, viewer);
foldingStructureProvider.install(result, viewer);
return result;
}
private void updateForegroundColor(IPreferenceStore store, IVerticalRulerColumn column) {
RGB rgb = getColorFromStore(store, FG_COLOR_KEY);
if (rgb == null) {
rgb = new RGB(0, 0, 0);
}
ISharedTextColors sharedColors = getSharedColors();
if (column instanceof LineNumberRulerColumn) {
((LineNumberRulerColumn) column).setForeground(sharedColors.getColor(rgb));
}
}
private void updateBackgroundColor(IPreferenceStore store, IVerticalRulerColumn column) {
// background color: same as editor, or system default
RGB rgb;
if (store.getBoolean(USE_DEFAULT_BG_KEY)) {
rgb = null;
} else {
rgb = getColorFromStore(store, BG_COLOR_KEY);
}
ISharedTextColors sharedColors = getSharedColors();
if (column instanceof LineNumberRulerColumn) {
((LineNumberRulerColumn) column).setBackground(sharedColors.getColor(rgb));
}
}
private static RGB getColorFromStore(IPreferenceStore store, String key) {
RGB rgb = null;
if (store.contains(key)) {
if (store.isDefault(key)) {
rgb = PreferenceConverter.getDefaultColor(store, key);
} else {
rgb = PreferenceConverter.getColor(store, key);
}
}
return rgb;
}
}
public class CustomEmbeddedEditorFoldingStructureProvider implements IXtextModelListener {
@Inject
private IFoldingRegionProvider foldingRegionProvider;
private EmbeddedEditor editor;
private ProjectionViewer viewer;
private ProjectionChangeListener projectionListener;
public void install(EmbeddedEditor editor, ProjectionViewer viewer) {
Assert.isNotNull(editor);
Assert.isNotNull(viewer);
uninstall();
this.editor = editor;
this.viewer = viewer;
projectionListener = new ProjectionChangeListener(viewer);
}
public void initialize() {
calculateProjectionAnnotationModel(true);
}
public void uninstall() {
if (isInstalled()) {
handleProjectionDisabled();
projectionListener.dispose();
projectionListener = null;
editor = null;
}
}
/**
* Returns <code>true</code> if the provider is installed, <code>false</code> otherwise.
*
* @return <code>true</code> if the provider is installed, <code>false</code> otherwise
*/
protected final boolean isInstalled() {
return editor != null;
}
/**
* @see org.eclipse.xtext.ui.editor.model.IXtextModelListener#modelChanged(org.eclipse.xtext.resource.XtextResource)
*/
@Override
public void modelChanged(XtextResource resource) {
if (resource == null) {
return;
}
boolean existingSyntaxErrors = Iterables.any(resource.getErrors(), new Predicate<Diagnostic>() {
@Override
public boolean apply(Diagnostic diagnostic) {
return diagnostic instanceof XtextSyntaxDiagnostic;
}
});
if (!existingSyntaxErrors) {
calculateProjectionAnnotationModel(false);
}
}
protected void handleProjectionEnabled() {
handleProjectionDisabled();
if (isInstalled()) {
initialize();
editor.getDocument().addModelListener(this);
}
}
protected void handleProjectionDisabled() {
if (editor.getDocument() != null) {
editor.getDocument().removeModelListener(this);
}
}
protected void calculateProjectionAnnotationModel(boolean allowCollapse) {
ProjectionAnnotationModel projectionAnnotationModel = this.viewer.getProjectionAnnotationModel();
if (projectionAnnotationModel != null) {
// make a defensive copy as we modify the folded positions in subsequent operations
Collection<FoldedPosition> foldedPositions = Sets.newLinkedHashSet(foldingRegionProvider.getFoldingRegions(editor.getDocument()));
Annotation[] newRegions = mergeFoldingRegions(foldedPositions, projectionAnnotationModel);
updateFoldingRegions(allowCollapse, projectionAnnotationModel, foldedPositions, newRegions);
}
}
@SuppressWarnings("unchecked")
protected Annotation[] mergeFoldingRegions(Collection<FoldedPosition> foldedPositions, ProjectionAnnotationModel projectionAnnotationModel) {
List<Annotation> deletions = new ArrayList<Annotation>();
for (Iterator<Annotation> iterator = projectionAnnotationModel.getAnnotationIterator(); iterator.hasNext();) {
Annotation annotation = iterator.next();
if (annotation instanceof ProjectionAnnotation) {
Position position = projectionAnnotationModel.getPosition(annotation);
if (!foldedPositions.remove(position)) {
deletions.add(annotation);
}
}
}
return deletions.toArray(new Annotation[deletions.size()]);
}
protected void updateFoldingRegions(boolean allowCollapse, ProjectionAnnotationModel model, Collection<FoldedPosition> foldedPositions, Annotation[] deletions) {
Map<ProjectionAnnotation, Position> additionsMap = Maps.newHashMap();
for (FoldedPosition foldedPosition : foldedPositions) {
addProjectionAnnotation(allowCollapse, foldedPosition, additionsMap);
}
if (deletions.length != 0 || additionsMap.size() != 0) {
model.modifyAnnotations(deletions, additionsMap, new Annotation[] {});
}
}
protected void addProjectionAnnotation(boolean allowCollapse, Position foldingRegion, Map<ProjectionAnnotation, Position> additionsMap) {
boolean collapse = allowCollapse && foldingRegion instanceof DefaultFoldedPosition && ((DefaultFoldedPosition) foldingRegion).isInitiallyFolded();
ProjectionAnnotation projectionAnnotation = createProjectionAnnotation(collapse, foldingRegion);
additionsMap.put(projectionAnnotation, foldingRegion);
}
protected ProjectionAnnotation createProjectionAnnotation(boolean isCollapsed, Position foldedRegion) {
return new ProjectionAnnotation(isCollapsed);
}
/**
* Internal projection listener.
*/
public class ProjectionChangeListener implements IProjectionListener {
private ProjectionViewer projectionViewer;
/**
* Registers the listener with the viewer.
*
* @param viewer
* the viewer to register a listener with
*/
public ProjectionChangeListener(ProjectionViewer viewer) {
Assert.isLegal(viewer != null);
projectionViewer = viewer;
projectionViewer.addProjectionListener(this);
}
/**
* Disposes of this listener and removes the projection listener from the viewer.
*/
public void dispose() {
if (projectionViewer != null) {
projectionViewer.removeProjectionListener(this);
projectionViewer = null;
}
}
@Override
public void projectionEnabled() {
handleProjectionEnabled();
}
@Override
public void projectionDisabled() {
handleProjectionDisabled();
}
}
}
public class CustomEmbeddedEditorResourceProvider implements IEditedResourceProvider {
public static final String SYNTHETIC_SCHEME = "synthetic";
@Inject
private IResourceSetProvider resourceSetProvider;
@Inject
private FileExtensionProvider ext;
@Override
public XtextResource createResource() {
ResourceSet resourceSet = resourceSetProvider.get(null);
URI uri = URI.createURI(SYNTHETIC_SCHEME + ":/" + "MyDSL." + ext.getPrimaryFileExtension());
XtextResource result = (XtextResource) resourceSet.createResource(uri);
resourceSet.getResources().add(result);
return result;
}
}
Best regards,
Luis
|
|
|
Re: Embed Editor [message #1785347 is a reply to message #1698571] |
Thu, 12 April 2018 19:52 |
Parsa Pourali Messages: 210 Registered: February 2014 |
Senior Member |
|
|
Hi Luis,
Thanks for sharing your code with us. I have also tried embedded editors in different ways and still I feel like it is not robust enough. Could you please comment on how do you save your resource after changing it through the editor ?
I know this is an old thread by perhaps you are still around and remember what you did :)
Thanks,
Parsa
[Updated on: Thu, 12 April 2018 21:55] Report message to a moderator
|
|
| |
Goto Forum:
Current Time: Wed Sep 25 08:52:45 GMT 2024
Powered by FUDForum. Page generated in 0.07183 seconds
|