kopia lustrzana https://github.com/maccasoft/z80-tools
Added file browser and source code editor
rodzic
441a344dcf
commit
4404375e85
12
build.xml
12
build.xml
|
@ -34,7 +34,7 @@
|
|||
|
||||
<mkdir dir="${work}/bin" />
|
||||
<javac target="1.8" source="1.8" destdir="${work}/bin" srcdir="src" debug="true" includeantruntime="false">
|
||||
<classpath refid="lib.path.ref"/>
|
||||
<classpath refid="lib.path.ref"/>
|
||||
<classpath>
|
||||
<pathelement location="lib/org.eclipse.swt.gtk.linux.x86_64_3.104.2.v20160212-1350.jar"/>
|
||||
</classpath>
|
||||
|
@ -184,7 +184,7 @@
|
|||
|
||||
</target>
|
||||
|
||||
<path id="lib.path.ref">
|
||||
<path id="lib.path.ref">
|
||||
<pathelement location="lib/org.eclipse.core.commands_3.7.0.v20150422-0725.jar"/>
|
||||
<pathelement location="lib/org.eclipse.core.databinding_1.5.0.v20150422-0725.jar"/>
|
||||
<pathelement location="lib/org.eclipse.core.databinding.beans_1.3.0.v20150422-0725.jar"/>
|
||||
|
@ -194,7 +194,10 @@
|
|||
<pathelement location="lib/org.eclipse.jface_3.11.1.v20160128-1644.jar"/>
|
||||
<pathelement location="lib/org.eclipse.jface.databinding_1.7.0.v20150406-2148.jar"/>
|
||||
<pathelement location="lib/org.eclipse.jface.text_3.10.0.v20150603-1752.jar"/>
|
||||
</path>
|
||||
<pathelement location="lib/jackson-annotations-2.7.0.jar"/>
|
||||
<pathelement location="lib/jackson-core-2.7.0.jar"/>
|
||||
<pathelement location="lib/jackson-databind-2.7.0.jar"/>
|
||||
</path>
|
||||
|
||||
<target name="copy-common-files">
|
||||
<jar destfile="${work}/${folder}/lib/${package}.jar">
|
||||
|
@ -214,6 +217,9 @@
|
|||
<fileset dir="lib" includes="org.eclipse.jface_3.11.1.v20160128-1644.jar"/>
|
||||
<fileset dir="lib" includes="org.eclipse.jface.databinding_1.7.0.v20150406-2148.jar"/>
|
||||
<fileset dir="lib" includes="org.eclipse.jface.text_3.10.0.v20150603-1752.jar"/>
|
||||
<fileset dir="lib" includes="jackson-annotations-2.7.0.jar"/>
|
||||
<fileset dir="lib" includes="jackson-core-2.7.0.jar"/>
|
||||
<fileset dir="lib" includes="jackson-databind-2.7.0.jar"/>
|
||||
</copy>
|
||||
|
||||
<copy todir="${work}/${folder}">
|
||||
|
|
|
@ -10,14 +10,32 @@
|
|||
|
||||
package com.maccasoft.tools;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.databinding.observable.Realm;
|
||||
import org.eclipse.jface.databinding.swt.DisplayRealm;
|
||||
import org.eclipse.jface.dialogs.IDialogConstants;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.jface.viewers.IOpenListener;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.OpenEvent;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.custom.CTabFolder;
|
||||
import org.eclipse.swt.custom.CTabFolder2Adapter;
|
||||
import org.eclipse.swt.custom.CTabFolderEvent;
|
||||
import org.eclipse.swt.custom.CTabItem;
|
||||
import org.eclipse.swt.custom.SashForm;
|
||||
import org.eclipse.swt.events.DisposeEvent;
|
||||
import org.eclipse.swt.events.DisposeListener;
|
||||
import org.eclipse.swt.events.MenuEvent;
|
||||
|
@ -29,9 +47,11 @@ import org.eclipse.swt.graphics.GC;
|
|||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.FillLayout;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.FileDialog;
|
||||
import org.eclipse.swt.widgets.Listener;
|
||||
import org.eclipse.swt.widgets.Menu;
|
||||
import org.eclipse.swt.widgets.MenuItem;
|
||||
|
@ -47,10 +67,14 @@ public class Application {
|
|||
Display display;
|
||||
Shell shell;
|
||||
|
||||
SashForm sashForm;
|
||||
FileBrowser browser;
|
||||
CTabFolder tabFolder;
|
||||
|
||||
public Application() {
|
||||
Preferences preferences;
|
||||
|
||||
public Application() {
|
||||
preferences = Preferences.getInstance();
|
||||
}
|
||||
|
||||
public void open() {
|
||||
|
@ -96,10 +120,22 @@ public class Application {
|
|||
|
||||
shell.open();
|
||||
|
||||
shell.addListener(SWT.Close, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event event) {
|
||||
event.doit = handleUnsavedContent();
|
||||
}
|
||||
});
|
||||
shell.addDisposeListener(new DisposeListener() {
|
||||
|
||||
@Override
|
||||
public void widgetDisposed(DisposeEvent e) {
|
||||
try {
|
||||
preferences.save();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
ImageRegistry.dispose();
|
||||
}
|
||||
});
|
||||
|
@ -107,46 +143,93 @@ public class Application {
|
|||
|
||||
void createFileMenu(Menu parent) {
|
||||
final Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN);
|
||||
menu.addMenuListener(new MenuListener() {
|
||||
|
||||
@Override
|
||||
public void menuShown(MenuEvent e) {
|
||||
MenuItem[] item = menu.getItems();
|
||||
for (int i = 0; i < item.length; i++) {
|
||||
item[i].dispose();
|
||||
}
|
||||
populateFileMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void menuHidden(MenuEvent e) {
|
||||
}
|
||||
});
|
||||
populateFileMenu(menu);
|
||||
|
||||
MenuItem item = new MenuItem(parent, SWT.CASCADE);
|
||||
item.setText("&File");
|
||||
item.setMenu(menu);
|
||||
}
|
||||
|
||||
void populateFileMenu(Menu menu) {
|
||||
MenuItem item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("New");
|
||||
item = new MenuItem(menu, SWT.CASCADE);
|
||||
item.setText("New\tCtrl+N");
|
||||
item.setAccelerator(SWT.MOD1 + 'N');
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
SourceEditorTab tab = new SourceEditorTab(tabFolder, "");
|
||||
tab.setText(getDefaultName());
|
||||
tab.setFocus();
|
||||
}
|
||||
|
||||
String getDefaultName() {
|
||||
Date date = new Date();
|
||||
SimpleDateFormat df = new SimpleDateFormat("MMMdd");
|
||||
|
||||
for (char c = 'a'; c <= 'z'; c++) {
|
||||
String result = String.format("%s%c.asm", df.format(date), c);
|
||||
File file = new File(result);
|
||||
if (!file.exists() && !nameExists(result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean nameExists(String name) {
|
||||
CTabItem[] tabItem = tabFolder.getItems();
|
||||
for (int n = 0; n < tabItem.length; n++) {
|
||||
if (name.equals(tabItem[n].getText())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Open...");
|
||||
item.setText("Open...\tCtrl+O");
|
||||
item.setAccelerator(SWT.MOD1 + 'O');
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
try {
|
||||
handleFileOpen();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new MenuItem(menu, SWT.SEPARATOR);
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Close");
|
||||
item.setAccelerator(SWT.MOD1 + 'O');
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
try {
|
||||
handleFileClose();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Close All");
|
||||
item.setAccelerator(SWT.MOD1 + 'O');
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
try {
|
||||
handleFileCloseAll();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -159,7 +242,11 @@ public class Application {
|
|||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
|
||||
try {
|
||||
handleFileSave();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -169,7 +256,11 @@ public class Application {
|
|||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
|
||||
try {
|
||||
handleFileSaveAs();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -180,7 +271,11 @@ public class Application {
|
|||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
|
||||
try {
|
||||
handleFileSaveAll();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -198,7 +293,26 @@ public class Application {
|
|||
|
||||
new MenuItem(menu, SWT.SEPARATOR);
|
||||
|
||||
populateLruFiles(menu);
|
||||
final int lruItemIndex = menu.getItemCount();
|
||||
menu.addMenuListener(new MenuListener() {
|
||||
|
||||
MenuItem[] lruItems;
|
||||
|
||||
@Override
|
||||
public void menuShown(MenuEvent e) {
|
||||
if (lruItems != null) {
|
||||
for (int i = 0; i < lruItems.length; i++) {
|
||||
lruItems[i].dispose();
|
||||
}
|
||||
}
|
||||
lruItems = populateLruFiles(menu, lruItemIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void menuHidden(MenuEvent e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Exit");
|
||||
|
@ -211,27 +325,42 @@ public class Application {
|
|||
});
|
||||
}
|
||||
|
||||
void populateLruFiles(Menu menu) {
|
||||
MenuItem[] populateLruFiles(Menu menu, int itemIndex) {
|
||||
int index = 0;
|
||||
List<MenuItem> list = new ArrayList<MenuItem>();
|
||||
|
||||
Iterator<String> iter = new ArrayList<String>().iterator();
|
||||
Iterator<String> iter = preferences.getLru().iterator();
|
||||
while (iter.hasNext() && index < 4) {
|
||||
final File fileToOpen = new File(iter.next());
|
||||
MenuItem item = new MenuItem(menu, SWT.PUSH);
|
||||
MenuItem item = new MenuItem(menu, SWT.PUSH, itemIndex++);
|
||||
item.setText(String.format("%d %s", index + 1, fileToOpen.getName()));
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
|
||||
try {
|
||||
if (!fileToOpen.exists()) {
|
||||
preferences.removeLru(fileToOpen);
|
||||
return;
|
||||
}
|
||||
SourceEditorTab tab = openSourceTab(fileToOpen);
|
||||
tabFolder.setSelection(tab.getTabItem());
|
||||
tab.setFocus();
|
||||
preferences.addLru(fileToOpen);
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
list.add(item);
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
new MenuItem(menu, SWT.SEPARATOR);
|
||||
list.add(new MenuItem(menu, SWT.SEPARATOR, itemIndex));
|
||||
}
|
||||
|
||||
return list.toArray(new MenuItem[list.size()]);
|
||||
}
|
||||
|
||||
void createEditMenu(Menu parent) {
|
||||
|
@ -378,7 +507,15 @@ public class Application {
|
|||
FontMetrics fontMetrics = gc.getFontMetrics();
|
||||
gc.dispose();
|
||||
|
||||
tabFolder = new CTabFolder(parent, SWT.BORDER);
|
||||
sashForm = new SashForm(parent, SWT.HORIZONTAL);
|
||||
sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
browser = new FileBrowser(sashForm);
|
||||
browser.setRoots(new File[] {
|
||||
new File(System.getProperty("user.home"))
|
||||
});
|
||||
|
||||
tabFolder = new CTabFolder(sashForm, SWT.BORDER);
|
||||
tabFolder.setTabHeight((int) (fontMetrics.getHeight() * 1.5));
|
||||
tabFolder.addSelectionListener(new SelectionAdapter() {
|
||||
|
||||
|
@ -387,6 +524,265 @@ public class Application {
|
|||
|
||||
}
|
||||
});
|
||||
tabFolder.addCTabFolder2Listener(new CTabFolder2Adapter() {
|
||||
|
||||
@Override
|
||||
public void close(CTabFolderEvent event) {
|
||||
SourceEditorTab tab = (SourceEditorTab) event.item.getData();
|
||||
event.doit = canCloseSourceTab(tab);
|
||||
}
|
||||
});
|
||||
|
||||
sashForm.setWeights(new int[] {
|
||||
20, 80
|
||||
});
|
||||
|
||||
String lastPath = preferences.getLastPath();
|
||||
if (lastPath == null) {
|
||||
lastPath = new File("").getAbsolutePath();
|
||||
}
|
||||
File file = new File(lastPath).getAbsoluteFile();
|
||||
if (!file.isDirectory()) {
|
||||
file = file.getParentFile();
|
||||
}
|
||||
browser.setSelection(file);
|
||||
browser.addOpenListener(new IOpenListener() {
|
||||
|
||||
@Override
|
||||
public void open(OpenEvent event) {
|
||||
try {
|
||||
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
|
||||
if (selection.getFirstElement() instanceof File) {
|
||||
File file = (File) selection.getFirstElement();
|
||||
if (file.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
SourceEditorTab tab = openSourceTab(file);
|
||||
tabFolder.setSelection(tab.getTabItem());
|
||||
tab.setFocus();
|
||||
preferences.addLru(file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleFileOpen() {
|
||||
FileDialog dlg = new FileDialog(shell, SWT.OPEN);
|
||||
String[] filterNames = new String[] {
|
||||
"Assembler Files"
|
||||
};
|
||||
String[] filterExtensions = new String[] {
|
||||
"*.ASM;*.asm"
|
||||
};
|
||||
dlg.setFilterNames(filterNames);
|
||||
dlg.setFilterExtensions(filterExtensions);
|
||||
if (preferences.getLastPath() != null) {
|
||||
dlg.setFilterPath(preferences.getLastPath());
|
||||
}
|
||||
dlg.setText("Open File");
|
||||
|
||||
final String fileName = dlg.open();
|
||||
if (fileName != null) {
|
||||
File file = new File(fileName);
|
||||
try {
|
||||
SourceEditorTab tab = openSourceTab(file);
|
||||
tabFolder.setSelection(tab.getTabItem());
|
||||
tab.setFocus();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
preferences.addLru(file);
|
||||
preferences.setLastPath(file.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
SourceEditorTab openSourceTab(File file) throws IOException {
|
||||
String line;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (file.exists()) {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(file));
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
sb.append("\n");
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
|
||||
SourceEditorTab tab = new SourceEditorTab(tabFolder, sb.toString());
|
||||
tab.setText(file.getName());
|
||||
tab.setToolTipText(file.getAbsolutePath());
|
||||
tab.setFile(file);
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
private void handleFileSave() throws IOException {
|
||||
CTabItem tabItem = tabFolder.getSelection();
|
||||
if (tabItem == null) {
|
||||
return;
|
||||
}
|
||||
saveSourceTab((SourceEditorTab) tabItem.getData(), false);
|
||||
}
|
||||
|
||||
private void handleFileSaveAs() throws IOException {
|
||||
CTabItem tabItem = tabFolder.getSelection();
|
||||
if (tabItem == null) {
|
||||
return;
|
||||
}
|
||||
saveSourceTab((SourceEditorTab) tabItem.getData(), true);
|
||||
}
|
||||
|
||||
private void handleFileSaveAll() {
|
||||
CTabItem[] tabItem = tabFolder.getItems();
|
||||
for (int i = 0; i < tabItem.length; i++) {
|
||||
SourceEditorTab tab = (SourceEditorTab) tabItem[i].getData();
|
||||
if (tab.isDirty()) {
|
||||
try {
|
||||
if (saveSourceTab(tab, false) == null) {
|
||||
break;
|
||||
}
|
||||
tab.clearDirty();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleUnsavedContent() {
|
||||
boolean dirty = false;
|
||||
|
||||
CTabItem[] tabItem = tabFolder.getItems();
|
||||
for (int i = 0; i < tabItem.length; i++) {
|
||||
SourceEditorTab tab = (SourceEditorTab) tabItem[i].getData();
|
||||
if (tab.isDirty()) {
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
String msg = "Editor contains unsaved changes. Save before exit?";
|
||||
String[] buttons = new String[] {
|
||||
IDialogConstants.YES_LABEL,
|
||||
IDialogConstants.NO_LABEL,
|
||||
IDialogConstants.CANCEL_LABEL
|
||||
};
|
||||
MessageDialog dlg = new MessageDialog(shell, APP_TITLE, null, msg, MessageDialog.QUESTION, buttons, 0);
|
||||
switch (dlg.open()) {
|
||||
case 0: // YES
|
||||
try {
|
||||
for (int i = 0; i < tabItem.length; i++) {
|
||||
SourceEditorTab tab = (SourceEditorTab) tabItem[i].getData();
|
||||
if (tab.isDirty()) {
|
||||
if (saveSourceTab(tab, false) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
dirty = false;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
case 1: // NO
|
||||
return true;
|
||||
case 2: // CANCEL
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !dirty;
|
||||
}
|
||||
|
||||
File saveSourceTab(SourceEditorTab tab, boolean saveAs) throws IOException {
|
||||
File file = tab.getFile();
|
||||
|
||||
if (saveAs || file == null) {
|
||||
FileDialog dlg = new FileDialog(shell, SWT.SAVE);
|
||||
if (preferences.getLastPath() != null) {
|
||||
dlg.setFilterPath(preferences.getLastPath());
|
||||
}
|
||||
dlg.setFileName(tab.getText());
|
||||
dlg.setText("Save File");
|
||||
|
||||
String fileName = dlg.open();
|
||||
if (fileName == null) {
|
||||
return null;
|
||||
}
|
||||
file = new File(fileName);
|
||||
}
|
||||
|
||||
Writer os = new OutputStreamWriter(new FileOutputStream(file));
|
||||
os.write(tab.getEditor().getText());
|
||||
os.close();
|
||||
|
||||
if (saveAs || tab.getFile() == null) {
|
||||
tab.setFile(file);
|
||||
tab.setText(file.getName());
|
||||
tab.setToolTipText(file.getAbsolutePath());
|
||||
preferences.setLastPath(file.getParent());
|
||||
}
|
||||
|
||||
tab.clearDirty();
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private void handleFileClose() {
|
||||
CTabItem tabItem = tabFolder.getSelection();
|
||||
if (tabItem == null) {
|
||||
return;
|
||||
}
|
||||
SourceEditorTab tab = (SourceEditorTab) tabItem.getData();
|
||||
if (canCloseSourceTab(tab)) {
|
||||
tabItem.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFileCloseAll() {
|
||||
CTabItem[] tabItem = tabFolder.getItems();
|
||||
for (int i = 0; i < tabItem.length; i++) {
|
||||
SourceEditorTab tab = (SourceEditorTab) tabItem[i].getData();
|
||||
if (!canCloseSourceTab(tab)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < tabItem.length; i++) {
|
||||
tabItem[i].dispose();
|
||||
}
|
||||
}
|
||||
|
||||
boolean canCloseSourceTab(SourceEditorTab tab) {
|
||||
if (tab.isDirty()) {
|
||||
String msg = "Save changes in '" + tab.getText() + "'?";
|
||||
String[] buttons = new String[] {
|
||||
IDialogConstants.YES_LABEL,
|
||||
IDialogConstants.NO_LABEL,
|
||||
IDialogConstants.CANCEL_LABEL
|
||||
};
|
||||
MessageDialog dlg = new MessageDialog(shell, APP_TITLE, null, msg, MessageDialog.QUESTION, buttons, 0);
|
||||
switch (dlg.open()) {
|
||||
case 0:
|
||||
try {
|
||||
if (saveSourceTab(tab, false) == null) {
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
return false;
|
||||
}
|
||||
tab.clearDirty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static {
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jface.viewers.IOpenListener;
|
||||
import org.eclipse.jface.viewers.ISelectionChangedListener;
|
||||
import org.eclipse.jface.viewers.ITreeContentProvider;
|
||||
import org.eclipse.jface.viewers.LabelProvider;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.viewers.TreeViewer;
|
||||
import org.eclipse.jface.viewers.Viewer;
|
||||
import org.eclipse.jface.viewers.ViewerSorter;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
||||
import com.maccasoft.tools.internal.ImageRegistry;
|
||||
|
||||
public class FileBrowser {
|
||||
|
||||
TreeViewer viewer;
|
||||
File[] roots;
|
||||
|
||||
Set<String> visibleExtensions;
|
||||
|
||||
class FileLabelProvider extends LabelProvider {
|
||||
|
||||
@Override
|
||||
public String getText(Object element) {
|
||||
return ((File) element).getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getImage(Object element) {
|
||||
if (((File) element).isDirectory()) {
|
||||
return ImageRegistry.getImageFromResources("folder.png");
|
||||
}
|
||||
return ImageRegistry.getImageFromResources("document-code.png");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FileTreeContentProvider implements ITreeContentProvider {
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getElements(Object inputElement) {
|
||||
if (inputElement instanceof File[]) {
|
||||
return (File[]) inputElement;
|
||||
}
|
||||
return new File[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getChildren(Object parentElement) {
|
||||
File[] childs = ((File) parentElement).listFiles(visibleExtensionsFilter);
|
||||
return childs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParent(Object element) {
|
||||
return ((File) element).getParentFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChildren(Object element) {
|
||||
return ((File) element).isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FileSorter extends ViewerSorter {
|
||||
|
||||
@Override
|
||||
public int compare(Viewer viewer, Object e1, Object e2) {
|
||||
if (((File) e1).isDirectory() && !((File) e2).isDirectory()) {
|
||||
return -1;
|
||||
}
|
||||
if (!((File) e1).isDirectory() && ((File) e2).isDirectory()) {
|
||||
return 1;
|
||||
}
|
||||
return super.compare(viewer, e1, e2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final FileFilter visibleExtensionsFilter = new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
String name = pathname.getName();
|
||||
if (pathname.isDirectory()) {
|
||||
if (name.equals(".") || name.equals("..")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (name.startsWith(".")) {
|
||||
return false;
|
||||
}
|
||||
return accept(name);
|
||||
}
|
||||
|
||||
boolean accept(String name) {
|
||||
int i = name.lastIndexOf('.');
|
||||
if (i != -1) {
|
||||
return visibleExtensions.contains(name.substring(i).toLowerCase());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public FileBrowser(Composite parent) {
|
||||
viewer = new TreeViewer(parent);
|
||||
viewer.setLabelProvider(new FileLabelProvider());
|
||||
viewer.setSorter(new FileSorter());
|
||||
viewer.setContentProvider(new FileTreeContentProvider());
|
||||
viewer.setUseHashlookup(true);
|
||||
|
||||
visibleExtensions = new HashSet<String>();
|
||||
visibleExtensions.add(".asm");
|
||||
}
|
||||
|
||||
public File[] getRoots() {
|
||||
return roots;
|
||||
}
|
||||
|
||||
public void setRoots(File[] roots) {
|
||||
this.roots = roots;
|
||||
viewer.setInput(roots);
|
||||
}
|
||||
|
||||
public void setSelection(File file) {
|
||||
viewer.setSelection(new StructuredSelection(file), true);
|
||||
viewer.setExpandedState(file, true);
|
||||
}
|
||||
|
||||
public void addSelectionChangedListener(ISelectionChangedListener l) {
|
||||
viewer.addSelectionChangedListener(l);
|
||||
}
|
||||
|
||||
public void removeSelectionChangedListener(ISelectionChangedListener l) {
|
||||
viewer.removeSelectionChangedListener(l);
|
||||
}
|
||||
|
||||
public void addOpenListener(IOpenListener l) {
|
||||
viewer.addOpenListener(l);
|
||||
}
|
||||
|
||||
public void removeOpenListener(IOpenListener l) {
|
||||
viewer.removeOpenListener(l);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
public class Preferences {
|
||||
|
||||
public static final String PROP_LRU = "lru";
|
||||
public static final String PROP_LASTPATH = "lastPath";
|
||||
|
||||
public static final String PREFERENCES_NAME = ".z80-tools";
|
||||
|
||||
private static Preferences instance;
|
||||
|
||||
public static Preferences getInstance() {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
File file = new File(System.getProperty("user.home"), PREFERENCES_NAME);
|
||||
if (file.exists()) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
instance = mapper.readValue(file, Preferences.class);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
instance = new Preferences();
|
||||
}
|
||||
}
|
||||
else {
|
||||
instance = new Preferences();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
String lastPath;
|
||||
List<String> lru;
|
||||
|
||||
final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
|
||||
|
||||
Preferences() {
|
||||
lru = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
changeSupport.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
changeSupport.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
changeSupport.addPropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
changeSupport.removePropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
public String getLastPath() {
|
||||
return lastPath;
|
||||
}
|
||||
|
||||
public void setLastPath(String lastPath) {
|
||||
this.lastPath = lastPath;
|
||||
}
|
||||
|
||||
public List<String> getLru() {
|
||||
return lru;
|
||||
}
|
||||
|
||||
public void addLru(File file) {
|
||||
lru.remove(file.getAbsolutePath());
|
||||
lru.add(0, file.getAbsolutePath());
|
||||
while (lru.size() > 10) {
|
||||
lru.remove(lru.size() - 1);
|
||||
}
|
||||
changeSupport.firePropertyChange(PROP_LRU, null, this.lru);
|
||||
}
|
||||
|
||||
public void removeLru(File file) {
|
||||
lru.remove(file.getAbsolutePath());
|
||||
changeSupport.firePropertyChange(PROP_LRU, null, this.lru);
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
|
||||
mapper.setSerializationInclusion(Include.NON_EMPTY);
|
||||
mapper.writeValue(new File(System.getProperty("user.home"), PREFERENCES_NAME), this);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
instance = null;
|
||||
for (PropertyChangeListener l : changeSupport.getPropertyChangeListeners()) {
|
||||
changeSupport.removePropertyChangeListener(l);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.custom.CTabFolder;
|
||||
import org.eclipse.swt.custom.CTabItem;
|
||||
import org.eclipse.swt.events.DisposeEvent;
|
||||
import org.eclipse.swt.events.DisposeListener;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
|
||||
import com.maccasoft.tools.editor.SourceEditor;
|
||||
import com.maccasoft.tools.editor.Z80TokenMarker;
|
||||
|
||||
public class SourceEditorTab {
|
||||
|
||||
String text;
|
||||
SourceEditor editor;
|
||||
CTabItem tabItem;
|
||||
|
||||
File file;
|
||||
boolean dirty;
|
||||
|
||||
final ModifyListener modifyListener = new ModifyListener() {
|
||||
|
||||
@Override
|
||||
public void modifyText(ModifyEvent e) {
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
updateTabItemText();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public SourceEditorTab(CTabFolder parent, String text) {
|
||||
editor = new SourceEditor(parent, new Z80TokenMarker());
|
||||
editor.setText(text);
|
||||
|
||||
tabItem = new CTabItem(parent, SWT.NONE);
|
||||
tabItem.setShowClose(true);
|
||||
tabItem.setControl(editor.getControl());
|
||||
tabItem.setData(this);
|
||||
|
||||
editor.addModifyListener(modifyListener);
|
||||
|
||||
tabItem.addDisposeListener(new DisposeListener() {
|
||||
|
||||
@Override
|
||||
public void widgetDisposed(DisposeEvent e) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setFocus() {
|
||||
tabItem.getParent().setSelection(tabItem);
|
||||
editor.getControl().setFocus();
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
if (this.text != null && !text.equals(this.text)) {
|
||||
dirty = true;
|
||||
}
|
||||
this.text = text;
|
||||
updateTabItemText();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setToolTipText(String text) {
|
||||
tabItem.setToolTipText(text);
|
||||
}
|
||||
|
||||
public String getToolTipText() {
|
||||
return tabItem.getToolTipText();
|
||||
}
|
||||
|
||||
public void addModifyListener(ModifyListener listener) {
|
||||
editor.addModifyListener(listener);
|
||||
}
|
||||
|
||||
public void removeModifyListener(ModifyListener listener) {
|
||||
editor.removeModifyListener(listener);
|
||||
}
|
||||
|
||||
public void addDisposeListener(DisposeListener listener) {
|
||||
tabItem.addDisposeListener(listener);
|
||||
}
|
||||
|
||||
public void removeDisposeListener(DisposeListener listener) {
|
||||
tabItem.removeDisposeListener(listener);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
editor.removeModifyListener(modifyListener);
|
||||
editor.getControl().dispose();
|
||||
tabItem.dispose();
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
public void clearDirty() {
|
||||
if (dirty) {
|
||||
dirty = false;
|
||||
}
|
||||
updateTabItemText();
|
||||
}
|
||||
|
||||
void updateTabItemText() {
|
||||
String dirtyFlag = dirty ? "*" : "";
|
||||
tabItem.setText(dirtyFlag + text);
|
||||
}
|
||||
|
||||
public SourceEditor getEditor() {
|
||||
return editor;
|
||||
}
|
||||
|
||||
public CTabItem getTabItem() {
|
||||
return tabItem;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools.editor;
|
||||
|
||||
public class EditorUtil {
|
||||
|
||||
public static boolean isWhitespace(char c) {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
public static boolean isSeparator(char c) {
|
||||
if (Character.isWhitespace(c)) {
|
||||
return false;
|
||||
}
|
||||
return !Character.isLetterOrDigit(c);
|
||||
}
|
||||
|
||||
public static String replaceTabs(String text, int tabs) {
|
||||
final StringBuilder spaces = new StringBuilder(tabs);
|
||||
for (int i = 0; i < tabs; i++) {
|
||||
spaces.append(' ');
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while ((i = text.indexOf('\t', i)) != -1) {
|
||||
int s = i;
|
||||
while (s > 0) {
|
||||
s--;
|
||||
if (text.charAt(s) == '\r' || text.charAt(s) == '\n') {
|
||||
s++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int n = ((i - s) % tabs);
|
||||
text = text.substring(0, i) + spaces.substring(0, tabs - n) + text.substring(i + 1);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools.editor;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.custom.StyledText;
|
||||
import org.eclipse.swt.events.PaintEvent;
|
||||
import org.eclipse.swt.events.PaintListener;
|
||||
import org.eclipse.swt.graphics.Font;
|
||||
import org.eclipse.swt.graphics.FontMetrics;
|
||||
import org.eclipse.swt.graphics.GC;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Canvas;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.ScrollBar;
|
||||
|
||||
public class LineNumbersRuler {
|
||||
|
||||
Canvas canvas;
|
||||
GridData layoutData;
|
||||
FontMetrics fontMetrics;
|
||||
|
||||
StyledText text;
|
||||
|
||||
int leftMargin;
|
||||
int rightMargin;
|
||||
|
||||
private int scrollBarSelection;
|
||||
private int lineCount;
|
||||
|
||||
final PaintListener paintListener = new PaintListener() {
|
||||
|
||||
@Override
|
||||
public void paintControl(PaintEvent e) {
|
||||
if (text != null) {
|
||||
onPaintControl(e.gc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final PaintListener textPaintListener = new PaintListener() {
|
||||
|
||||
@Override
|
||||
public void paintControl(PaintEvent e) {
|
||||
ScrollBar scrollBar = text.getVerticalBar();
|
||||
if (scrollBarSelection != scrollBar.getSelection() || lineCount != text.getLineCount()) {
|
||||
canvas.redraw();
|
||||
scrollBarSelection = scrollBar.getSelection();
|
||||
lineCount = text.getLineCount();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public LineNumbersRuler(Composite parent) {
|
||||
canvas = new Canvas(parent, SWT.NO_FOCUS);
|
||||
canvas.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
|
||||
canvas.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND));
|
||||
canvas.setLayoutData(layoutData = new GridData(SWT.FILL, SWT.FILL, false, true));
|
||||
canvas.addPaintListener(paintListener);
|
||||
|
||||
GC gc = new GC(canvas);
|
||||
fontMetrics = gc.getFontMetrics();
|
||||
gc.dispose();
|
||||
|
||||
leftMargin = rightMargin = 5;
|
||||
|
||||
layoutData.widthHint = leftMargin + fontMetrics.getAverageCharWidth() * 4 + rightMargin;
|
||||
|
||||
scrollBarSelection = lineCount = -1;
|
||||
}
|
||||
|
||||
public void setText(StyledText text) {
|
||||
this.text = text;
|
||||
this.text.addPaintListener(textPaintListener);
|
||||
}
|
||||
|
||||
void onPaintControl(GC gc) {
|
||||
Rectangle rect = canvas.getClientArea();
|
||||
|
||||
gc.setClipping(0, text.getTopMargin(), rect.width, rect.height - text.getTopMargin() - text.getBottomMargin());
|
||||
|
||||
int lineNumber = text.getTopIndex() - 1;
|
||||
if (lineNumber < 0) {
|
||||
lineNumber = 0;
|
||||
}
|
||||
while (lineNumber < text.getLineCount()) {
|
||||
int y = text.getLinePixel(lineNumber);
|
||||
if (y >= rect.height) {
|
||||
break;
|
||||
}
|
||||
String s = Integer.toString(lineNumber + 1);
|
||||
gc.drawString(s, rect.width - gc.stringExtent(s).x - rightMargin, y);
|
||||
lineNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
canvas.setFont(font);
|
||||
|
||||
GC gc = new GC(canvas);
|
||||
fontMetrics = gc.getFontMetrics();
|
||||
gc.dispose();
|
||||
|
||||
layoutData.widthHint = leftMargin + fontMetrics.getAverageCharWidth() * 4 + rightMargin;
|
||||
|
||||
canvas.redraw();
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
canvas.setVisible(visible);
|
||||
layoutData.exclude = !visible;
|
||||
}
|
||||
|
||||
public void redraw() {
|
||||
canvas.redraw();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools.editor;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.custom.StyledText;
|
||||
import org.eclipse.swt.events.DisposeListener;
|
||||
import org.eclipse.swt.events.FocusEvent;
|
||||
import org.eclipse.swt.events.FocusListener;
|
||||
import org.eclipse.swt.events.KeyEvent;
|
||||
import org.eclipse.swt.events.KeyListener;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.TraverseEvent;
|
||||
import org.eclipse.swt.events.TraverseListener;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
public class SearchBox {
|
||||
|
||||
Shell parentShell;
|
||||
StyledText control;
|
||||
|
||||
Shell shell;
|
||||
Label label;
|
||||
Text text;
|
||||
|
||||
static String lastSearch;
|
||||
int lastIndex;
|
||||
|
||||
final TraverseListener traverseListener = new TraverseListener() {
|
||||
|
||||
@Override
|
||||
public void keyTraversed(TraverseEvent e) {
|
||||
}
|
||||
};
|
||||
|
||||
final ModifyListener modifyListener = new ModifyListener() {
|
||||
|
||||
@Override
|
||||
public void modifyText(ModifyEvent e) {
|
||||
if (text.getText().length() == 0) {
|
||||
return;
|
||||
}
|
||||
text.getDisplay().timerExec(250, searchRunnable);
|
||||
}
|
||||
};
|
||||
|
||||
final Runnable searchRunnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (text.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
searchNext(text.getText());
|
||||
}
|
||||
};
|
||||
|
||||
final KeyListener keyListener = new KeyListener() {
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.stateMask != 0) {
|
||||
return;
|
||||
}
|
||||
if (e.character == 0x1B) {
|
||||
shell.setVisible(false);
|
||||
shell.dispose();
|
||||
e.doit = false;
|
||||
return;
|
||||
}
|
||||
if (e.character == 0x0D) {
|
||||
lastSearch = text.getText();
|
||||
searchNext();
|
||||
e.doit = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final FocusListener focusListener = new FocusListener() {
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
shell.setVisible(false);
|
||||
shell.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
}
|
||||
};
|
||||
|
||||
public SearchBox(StyledText control) {
|
||||
this.control = control;
|
||||
this.parentShell = control.getShell();
|
||||
}
|
||||
|
||||
public void setLabelText(String text) {
|
||||
label.setText(text);
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text.setText(text);
|
||||
}
|
||||
|
||||
public void open() {
|
||||
shell = new Shell(parentShell, SWT.ON_TOP | SWT.TOOL);
|
||||
GridLayout rowLayout = new GridLayout(1, false);
|
||||
rowLayout.marginWidth = 5;
|
||||
rowLayout.marginHeight = 5;
|
||||
rowLayout.verticalSpacing = 2;
|
||||
shell.setLayout(rowLayout);
|
||||
shell.addTraverseListener(traverseListener);
|
||||
|
||||
label = new Label(shell, SWT.NONE);
|
||||
label.setText("Search:");
|
||||
label.setLayoutData(new GridData(SWT.BEGINNING, SWT.TOP, false, false));
|
||||
|
||||
text = new Text(shell, SWT.BORDER);
|
||||
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
|
||||
if (lastSearch != null) {
|
||||
text.setText(lastSearch);
|
||||
text.setSelection(0, lastSearch.length());
|
||||
}
|
||||
text.addKeyListener(keyListener);
|
||||
text.addFocusListener(focusListener);
|
||||
text.addModifyListener(modifyListener);
|
||||
|
||||
Point size = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
|
||||
if (size.x < 200) {
|
||||
size.x = 200;
|
||||
}
|
||||
Point location = control.toDisplay(control.getLocation());
|
||||
Point parentSize = control.getSize();
|
||||
shell.setBounds(location.x + parentSize.x - size.x - 10, location.y + parentSize.y - size.y - 10, size.x, size.y);
|
||||
|
||||
shell.open();
|
||||
text.setFocus();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (shell != null && !shell.isDisposed()) {
|
||||
shell.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void searchNext(String string) {
|
||||
lastSearch = string;
|
||||
searchNext(control.getCaretOffset());
|
||||
}
|
||||
|
||||
public void searchNext() {
|
||||
searchNext(control.getCaretOffset() + 1);
|
||||
}
|
||||
|
||||
public void searchNext(int fromIndex) {
|
||||
if (lastSearch == null) {
|
||||
return;
|
||||
}
|
||||
String search = lastSearch.toLowerCase();
|
||||
String controlText = control.getText().toLowerCase();
|
||||
int index = controlText.indexOf(search, fromIndex);
|
||||
if (index == -1) {
|
||||
index = controlText.indexOf(search);
|
||||
}
|
||||
if (index != -1) {
|
||||
control.setCaretOffset(index);
|
||||
control.setSelection(index + lastSearch.length(), index);
|
||||
|
||||
int line = control.getLineAtOffset(index);
|
||||
control.setTopIndex(line > 10 ? line - 10 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void addDisposeListenr(DisposeListener l) {
|
||||
text.addDisposeListener(l);
|
||||
}
|
||||
|
||||
public void removeDisposeListenr(DisposeListener l) {
|
||||
text.removeDisposeListener(l);
|
||||
}
|
||||
|
||||
public void setLastSearch(String string) {
|
||||
lastSearch = string;
|
||||
}
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools.editor;
|
||||
|
||||
public class Token {
|
||||
|
||||
public TokenId id;
|
||||
public int length;
|
||||
public Token next;
|
||||
|
||||
public Token(int length, TokenId id) {
|
||||
this.length = length;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[id=" + id + ",length=" + length + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools.editor;
|
||||
|
||||
public enum TokenId {
|
||||
Null,
|
||||
Comment,
|
||||
StringLiteral1,
|
||||
StringLiteral2,
|
||||
NumberLiteral,
|
||||
Instruction,
|
||||
Register,
|
||||
Flag,
|
||||
Directive1,
|
||||
Directive2,
|
||||
End
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools.editor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class TokenMarker {
|
||||
|
||||
static Map<String, TokenId> keywords = new HashMap<String, TokenId>();
|
||||
|
||||
final Map<String, TokenId> additionalKeywords = new HashMap<String, TokenId>();
|
||||
|
||||
Token firstToken;
|
||||
Token lastToken;
|
||||
|
||||
int lastKeyword;
|
||||
int lastOffset;
|
||||
|
||||
public TokenMarker() {
|
||||
}
|
||||
|
||||
public boolean refreshMultilineComments(String text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract Token markTokens(String text, int offset);
|
||||
|
||||
public void clearKeywords() {
|
||||
additionalKeywords.clear();
|
||||
}
|
||||
|
||||
public void addKeyword(String text, TokenId id) {
|
||||
if (text == null || "".equals(text)) {
|
||||
return;
|
||||
}
|
||||
additionalKeywords.put(text, id);
|
||||
}
|
||||
|
||||
public void removeKeyword(String text) {
|
||||
additionalKeywords.remove(text);
|
||||
}
|
||||
|
||||
protected void addToken(int length, TokenId id) {
|
||||
if (length == 0 && id != TokenId.End) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstToken == null) {
|
||||
firstToken = new Token(length, id);
|
||||
lastToken = firstToken;
|
||||
}
|
||||
else if (lastToken == null) {
|
||||
lastToken = firstToken;
|
||||
firstToken.length = length;
|
||||
firstToken.id = id;
|
||||
}
|
||||
else if (lastToken.next == null) {
|
||||
lastToken.next = new Token(length, id);
|
||||
lastToken = lastToken.next;
|
||||
}
|
||||
else {
|
||||
lastToken = lastToken.next;
|
||||
lastToken.length = length;
|
||||
lastToken.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
protected TokenId doKeyword(String line, int i, char c) {
|
||||
int i1 = i + 1;
|
||||
int len = i - lastKeyword;
|
||||
String s = line.substring(lastKeyword, i);
|
||||
|
||||
TokenId id = getNumberTokenId(s);
|
||||
if (id == null) {
|
||||
id = keywords.get(s.toUpperCase());
|
||||
}
|
||||
if (id == null) {
|
||||
id = additionalKeywords.get(s);
|
||||
}
|
||||
if (id != null) {
|
||||
if (lastKeyword != lastOffset) {
|
||||
addToken(lastKeyword - lastOffset, TokenId.Null);
|
||||
}
|
||||
addToken(len, id);
|
||||
lastOffset = i;
|
||||
}
|
||||
lastKeyword = i1;
|
||||
return id;
|
||||
}
|
||||
|
||||
TokenId getNumberTokenId(String s) {
|
||||
if (s.length() > 0 && (Character.isDigit(s.charAt(0)) || s.charAt(0) == '$')) {
|
||||
int i = 1;
|
||||
char c0 = s.charAt(0);
|
||||
while (i < s.length()) {
|
||||
if (!isHexDigit(s.charAt(i))) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i == s.length()) {
|
||||
return TokenId.NumberLiteral;
|
||||
}
|
||||
if (c0 == '$' && !Character.isWhitespace(s.charAt(i))) {
|
||||
return null;
|
||||
}
|
||||
if (s.charAt(i) == 'H' || s.charAt(i) == 'h' || s.charAt(i) == 'B' || s.charAt(i) == 'b') {
|
||||
return TokenId.NumberLiteral;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isHexDigit(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return true;
|
||||
}
|
||||
if ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools.editor;
|
||||
|
||||
public class Z80TokenMarker extends TokenMarker {
|
||||
|
||||
static {
|
||||
keywords.put("ADC", TokenId.Instruction);
|
||||
keywords.put("ADD", TokenId.Instruction);
|
||||
keywords.put("AND", TokenId.Instruction);
|
||||
keywords.put("BIT", TokenId.Instruction);
|
||||
keywords.put("CALL", TokenId.Instruction);
|
||||
keywords.put("CCF", TokenId.Instruction);
|
||||
keywords.put("CP", TokenId.Instruction);
|
||||
keywords.put("CPD", TokenId.Instruction);
|
||||
keywords.put("CPDR", TokenId.Instruction);
|
||||
keywords.put("CPI", TokenId.Instruction);
|
||||
keywords.put("CPIR", TokenId.Instruction);
|
||||
keywords.put("CPL", TokenId.Instruction);
|
||||
keywords.put("DAA", TokenId.Instruction);
|
||||
keywords.put("DEC", TokenId.Instruction);
|
||||
keywords.put("DI", TokenId.Instruction);
|
||||
keywords.put("DJNZ", TokenId.Instruction);
|
||||
keywords.put("EI", TokenId.Instruction);
|
||||
keywords.put("EX", TokenId.Instruction);
|
||||
keywords.put("EXX", TokenId.Instruction);
|
||||
keywords.put("HALT", TokenId.Instruction);
|
||||
keywords.put("IM", TokenId.Instruction);
|
||||
keywords.put("INC", TokenId.Instruction);
|
||||
keywords.put("IN", TokenId.Instruction);
|
||||
keywords.put("INI", TokenId.Instruction);
|
||||
keywords.put("IND", TokenId.Instruction);
|
||||
keywords.put("INIR", TokenId.Instruction);
|
||||
keywords.put("INDR", TokenId.Instruction);
|
||||
keywords.put("JR", TokenId.Instruction);
|
||||
keywords.put("JP", TokenId.Instruction);
|
||||
keywords.put("LD", TokenId.Instruction);
|
||||
keywords.put("LDI", TokenId.Instruction);
|
||||
keywords.put("LDIR", TokenId.Instruction);
|
||||
keywords.put("LDD", TokenId.Instruction);
|
||||
keywords.put("LDDR", TokenId.Instruction);
|
||||
keywords.put("NEG", TokenId.Instruction);
|
||||
keywords.put("NOP", TokenId.Instruction);
|
||||
keywords.put("OR", TokenId.Instruction);
|
||||
keywords.put("OUT", TokenId.Instruction);
|
||||
keywords.put("OUTI", TokenId.Instruction);
|
||||
keywords.put("OUTD", TokenId.Instruction);
|
||||
keywords.put("OTIR", TokenId.Instruction);
|
||||
keywords.put("OTDR", TokenId.Instruction);
|
||||
keywords.put("POP", TokenId.Instruction);
|
||||
keywords.put("PUSH", TokenId.Instruction);
|
||||
keywords.put("RES", TokenId.Instruction);
|
||||
keywords.put("RET", TokenId.Instruction);
|
||||
keywords.put("RETI", TokenId.Instruction);
|
||||
keywords.put("RETN", TokenId.Instruction);
|
||||
keywords.put("RL", TokenId.Instruction);
|
||||
keywords.put("RLA", TokenId.Instruction);
|
||||
keywords.put("RLC", TokenId.Instruction);
|
||||
keywords.put("RLCA", TokenId.Instruction);
|
||||
keywords.put("RLD", TokenId.Instruction);
|
||||
keywords.put("RR", TokenId.Instruction);
|
||||
keywords.put("RRA", TokenId.Instruction);
|
||||
keywords.put("RRC", TokenId.Instruction);
|
||||
keywords.put("RRCA", TokenId.Instruction);
|
||||
keywords.put("RRD", TokenId.Instruction);
|
||||
keywords.put("RST", TokenId.Instruction);
|
||||
keywords.put("SBC", TokenId.Instruction);
|
||||
keywords.put("SCF", TokenId.Instruction);
|
||||
keywords.put("SET", TokenId.Instruction);
|
||||
keywords.put("SLA", TokenId.Instruction);
|
||||
keywords.put("SRA", TokenId.Instruction);
|
||||
keywords.put("SRL", TokenId.Instruction);
|
||||
keywords.put("SUB", TokenId.Instruction);
|
||||
keywords.put("XOR", TokenId.Instruction);
|
||||
|
||||
keywords.put("DB", TokenId.Directive2);
|
||||
keywords.put("DD", TokenId.Directive2);
|
||||
keywords.put("DS", TokenId.Directive2);
|
||||
keywords.put("DW", TokenId.Directive2);
|
||||
|
||||
keywords.put("INCLUDE", TokenId.Directive1);
|
||||
keywords.put("EQU", TokenId.Directive1);
|
||||
keywords.put("ORG", TokenId.Directive2);
|
||||
keywords.put("ENDM", TokenId.Directive1);
|
||||
keywords.put("ENDP", TokenId.Directive1);
|
||||
keywords.put("ENDS", TokenId.Directive1);
|
||||
keywords.put("END", TokenId.Directive1);
|
||||
keywords.put("ENDIF", TokenId.Directive1);
|
||||
keywords.put("ELSE", TokenId.Directive1);
|
||||
keywords.put("ERROR", TokenId.Directive1);
|
||||
keywords.put("WARNING", TokenId.Directive1);
|
||||
keywords.put("FILL", TokenId.Directive1);
|
||||
|
||||
keywords.put(".DB", TokenId.Directive2);
|
||||
keywords.put(".DD", TokenId.Directive2);
|
||||
keywords.put(".DS", TokenId.Directive2);
|
||||
keywords.put(".DW", TokenId.Directive2);
|
||||
|
||||
keywords.put(".INCLUDE", TokenId.Directive1);
|
||||
keywords.put(".EQU", TokenId.Directive1);
|
||||
keywords.put(".ORG", TokenId.Directive2);
|
||||
keywords.put(".ENDM", TokenId.Directive1);
|
||||
keywords.put(".ENDP", TokenId.Directive1);
|
||||
keywords.put(".ENDS", TokenId.Directive1);
|
||||
keywords.put(".END", TokenId.Directive1);
|
||||
keywords.put(".ENDIF", TokenId.Directive1);
|
||||
keywords.put(".ELSE", TokenId.Directive1);
|
||||
keywords.put(".ERROR", TokenId.Directive1);
|
||||
keywords.put(".WARNING", TokenId.Directive1);
|
||||
keywords.put(".FILL", TokenId.Directive1);
|
||||
|
||||
keywords.put("A", TokenId.Register);
|
||||
keywords.put("B", TokenId.Register);
|
||||
keywords.put("C", TokenId.Register);
|
||||
keywords.put("D", TokenId.Register);
|
||||
keywords.put("E", TokenId.Register);
|
||||
keywords.put("F", TokenId.Register);
|
||||
keywords.put("H", TokenId.Register);
|
||||
keywords.put("L", TokenId.Register);
|
||||
keywords.put("AF", TokenId.Register);
|
||||
keywords.put("BC", TokenId.Register);
|
||||
keywords.put("DE", TokenId.Register);
|
||||
keywords.put("HL", TokenId.Register);
|
||||
keywords.put("SP", TokenId.Register);
|
||||
keywords.put("IX", TokenId.Register);
|
||||
keywords.put("IY", TokenId.Register);
|
||||
keywords.put("I", TokenId.Register);
|
||||
keywords.put("R", TokenId.Register);
|
||||
|
||||
keywords.put("Z", TokenId.Flag);
|
||||
keywords.put("NZ", TokenId.Flag);
|
||||
keywords.put("C", TokenId.Flag);
|
||||
keywords.put("NC", TokenId.Flag);
|
||||
keywords.put("PO", TokenId.Flag);
|
||||
keywords.put("PE", TokenId.Flag);
|
||||
keywords.put("P", TokenId.Flag);
|
||||
keywords.put("M", TokenId.Flag);
|
||||
}
|
||||
|
||||
public Z80TokenMarker() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token markTokens(String text, int offset) {
|
||||
boolean backslash = false;
|
||||
|
||||
TokenId token = TokenId.Null;
|
||||
firstToken = lastToken = null;
|
||||
lastOffset = lastKeyword = 0;
|
||||
|
||||
int start = 0;
|
||||
|
||||
for (int i = start; i < text.length(); i++) {
|
||||
int i1 = (i + 1);
|
||||
char c = text.charAt(i);
|
||||
|
||||
if (c == '\\') {
|
||||
backslash = !backslash;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (token) {
|
||||
case Null:
|
||||
switch (c) {
|
||||
case '\'':
|
||||
case '"':
|
||||
doKeyword(text, i, c);
|
||||
if (backslash) {
|
||||
backslash = false;
|
||||
}
|
||||
else {
|
||||
addToken(i - lastOffset, token);
|
||||
token = c == '"' ? TokenId.StringLiteral1 : TokenId.StringLiteral2;
|
||||
lastOffset = lastKeyword = i;
|
||||
}
|
||||
break;
|
||||
case ';': {
|
||||
backslash = false;
|
||||
doKeyword(text, i, c);
|
||||
addToken(i - lastOffset, token);
|
||||
int mlength = text.indexOf('\n', i);
|
||||
if (mlength == -1) {
|
||||
mlength = text.length();
|
||||
}
|
||||
addToken(mlength - i, TokenId.Comment);
|
||||
lastOffset = lastKeyword = i = mlength;
|
||||
break;
|
||||
}
|
||||
case '$':
|
||||
backslash = false;
|
||||
doKeyword(text, i, c);
|
||||
addToken(i - lastOffset, token);
|
||||
|
||||
int mlength = i + 1;
|
||||
while (mlength < text.length() && isHexDigit(text.charAt(mlength))) {
|
||||
mlength++;
|
||||
}
|
||||
if ((mlength - i) > 1) {
|
||||
addToken(mlength - i, TokenId.NumberLiteral);
|
||||
lastOffset = lastKeyword = i = mlength;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
backslash = false;
|
||||
if (!Character.isLetterOrDigit(c) && c != '_' && c != '.') {
|
||||
doKeyword(text, i, c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case StringLiteral1:
|
||||
if (backslash) {
|
||||
backslash = false;
|
||||
}
|
||||
else if (c == '"') {
|
||||
addToken(i1 - lastOffset, token);
|
||||
token = TokenId.Null;
|
||||
lastOffset = lastKeyword = i1;
|
||||
}
|
||||
break;
|
||||
case StringLiteral2:
|
||||
if (backslash) {
|
||||
backslash = false;
|
||||
}
|
||||
else if (c == '\'') {
|
||||
addToken(i1 - lastOffset, token);
|
||||
token = TokenId.Null;
|
||||
lastOffset = lastKeyword = i1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (token == TokenId.Null) {
|
||||
doKeyword(text, text.length(), '\0');
|
||||
}
|
||||
|
||||
switch (token) {
|
||||
case StringLiteral1:
|
||||
case StringLiteral2:
|
||||
addToken(text.length() - lastOffset, null);
|
||||
token = null;
|
||||
break;
|
||||
default:
|
||||
addToken(text.length() - lastOffset, token);
|
||||
break;
|
||||
}
|
||||
|
||||
return firstToken;
|
||||
}
|
||||
|
||||
}
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 590 B |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 537 B |
Ładowanie…
Reference in New Issue