IscanderChe
IscanderChe30 июля 2019 г. 3:06

Проект Simple Tracker. Часть 6: сервер. Слоты сервера

Слоты, имеющие отношение непосредственно к серверу, мы рассмотрим в связке с клиентом. Пока же сосредоточимся на тех слотах, которые относятся к управлению проектами и задачами и общих слотах приложения.

Начнём с соединений. Их я вынес в отдельный метод void TrackerServer::createConnections() .

// trackerserver.cpp

// Метод создания подключений
void TrackerServer::createConnections()
{
    // Установка текущего проекта
    connect(projectView, &QListView::clicked, this, &TrackerServer::slotSetCurrentProject);
    connect(projectView, &QListView::activated, this, &TrackerServer::slotSetCurrentProject);

    // Подключение сигнала о смене статуса к обработчику
    connect(statusDelegate, &ComboBoxDelegate::commitData, this, &TrackerServer::slotChangeTaskStatus);

    // Создание новой задачи
    connect(newTaskButton, &QPushButton::clicked, this, &TrackerServer::slotNewTask);

    // Редактирование существующей задачи
    connect(editTaskButton, &QPushButton::clicked, this, &TrackerServer::slotEditTask);
    connect(taskView, &QTableView::doubleClicked, this, &TrackerServer::slotEditTask);

    // Удаление существующей задачи
    connect(deleteTaskButton, &QPushButton::clicked, this, &TrackerServer::slotDeleteTask);

    // Создание нового проекта
    connect(newProjectButton, &QPushButton::clicked, this, &TrackerServer::slotNewProject);

    // Архивация существующего проекта
    connect(archiveProjectButton, &QPushButton::clicked, this, &TrackerServer::slotArchiveProject);

    // Открытие архива проектов
    connect(openArchiveButton, &QPushButton::clicked, this, &TrackerServer::slotOpenArchive);

    // Открытие окна трекера
    connect(actionOpenWindow, &QAction::triggered, this, &TrackerServer::slotOpenTrackerWindow);

    // Открытие окна трекера по двойному клику на иконке в трее
    connect(trayIcon, &QSystemTrayIcon::activated, this, &TrackerServer::slotDoubleClickOpenTrackerWindow);

    // Завершение работы трекера
    connect(actionQuit, &QAction::triggered, qApp, &QApplication::quit);

    // Открытие окна настроек приложения
    connect(actionSettings, &QAction::triggered, this, &TrackerServer::slotOpenSettingsWindow);
}

Для упрощения работы с элементами списка проектов определяем перечисление для Qt::DisplayRole и дополнительных ролей Qt::UserRole .

// trackerserver.h

enum ProjectProperty
{
    VisibleName = Qt::DisplayRole,
    Name = Qt::UserRole + 1,
    Id = Qt::UserRole + 2,
    Manual = Qt::UserRole + 3,
    Arch = Qt::UserRole + 4
};
// trackerserver.cpp

// Слот установки текущего проекта
void TrackerServer::slotSetCurrentProject(QModelIndex index)
{
    // Получаем элемент модели, соответствующий полученному индексу
    QStandardItem* item = projectListModel->itemFromIndex(index);
    // Получаем id текущего проекта
    projectCurrentId = item->data(ProjectProperty::Id).toInt();
    // Устанавливаем условие фильтрации таблицы задач по текущему id проекта
    taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId));

    // Обновляем представление таблицы задач
    updateTaskView();

    // Считаем количество строк в модели задач
    int rowNumber = taskFilterModel->rowCount();

    // Если количество строк больше нуля
    if(rowNumber > 0)
    {
        // Получаем индекс нулевого элемента модели задач
        int row = 0;
        QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
        // Устанавливаем в представлении текущий элемент с этим индексом
        taskView->setCurrentIndex(index);
        // Делаем доступными кнопки редактирования и удаления задач
        editTaskButton->setEnabled(true);
        deleteTaskButton->setEnabled(true);
    }
    else
    {
        // Иначе блокируем кнопки редактирования и удаления задач
        editTaskButton->setDisabled(true);
        deleteTaskButton->setDisabled(true);
    }

    // Делаем доступной кнопку создания новой задачи
    newTaskButton->setEnabled(true);

    // Проверяем, закрыты ли все задачи текущего проекта
    if(database->isClosedAllTasks(projectCurrentId))
        // Если да, то делаем доступной кнопку архивирования проекта
        archiveProjectButton->setEnabled(true);
    else
        // Если нет, то блокируем кнопку архивирования проекта
        archiveProjectButton->setDisabled(true);

    // Устанавливаем в виде текущим элемент с полученным индексом
    projectView->setCurrentIndex(index);
}

// Слот создания нового проекта
void TrackerServer::slotNewProject()
{
    // Объявляем диалог создания нового проекта
    QSharedPointer<NewProjectDialog> dialog(new NewProjectDialog());
    // Созадаём диалоговое окно
    if(dialog->exec() == QDialog::Accepted)
    {
        // Если в диалоге нажата кнопка "ОК",
        // получаем из диалога наименование проекта
        QString projectName = dialog->getProjectName();
        // Если наименование проекта не пустое
        if(!projectName.isEmpty())
        {
            // Добавляем проект со свойство "без контроля СКВ"
            database->insertNewProject(projectName, ProjectVCS::WithoutVCS);
            // Обновляем модель проектов
            projectTableModel->select();

            // Устанавливаем свойства элемента
            QString visibleProjectName = projectName;
            int row = projectTableModel->rowCount() - 1;
            int projectId =
                projectTableModel->record(row).value(idProjectCol).toInt();
            int projectManual = ProjectVCS::WithoutVCS;
            int projectArch = ProjectArchive::Active;

            visibleProjectName = "(M) " + projectName;

            QStandardItem* item = new QStandardItem();
            // Загружаем данные в элемент
            item->setData(visibleProjectName, ProjectProperty::VisibleName);
            item->setData(projectName, ProjectProperty::Name);
            item->setData(projectId, ProjectProperty::Id);
            item->setData(projectManual, ProjectProperty::Manual);
            item->setData(projectArch, ProjectProperty::Arch);

            // Загружаем элемент в модель представления проектов
            projectListModel->appendRow(item);

            // Устанавливаем текущий id проекта равный id нового проекта
            projectCurrentId = projectId;
            // Получаем индекс элемента нового проекта в модели представления
            int column = 0;
            QModelIndex currentProjectIndex = projectListModel->index(row, column);
            // Устанавливаем условие фильтрации таблицы задач по текущему id проекта
            taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId));
            // Устанвливаем текущий проект по индексу
            slotSetCurrentProject(currentProjectIndex);

            // Поскольку новый проект пустой, блокируем кнопки удаления и редактирования задач,
            // и делаем доступной кнопку создания новой задачи
            editTaskButton->setDisabled(true);
            deleteTaskButton->setDisabled(true);
            newTaskButton->setEnabled(true);
        }
    }
}

// Слот архивации проекта
void TrackerServer::slotArchiveProject()
{
    // Определяем индекс текущего проекта
    QModelIndex currentProjectIndex = projectView->currentIndex();
    int row = currentProjectIndex.row();

    // Скрываем проект в представлении
    projectView->setRowHidden(row, true);

    // Устанавливаем для элемента модели пресдтавления свойство архивации проекта
    QStandardItem* item = projectListModel->item(row);
    item->setData(ProjectArchive::Archive, ProjectProperty::Arch);
    projectListModel->setItem(row, item);

    // Получаем из элемента модели представления id проекта
    int projectId = item->data(ProjectProperty::Id).toInt();
    // Архивируем проект
    database->archiveProject(projectId);

    // Блокируем кнопку архивации проектов
    archiveProjectButton->setDisabled(true);

    // Определяем, остались ли видимые элементы в списке проектов
    int numberRows = projectListModel->rowCount();
    int numberRowsNotHidden = 0;
    int firstRowNotHidden = 0;

    for(int row = 0; row < numberRows; ++row)
    {
        if(!projectView->isRowHidden(row))
        {
            ++numberRowsNotHidden;
            firstRowNotHidden = row;
            break;
        }
    }

    if(numberRowsNotHidden > 0)
    {
        // Если видимые элементы остались, определем индекс первого
        QModelIndex index = projectListModel->index(firstRowNotHidden, 0);
        // Устанавливаем этот элемент текущим проектом
        slotSetCurrentProject(index);
    }
    else
    {
        // Если видимых элементов нет, обнуляем текущий id проекта
        projectCurrentId = 0;
        // Устанавливаем условие фильтрации таблицы задач по текущему id проекта
        taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId));
        // Обновляем представление таблицы задач
        updateTaskView();
    }

}

// Слот открытия архива
void TrackerServer::slotOpenArchive()
{
    // Объявляем диалог открытия архива
    QSharedPointer<OpenArchiveDialog> dialog(new OpenArchiveDialog(projectListModel));
    // Если подтверждено извлечение из архива проекта/проектов
    if(dialog->exec() == QDialog::Accepted)
    {
        // Получаем список id извлекаемых из архива проектов
        QList<int> projectIdList = dialog->getListProjectId();
        int lastId = projectIdList.last();
        // Извлекаем проекты из архива
        foreach(int id, projectIdList)
            database->extractProject(id);

        // Обновляем модель списка проектов
        updateProjectListModel();
        // Обновляем виджет списка проектов
        updateProjectView();

        int numberRows = projectListModel->rowCount();

        // Определяем индес последнего извлечённого проекта
        QModelIndex lastIndex;
        for(int row = 0; row < numberRows; ++row)
        {
            QStandardItem* item = projectListModel->item(row);
            int projectId = item->data(ProjectProperty::Id).toInt();
            if(projectId == lastId)
                lastIndex = projectListModel->indexFromItem(item);
        }

        // Устанавливаем текущим последний извлечённый проект
        slotSetCurrentProject(lastIndex);
    }
}

// Слот изменения состояния задачи
void TrackerServer::slotChangeTaskStatus(QWidget* /* editor */)
{
    // Обновляем модель SQL-таблицы задач
    taskSqlTableModel->select();

    if(database->isClosedAllTasks(projectCurrentId))
        // Если все задачи закрыты, делаем доступной кнопку архивации проектов
        archiveProjectButton->setEnabled(true);
    else
        // Если нет, блокируем кнопку архивации проектов
        archiveProjectButton->setDisabled(true);
}

// Слот создания новой задачи
void TrackerServer::slotNewTask()
{
    // Объявляем диалог создания / редактирования задачи
    QSharedPointer<NewEditTaskDialog> dialog(new NewEditTaskDialog());
    // Если создание новой задачи подтверждено
    if(dialog->exec() == QDialog::Accepted)
    {
        // Получаем из диалога тип и описание задачи
        QString type = dialog->getType();
        QString description = dialog->getDescription();
        // Если тип задачи и описание не пустые
        if(!type.isEmpty() && !description.isEmpty())
        {
            // Добавляем новую задачу
            database->insertNewTask(projectCurrentId, type, description);
            // Обновляем модель SQL-таблицы задач
            taskSqlTableModel->select();
            // Обновляем представление задач
            updateTaskView();
            // Определяем номер строки созданной задачи
            int row = taskFilterModel->rowCount() - 1;
            // Определяем индекс новой задачи в модели представления задач
            QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
            // Устанавливаем эту задачу текущей в представлении
            taskView->setCurrentIndex(index);
            // Делаем доступными кнопки редактирования и удаления задач
            editTaskButton->setEnabled(true);
            deleteTaskButton->setEnabled(true);
        }
    }
}

// Слот редактирования задачи
void TrackerServer::slotEditTask()
{
    // Объявляем диалог создания / редактирования задачи
    QSharedPointer<NewEditTaskDialog> dialog(new NewEditTaskDialog());

    // Извлекаем из текущего индекса задачи id, тип и описание задачи
    QModelIndex currentIndex = taskView->currentIndex();
    QModelIndex idIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_NUMBER);
    QModelIndex typeIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_TYPE);
    QModelIndex descriptionIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_DESCRIPTION);

    QString type = taskFilterModel->data(typeIndex, Qt::DisplayRole).toString();
    QString description = taskFilterModel->data(descriptionIndex, Qt::DisplayRole).toString();
    int id = taskFilterModel->data(idIndex, Qt::DisplayRole).toInt();

    // Передаём в диалог тип и описание задачи
    dialog->setType(type);
    dialog->setDescription(description);

    // Если редактирование задачи подтверждено
    if(dialog->exec() == QDialog::Accepted)
    {
        // Если тип или описание задачи отредактированы
        if(type != dialog->getType() || description != dialog->getDescription())
        {
            // Редактируем задачу в базе
            database->editTask(id, dialog->getType(), dialog->getDescription());
            // Обновляем модель SQL-таблицы задач
            taskSqlTableModel->select();
            // Обновляем представление задач
            updateTaskView();
            // Устанавливаем отредактированную задачу текущей в представлении
            taskView->setCurrentIndex(currentIndex);
        }
    }
}

// Слот удаления задачи
void TrackerServer::slotDeleteTask()
{
    // Объявляем диалог удаления задачи
    QSharedPointer<DeleteTaskDialog> dialog(new DeleteTaskDialog());

    // Извлекаем из индекса id задачи
    QModelIndex currentIndex = taskView->currentIndex();
    QModelIndex idIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_NUMBER);
    int id = taskFilterModel->data(idIndex, Qt::DisplayRole).toInt();

    // Если удаление задачи подтверждено
    if(dialog->exec() == QDialog::Accepted)
    {
        // Удаляем задачу из базы
        database->deleteTask(id);
        // Обновляем модель SQL-таблицы задач
        taskSqlTableModel->select();
        // Обновляем представление задач
        updateTaskView();
        // Определяем, остались ли видимые задачи
        int numberRows = taskFilterModel->rowCount();
        if(numberRows > 0)
        {
            // Если остались, делаем текущей первую
            int row = 0;
            QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
            taskView->setCurrentIndex(index);
        }
        else if(numberRows == 0)
        {
            // Если нет, блокируем кнопки редактирования и удаления задач
            editTaskButton->setDisabled(true);
            deleteTaskButton->setDisabled(true);
        }
    }
}

// Слот открытия окна трекера
void TrackerServer::slotOpenTrackerWindow()
{
    setVisible(true);
}

// Слот открытия окна трекера по двойному клику на иконке в трее

void TrackerServer::slotDoubleClickOpenTrackerWindow(QSystemTrayIcon::ActivationReason reason)
{
    if(reason == QSystemTrayIcon::DoubleClick)
        setVisible(true);
}

// Слот открытия окна настроек программы
void TrackerServer::slotOpenSettingsWindow()
{
    QSharedPointer<SettingsDialog> dialog(new SettingsDialog());
    dialog->exec();
}

Создание проекта через трекер ограничено только проектами без контроля СКВ. Это сделано для упрощения функционала самого трекера. Проекты под контролем СКВ отслеживаются трекером с помощью следующего кода.

// trackerserver.cpp

// Метод инициализации проектов под СКВ
void TrackerServer::initProjectsWithVCS()
{
    // В переменную записываем путь к репозиториям из объекта settings класса QSettings
    QString pathToProjects = settings.value("PathToProjects", "").toString();
    if(!pathToProjects.isEmpty())
    {
        // Если путь не пустой, формируем из списка репозиториев список проектов под СКВ
        QDir dir(pathToProjects);
        QStringList projectFileList;

        foreach(QFileInfo item, dir.entryInfoList())
        {
            if(item.isDir())
            {
                QString dirPath = item.absoluteFilePath();
                QString dirName = item.fileName();
                // Критерием проекта под СКВ является наличие в репозитории в папке hooks
                // файла log.txt, куда хуки pre-commit и post-commit вносят информацию
                // о номере коммита и текст коммита с номером закрываемой задачи
                QString fileNamePath = dirPath + "/hooks/log.txt";
                QFile file(fileNamePath);
                if(file.exists())
                    projectFileList << dirName;
            }
        }

        if(!projectFileList.isEmpty())
        {
            // Если список проектов под СКВ не пустой, извлекаем из базы данных
            // список проектов и соответствующих им id проектов
            QStringList projectNameList;
            QList<int> projectIdList;

            projectTableModel->select();

            int numberRows = projectTableModel->rowCount();

            for(int row = 0; row < numberRows; ++row)
            {
                int projectId =
                    projectTableModel->record(row).value(idProjectCol).toInt();
                QString projectName =
                    projectTableModel->record(row).value(visibleNameProjectCol).toString();
                int manualFlag =
                    projectTableModel->record(row).value(manualPropertyCol).toInt();
                if(manualFlag == ProjectVCS::WithVCS)
                {
                    projectIdList << projectId;
                    projectNameList << projectName;
                }
            }

            // Формируем список новых проектов, отсутствующих в базе
            QStringList newProjectList;

            foreach(QString file, projectFileList)
            {
                int count = 0;
                foreach(QString project, projectNameList)
                {
                    if(file == project)
                        ++count;
                }
                if(count == 0)
                    newProjectList << file;
            }

            // Формируем список проектов, подлежащих автоматической архивации,
            // если репозитории этих проектов были удалены
            QStringList archiveProjectList;

            foreach(QString project, projectNameList)
            {
                int count = 1;
                foreach(QString file, projectFileList)
                {
                    if(project == file)
                        --count;
                }
                if(count > 0)
                    archiveProjectList << project;
            }

            // Вносим новые проекты по списку
            foreach(QString project, newProjectList)
                    database->insertNewProject(project, ProjectVCS::WithVCS);

            // Архивируем проекты по списку
            foreach(QString project, archiveProjectList)
            {
                for(int i = 0; i < projectNameList.size(); ++i)
                {
                    if(project == projectNameList.at(i))
                    {
                        database->archiveProject(projectIdList.at(i));
                        // Получае перечень задач для данного проекта
                        QList<int> taskIdList = database->getIdListForProject(projectIdList.at(i));
                        foreach(int taskId, taskIdList)
                        {
                            if(!database->isClosed(taskId))
                                // Если задача не закрыта, закрываем её с пометкой "forced"
                                database->closeTask(taskId, "forced");
                        }
                    }
                }
            }

            // Обновляем SQL-модель таблицы задач
            taskSqlTableModel->select();
        }
    }
}
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Evgenii Legotckoi
  • 30 июля 2019 г. 4:24

В общем-то достаточно написать так

// trackerserver.h

enum ProjectProperty
{
    VisibleName = Qt::DisplayRole,
    Name = Qt::UserRole + 1,
    Id,
    Manual,
    Arch
};

Все enum после Name будут и так увеличиваться на единицу.

Объявление переменной row не нужно

        int row = 0;
        QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
        // Устанавливаем в представлении текущий элемент с этим индексом
        taskView->setCurrentIndex(index);

Можно переписать так, читабельность от этого не страдает. 0 здесь не будет магической цифрой, поскольку здесь очевидно, что выбирается первая строка.

        taskView->setCurrentIndex(taskFilterModel->index(0, CustomSortFilterProxyModel::E_COLUMN_NUMBER));

foreach выполняет глубокое копирование , лучше переходите полноценно на современные стандарты C++. То есть вместо этого

        foreach(int id, projectIdList)
            database->extractProject(id);

Можно написать так

        for(int id : projectIdList)
            database->extractProject(id);

К тому же foreach - это макрос, а значит получаем работу препроцессора, которае в случае конструкций языка, например for, не происходит. Конечно есть случае, где foreach ещё оправдан, но их мало на самом деле.

Там где у вас QModelIndex берутся, также можно переписать в одну строку, как в предыдущем варианте. В конечном счёте и так ясно, что за это индексы по enum CustomSortFilterProxyModel::E_COLUMN_TYPE например.

Думаю, что вот это

int numberRows = taskFilterModel->rowCount();
        if(numberRows > 0)
        {
            // Если остались, делаем текущей первую
            int row = 0;
            QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
            taskView->setCurrentIndex(index);
        }
        else if(numberRows == 0)
        {
            // Если нет, блокируем кнопки редактирования и удаления задач
            editTaskButton->setDisabled(true);
            deleteTaskButton->setDisabled(true);
        }

Можно переписать так

// rowCount не должен возвращать -1 в принципе, поэтому второе условие просто лишнее
if (taskFilterModel->rowCount() > 0)
{
    // Одной строкой достаточно, и так ясно, что происходит благодаря CustomSortFilterProxyModel::E_COLUMN_NUMBER
    taskView->setCurrentIndex(taskFilterModel->index(0, CustomSortFilterProxyModel::E_COLUMN_NUMBER));
}
else
{
    editTaskButton->setDisabled(true);
    deleteTaskButton->setDisabled(true);
}

Вот это лишнее переусложнение

void TrackerServer::slotOpenSettingsWindow()
{
    QSharedPointer<SettingsDialog> dialog(new SettingsDialog());
    dialog->exec();
}

Просто создайте на стеке и тогда по окончанию работы диалог сам уничтожиться, не говоря уже о том, что в предыдущем варианте лишнее создание умного указателя с созданием объекта диалога и его копированием.

void TrackerServer::slotOpenSettingsWindow()
{
    SettingsDialog dialog;
    dialog.exec();
}

Вот это

QList<int> taskIdList = database->getIdListForProject(projectIdList.at(i));
foreach(int taskId, taskIdList)
{
    if(!database->isClosed(taskId))
        // Если задача не закрыта, закрываем её с пометкой "forced"
        database->closeTask(taskId, "forced");
}

Можно ак переписать

for (int taskId :  database->getIdListForProject(projectIdList.at(i)))
{
    if (!database->isClosed(taskId))
        database->closeTask(taskId, "forced");
}

P/S/ Вы можете в статье добавлять рекомендуемые статьи, например, список всех статей по Simple Tracker. Тогда пользователи смогу лучше ориентироваться по всем вашим статьям.

IscanderChe
  • 30 июля 2019 г. 5:19

Все enum после Name будут и так увеличиваться на единицу.

Это мне самому нужно было, чтобы при переписываниии с Qt::UserRole + n на enum не запутаться. Сам себе проблему придумал, поскольку сразу не ввёл enum.

Там где у вас QModelIndex берутся, также можно переписать в одну строку, как в предыдущем варианте.

Тоже пишу так для себя, поскольку ещё не до окнца освоился с QModelIndex.

lang-cpp for(int id : projectIdList)

Понял. Да, спасибо.

// rowCount не должен возвращать -1 в принципе, поэтому второе условие просто лишнее

Согласен.

Просто создайте на стеке и тогда по окончанию работы диалог сам уничтожиться, не говоря уже о том, что в предыдущем варианте лишнее создание умного указателя с созданием объекта диалога и его копированием.

Понял.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
m
  • molni99
  • 25 октября 2024 г. 22:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
m
  • molni99
  • 25 октября 2024 г. 22:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
Последние комментарии
i
innorwall14 ноября 2024 г. 8:42
Как Копировать Файлы в Linux If only females relatives with DZ offspring were considered these percentages were 23 order priligy online uk
i
innorwall14 ноября 2024 г. 6:09
Qt/C++ - Урок 068. Hello World с использованием системы сборки CMAKE в CLion ditropan pristiq dosing With the Yankees leading, 4 3, Rivera jogged in from the bullpen to a standing ovation as he prepared for his final appearance in Chicago buy priligy pakistan
i
innorwall14 ноября 2024 г. 1:05
EVILEG-CORE. Использование Google reCAPTCHA 2001; 98 29 34 priligy buy
i
innorwall14 ноября 2024 г. 1:00
PyQt5 - Урок 007. Работаем с QML QtQuick (Сигналы и слоты) priligy 30mg Am J Obstet Gynecol 171 1488 505
Сейчас обсуждают на форуме
i
innorwall14 ноября 2024 г. 0:39
добавить qlineseries в функции priligy amazon canada 93 GREB1 protein GREB1 AB011147 6
i
innorwall11 ноября 2024 г. 7:55
Всё ещё разбираюсь с кешем. priligy walgreens levitra dulcolax carbs The third ring was found to be made up of ultra relativistic electrons, which are also present in both the outer and inner rings
9
9Anonim25 октября 2024 г. 6:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…
ИМ
Игорь Максимов3 октября 2024 г. 1:05
Реализация навигации по разделам Спасибо Евгений!

Следите за нами в социальных сетях