/*
    Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
    и других спецификаций для функционирования компактных приложений на языке
    Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
*/

package malik.emulator.microedition.system;

import com.nokia.mid.ui.*;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import javax.microedition.lcdui.*;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import javax.microedition.media.protocol.*;
import javax.microedition.rms.*;
import javax.wireless.messaging.*;
import malik.emulator.application.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.graphics.*;
import malik.emulator.fileformats.graphics.jpeg.*;
import malik.emulator.fileformats.graphics.png.*;
import malik.emulator.fileformats.sound.sampled.*;
import malik.emulator.fileformats.sound.sampled.wave.*;
import malik.emulator.fileformats.sound.synthetic.*;
import malik.emulator.fileformats.sound.synthetic.midi.*;
import malik.emulator.io.cloud.*;
import malik.emulator.media.graphics.*;
import malik.emulator.microedition.*;
import malik.emulator.microedition.lcdui.*;
import malik.emulator.microedition.media.*;
import malik.emulator.microedition.system.player.*;
import malik.emulator.microedition.system.protocol.*;
import malik.emulator.microedition.system.protocol.file.*;
import malik.emulator.microedition.system.protocol.resource.*;
import malik.emulator.microedition.system.protocol.sms.*;
import malik.emulator.util.*;

public class SystemManager extends DeviceManager implements CommandListener, ItemCommandListener, ItemStateListener, ThreadTerminationListener
{
    public static final int MIDLET_NAME = 0;
    public static final int MIDLET_ICON = 1;
    public static final int MIDLET_CLASS = 2;

    private static final int[] FONT_SIZES;
    private static final int[] DEVICE_KEYS;
    private static final int[] KEYBOARD_KEYS;
    private static final KeyCodeSet[] KEY_CODE_SETS;
    private static final String[] EMPTY_STRING_ARRAY;

    static {
        FONT_SIZES = new int[] {
            DeviceSettings.FONT_SIZE_SMALL, DeviceSettings.FONT_SIZE_MEDIUM, DeviceSettings.FONT_SIZE_LARGE
        };
        DEVICE_KEYS = new int[] {
            DeviceSettings.DEVICE_KEY_UP, DeviceSettings.DEVICE_KEY_DOWN, DeviceSettings.DEVICE_KEY_LEFT,
            DeviceSettings.DEVICE_KEY_RIGHT, DeviceSettings.DEVICE_KEY_SELECT, DeviceSettings.DEVICE_KEY_SOFT1,
            DeviceSettings.DEVICE_KEY_SOFT2, DeviceSettings.DEVICE_KEY_CONSOLE, DeviceSettings.DEVICE_KEY_EXITAPP,
            DeviceSettings.DEVICE_KEY_1, DeviceSettings.DEVICE_KEY_2, DeviceSettings.DEVICE_KEY_3,
            DeviceSettings.DEVICE_KEY_4, DeviceSettings.DEVICE_KEY_5, DeviceSettings.DEVICE_KEY_6,
            DeviceSettings.DEVICE_KEY_7, DeviceSettings.DEVICE_KEY_8, DeviceSettings.DEVICE_KEY_9,
            DeviceSettings.DEVICE_KEY_STAR, DeviceSettings.DEVICE_KEY_0, DeviceSettings.DEVICE_KEY_POUND
        };
        KEYBOARD_KEYS = new int[] {
            0, KeyboardEvent.KEY_ESCAPE, KeyboardEvent.KEY_F1, KeyboardEvent.KEY_F2, KeyboardEvent.KEY_F3, KeyboardEvent.KEY_F4, KeyboardEvent.KEY_F5, KeyboardEvent.KEY_F6, KeyboardEvent.KEY_F7,
            KeyboardEvent.KEY_F8, KeyboardEvent.KEY_F9, KeyboardEvent.KEY_F10, KeyboardEvent.KEY_F11, KeyboardEvent.KEY_F12, KeyboardEvent.KEY_ENTER, KeyboardEvent.KEY_SPACE, KeyboardEvent.KEY_INSERT,
            KeyboardEvent.KEY_DELETE, KeyboardEvent.KEY_HOME, KeyboardEvent.KEY_END, KeyboardEvent.KEY_PAGE_UP, KeyboardEvent.KEY_PAGE_DOWN, KeyboardEvent.KEY_UP, KeyboardEvent.KEY_DOWN,
            KeyboardEvent.KEY_LEFT, KeyboardEvent.KEY_RIGHT, KeyboardEvent.KEY_NUM_PLUS, KeyboardEvent.KEY_NUM_MINUS, KeyboardEvent.KEY_NUM_STAR, KeyboardEvent.KEY_NUM_DIVIDE,
            KeyboardEvent.KEY_NUM_DECIMAL, KeyboardEvent.KEY_NUM_0, KeyboardEvent.KEY_NUM_1, KeyboardEvent.KEY_NUM_2, KeyboardEvent.KEY_NUM_3, KeyboardEvent.KEY_NUM_4, KeyboardEvent.KEY_NUM_5,
            KeyboardEvent.KEY_NUM_6, KeyboardEvent.KEY_NUM_7, KeyboardEvent.KEY_NUM_8, KeyboardEvent.KEY_NUM_9
        };
        KEY_CODE_SETS = new KeyCodeSet[] {
            new KeyCodeSet("Свои настройки", 0, 0, 0, 0, 0, 0, 0),
            new KeyCodeSet("Motorola", -1, -6, -2, -5, -20, -21, -22),
            new KeyCodeSet("Nokia (по умолчанию)", -1, -2, -3, -4, -5, -6, -7),
            new KeyCodeSet("Siemens", -59, -60, -61, -62, -26, -1, -4)
        };
        EMPTY_STRING_ARRAY = new String[0];
    }

    public static boolean containsString(String[] array, String string) {
        int len;
        if(array == null || (len = array.length) <= 0) return false;
        if(string == null) return Array.findf(array, 0, null) < len;
        for(int i = len; i-- > 0; ) if(string.equals(array[i])) return true;
        return false;
    }

    public static String deviceKeyToString(int deviceKey) {
        DeviceSettings settings;
        switch(deviceKey)
        {
        case DeviceSettings.DEVICE_KEY_CONSOLE:
            return "Вызов консоли";
        case DeviceSettings.DEVICE_KEY_EXITAPP:
            return "Выход из приложения";
        default:
            return (settings = DeviceManager.getInstance().getSettings()).getKeyName(settings.getKeyCodeFor(deviceKey));
        }
    }

    public static String calendarToString(Calendar calendar) {
        StringBuilder result = new StringBuilder();
        switch(calendar.get(Calendar.DAY_OF_WEEK))
        {
        case Calendar.MONDAY:
            result.append("понедельник, ");
            break;
        case Calendar.TUESDAY:
            result.append("вторник, ");
            break;
        case Calendar.WEDNESDAY:
            result.append("среда, ");
            break;
        case Calendar.THURSDAY:
            result.append("четверг, ");
            break;
        case Calendar.FRIDAY:
            result.append("пятница, ");
            break;
        case Calendar.SATURDAY:
            result.append("суббота, ");
            break;
        case Calendar.SUNDAY:
            result.append("воскресенье, ");
            break;
        }
        result.append(calendar.get(Calendar.DAY_OF_MONTH));
        switch(calendar.get(Calendar.MONTH))
        {
        case Calendar.JANUARY:
            result.append(" января ");
            break;
        case Calendar.FEBRUARY:
            result.append(" февраля ");
            break;
        case Calendar.MARCH:
            result.append(" марта ");
            break;
        case Calendar.APRIL:
            result.append(" апреля ");
            break;
        case Calendar.MAY:
            result.append(" мая ");
            break;
        case Calendar.JUNE:
            result.append(" июня ");
            break;
        case Calendar.JULY:
            result.append(" июля ");
            break;
        case Calendar.AUGUST:
            result.append(" августа ");
            break;
        case Calendar.SEPTEMBER:
            result.append(" сентября ");
            break;
        case Calendar.OCTOBER:
            result.append(" октября ");
            break;
        case Calendar.NOVEMBER:
            result.append(" ноября ");
            break;
        case Calendar.DECEMBER:
            result.append(" декабря ");
            break;
        }
        return result.
            append(calendar.get(Calendar.YEAR)).append(", ").append(calendar.get(Calendar.HOUR_OF_DAY)).append(':').append(calendar.get(Calendar.MINUTE)).append(' ').append(calendar.getTimeZone()).
        toString();
    }

    public static Image loadImageFromFile(String fileName) {
        Image result;
        try
        {
            HandleInputStream stream;
            (stream = new FileInputStream(fileName)).checkOpenError();
            try
            {
                result = Image.createImage(stream);
            }
            finally
            {
                stream.close();
            }
        }
        catch(IOException e)
        {
            e.printRealStackTrace();
            result = null;
        }
        return result;
    }

    public static Image loadApplicationIcon() {
        String resourceName;
        if((resourceName = DeviceManager.getInstance().getSettings().getMIDletProperty("MIDlet-Icon")) == null) return null;
        if(resourceName.length() > 0 && resourceName.charAt(0) == '/') resourceName = resourceName.substring(1);
        return loadImageFromFile("/res/".concat(resourceName));
    }

    private static void fillKeysList(Choice list) {
        int[] keys = KEYBOARD_KEYS;
        list.deleteAll();
        for(int len = keys.length, i = 0; i < len; i++)
        {
            int key;
            list.append((key = keys[i]) == 0 ? "Свои настройки" : DeviceSettings.keyToString(key), null);
        }
    }

    private static int indexOfKey(int key) {
        int index;
        int[] keys;
        return (index = Array.findb(keys = KEYBOARD_KEYS, keys.length - 1, key)) <= 0 ? 0 : index;
    }

    private static int indexOfString(Choice list, String string) {
        for(int i = list.size(); i-- > 0; ) if(list.getString(i).equals(string)) return i;
        return -1;
    }

    private static MIDletDisplay createDefaultApplicationDisplay() {
        RasterCanvas screen = RasterCanvas.screen;
        return new MIDletDisplay(false, screen.getWidth(), screen.getHeight());
    }

    private boolean firstConnectionOpened;
    private boolean consoleGreetingPrinted;
    private int saveSettingsElementIndex;
    private String[] supportedProtocols;
    private String[] supportedContentTypes;
    private String[] applicationClassNames;
    private final SettingsForm[] screenSettings;
    private ChoiceGroup itemKeyCodeSets;
    private ButtonSet itemDeviceButtons;
    private StringItem itemRecordProperties;
    private StringItem itemKeyDescription;
    private StringItem itemKeyEquivalent;
    private StringItem itemErrorInfo;
    private Command commandNo;
    private Command commandYes;
    private Command commandExit;
    private Command commandBack;
    private Command commandApply;
    private Command commandClear;
    private Command commandDelete;
    private Command commandLaunch;
    private Command commandUpdate;
    private Command commandSettings;
    private Command commandProperties;
    private Command commandAboutApplication;
    private Command commandAboutImplementation;
    private List screenRecordsList;
    private List screenSettingsList;
    private List screenApplicationList;
    private Form screenApplicationError;
    private Form screenAboutApplication;
    private Form screenAboutImplementation;
    private Form screenRecordProperties;
    private Form screenDeviceButtonsView;
    private Alert screenError;
    private Alert screenExitApplication;
    private Alert screenRecordsClearConfirm;
    private Alert screenRecordDeleteConfirm;
    private Displayable screenBackground;
    private SettingsForm screenSettingFonts;
    private SettingsForm screenSettingSystem;
    private SettingsForm screenSettingControls;
    private SettingsForm screenSettingKeyCodes;
    private SurfaceTextViewer screenLongOperation;
    private final MIDletDisplay applicationDisplay;
    private final BackgroundConsoleCommand imagesCommand;
    private final BackgroundConsoleCommand soundsCommand;
    final Console console;

    public SystemManager() {
        Console console;
        MIDletDisplay ref1;
        BackgroundConsoleCommand cmd1;
        BackgroundConsoleCommand cmd2;
        Run.instance.setThreadTerminationListener(this);
        this.screenSettings = new SettingsForm[4];
        this.applicationDisplay = (ref1 = createApplicationDisplay()) == null ? createDefaultApplicationDisplay() : ref1;
        this.imagesCommand = cmd1 = new ImagesSearchConsoleCommand(this);
        this.soundsCommand = cmd2 = new SoundsSearchConsoleCommand(this);
        this.console = console = new Console("Консоль J2ME", null, false, this);
        console.addConsoleCommand(cmd1);
        console.addConsoleCommand(cmd2);
        for(int i = 1; ; i++)
        {
            String value;
            if((value = System.getSystemProperty("malik.emulator.microedition.console.command.".concat(Integer.toString(i)))) == null) break;
            try
            {
                console.addConsoleCommand((ConsoleCommand) Class.forName(value).newInstance());
            }
            catch(Exception e)
            {
                e.printRealStackTrace();
            }
        }
    }

    public void playTone(int note, int duration, int volume) throws MediaException {
        super.playTone(note, duration > 10000 ? 10000 : duration, volume);
    }

    public void applicationStarted(Display display) {
        super.applicationStarted(display);
        super.getMainDisplay().setCurrent(console);
    }

    public void applicationStopped(Display display) {
        super.applicationStopped(display);
        Run.instance.terminate();
    }

    public String[] getSupportedContentTypes(String protocol) {
        String[] protocols;
        String[] contentTypes;
        if((protocols = supportedProtocols) == null && ((protocols = supportedProtocols = getSupportedProtocols()) == null || protocols.length <= 0))
        {
            protocols = supportedProtocols = getSystemSupportedProtocols();
        }
        if((contentTypes = supportedContentTypes) == null && ((contentTypes = supportedContentTypes = getSupportedContentTypes()) == null || contentTypes.length <= 0))
        {
            contentTypes = supportedContentTypes = getSystemSupportedContentTypes();
        }
        if(protocol == null || containsString(protocols, protocol))
        {
            int len;
            String[] result;
            Array.copy(contentTypes, 0, result = new String[len = contentTypes.length], 0, len);
            return result;
        }
        return EMPTY_STRING_ARRAY;
    }

    public String[] getSupportedProtocols(String contentType) {
        String[] protocols;
        String[] contentTypes;
        if((protocols = supportedProtocols) == null && ((protocols = supportedProtocols = getSupportedProtocols()) == null || protocols.length <= 0))
        {
            protocols = supportedProtocols = getSystemSupportedProtocols();
        }
        if((contentTypes = supportedContentTypes) == null && ((contentTypes = supportedContentTypes = getSupportedContentTypes()) == null || contentTypes.length <= 0))
        {
            contentTypes = supportedContentTypes = getSystemSupportedContentTypes();
        }
        if(contentType == null || containsString(contentTypes, contentType))
        {
            int len;
            String[] result;
            Array.copy(protocols, 0, result = new String[len = protocols.length], 0, len);
            return result;
        }
        return EMPTY_STRING_ARRAY;
    }

    public Player createPlayer(String locator) throws IOException, MediaException {
        InputStream stream;
        if((stream = Connector.openInputStream(locator)) != null)
        {
            Player result = null;
            try
            {
                long signature = (long) (new DataInputStream(stream)).readInt() & 0x00000000ffffffffL;
                if(WaveDecoder.SIGNATURE == signature) result = new SampledPlayer(new WaveDecoder(), stream, true);
                if(MIDIDecoder.SIGNATURE == signature) result = new SyntheticPlayer(new MIDIDecoder(), stream, true);
            }
            finally
            {
                if(result == null) stream.close();
            }
            if(result != null) return result;
        }
        throw new MediaException("Manager.createPlayer: невозможно создать проигрыватель.");
    }

    public Player createPlayer(DataSource source) throws IOException, MediaException {
        throw new MediaException("Manager.createPlayer: эта реализация не поддерживает создание проигрывателей из DataSource.");
    }

    public Player createPlayer(InputStream stream, String contentType) throws IOException, MediaException {
        long signature = (long) (new DataInputStream(stream)).readInt() & 0x00000000ffffffffL;
        if(WaveDecoder.SIGNATURE == signature) return new SampledPlayer(new WaveDecoder(), stream, false);
        if(MIDIDecoder.SIGNATURE == signature) return new SyntheticPlayer(new MIDIDecoder(), stream, false);
        throw new MediaException("Manager.createPlayer: невозможно создать проигрыватель.");
    }

    public Connection openConnection(String url, int mode, boolean timeouts) throws IOException {
        int localPort;
        String address;
        String loc = url.toLowerCase();
        synchronized(screenSettings)
        {
            if(!firstConnectionOpened && loc.startsWith("sms://"))
            {
                MessageConnection ourMessageServer;
                firstConnectionOpened = true;
                InboundConnections.instance.registerLocalPort(InboundConnections.UDP, ClientMessageConnection.LOCAL_PORT);
                (ourMessageServer = new ServerMessageConnection(
                    ClientMessageConnection.LOCAL_ADDRESS, ClientMessageConnection.LOCAL_PORT
                )).setMessageListener(new DefaultMessageHandler(ourMessageServer));
            }
        }
        label0:
        {
            if(loc.startsWith("sms://"))
            {
                if(!(address = url.substring("sms://".length())).startsWith(":")) return new ClientMessageConnection(url);
                if((localPort = Integer.parseInt(address.substring(1))) < 0x0000 || localPort > 0xffff)
                {
                    throw new NumberFormatException("Connector.open: номер порта выходит из диапазона.");
                }
                InboundConnections.instance.registerLocalPort(InboundConnections.UDP, localPort);
                return new ServerMessageConnection(url, localPort);
            }
            if(loc.startsWith("file://"))
            {
                if(!(address = url.substring("file://".length())).startsWith("/")) url = "file://".concat(address = "/".concat(address));
                if(address.indexOf("//") >= 0)
                {
                    throw new IllegalArgumentException("Connector.open: аргумент url имеет неправильный формат.");
                }
                if(VFSConnection.isForbiddenPath(null, address))
                {
                    throw new SecurityException("Connector.open: доступ к заданному объекту запрещён для протокола file.");
                }
                return new VFSConnection(url, null, address, mode);
            }
            if(loc.startsWith("resource://"))
            {
                if(mode != READ)
                {
                    throw new IllegalArgumentException("Connector.open: аргумент mode может быть только READ для протокола resource.");
                }
                if(!(address = url.substring("resource://".length())).startsWith("/")) url = "resource://".concat(address = "/".concat(address));
                if(address.indexOf("//") >= 0)
                {
                    throw new IllegalArgumentException("Connector.open: аргумент url имеет неправильный формат.");
                }
                try
                {
                    CloudFileSystem.instance.readAttributes("/res".concat(address), null);
                }
                catch(IOException e)
                {
                    e.printRealStackTrace();
                    break label0;
                }
                return new ResourceConnection(url, address);
            }
        }
        throw new ConnectionNotFoundException("Connector.open: соединение не найдено: ".concat(url));
    }

    public ImageDecoder openImageDecoder(InputStream stream) throws IOException {
        long signature;
        DataInputStream data;
        signature = (long) (data = new DataInputStream(stream)).readUnsignedShort();
        if(JPEGDecoder.SIGNATURE == signature) return new JPEGDecoder();
        signature = signature << 48 | (long) data.readUnsignedShort() << 32 | (long) data.readInt() & 0x00000000ffffffffL;
        if(PNGDecoder.SIGNATURE == signature) return new PNGDecoder();
        throw new InvalidDataFormatException("SystemManager.openImageDecoder: неизвестный формат изображения.");
    }

    public void commandAction(Command command, Displayable screen) {
        SettingsForm[] settingsForms;
        List screenList;
        final Display display = super.getMainDisplay();
        if(Array.findb(settingsForms = screenSettings, settingsForms.length - 1, screen) >= 0)
        {
            if(command == commandApply) settingApply((SettingsForm) screen);
            display.setCurrent(getScreenSettingsList());
            return;
        }
        if(screen == screenExitApplication)
        {
            if(command == commandYes)
            {
                applicationDisplay.applicationStop(true);
                return;
            }
            if(command == commandNo) switchToApplicationDisplay();
            return;
        }
        if(screen == (screenList = screenSettingsList))
        {
            if(command == List.SELECT_COMMAND)
            {
                int settingIndex = screenList.getSelectedIndex();
                settingSelect(settingIndex, screenList.getString(settingIndex));
                return;
            }
            if(command == commandBack) display.setCurrent(getScreenApplicationList());
            return;
        }
        if(screen == (screenList = screenRecordsList))
        {
            if(command == commandClear)
            {
                display.setCurrent(getScreenRecordsClearConfirm());
                return;
            }
            if(command == commandDelete)
            {
                display.setCurrent(getScreenRecordDeleteConfirm(screenList.getString(screenList.getSelectedIndex())));
                return;
            }
            if(command == commandUpdate)
            {
                fillRecordsList(screenList);
                return;
            }
            if(command == commandProperties)
            {
                display.setCurrent(getScreenRecordProperties(screenList.getString(screenList.getSelectedIndex())));
                return;
            }
            if(command == commandBack) display.setCurrent(getScreenSettingsList());
            return;
        }
        if(screen == screenRecordsClearConfirm)
        {
            if(command == commandYes)
            {
                final Runnable nextTask = new Runnable() {
                    public void run() {
                        display.setCurrent((SystemManager.this).getScreenRecordsList());
                    }
                };
                display.setCurrent(getScreenLongOperation("Очистка записей…"));
                ((Thread) (new Thread("Очистка записей…") {
                    public void run() {
                        String[] recordStores;
                        for(int i = (recordStores = RecordStore.listRecordStores()).length; i-- > 0; )
                        {
                            try
                            {
                                RecordStore.deleteRecordStore(recordStores[i]);
                            }
                            catch(RecordStoreException e)
                            {
                                e.printRealStackTrace();
                            }
                        }
                        try
                        {
                            sleep(1000L);
                        }
                        catch(InterruptedException e)
                        {
                            e.printRealStackTrace();
                        }
                        display.callSerially(nextTask);
                    }
                })).start();
                return;
            }
            if(command == commandNo) display.setCurrent(screenRecordsList);
            return;
        }
        if(screen == screenRecordDeleteConfirm)
        {
            if(command == commandYes)
            {
                try
                {
                    RecordStore.deleteRecordStore((screenList = screenRecordsList).getString(screenList.getSelectedIndex()));
                    fillRecordsList(screenList);
                }
                catch(RecordStoreException e)
                {
                    e.printRealStackTrace();
                    fillRecordsList(screenList);
                    display.setCurrent(getScreenError("Ошибка возникла при удалении записи.\n\n".concat(e.getRealMessage())));
                    return;
                }
            }
            display.setCurrent(screenRecordsList);
            return;
        }
        if(screen == screenRecordProperties)
        {
            if(command == commandBack) display.setCurrent(screenRecordsList);
            return;
        }
        if(screen == screenDeviceButtonsView)
        {
            if(command == commandBack) display.setCurrent(getScreenSettingsList());
            return;
        }
        if(screen == screenAboutApplication || screen == screenAboutImplementation)
        {
            if(command == commandBack) display.setCurrent(getScreenApplicationList());
            return;
        }
        if(screen == screenApplicationError)
        {
            if(command == commandExit) Run.instance.terminate();
            return;
        }
        if(screen == (screenList = screenApplicationList))
        {
            if(command == commandLaunch)
            {
                String applicationClassName = applicationClassNames[screenList.getSelectedIndex()];
                display.setCurrent(getScreenLongOperation("Приложение запускается…"));
                clearFields();
                prepareForApplicationLaunch(console);
                applicationDisplay.applicationStart(applicationClassName);
                return;
            }
            if(command == commandSettings)
            {
                display.setCurrent(getScreenSettingsList());
                return;
            }
            if(command == commandAboutApplication)
            {
                display.setCurrent(getScreenAboutApplication());
                return;
            }
            if(command == commandAboutImplementation)
            {
                display.setCurrent(getScreenAboutImplementation());
                return;
            }
            if(command == commandExit) Run.instance.terminate();
        }
    }

    public void commandAction(Command command, Item item) {
        SettingsForm[] settingsForms;
        Display display = super.getMainDisplay();
        for(int i = (settingsForms = screenSettings).length; i-- > 0; )
        {
            SettingsForm screen;
            if((screen = settingsForms[i]) == null) continue;
            if(item == screen.getApplyButton())
            {
                settingApply(screen);
                display.setCurrent(getScreenSettingsList());
                break;
            }
            if(item == screen.getBackButton())
            {
                display.setCurrent(getScreenSettingsList());
                break;
            }
        }
    }

    public void itemStateChanged(Item item) {
        ButtonSet itemButtonSet;
        ChoiceGroup itemChoiceGroup;
        if(item == (itemChoiceGroup = itemKeyCodeSets))
        {
            int index;
            if((index = itemChoiceGroup.getSelectedIndex()) > 0)
            {
                int[] keys = KEY_CODE_SETS[index].getKeyCodesAsArray();
                SettingsForm screen;
                for(int j = (screen = screenSettingKeyCodes).size() - 1, i = j; i-- > 0; j--) ((Input) screen.get(j)).setString(Integer.toString(keys[i]));
            }
            return;
        }
        if(item == (itemButtonSet = itemDeviceButtons))
        {
            int keyCode;
            int deviceKey;
            DeviceSettings settings = super.getSettings();
            switch(deviceKey = itemButtonSet.getPressedButtonID())
            {
            case DeviceSettings.DEVICE_KEY_UP:
            case DeviceSettings.DEVICE_KEY_DOWN:
            case DeviceSettings.DEVICE_KEY_LEFT:
            case DeviceSettings.DEVICE_KEY_RIGHT:
            case DeviceSettings.DEVICE_KEY_SELECT:
            case DeviceSettings.DEVICE_KEY_SOFT1:
            case DeviceSettings.DEVICE_KEY_SOFT2:
            case DeviceSettings.DEVICE_KEY_1:
            case DeviceSettings.DEVICE_KEY_2:
            case DeviceSettings.DEVICE_KEY_3:
            case DeviceSettings.DEVICE_KEY_4:
            case DeviceSettings.DEVICE_KEY_5:
            case DeviceSettings.DEVICE_KEY_6:
            case DeviceSettings.DEVICE_KEY_7:
            case DeviceSettings.DEVICE_KEY_8:
            case DeviceSettings.DEVICE_KEY_9:
            case DeviceSettings.DEVICE_KEY_0:
            case DeviceSettings.DEVICE_KEY_STAR:
                itemKeyDescription.setText((new StringBuilder()).append(settings.getKeyName(keyCode = settings.getKeyCodeFor(deviceKey))).append("\nКод клавиши: ").append(keyCode).append(
                    "\nНазначение: посылает в приложение соответствующий код клавиши."
                ).toString());
                break;
            case DeviceSettings.DEVICE_KEY_POUND:
                itemKeyDescription.setText((new StringBuilder()).append(settings.getKeyName(keyCode = settings.getKeyCodeFor(deviceKey))).append("\nКод клавиши: ").append(keyCode).append(
                    "\nНазначение: посылает в приложение соответствующий код клавиши.\n" +
                    "Примечание: эта клавиша позволяет полностью просмотреть выделенный элемент списка. Если на экране отображается стандартный список каких-либо элементов и среди них есть " +
                    "такие, которые заканчиваются на многоточие («…»), выделите такой элемент и нажмите эту клавишу. Пока вы её удерживаете, на экране отображается полный текст выделенного " +
                    "элемента, без многоточия."
                ).toString());
                break;
            case DeviceSettings.DEVICE_KEY_CONSOLE:
                itemKeyDescription.setText("Вызов консоли\nНазначение: вызывает системную консоль (доступно только во время работы приложения).");
                break;
            case DeviceSettings.DEVICE_KEY_EXITAPP:
                itemKeyDescription.setText("Выход из приложения\nНазначение: вызывает диалог выхода из приложения (доступно только во время работы приложения).");
                break;
            default:
                return;
            }
            itemKeyEquivalent.setText(DeviceSettings.keyToString(settings.getKeyUsedAs(deviceKey)));
        }
    }

    public void threadTerminated(Thread terminatedThread, Throwable exitThrowable) {
        if(exitThrowable != null)
        {
            Display main = super.getMainDisplay();
            Display curr = super.getCurrentDisplay();
            RecordStore.closeAllRecordStores();
            exitThrowable.printRealStackTrace();
            getItemErrorInfo().setText((new StringBuilder()).append(exitThrowable.getRealMessage()).append("\n\n – ").append(exitThrowable.getClass().getCanonicalName()).toString());
            main.setCurrent(getScreenApplicationError());
            if(curr != main) super.setCurrentDisplay(main);
        }
    }

    public final MIDletDisplay getApplicationDisplay() {
        return applicationDisplay;
    }

    protected void showStartScreen(Display display) {
        display.setCurrent(getScreenApplicationList());
    }

    protected void showConsoleScreen(Display display) {
        Console console = this.console;
        if(!consoleGreetingPrinted)
        {
            consoleGreetingPrinted = true;
            console.println(
                (new StringBuilder()).append("Добро пожаловать в ").append(console.getTitle()).append("!\nНаберите на консоли команду\n ?\nчтобы увидеть список доступных команд.").toString()
            );
        }
        display.setCurrent(console);
    }

    protected void showExitAppScreen(Display display) {
        display.setCurrent(getScreenExitApplication(), getScreenBackground());
    }

    protected void keyboardEvent(KeyboardEvent event) {
        super.keyboardEvent(event);
        if(super.getCurrentDisplay() instanceof MIDletDisplay && event.getAction() == KeyboardEvent.ACTION_KEY_PRESSED) switch(event.getKey())
        {
        case KeyboardEvent.KEY_F5:
            if(!isKeyUsed(KeyboardEvent.KEY_F5)) imagesCommand.executeInBackground();
            break;
        case KeyboardEvent.KEY_F6:
            if(!isKeyUsed(KeyboardEvent.KEY_F6)) soundsCommand.executeInBackground();
            break;
        }
    }

    protected void buildAuthorList(ManagerStringList authors) {
    }

    protected void buildLicenseList(ManagerStringList licenses) {
    }

    protected void buildSpecificationList(ManagerStringList specifications) {
    }

    protected void buildSettingList(ManagerSettingList settings) {
    }

    protected void settingSelected(int settingIndex, String settingName, Display display) {
    }

    protected void prepareForApplicationLaunch(Console console) {
    }

    protected String[] getSupportedContentTypes() {
        return getSystemSupportedContentTypes();
    }

    protected String[] getSupportedProtocols() {
        return getSystemSupportedProtocols();
    }

    protected MIDletDisplay createApplicationDisplay() {
        return null;
    }

    protected final void returnToSettingsScreen() {
        super.getMainDisplay().setCurrent(getScreenSettingsList());
    }

    final void switchToApplicationDisplay() {
        super.setCurrentDisplay(applicationDisplay);
    }

    final void switchToSystemDisplay() {
        super.setCurrentDisplay(super.getMainDisplay());
    }

    final List getScreenRecordsList() {
        List result;
        if((result = screenRecordsList) == null)
        {
            (result = screenRecordsList = new List("Записи", null, false, null, Choice.IMPLICIT, "(нет записей)", new String[0], null)).setCommandListener(this);
            result.setSelectCommand(getCommandProperties());
            result.addCommand(getCommandUpdate());
            result.addCommand(getCommandBack());
        }
        fillRecordsList(result);
        return result;
    }

    private void clearFields() {
        Object[] array;
        applicationClassNames = null;
        itemKeyCodeSets = null;
        itemDeviceButtons = null;
        itemRecordProperties = null;
        itemKeyDescription = null;
        itemKeyEquivalent = null;
        commandBack = null;
        commandApply = null;
        commandClear = null;
        commandDelete = null;
        commandLaunch = null;
        commandUpdate = null;
        commandSettings = null;
        commandProperties = null;
        commandAboutApplication = null;
        commandAboutImplementation = null;
        screenRecordsList = null;
        screenSettingsList = null;
        screenApplicationList = null;
        screenAboutApplication = null;
        screenAboutImplementation = null;
        screenRecordProperties = null;
        screenDeviceButtonsView = null;
        screenError = null;
        screenRecordsClearConfirm = null;
        screenRecordDeleteConfirm = null;
        screenSettingFonts = null;
        screenSettingSystem = null;
        screenSettingControls = null;
        screenSettingKeyCodes = null;
        screenLongOperation = null;
        Array.fill(array = screenSettings, 0, array.length, null);
    }

    private void fillRecordsList(List list) {
        int len;
        int selected;
        String[] records;
        len = (records = RecordStore.listRecordStores()).length;
        selected = list.getSelectedIndex();
        list.deleteAll();
        for(int i = 0; i < len; i++) list.append(records[i], null);
        if(selected >= 0 && len > 0)
        {
            if(selected >= len) selected = len - 1;
            list.setSelectedIndex(selected, true);
        }
        if(len > 0)
        {
            list.addCommand(getCommandClear());
            list.addCommand(getCommandDelete());
            list.addCommand(getCommandProperties());
            return;
        }
        list.removeCommand(getCommandClear());
        list.removeCommand(getCommandDelete());
        list.removeCommand(getCommandProperties());
    }

    private void settingSelect(int settingIndex, String settingName) {
        final Display display = super.getMainDisplay();
        if(settingIndex == saveSettingsElementIndex)
        {
            final DeviceSettings settings = super.getSettings();
            final Displayable nextScreen = getScreenApplicationList();
            display.setCurrent(getScreenLongOperation("Сохранение настроек…"));
            ((Thread) (new Thread("Сохранение настроек…") {
                public void run() {
                    settings.saveMIDletProperties();
                    try
                    {
                        sleep(1000L);
                    }
                    catch(InterruptedException e)
                    {
                        e.printRealStackTrace();
                    }
                    display.setCurrent(nextScreen);
                }
            })).start();
            return;
        }
        switch(settingIndex)
        {
        case 0:
            display.setCurrent(getScreenSettingControls());
            break;
        case 1:
            display.setCurrent(getScreenSettingKeyCodes());
            break;
        case 2:
            display.setCurrent(getScreenSettingFonts());
            break;
        case 3:
            display.setCurrent(getScreenSettingSystem());
            break;
        case 4:
            display.setCurrent(getScreenRecordsList());
            break;
        case 5:
            display.setCurrent(getScreenDeviceButtonsView());
            break;
        default:
            settingSelected(settingIndex, settingName, display);
            break;
        }
    }

    private void settingApply(SettingsForm settingForm) {
        DeviceSettings settings = super.getSettings();
        if(settingForm == screenSettingControls)
        {
            int[] deviceKeys = DEVICE_KEYS;
            int[] keybrdKeys = KEYBOARD_KEYS;
            for(int i = settingForm.size(); i-- > 0; )
            {
                int key;
                if((key = keybrdKeys[((Choice) settingForm.get(i)).getSelectedIndex()]) != 0) settings.setKeyUsedAs(deviceKeys[i], key);
            }
            return;
        }
        if(settingForm == screenSettingKeyCodes)
        {
            int[] deviceKeys = DEVICE_KEYS;
            for(int j = settingForm.size() - 1, i = j; i-- > 0; j--)
            {
                int keyCode;
                try
                {
                    keyCode = Integer.parseInt(((TextField) settingForm.get(j)).getString());
                    settings.setKeyCodeFor(deviceKeys[i], keyCode);
                }
                catch(NumberFormatException e)
                {
                }
            }
            return;
        }
        if(settingForm == screenSettingFonts)
        {
            int[] sizes = FONT_SIZES;
            for(int i = settingForm.size(); i-- > 0; )
            {
                Choice list;
                settings.setFont(i & 3, sizes[i >> 2], SystemFont.get((list = (Choice) settingForm.get(i)).getString(list.getSelectedIndex())));
            }
            return;
        }
        if(settingForm == screenSettingSystem)
        {
            Choice list;
            settings.setMaximumFrequency(((Gauge) settingForm.get(0)).getValue());
            settings.setEnableStackTrace((list = (Choice) settingForm.get(1)).isSelected(0));
            settings.setKeyRepeatedEvent(list.isSelected(1));
        }
    }

    private boolean isKeyUsed(int key) {
        int[] deviceKeys;
        DeviceSettings settings = super.getSettings();
        for(int i = (deviceKeys = DEVICE_KEYS).length; i-- > 0; ) if(settings.getKeyUsedAs(deviceKeys[i]) == key) return true;
        return false;
    }

    private String[] buildApplicationList(List dest) {
        int len = 0;
        String[] attributes;
        String[] result = new String[1];
        DeviceSettings settings = super.getSettings();
        dest.setTitle(settings.getMIDletProperty("MIDlet-Name"));
        dest.deleteAll();
        for(int i = 0; (attributes = settings.getMIDletAttributes("MIDlet-".concat(Integer.toString(i + 1)))) != null && attributes.length >= 3; i++)
        {
            Image icon = null;
            String appName = attributes[MIDLET_NAME];
            String iconName = attributes[MIDLET_ICON];
            String className = attributes[MIDLET_CLASS];
            if(appName == null || className == null || className.length() <= 0) continue;
            if(iconName != null && iconName.length() > 0)
            {
                if(iconName.charAt(0) == '/') iconName = iconName.substring(1);
                icon = loadImageFromFile("/res/".concat(iconName));
            }
            if(len == result.length) Array.copy(result, 0, result = new String[(len << 1) + 1], 0, len);
            dest.append(appName, icon);
            result[len++] = className;
        }
        if(len != result.length) Array.copy(result, 0, result = new String[len], 0, len);
        return result;
    }

    private String[] getSystemSupportedContentTypes() {
        return new String[] { MIDIDecoder.MIME_TYPE, WaveDecoder.MIME_TYPE };
    }

    private String[] getSystemSupportedProtocols() {
        return new String[] { "file", "resource" };
    }

    private ChoiceGroup getItemKeyCodeSets() {
        ChoiceGroup result;
        if((result = itemKeyCodeSets) == null)
        {
            int len;
            String[] names;
            KeyCodeSet[] sets;
            names = new String[len = (sets = KEY_CODE_SETS).length];
            for(int i = len; i-- > 0; names[i] = sets[i].getName());
            (result = itemKeyCodeSets = new ChoiceGroup("Предустановки", Choice.POPUP, names, null)).setLayout(Item.LAYOUT_EXPAND);
        }
        return result;
    }

    private ButtonSet getItemDeviceButtons() {
        ButtonSet result;
        if((result = itemDeviceButtons) == null)
        {
            Image background;
            if((background = loadImageFromFile("/ui/keypad.png")) == null)
            {
                throw new RuntimeException("SystemManager.getItemDeviceButtons: ошибка при загрузке /ui/keypad.png.");
            }
            (result = itemDeviceButtons = new ButtonSet(null, background)).setLayout(Item.LAYOUT_CENTER | Item.LAYOUT_NEWLINE_AFTER);
            try
            {
                HandleInputStream input;
                (input = new FileInputStream("/ui/keypad.dat")).checkOpenError();
                try
                {
                    for(DataInputStream stream = new DataInputStream(input); stream.available() >= 5; )
                    {
                        int buttonID = stream.readUnsignedByte();
                        int left = stream.readUnsignedByte();
                        int top = stream.readUnsignedByte();
                        int width = stream.readUnsignedByte();
                        int height = stream.readUnsignedByte();
                        result.setButton(buttonID, left, top, width, height);
                    }
                }
                finally
                {
                    input.close();
                }
            }
            catch(IOException e)
            {
                e.printRealStackTrace();
                throw new RuntimeException("SystemManager.getItemDeviceButtons: ошибка при загрузке /ui/keypad.dat.");
            }
        }
        return result;
    }

    private StringItem getItemRecordProperties() {
        StringItem result;
        if((result = itemRecordProperties) == null)
        {
            (result = itemRecordProperties = new StringItem(null, null)).setLayout(Item.LAYOUT_EXPAND);
            result.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM));
        }
        return result;
    }

    private StringItem getItemKeyDescription() {
        StringItem result;
        if((result = itemKeyDescription) == null)
        {
            (result = itemKeyDescription = new StringItem(
                "Описание", "Укажите клавишу на этой виртуальной клавиатуре, чтобы увидеть её описание здесь."
            )).setLayout(Item.LAYOUT_EXPAND | Item.LAYOUT_NEWLINE_AFTER);
        }
        return result;
    }

    private StringItem getItemKeyEquivalent() {
        StringItem result;
        if((result = itemKeyEquivalent) == null)
        {
            (result = itemKeyEquivalent = new StringItem("Эквивалентная клавиша", "(Клавиша не выбрана)")).setLayout(Item.LAYOUT_EXPAND | Item.LAYOUT_NEWLINE_AFTER);
        }
        return result;
    }

    private StringItem getItemErrorInfo() {
        StringItem result;
        if((result = itemErrorInfo) == null)
        {
            (result = itemErrorInfo = new StringItem(null, null)).setLayout(Item.LAYOUT_EXPAND);
            result.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL));
        }
        return result;
    }

    private Command getCommandNo() {
        Command result;
        if((result = commandNo) == null) result = commandNo = new Command("Нет", Command.BACK, 0);
        return result;
    }

    private Command getCommandYes() {
        Command result;
        if((result = commandYes) == null) result = commandYes = new Command("Да", Command.OK, 0);
        return result;
    }

    private Command getCommandExit() {
        Command result;
        if((result = commandExit) == null) result = commandExit = new Command("Выход", Command.EXIT, 0);
        return result;
    }

    private Command getCommandBack() {
        Command result;
        if((result = commandBack) == null) result = commandBack = new Command("Назад", Command.BACK, 0);
        return result;
    }

    private Command getCommandApply() {
        Command result;
        if((result = commandApply) == null) result = commandApply = new Command("Применить", Command.SCREEN, Integer.MAX_VALUE);
        return result;
    }

    private Command getCommandClear() {
        Command result;
        if((result = commandClear) == null) result = commandClear = new Command("Удалить все", Command.SCREEN, 3);
        return result;
    }

    private Command getCommandDelete() {
        Command result;
        if((result = commandDelete) == null) result = commandDelete = new Command("Удалить", Command.SCREEN, 2);
        return result;
    }

    private Command getCommandLaunch() {
        Command result;
        if((result = commandLaunch) == null) result = commandLaunch = new Command("Запустить", Command.OK, 0);
        return result;
    }

    private Command getCommandUpdate() {
        Command result;
        if((result = commandUpdate) == null) result = commandUpdate = new Command("Обновить", Command.SCREEN, 1);
        return result;
    }

    private Command getCommandSettings() {
        Command result;
        if((result = commandSettings) == null) result = commandSettings = new Command("Настройки…", Command.SCREEN, 1);
        return result;
    }

    private Command getCommandProperties() {
        Command result;
        if((result = commandProperties) == null) result = commandProperties = new Command("Свойства", Command.OK, 0);
        return result;
    }

    private Command getCommandAboutApplication() {
        Command result;
        if((result = commandAboutApplication) == null) result = commandAboutApplication = new Command("О приложении…", Command.SCREEN, 2);
        return result;
    }

    private Command getCommandAboutImplementation() {
        Command result;
        if((result = commandAboutImplementation) == null) result = commandAboutImplementation = new Command("О реализации…", Command.SCREEN, 3);
        return result;
    }

    private List getScreenSettingsList() {
        List result;
        if((result = screenSettingsList) == null)
        {
            SystemSettingList settings;
            Ticker ticker = new Ticker("Это настройки приложения. ВАЖНО: не забудьте сохранить настройки. Настройки шрифтов применяются только после перезапуска программы.");
            (result = screenSettingsList = settings = new SystemSettingList("Настройки", ticker, false, null, null)).setCommandListener(this);
            settings.addCommand(getCommandBack());
            settings.append("Управление", null);
            settings.append("Коды клавиш", null);
            settings.append("Шрифты", null);
            settings.append("Система", null);
            settings.append("Записи", null);
            settings.append("Показать клавиатуру", null);
            buildSettingList(settings);
            saveSettingsElementIndex = settings.append("Сохранить настройки", null);
        }
        return result;
    }

    private List getScreenApplicationList() {
        List result;
        if((result = screenApplicationList) == null)
        {
            (result = screenApplicationList = new List(null, Choice.IMPLICIT)).setCommandListener(this);
            applicationClassNames = buildApplicationList(result);
            result.setTicker(new Ticker("Выберите приложение и нажмите кнопку «Запустить». Если хотите настроить приложение, то сперва нажмите кнопку «Меню» и выберите «Настройки…»."));
            result.addCommand(getCommandExit());
            result.addCommand(getCommandSettings());
            result.addCommand(getCommandAboutApplication());
            result.addCommand(getCommandAboutImplementation());
            result.setSelectCommand(getCommandLaunch());
        }
        return result;
    }

    private Form getScreenApplicationError() {
        Form result;
        if((result = screenApplicationError) == null)
        {
            (result = screenApplicationError = new Form("Ошибка приложения", new Item[] { getItemErrorInfo() })).setCommandListener(this);
            result.addCommand(getCommandExit());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }

    private Form getScreenAboutApplication() {
        Form result;
        if((result = screenAboutApplication) == null)
        {
            Item[] items;
            String[] classes;
            DeviceSettings settings = super.getSettings();
            Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
            String site1 = settings.getMIDletProperty("MIDlet-Info-URL");
            String site2 = settings.getMIDletProperty("SiteURL");
            ImageItem icon = new ImageItem(null, loadApplicationIcon(), Item.LAYOUT_VCENTER, null);
            Spacer space = new Spacer(4, 1);
            StringItem name = new StringItem(null, settings.getMIDletProperty("MIDlet-Name"));
            StringItem version = new StringItem("Версия", "\u0020".concat(settings.getMIDletProperty("MIDlet-Version")));
            StringItem vendor = new StringItem("Поставщик", "\u0020".concat(settings.getMIDletProperty("MIDlet-Vendor")));
            StringItem config = new StringItem("Конфигурация", "\u0020".concat(settings.getMIDletProperty("MicroEdition-Configuration")));
            StringItem profile = new StringItem("Профиль", "\u0020".concat(settings.getMIDletProperty("MicroEdition-Profile")));
            SystemStringItem midlets = new SystemStringItem("Класс мидлета");
            StringItem internet =
                site1 != null && site1.length() > 0 ? new StringItem("Интернет", "\u0020".concat(site1)) :
                site2 != null && site2.length() > 0 ? new StringItem("Интернет", "\u0020".concat(site2)) :
                null
            ;
            name.setLayout(Item.LAYOUT_EXPAND | Item.LAYOUT_VCENTER | Item.LAYOUT_NEWLINE_AFTER);
            name.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM));
            for(int len = (classes = applicationClassNames).length, i = 0; i < len; i++) midlets.add(classes[i]);
            if(internet == null)
            {
                items = new Item[] { icon, space, name, version, vendor, config, profile, midlets };
            } else
            {
                items = new Item[] { icon, space, name, version, vendor, internet, config, profile, midlets };
            }
            for(int i = items.length; i-- > 0; )
            {
                Item item;
                if((item = items[i]) != name && item instanceof StringItem)
                {
                    item.setLayout(Item.LAYOUT_EXPAND | Item.LAYOUT_NEWLINE_AFTER);
                    ((StringItem) item).setFont(font);
                }
            }
            (result = screenAboutApplication = new Form("О приложении", items)).setCommandListener(this);
            result.addCommand(getCommandBack());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }

    private Form getScreenAboutImplementation() {
        Form result;
        if((result = screenAboutImplementation) == null)
        {
            StringItem[] items;
            Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
            String softwareVersion = System.getProperty("software.version");
            String configuration = System.getProperty("microedition.configuration");
            String profiles = System.getProperty("microedition.profiles");
            StringItem version = new StringItem(
                "Информация о версии",
                (new StringBuilder()).append(" Версия: ").append(softwareVersion).append("\n Конфигурация: ").append(configuration).append("\n Профили: ").append(profiles).toString()
            );
            StringItem classList = new StringItem(
                "Важные классы",
                (new StringBuilder()).append(' ').append(super.getSettings().getClass().getCanonicalName()).append("\n ").append(super.getClass().getCanonicalName()).toString()
            );
            SystemStringItem specList = new SystemStringItem("Поддерживаемые спецификации");
            SystemStringItem authorList = new SystemStringItem("Авторы");
            SystemStringItem licenseList = new SystemStringItem("Лицензии");
            for(int i = (items = new StringItem[] { version, specList, classList, authorList, licenseList }).length; i-- > 0; )
            {
                StringItem item;
                (item = items[i]).setLayout(Item.LAYOUT_NEWLINE_AFTER | Item.LAYOUT_EXPAND);
                item.setFont(font);
            }
            specList.add("WMA 1.1 (JSR-120)");
            specList.add("MMAPI 1.2 (JSR-135)");
            specList.add("File connection (JSR-75)");
            specList.add("Nokia UI API 1.1");
            specList.add("Samsung API");
            buildSpecificationList(specList);
            authorList.add("Малик Разработчик (malik-elaborarer@protonmail.com)");
            buildAuthorList(authorList);
            licenseList.add("GNU LGPL 3");
            licenseList.add("ZLib");
            buildLicenseList(licenseList);
            licenseList.add("Обратитесь к исходному коду за подробностями.");
            (result = screenAboutImplementation = new Form("О реализации", items)).setCommandListener(this);
            result.addCommand(getCommandBack());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }

    private Form getScreenRecordProperties(String recordStoreName) {
        Form result;
        if((result = screenRecordProperties) == null)
        {
            (result = screenRecordProperties = new Form(null, new Item[] { getItemRecordProperties() })).setCommandListener(this);
            result.addCommand(getCommandBack());
            result.getHorizontalScrollBar().hide();
        }
        result.setTitle("Свойства: ".concat(recordStoreName));
        try
        {
            RecordStore store = RecordStore.openRecordStore(recordStoreName, false);
            try
            {
                Calendar calendar;
                (calendar = Calendar.getInstance()).setTime(new Date(store.getLastModified()));
                itemRecordProperties.setText((new StringBuilder()).
                    append(" Название: ").append(recordStoreName).append("\n Размер: ").append(store.getSize()).append(" байт\n Количество подзаписей: ").append(store.getNumRecords()).
                    append("\n Последнее изменение: ").append(calendarToString(calendar)).append("\n Количество изменений: ").append(store.getVersion()).
                toString());
            }
            finally
            {
                store.closeRecordStore();
            }
        }
        catch(RecordStoreException e)
        {
            e.printRealStackTrace();
            itemRecordProperties.setText("Ошибка возникла при чтении свойств записи.\n\n".concat(e.getRealMessage()));
        }
        return result;
    }

    private Form getScreenDeviceButtonsView() {
        Form result;
        if((result = screenDeviceButtonsView) == null)
        {
            try
            {
                (result = screenDeviceButtonsView = new Form("Клавиатура", new Item[] { getItemDeviceButtons(), getItemKeyDescription(), getItemKeyEquivalent() })).setCommandListener(this);
                result.setItemStateListener(this);
                result.addCommand(getCommandBack());
                result.getHorizontalScrollBar().hide();
            }
            catch(RuntimeException e)
            {
                threadTerminated(Thread.currentThread(), e);
                throw e;
            }
        }
        return result;
    }

    private Alert getScreenError(String message) {
        Alert result;
        if((result = screenError) == null) result = screenError = new Alert(null, null, null, AlertType.ERROR);
        result.setString(message);
        return result;
    }

    private Alert getScreenExitApplication() {
        Alert result;
        if((result = screenExitApplication) == null)
        {
            String applicationName = super.getSettings().getMIDletProperty("MIDlet-Name");
            (result = screenExitApplication = new Alert(
                null, (new StringBuilder()).append("Выйти из приложения ").append(applicationName).append("? Все несохранённые данные будут утеряны.").toString(), null, AlertType.CONFIRMATION
            )).setCommandListener(this);
            result.addCommand(getCommandYes());
            result.addCommand(getCommandNo());
        }
        return result;
    }

    private Alert getScreenRecordsClearConfirm() {
        Alert result;
        if((result = screenRecordsClearConfirm) == null)
        {
            (result = screenRecordsClearConfirm = new Alert(
                null, "Удаление всех записей приведёт приложение к состоянию, в котором оно находилось сразу после установки. Удалить все записи?", null, AlertType.CONFIRMATION
            )).setCommandListener(this);
            result.addCommand(getCommandYes());
            result.addCommand(getCommandNo());
        }
        return result;
    }

    private Alert getScreenRecordDeleteConfirm(String recordStoreName) {
        Alert result;
        if((result = screenRecordDeleteConfirm) == null)
        {
            (result = screenRecordDeleteConfirm = new Alert(null, null, null, AlertType.WARNING)).setCommandListener(this);
            result.addCommand(getCommandYes());
            result.addCommand(getCommandNo());
        }
        result.setString((new StringBuilder()).
            append("Осторожно: удаление одной записи может привести к ошибкам в работе приложения. Удаляйте записи только если уверены, что всё делаете правильно. Удалить запись ").
            append(recordStoreName).append('?').
        toString());
        return result;
    }

    private Displayable getScreenBackground() {
        Displayable result;
        if((result = screenBackground) == null) result = screenBackground = new BlackScreen();
        return result;
    }

    private SettingsForm getScreenSettingFonts() {
        int[] sizes;
        DeviceSettings proxy;
        SettingsForm result;
        if((result = screenSettingFonts) == null)
        {
            int len;
            String[] fontTypes = new String[] {
                "Мелкий нормальный", "Мелкий жирный", "Мелкий курсив", "Мелкий жирный курсив",
                "Средний нормальный", "Средний жирный", "Средний курсив", "Средний жирный курсив",
                "Крупный нормальный", "Крупный жирный", "Крупный курсив", "Крупный жирный курсив"
            };
            ChoiceGroup[] items = new ChoiceGroup[len = fontTypes.length];
            for(int i = len; i-- > 0; )
            {
                ChoiceGroup list;
                items[i] = list = new ChoiceGroup(fontTypes[i], Choice.POPUP);
                list.setLayout(Item.LAYOUT_2 | Item.LAYOUT_EXPAND);
                list.setPreferredSize(150, -1);
                for(Enumeration e = SystemFont.systemFonts(); e.hasMoreElements(); )
                {
                    SystemFont sysFont;
                    Font midpFont = Font.getFont(sysFont = (SystemFont) e.nextElement(), 0, 0, 0);
                    list.setFont(list.append(sysFont.getName(), null), midpFont);
                }
            }
            (result = screenSettings[2] = screenSettingFonts = new SettingsForm("Шрифты", items)).setCommandListener(this);
            result.setButtonListener(this);
            result.setHelpContents(
                "Здесь можно задать шрифты, которые приложение будет использовать для вывода текста.\nНастройки шрифтов вступят в силу только после перезапуска программы. " +
                "До перезапуска программы надписи на элементах могут быть выполнены не тем шрифтом, что был задан здесь."
            );
            result.addCommand(getCommandBack());
            result.addCommand(getCommandApply());
            result.getHorizontalScrollBar().hide();
        }
        sizes = FONT_SIZES;
        proxy = super.getSettings();
        for(int i = result.size(); i-- > 0; )
        {
            Choice list;
            (list = (Choice) result.get(i)).setSelectedIndex(indexOfString(list, proxy.getFont(i & 3, sizes[i >> 2]).getName()), true);
        }
        return result;
    }

    private SettingsForm getScreenSettingSystem() {
        Choice list;
        DeviceSettings proxy;
        SettingsForm result;
        if((result = screenSettingSystem) == null)
        {
            Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
            Gauge frequency = new Gauge("Максимальная частота кадров в секунду", true, 99, 0);
            ChoiceGroup system = new ChoiceGroup("Поведение системы", Choice.MULTIPLE, new String[] {
                "Трассировка стака в исключениях\n\n" +
                "Отключите эту опцию, если приложение работает с ошибками и генерирует много исключений, увеличивая размер файла /err.txt. " +
                "В противном случае эту опцию рекомендуется включить (для отслеживания ошибок в программе).",
                "Вызов метода Canvas.keyRepeated(int)\n\n" +
                "Изменение состояния этого флага может привести к изменению поведения приложения в тот момент, когда вы удерживаете нажатой какую-либо клавишу. " +
                "Установите или снимите этот флаг в соответствии с вашими предпочтениями."
            }, null);
            frequency.setLayout(Item.LAYOUT_EXPAND);
            system.setLayout(Item.LAYOUT_EXPAND);
            for(int i = system.size(); i-- > 0; system.setFont(i, font));
            (result = screenSettings[3] = screenSettingSystem = new SettingsForm("Система", new Item[] { frequency, system })).setCommandListener(this);
            result.setButtonListener(this);
            result.setHelpContents(
                "Здесь можно настроить максимальную частоту обновления экрана и поведение системы.\nРекомендуемый максимум частоты – 25 кадров в секунду. Большие значения могут привести к " +
                "«подвисанию» программы и пропуску некоторых кадров. Проверьте также максимум кадров в секунду в настройках самого Малик Эмулятора.\nВ опциях поведения системы выберите опцию, " +
                "нажмите и удерживайте клавишу #, чтобы увидеть подробное описание выбранной опции."
            );
            result.addCommand(getCommandBack());
            result.addCommand(getCommandApply());
            result.getHorizontalScrollBar().hide();
        }
        proxy = super.getSettings();
        ((Gauge) result.get(0)).setValue(proxy.getMaximumFrequency());
        (list = (Choice) result.get(1)).setSelectedIndex(0, proxy.isEnableStackTrace());
        list.setSelectedIndex(1, proxy.isKeyRepeatedEvent());
        return result;
    }

    private SettingsForm getScreenSettingControls() {
        int len;
        int[] keys;
        DeviceSettings proxy;
        SettingsForm result;
        len = (keys = DEVICE_KEYS).length;
        if((result = screenSettingControls) == null)
        {
            ChoiceGroup[] items = new ChoiceGroup[len];
            for(int i = len; i-- > 0; )
            {
                ChoiceGroup list;
                fillKeysList(items[i] = list = new ChoiceGroup(deviceKeyToString(keys[i]), Choice.POPUP));
                list.setLayout(Item.LAYOUT_2 | Item.LAYOUT_EXPAND);
                list.setPreferredSize(150, -1);
            }
            (result = screenSettings[0] = screenSettingControls = new SettingsForm("Управление", items)).setCommandListener(this);
            result.setButtonListener(this);
            result.setHelpContents(
                "Здесь можно задать соответствие между клавишами виртуального устройства и клавишами компьютера.\nВАЖНО: следите, чтобы в разных полях были разные названия клавиш компьютера, " +
                "так как программа этого не проверяет.\nРасшифровка непонятных названий клавиш компьютера:\n  UP – \u2191\n  DOWN – \u2193\n  LEFT – \u2190\n  RIGHT – \u2192\n  NUM_PLUS – Num +\n" +
                "  NUM_MINUS – Num -\n  NUM_STAR – Num *\n  NUM_DIVIDE – Num /\n  NUM_DECIMAL – Num .\nОпция «Свои настройки» не изменяет заданную клавишу компьютера при нажатии кнопки " +
                "«Применить», оставляя раннее заданную клавишу. Эта опция была специально добавлена на случай, когда файл настроек /midlet.properties редактировался вручную."
            );
            result.addCommand(getCommandBack());
            result.addCommand(getCommandApply());
            result.getHorizontalScrollBar().hide();
        }
        proxy = super.getSettings();
        for(int i = len; i-- > 0; ) ((Choice) result.get(i)).setSelectedIndex(indexOfKey(proxy.getKeyUsedAs(keys[i])), true);
        return result;
    }

    private SettingsForm getScreenSettingKeyCodes() {
        int[] keys = DEVICE_KEYS;
        SettingsForm result;
        DeviceSettings proxy = super.getSettings();
        if((result = screenSettingKeyCodes) == null)
        {
            Item[] items;
            (items = new Item[8])[0] = getItemKeyCodeSets();
            for(int j = items.length - 1, i = j; i-- > 0; j--)
            {
                TextField field;
                items[j] = field = new TextField(proxy.getKeyName(proxy.getKeyCodeFor(keys[i])), null, 11, Input.NUMERIC);
                field.setLayout(Item.LAYOUT_2 | Item.LAYOUT_EXPAND);
                field.setPreferredSize(150, -1);
            }
            (result = screenSettings[1] = screenSettingKeyCodes = new SettingsForm("Коды клавиш", items)).setCommandListener(this);
            result.setItemStateListener(this);
            result.setButtonListener(this);
            result.setHelpContents(
                "Здесь можно задать коды тех клавиш, которые не были предусмотрены спецификацией MIDP, в результате чего коды этих клавиш стали зависимыми от конкретных моделей устройств. " +
                "Особенно это касается тех приложений, которые работают только на устройствах определённых моделей. Вы можете задать особые числовые коды для клавиш или выбрать предустановленные " +
                "коды клавиш для некоторых моделей устройств."
            );
            result.addCommand(getCommandBack());
            result.addCommand(getCommandApply());
            result.getHorizontalScrollBar().hide();
        }
        for(int j = result.size() - 1, i = j; i-- > 0; j--) ((Input) result.get(j)).setString(Integer.toString(proxy.getKeyCodeFor(keys[i])));
        return result;
    }

    private SurfaceTextViewer getScreenLongOperation(String message) {
        SurfaceTextViewer result;
        if((result = screenLongOperation) == null) (result = screenLongOperation = new SurfaceTextViewer(null, Font.getDefaultFont())).getScrollBar().hide();
        result.setText(message);
        return result;
    }
}

final class SystemStringItem extends StringItem implements ManagerStringList
{
    public SystemStringItem(String label) {
        super(label, null);
    }

    public void add(String string) {
        String text;
        if(string == null) return;
        if((text = getText()) == null) text = "";
        setText(text.concat("\n ".concat(string)));
    }
}

final class SystemSettingList extends List implements ManagerSettingList
{
    public SystemSettingList(String title, Ticker ticker, boolean fullScreen, ScrollBarStyle style, String emptyLabel) {
        super(title, ticker, fullScreen, style, IMPLICIT, emptyLabel, new String[0], null);
    }

    public int add(String settingName) {
        return append(settingName, null);
    }

    public int add(String settingName, Image settingIcon) {
        return append(settingName, settingIcon);
    }
}

final class BlackScreen extends Canvas
{
    public BlackScreen() {
        super(null, null, true);
    }

    protected void paint(Graphics render) {
        render.setColor(0x000000);
        render.fillRect(0, 0, super.getWidth(), super.getHeight());
    }
}

final class DefaultMessageHandler extends Object implements MessageListener, Runnable
{
    private final MessageConnection connection;

    public DefaultMessageHandler(MessageConnection connection) {
        this.connection = connection;
    }

    public void notifyIncomingMessage(MessageConnection connection) {
        if(connection == this.connection) Run.instance.request(this);
    }

    public void run() {
        String address;
        Message message;
        try
        {
            message = connection.receive();
        }
        catch(IOException e)
        {
            e.printRealStackTrace();
            return;
        }
        address = message.getAddress();
        if(message instanceof TextMessage)
        {
            String text = ((TextMessage) message).getPayloadText();
            System.out.println((new StringBuilder()).
                append("Приложение отправило на адрес ").append(address == null || address.length() <= 0 ? "<не указан>" : address).append(" сообщение с текстом:\n").
                append(text == null || text.length() <= 0 ? "<нет содержимого>\n" : text.concat("\n")).
            toString());
            return;
        }
        if(message instanceof BinaryMessage)
        {
            byte[] data = ((BinaryMessage) message).getPayloadData();
            System.out.println((new StringBuilder()).
                append("Приложение отправило на адрес ").append(address == null || address.length() <= 0 ? "<не указан>" : address).append(" двоичное сообщение длиной ").
                append(data == null ? 0 : data.length).append(" байт.").
            toString());
        }
    }
}

abstract class BackgroundConsoleCommand extends ConsoleCommand
{
    protected BackgroundConsoleCommand(String name) {
        super(name);
    }

    protected abstract void execute(String[] arguments, Console console);

    protected abstract void executeInBackground();
}

abstract class SearchConsoleCommand extends BackgroundConsoleCommand implements Runnable
{
    private static final int SEARCH = 1;
    private static final int CANCELLED = 2;
    private static final int INITIALIZED = 3;
    private static final int UNINITIALIZED = 0;

    private boolean calledFromConsole;
    private int state;
    protected final SystemManager manager;

    protected SearchConsoleCommand(String name, SystemManager manager) {
        super(name);
        this.manager = manager;
    }

    public void run() {
        switch(state)
        {
        case SEARCH:
            state = INITIALIZED;
            showFoundScreen(DeviceManager.getInstance().getMainDisplay());
            manager.switchToSystemDisplay();
            break;
        case CANCELLED:
            state = UNINITIALIZED;
            break;
        }
    }

    protected abstract void searchProcess();

    protected abstract void showProgressScreen(Display display);

    protected abstract void showFoundScreen(Display display);

    protected void execute(String[] arguments, Console console) {
        calledFromConsole = true;
        startSearchProcess();
    }

    protected void executeInBackground() {
        calledFromConsole = false;
        startSearchProcess();
    }

    protected final void cancelSearchProcess() {
        if(state == SEARCH)
        {
            state = CANCELLED;
            returnToSystem();
        }
    }

    protected final void returnToSystem() {
        if(calledFromConsole)
        {
            DeviceManager.getInstance().getMainDisplay().setCurrent(manager.console);
            return;
        }
        manager.switchToApplicationDisplay();
    }

    private void startSearchProcess() {
        int s = state;
        state = SEARCH;
        if(calledFromConsole) showProgressScreen(DeviceManager.getInstance().getMainDisplay());
        if(s == CANCELLED || s == SEARCH) return;
        ((Thread) (new Thread() {
            public void run() {
                SearchConsoleCommand parent;
                (parent = SearchConsoleCommand.this).searchProcess();
                DeviceManager.getInstance().getMainDisplay().callSerially(parent);
            }
        })).start();
    }
}

final class SoundsSearchConsoleCommand extends SearchConsoleCommand implements PlayerListener, CommandListener, ItemCommandListener, ItemStateListener
{
    private static final int GLYPH_COLOR;

    static {
        GLYPH_COLOR = RasterCanvas.getSystemColor(0x24);
    }

    private boolean delay;
    private int current;
    private CustomPlayer[] sounds;
    private Image imagePrev;
    private Image imageNext;
    private Image imagePlay;
    private Image imagePause;
    private Command commandSaveAction;
    private Command commandCancel;
    private Command commandSelect;
    private Command commandSave;
    private Command commandBack;
    private Gauge itemVolumeBar;
    private Gauge itemProgressBar;
    private StringItem itemSoundNum;
    private ImageItem itemSoundPrev;
    private ImageItem itemSoundNext;
    private ImageItem itemSoundPlay;
    private TextField itemFileName;
    private Alert screenProgress;
    private Form screenSounds;
    private Form screenEmpty;
    private Form screenSave;

    public SoundsSearchConsoleCommand(SystemManager manager) {
        super("звуки", manager);
    }

    public void playerUpdate(Player player, String event, Object data) {
        Player[] found;
        if((found = sounds) != null && current == Array.findf(found, 0, player)) updateScreenSounds();
    }

    public void commandAction(Command command, Displayable screen) {
        int index;
        CustomPlayer[] found;
        if(screen == screenSounds)
        {
            if(command == commandSave)
            {
                if((found = sounds) != null && (index = current) >= 0 && index < found.length)
                {
                    CustomPlayer player;
                    if((player = found[index]) instanceof SyntheticPlayer)
                    {
                        getItemFileName().setString("/имя.mid");
                    }
                    else if(player instanceof SampledPlayer)
                    {
                        getItemFileName().setString("/имя.wav");
                    }
                    else
                    {
                        getItemFileName().setString("/имя");
                    }
                }
                DeviceManager.getInstance().getMainDisplay().setCurrent(getScreenSave());
                return;
            }
            if(command == commandBack)
            {
                for(int i = (found = sounds) == null ? 0 : found.length; i-- > 0; )
                {
                    CustomPlayer player;
                    if((player = found[i]).getState() == Player.CLOSED) continue;
                    try
                    {
                        player.removePlayerListener(this);
                    }
                    catch(IllegalStateException e)
                    {
                        e.printRealStackTrace();
                    }
                }
                sounds = null;
                returnToSystem();
            }
            return;
        }
        if(screen == screenSave)
        {
            if(command == commandSaveAction && (found = sounds) != null && (index = current) >= 0 && index < found.length)
            {
                final String fileName = getItemFileName().getString();
                final CustomPlayer player = found[index];
                ((Thread) (new Thread("Сохранение звука") {
                    public void run() {
                        String destFileName = fileName;
                        CustomPlayer destPlayer = player;
                        System.out.println((new StringBuilder()).append("Сохранение звука в ").append(destFileName).append(" …").toString());
                        try
                        {
                            OutputStream stream;
                            OutputAdapter adapter;
                            FileConnection connection;
                            if(destPlayer instanceof SyntheticPlayer)
                            {
                                SyntheticSoundEncoder encoder;
                                SyntheticSoundDecoder decoder = ((SyntheticPlayer) destPlayer).getDecoder();
                                (encoder = new MIDIEncoder()).setMessages(decoder.getMessages());
                                adapter = encoder;
                            }
                            else if(destPlayer instanceof SampledPlayer)
                            {
                                SampledSoundEncoder encoder;
                                SampledSoundDecoder decoder = ((SampledPlayer) destPlayer).getDecoder();
                                (encoder = new WaveEncoder()).setSamples(decoder.getChannels(), decoder.getSamplesPerSecond(), decoder.getSamples());
                                adapter = encoder;
                            }
                            else
                            {
                                adapter = null;
                            }
                            connection = (FileConnection) Connector.open("file://".concat(destFileName));
                            try
                            {
                                if(connection.exists()) connection.delete();
                                connection.create();
                                stream = connection.openOutputStream();
                                try
                                {
                                    adapter.saveToOutputStream(stream);
                                }
                                finally
                                {
                                    stream.close();
                                }
                            }
                            finally
                            {
                                connection.close();
                            }
                            System.out.println((new StringBuilder()).append("Звук успешно сохранён в ").append(destFileName).append('.').toString());
                        }
                        catch(Exception e)
                        {
                            System.out.println((new StringBuilder()).append("Ошибка возникла при сохранении звука в ").append(destFileName).append('.').toString());
                            e.printRealStackTrace();
                        }
                    }
                })).start();
            }
            DeviceManager.getInstance().getMainDisplay().setCurrent(getScreenSounds());
            return;
        }
        if(screen == screenProgress && command == commandCancel)
        {
            cancelSearchProcess();
            return;
        }
        if(screen == screenEmpty && command == commandBack) returnToSystem();
    }

    public void commandAction(Command command, Item item) {
        int len;
        CustomPlayer[] found;
        if((found = sounds) == null || (len = found.length) <= 0) return;
        if(item == itemSoundPrev)
        {
            current = current == 0 ? len - 1 : current - 1;
            updateScreenSounds();
            return;
        }
        if(item == itemSoundNext)
        {
            current = current == len - 1 ? 0 : current + 1;
            updateScreenSounds();
            return;
        }
        if(item == itemSoundPlay)
        {
            try
            {
                CustomPlayer player;
                if((player = found[current]).getState() == Player.STARTED)
                {
                    player.stop();
                } else
                {
                    player.start();
                }
            }
            catch(MediaException e)
            {
                e.printRealStackTrace();
            }
            updateScreenSounds();
        }
    }

    public void itemStateChanged(Item item) {
        Gauge itemGauge;
        if(item == (itemGauge = itemVolumeBar))
        {
            int index;
            CustomPlayer[] found;
            if((found = sounds) != null && (index = current) >= 0 && index < found.length)
            {
                VolumeControl volume;
                (volume = (VolumeControl) found[index].getControl("VolumeControl")).setMute(false);
                volume.setLevel(itemGauge.getValue() * 10);
            }
        }
    }

    protected void searchProcess() {
        int len;
        int count;
        Object[] memory = Memory.getAllObjects();
        Gauge progress = getItemProgressBar();
        Vector players = new Vector();
        if((len = memory != null ? memory.length : 0) >= 100) progress.setMaxValue(len / 100);
        for(int i = 0; i < len; i++)
        {
            Object curr;
            if(i % 100 == 0)
            {
                progress.setValue(i / 100);
                if(delay)
                {
                    try
                    {
                        Thread.sleep(100L);
                    }
                    catch(InterruptedException e)
                    {
                        e.printRealStackTrace();
                    }
                }
            }
            if((curr = memory[i]) != null && (curr instanceof SyntheticPlayer || curr instanceof SampledPlayer))
            {
                int state;
                CustomPlayer player;
                if((state = (player = (CustomPlayer) curr).getState()) == Player.STARTED || state == Player.PREFETCHED) players.addElement(player);
            }
        }
        if((count = players.size()) > 0) players.copyInto(this.sounds = new CustomPlayer[count]);
        progress.setValue(len / 100);
        if(delay)
        {
            try
            {
                Thread.sleep(100L);
            }
            catch(InterruptedException e)
            {
                e.printRealStackTrace();
            }
        }
    }

    protected void showProgressScreen(Display display) {
        Displayable screen;
        (screen = getScreenEmpty()).setTitle("Любите красить пасхальные яйца?");
        display.setCurrent(getScreenProgress(), screen);
    }

    protected void showFoundScreen(Display display) {
        int count;
        CustomPlayer[] found;
        Displayable screen;
        current = 0;
        if((found = sounds) == null || (count = found.length) <= 0)
        {
            screen = getScreenEmpty();
        } else
        {
            for(int i = count; i-- > 0; )
            {
                CustomPlayer player;
                if((player = found[i]).getState() == Player.CLOSED) continue;
                try
                {
                    player.addPlayerListener(this);
                }
                catch(IllegalStateException e)
                {
                    e.printRealStackTrace();
                }
            }
            updateScreenSounds();
            screen = getScreenSounds();
        }
        screen.setTitle("Звуки");
        display.setCurrent(screen);
    }

    protected void execute(String[] arguments, Console console) {
        delay = true;
        super.execute(arguments, console);
    }

    protected void executeInBackground() {
        delay = false;
        super.executeInBackground();
    }

    private void updateScreenSounds() {
        int len;
        int index;
        CustomPlayer[] found;
        if((found = sounds) != null && (index = current) >= 0 && index < (len = found.length))
        {
            CustomPlayer player = found[index];
            try
            {
                VolumeControl volume = (VolumeControl) player.getControl("VolumeControl");
                getItemVolumeBar().setValue(volume.isMuted() ? 0 : volume.getLevel() / 10);
            }
            catch(Exception e)
            {
                getItemVolumeBar().setValue(0);
            }
            getItemSoundPlay().setImage(player.getState() == Player.STARTED ? getImagePause() : getImagePlay());
            getItemSoundNum().setText((new StringBuilder()).append(' ').append(index + 1).append(" / ").append(len).toString());
        }
    }

    private Image getImagePrev() {
        Image result;
        if((result = imagePrev) == null)
        {
            Graphics render;
            (render = (result = imagePrev = DirectUtils.createImage(24, 24, 0x00000000)).getGraphics()).setColor(GLYPH_COLOR);
            render.fillTriangle(19, 1, 9, 11, 19, 21);
            render.fillRect(4, 1, 5, 21);
        }
        return result;
    }

    private Image getImageNext() {
        Image result;
        if((result = imageNext) == null)
        {
            Graphics render;
            (render = (result = imageNext = DirectUtils.createImage(24, 24, 0x00000000)).getGraphics()).setColor(GLYPH_COLOR);
            render.fillTriangle(4, 1, 14, 11, 4, 21);
            render.fillRect(15, 1, 5, 21);
        }
        return result;
    }

    private Image getImagePlay() {
        Image result;
        if((result = imagePlay) == null)
        {
            Graphics render;
            (render = (result = imagePlay = DirectUtils.createImage(24, 24, 0x00000000)).getGraphics()).setColor(GLYPH_COLOR);
            render.fillTriangle(1, 1, 21, 11, 1, 21);
        }
        return result;
    }

    private Image getImagePause() {
        Image result;
        if((result = imagePause) == null)
        {
            Graphics render;
            (render = (result = imagePause = DirectUtils.createImage(24, 24, 0x00000000)).getGraphics()).setColor(GLYPH_COLOR);
            render.fillRect(5, 1, 5, 21);
            render.fillRect(14, 1, 5, 21);
        }
        return result;
    }

    private Command getCommandSaveAction() {
        Command result;
        if((result = commandSaveAction) == null) result = commandSaveAction = new Command("Сохранить", Command.OK, 0);
        return result;
    }

    private Command getCommandCancel() {
        Command result;
        if((result = commandCancel) == null) result = commandCancel = new Command("Отменить", Command.CANCEL, 0);
        return result;
    }

    private Command getCommandSelect() {
        Command result;
        if((result = commandSelect) == null) result = commandSelect = new Command("Выбрать", Command.OK, 0);
        return result;
    }

    private Command getCommandSave() {
        Command result;
        if((result = commandSave) == null) result = commandSave = new Command("Сохранить…", Command.SCREEN, 0);
        return result;
    }

    private Command getCommandBack() {
        Command result;
        if((result = commandBack) == null) result = commandBack = new Command("Назад", Command.BACK, 0);
        return result;
    }

    private Gauge getItemVolumeBar() {
        Gauge result;
        if((result = itemVolumeBar) == null) result = itemVolumeBar = new Gauge("Громкость", true, 10, 0);
        return result;
    }

    private Gauge getItemProgressBar() {
        Gauge result;
        if((result = itemProgressBar) == null) result = itemProgressBar = new Gauge(null, false, 1, 0);
        return result;
    }

    private StringItem getItemSoundNum() {
        StringItem result;
        if((result = itemSoundNum) == null) (result = itemSoundNum = new StringItem(null, " ")).setLayout(Item.LAYOUT_LEFT | Item.LAYOUT_EXPAND);
        return result;
    }

    private ImageItem getItemSoundPrev() {
        ImageItem result;
        if((result = itemSoundPrev) == null)
        {
            (result = itemSoundPrev = new ImageItem(null, getImagePrev(), Item.LAYOUT_EXPAND, null, Item.BUTTON)).setItemCommandListener(this);
            result.setDefaultCommand(getCommandSelect());
        }
        return result;
    }

    private ImageItem getItemSoundNext() {
        ImageItem result;
        if((result = itemSoundNext) == null)
        {
            (result = itemSoundNext = new ImageItem(null, getImageNext(), Item.LAYOUT_EXPAND, null, Item.BUTTON)).setItemCommandListener(this);
            result.setDefaultCommand(getCommandSelect());
        }
        return result;
    }

    private ImageItem getItemSoundPlay() {
        ImageItem result;
        if((result = itemSoundPlay) == null)
        {
            (result = itemSoundPlay = new ImageItem(null, getImagePlay(), Item.LAYOUT_EXPAND, null, Item.BUTTON)).setItemCommandListener(this);
            result.setDefaultCommand(getCommandSelect());
        }
        return result;
    }

    private TextField getItemFileName() {
        TextField result;
        if((result = itemFileName) == null) (result = itemFileName = new TextField("Имя файла", null, 128, Input.ANY)).setLayout(Item.LAYOUT_EXPAND);
        return result;
    }

    private Alert getScreenProgress() {
        Alert result;
        if((result = screenProgress) == null)
        {
            (result = screenProgress = new Alert(null, "Поиск звуков в памяти…", null, AlertType.INFO)).setCommandListener(this);
            result.addCommand(getCommandCancel());
            result.setIndicator(getItemProgressBar());
        }
        return result;
    }

    private Form getScreenSounds() {
        Form result;
        if((result = screenSounds) == null)
        {
            ImageItem soundIcon = new ImageItem(null, SystemManager.loadImageFromFile("/ui/audio.png"), Item.LAYOUT_NEWLINE_BEFORE | Item.LAYOUT_CENTER | Item.LAYOUT_NEWLINE_AFTER, null);
            (result = screenSounds = new Form(null, new Item[] { getItemSoundNum(), soundIcon, getItemVolumeBar(), getItemSoundPrev(), getItemSoundPlay(), getItemSoundNext() }) {
                protected void sizeChanged(int width, int height) {
                    get(1).setPreferredSize(-1, height - get(0).getPreferredHeight() - get(2).getPreferredHeight() - get(3).getPreferredHeight());
                }
            }).setCommandListener(this);
            result.setItemStateListener(this);
            result.addCommand(getCommandBack());
            result.addCommand(getCommandSave());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }

    private Form getScreenEmpty() {
        Form result;
        if((result = screenEmpty) == null)
        {
            Font font;
            String text;
            StringItem empty;
            (empty = new StringItem(null, text = "(нет звуков)")).setLayout(Item.LAYOUT_CENTER);
            empty.setFont(font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL));
            empty.setPreferredSize(font.stringWidth(text), -1);
            (result = screenEmpty = new Form(null, new Item[] { empty })).setCommandListener(this);
            result.addCommand(getCommandBack());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }

    private Form getScreenSave() {
        Form result;
        if((result = screenSave) == null)
        {
            (result = screenSave = new Form("Сохранить", new Item[] { getItemFileName() })).setCommandListener(this);
            result.addCommand(getCommandSaveAction());
            result.addCommand(getCommandBack());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }
}

final class ImagesSearchConsoleCommand extends SearchConsoleCommand implements CommandListener, ItemCommandListener
{
    private static final int GLYPH_COLOR;

    static {
        GLYPH_COLOR = RasterCanvas.getSystemColor(0x24);
    }

    private boolean delay;
    private int current;
    private Image[] images;
    private Image imagePrev;
    private Image imageNext;
    private Command commandSaveAction;
    private Command commandCancel;
    private Command commandSelect;
    private Command commandSave;
    private Command commandBack;
    private Gauge itemProgressBar;
    private StringItem itemImageNum;
    private ImageItem itemImagePrev;
    private ImageItem itemImageNext;
    private ImageItem itemImageShow;
    private TextField itemFileName;
    private Alert screenProgress;
    private Form screenImages;
    private Form screenEmpty;
    private Form screenSave;

    public ImagesSearchConsoleCommand(SystemManager manager) {
        super("изображения", manager);
    }

    public void commandAction(Command command, Displayable screen) {
        int index;
        Image[] found;
        if(screen == screenImages)
        {
            if(command == commandSave)
            {
                getItemFileName().setString("/имя.png");
                DeviceManager.getInstance().getMainDisplay().setCurrent(getScreenSave());
                return;
            }
            if(command == commandBack)
            {
                images = null;
                returnToSystem();
            }
            return;
        }
        if(screen == screenSave)
        {
            if(command == commandSaveAction && (found = images) != null && (index = current) >= 0 && index < found.length)
            {
                final String fileName = getItemFileName().getString();
                final Image image = found[index];
                ((Thread) (new Thread("Сохранение изображения") {
                    public void run() {
                        Image destImage = image;
                        String destFileName = fileName;
                        System.out.println((new StringBuilder()).append("Сохранение изображения в ").append(destFileName).append(" …").toString());
                        try
                        {
                            int width = destImage.getWidth();
                            int height = destImage.getHeight();
                            int[] pixels = new int[width * height];
                            OutputStream stream;
                            ImageEncoder encoder;
                            FileConnection connection;
                            destImage.getRGB(pixels, 0, width, 0, 0, width, height);
                            (encoder = new PNGEncoder()).setPixels(true, width, height, pixels);
                            connection = (FileConnection) Connector.open("file://".concat(destFileName));
                            try
                            {
                                if(connection.exists()) connection.delete();
                                connection.create();
                                stream = connection.openOutputStream();
                                try
                                {
                                    encoder.saveToOutputStream(stream);
                                }
                                finally
                                {
                                    stream.close();
                                }
                            }
                            finally
                            {
                                connection.close();
                            }
                            System.out.println((new StringBuilder()).append("Изображение успешно сохранено в ").append(destFileName).append('.').toString());
                        }
                        catch(Exception e)
                        {
                            System.out.println((new StringBuilder()).append("Ошибка возникла при сохранении изображения в ").append(destFileName).append('.').toString());
                            e.printRealStackTrace();
                        }
                    }
                })).start();
            }
            DeviceManager.getInstance().getMainDisplay().setCurrent(getScreenImages());
            return;
        }
        if(screen == screenProgress && command == commandCancel)
        {
            cancelSearchProcess();
            return;
        }
        if(screen == screenEmpty && command == commandBack) returnToSystem();
    }

    public void commandAction(Command command, Item item) {
        int len;
        Object[] found;
        if((found = images) == null || (len = found.length) <= 0) return;
        if(item == itemImagePrev)
        {
            current = current == 0 ? len - 1 : current - 1;
            updateScreenImages();
            return;
        }
        if(item == itemImageNext)
        {
            current = current == len - 1 ? 0 : current + 1;
            updateScreenImages();
        }
    }

    protected void searchProcess() {
        int len;
        int count;
        Object[] memory = Memory.getAllObjects();
        Gauge progress = getItemProgressBar();
        Vector images = new Vector();
        if((len = memory != null ? memory.length : 0) >= 100) progress.setMaxValue(len / 100);
        for(int i = 0; i < len; i++)
        {
            Object curr;
            if(i % 100 == 0)
            {
                progress.setValue(i / 100);
                if(delay)
                {
                    try
                    {
                        Thread.sleep(100L);
                    }
                    catch(InterruptedException e)
                    {
                        e.printRealStackTrace();
                    }
                }
            }
            if((curr = memory[i]) instanceof Image) images.addElement(curr);
        }
        if((count = images.size()) > 0) images.copyInto(this.images = new Image[count]);
        progress.setValue(len / 100);
        if(delay)
        {
            try
            {
                Thread.sleep(100L);
            }
            catch(InterruptedException e)
            {
                e.printRealStackTrace();
            }
        }
    }

    protected void showProgressScreen(Display display) {
        Displayable screen;
        (screen = getScreenEmpty()).setTitle("Любите красить пасхальные яйца?");
        display.setCurrent(getScreenProgress(), screen);
    }

    protected void showFoundScreen(Display display) {
        Image[] found;
        Displayable screen;
        current = 0;
        if((found = images) == null || found.length <= 0)
        {
            screen = getScreenEmpty();
        } else
        {
            updateScreenImages();
            screen = getScreenImages();
        }
        screen.setTitle("Изображения");
        display.setCurrent(screen);
    }

    protected void execute(String[] arguments, Console console) {
        delay = true;
        super.execute(arguments, console);
    }

    protected void executeInBackground() {
        delay = false;
        super.executeInBackground();
    }

    private void updateScreenImages() {
        int len;
        int index;
        Image[] found;
        if((found = images) != null && (index = current) >= 0 && index < (len = found.length))
        {
            getItemImageShow().setImage(found[index]);
            getItemImageNum().setText((new StringBuilder()).append(' ').append(index + 1).append(" / ").append(len).toString());
        }
    }

    private Image getImagePrev() {
        Image result;
        if((result = imagePrev) == null)
        {
            Graphics render;
            (render = (result = imagePrev = DirectUtils.createImage(24, 24, 0x00000000)).getGraphics()).setColor(GLYPH_COLOR);
            render.fillTriangle(19, 1, 9, 11, 19, 21);
            render.fillRect(4, 1, 5, 21);
        }
        return result;
    }

    private Image getImageNext() {
        Image result;
        if((result = imageNext) == null)
        {
            Graphics render;
            (render = (result = imageNext = DirectUtils.createImage(24, 24, 0x00000000)).getGraphics()).setColor(GLYPH_COLOR);
            render.fillTriangle(4, 1, 14, 11, 4, 21);
            render.fillRect(15, 1, 5, 21);
        }
        return result;
    }

    private Command getCommandSaveAction() {
        Command result;
        if((result = commandSaveAction) == null) result = commandSaveAction = new Command("Сохранить", Command.OK, 0);
        return result;
    }

    private Command getCommandCancel() {
        Command result;
        if((result = commandCancel) == null) result = commandCancel = new Command("Отменить", Command.CANCEL, 0);
        return result;
    }

    private Command getCommandSelect() {
        Command result;
        if((result = commandSelect) == null) result = commandSelect = new Command("Выбрать", Command.OK, 0);
        return result;
    }

    private Command getCommandSave() {
        Command result;
        if((result = commandSave) == null) result = commandSave = new Command("Сохранить…", Command.SCREEN, 0);
        return result;
    }

    private Command getCommandBack() {
        Command result;
        if((result = commandBack) == null) result = commandBack = new Command("Назад", Command.BACK, 0);
        return result;
    }

    private Gauge getItemProgressBar() {
        Gauge result;
        if((result = itemProgressBar) == null) result = itemProgressBar = new Gauge(null, false, 1, 0);
        return result;
    }

    private StringItem getItemImageNum() {
        StringItem result;
        if((result = itemImageNum) == null) (result = itemImageNum = new StringItem(null, " ")).setLayout(Item.LAYOUT_VCENTER | Item.LAYOUT_EXPAND);
        return result;
    }

    private ImageItem getItemImagePrev() {
        ImageItem result;
        if((result = itemImagePrev) == null)
        {
            (result = itemImagePrev = new ImageItem(null, getImagePrev(), Item.LAYOUT_VCENTER, null, Item.BUTTON)).setItemCommandListener(this);
            result.setDefaultCommand(getCommandSelect());
        }
        return result;
    }

    private ImageItem getItemImageNext() {
        ImageItem result;
        if((result = itemImageNext) == null)
        {
            (result = itemImageNext = new ImageItem(null, getImageNext(), Item.LAYOUT_VCENTER, null, Item.BUTTON)).setItemCommandListener(this);
            result.setDefaultCommand(getCommandSelect());
        }
        return result;
    }

    private ImageItem getItemImageShow() {
        ImageItem result;
        if((result = itemImageShow) == null) result = itemImageShow = new ImageItem(null, null, Item.LAYOUT_NEWLINE_BEFORE, null);
        return result;
    }

    private TextField getItemFileName() {
        TextField result;
        if((result = itemFileName) == null) (result = itemFileName = new TextField("Имя файла", null, 128, Input.ANY)).setLayout(Item.LAYOUT_EXPAND);
        return result;
    }

    private Alert getScreenProgress() {
        Alert result;
        if((result = screenProgress) == null)
        {
            (result = screenProgress = new Alert(null, "Поиск изображений в памяти…", null, AlertType.INFO)).setCommandListener(this);
            result.addCommand(getCommandCancel());
            result.setIndicator(getItemProgressBar());
        }
        return result;
    }

    private Form getScreenImages() {
        Form result;
        if((result = screenImages) == null)
        {
            (result = screenImages = new Form(null, new Item[] { getItemImagePrev(), getItemImageNext(), getItemImageNum(), getItemImageShow() })).setCommandListener(this);
            result.addCommand(getCommandBack());
            result.addCommand(getCommandSave());
        }
        return result;
    }

    private Form getScreenEmpty() {
        Form result;
        if((result = screenEmpty) == null)
        {
            Font font;
            String text;
            StringItem empty;
            (empty = new StringItem(null, text = "(нет изображений)")).setLayout(Item.LAYOUT_CENTER);
            empty.setFont(font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL));
            empty.setPreferredSize(font.stringWidth(text), -1);
            (result = screenEmpty = new Form(null, new Item[] { empty })).setCommandListener(this);
            result.addCommand(getCommandBack());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }

    private Form getScreenSave() {
        Form result;
        if((result = screenSave) == null)
        {
            (result = screenSave = new Form("Сохранить", new Item[] { getItemFileName() })).setCommandListener(this);
            result.addCommand(getCommandSaveAction());
            result.addCommand(getCommandBack());
            result.getHorizontalScrollBar().hide();
        }
        return result;
    }
}
