Russian Qt Forum
Апрель 25, 2024, 10:52 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Предметная голосовая беседа по Model-View для OpenSrc проекта по Skype/SIP/Viber  (Прочитано 10338 раз)
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« : Май 26, 2015, 17:02 »

Все, я дошел до предела.

Не могу осилить очередную ступеньку в доработке проекта MyTetra. Релиза небыло уже 4 года. Я застрял, и дальше двигаться не могу. Проблема в том, что мне не с кем посоветоваться. В моем окружении нет ни одного программиста, который бы писал на C++ и тем более знал Qt4/5. Черт возьми, да в моем окружении вообще нет ни одного программиста! Я живу в полной профессиональной изоляции.

Сейчас мне нужно переделать в MyTetra пару моделей и вид так, чтобы вид смог показывать сортированные данные. Я собрался это делать через proxy модель. Этот вопрос я уже поднимал здесь и здесь и здесь. Но если использовать proxy модель, то нужно, чтобы source модель была чистая, с реализациями только стандартных методов QAbstractTableModel. Но из-за этого полностью ломается вся концепция моделей.

В моем понимании модель - это такой класс, который позволяет сделать с данными все что ты хочешь, используя удобный для конкретных данных интерфейс. Хочешь удалить данные по списку индексов - пожалуйста! Хочешь добавить сразу несколько строк с данными в любое место? На тебе метод. Хочешь по номеру строки или по QModelIndex получить всю строку - да не вопрос.

Но в Qt получается, что в модели можно только читать/менять данные в ячейках (data/setData), добавлять/удалять строчки и столбцы (insertRows/removeRows) и... все! Конечно, этими базовыми методами можно полностью управлять данными, но тогда всю логику по работе с данными придется реализовывать в виде, который использует такую примитивную модель. Но это же неправильно. Вид должен давать модели простые и емкие команды управления данными, а уж модель сама с ними разбираться. И к тому же, с моделью может взаимодействовать не только вид, но и например объект буфера обмена, со своими требованиями и закидонами. В общем, имеем взаимоисключающие параграфы.

Я уже понял, что обсуждением в текстовом виде (переписка) много чего не решишь. Текст - это медленно, и то что можно сказать за минуту, пишется за десять. Мне нужно пообщаться со знающим человеком голосом.

Исходники: https://github.com/xintrea/mytetra_dev

Лютое бесцельное безумие начиная с коммита 4f9d3ce.


Прошу отписаться на емайл xintrea[сцобакка]gmail.com или в асю 115519OOO (заменить три буквы О на нули) идейных товарищей, которые готовы пообщаться по вышеприведенным вопросам голосом. Напишите в письме время, когда вы готовы поговорить (на рабочей неделе могу только вечером, после 18:00 по Москве, в выходные - по договоренности), напишите предпочитаемые средства связи. Для оперативной координации звонка по Skype/SIP/Viber не помешал бы номер мобильного телефона.



PS:

Что я штудировал, но просветление не пришло:

1. Официальная документация

2. Qt4: Программирование моделей и представлений. Подробное объяснение на русском языке.

http://webhamster.ru/mytetrashare/index/mtb0/1392580691nchkxju7yz

3. Программирование моделей в Qt - рекомендации по использованию, неочевидные моменты

http://webhamster.ru/mytetrashare/index/mtb0/14259955927q5bhzmfbg

4. Беседы о Qt: Парадигма Model-View

http://webhamster.ru/mytetrashare/index/mtb0/14064861764k4dumxivg

5. Модели-Посредники в Qt

http://webhamster.ru/mytetrashare/index/mtb0/1432497788j6rq3ufits
Записан

Собираю информацию по крупицам
http://webhamster.ru
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4349



Просмотр профиля
« Ответ #1 : Май 26, 2015, 20:35 »

К сожалению, у меня сейчас нет возможности поговорит голосом, поэтому напишу свои мысли.

Но в Qt получается, что в модели можно только читать/менять данные в ячейках (data/setData), добавлять/удалять строчки и столбцы (insertRows/removeRows) и... все!
Нет, это совсем не так.
Вы можете написать любые свои методы, которые будут добавлять одну или несколько строк/столбцов, менять целые строки или области в таблице, удалять хоть строки, хоть столбцы. Главное, что бы модель при таких операциях, правильно информировало вид о том, как изменилась модель, что бы правильно это визуализировать.
Для этого есть специальные сигналы, которые отсылаются с помощью специальных методов:
beginXXX(), endXXX().
Т.е. делайте любые свои методы со сложной удобной вам обработкой данных, главное правильно информируйте вид.
Записан
vbv
Чайник
*
Offline Offline

Сообщений: 59


Просмотр профиля
« Ответ #2 : Май 27, 2015, 08:11 »

Как по мне - то через прокси менять данные в исходной модели не имеет смысла.
Это как в серверах баз данных менять данные через представления (view).

Для серверов баз данных справедливы следующие утверждения:
1. Обновляемыми являются только простые представления.
2. Для сложных представлений создаются методы/правила для обновления/вставки/удаления.

А при некоторых представлениях исходных данных некоторые функции вообще невозможно выполнить корректно.

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

Это мое мнение, не претендующее на идеальное решение.

Сам занимаюсь программированием связанным с базами данных и функционал сортировки/отбора у меня реализованы на сервере (переформируется запрос) и моя модель данных отдает данные элементу отображения уже в том виде в котором надо и элемент отображения, например QTableView показывает его уже в том виде в котором мне нужно. И, да QTableView у меня тоже, уже обучен специальными методами которыми она манипулирует моей моделью без посредников.

Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #3 : Май 27, 2015, 09:32 »

К сожалению, у меня сейчас нет возможности поговорит голосом, поэтому напишу свои мысли.

Мы договорились с одним товарищем из Барнаула пообщаться в воскресенье 31.05.2015. Он очень горит желанием сделать конференцию на гуглоплюсе. Начнем в 9:00 (потому что в Барнауле будет уже обед). Если все получится, можно поучаствовать или просто посмотреть трансляцию. Но мне нужны контакты с вами, если решите - пишите на емайл.

Цитировать
Но в Qt получается, что в модели можно только читать/менять данные в ячейках (data/setData), добавлять/удалять строчки и столбцы (insertRows/removeRows) и... все!
Нет, это совсем не так.
Вы можете написать любые свои методы, которые будут добавлять одну или несколько строк/столбцов, менять целые строки или области в таблице, удалять хоть строки, хоть столбцы. Главное, что бы модель при таких операциях, правильно информировало вид о том, как изменилась модель, что бы правильно это визуализировать.
Для этого есть специальные сигналы, которые отсылаются с помощью специальных методов:
beginXXX(), endXXX().
Т.е. делайте любые свои методы со сложной удобной вам обработкой данных, главное правильно информируйте вид.

А теперь пойдем дальше. Нам нужно у вида подменить нашу удобную модель на прокси-модель. Мы создаем прокси-модель, устанавливаем ей соурс-модель через setSourceModel(), устанавливаем эту прокси-модель в вид. И тут обнаруживаем, что прокси-модель не умеет вызывать наши нестандартные методы.

Вот в этом у меня и проблема.

Ранее вид работал с моделью, используя нестандартные методы. После внедрения прокси-модели, он уже этого сделать не может. Как выходить из положения? Работать одновременно с двумя моделями? Сделать одну для вида, а вторую для удобной работы? А как их синхронизировать? В каких случаях использовать какую?

Я уже подозреваю, что в концепции модель-вид, принятой в Qt, не хватает контроллера. Но использование самописного класса контроллера в Qt неоднозначно. Потому что в классическом варианте пользователь взаимодействет с контроллером, а контроллер уже сам дергает модели и выдает пользователю нужные виды. А в Qt пользователь взаимодействует с видом. А куда же пристыковать контроллер? Непонятно. Получается, что вид в Qt выполняет функции контроллера. В общем, встает вопрос архитектуры приложения, который я сам все никак не могу решить.
Записан

Собираю информацию по крупицам
http://webhamster.ru
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #4 : Май 27, 2015, 09:45 »

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

Я пробовал сделать класс proxy-модели через множественное наследование - унаследовался от QSortFilterProxyModel и от моей модели (имеющей в качестве родителя QAbstractTableModel). Это нужно было для того, чтобы у proxy-модели работали нестандартные методы. Но так как моя модель имеет родителя QAbstractTableModel, а тот в свою очередь унаследован от QAbstractItemMode, и QSortFilterProxyModel унаследован от QAbstractProxyModel и следовательно от QAbstractItemMode, то скомпилять такое не смог, ибо непонятно, что делать с одинаковыми стандартными методами.
« Последнее редактирование: Май 27, 2015, 09:54 от xintrea » Записан

Собираю информацию по крупицам
http://webhamster.ru
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4349



Просмотр профиля
« Ответ #5 : Май 27, 2015, 09:50 »

И тут обнаруживаем, что прокси-модель не умеет вызывать наши нестандартные методы.
А для чего ей это уметь?
Вы можете вызывать методы рабочей модели, они будут отсылать сигналы, прокси-модель их обрабатывает (так же как view) и будет отсылать аналогичные сигналы дальше для view.

Для чего view учить работе с нестандартными методами ваших моделей? View должен только отображать данные, управлять ими должны другие объекты.
Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #6 : Май 27, 2015, 09:56 »

Для чего view учить работе с нестандартными методами ваших моделей? View должен только отображать данные, управлять ими должны другие объекты.

Я не против, об этом я и написал:

Цитировать
Я уже подозреваю, что в концепции модель-вид, принятой в Qt, не хватает контроллера. Но использование самописного класса контроллера в Qt неоднозначно. Потому что в классическом варианте пользователь взаимодействет с контроллером, а контроллер уже сам дергает модели и выдает пользователю нужные виды. А в Qt пользователь взаимодействует с видом. А куда же пристыковать контроллер? Непонятно. Получается, что вид в Qt выполняет функции контроллера. В общем, встает вопрос архитектуры приложения, который я сам все никак не могу решить.

Проблема то в том, что в Qt вид является по-сути контроллером. И как разделить функции вида и контроллера при такой архитектуре - мне непонятно.
Записан

Собираю информацию по крупицам
http://webhamster.ru
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4349



Просмотр профиля
« Ответ #7 : Май 27, 2015, 10:02 »

Проблема то в том, что в Qt вид является по-сути контроллером. И как разделить функции вида и контроллера при такой архитектуре - мне непонятно.
Вовсе нет, вид в Qt, через стандартный интерфейс модели, может немного ей управлять, например, менять сортировку. Но это все настраиваемое поведение и вы всегда можете его переназначить на удобное вам.
А дальше вам ничто не мешает создавать свои контроллеры, которые и будут управлять моделями.
Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #8 : Май 27, 2015, 10:37 »

Если proxy модель хочется использовать только для сортировки, то лучше сразу забыть про это.
Ибо больше проблем, чем бонусов.
Записан

Qt 5.11/4.8.7 (X11/Win)
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #9 : Май 27, 2015, 11:51 »

Проблема то в том, что в Qt вид является по-сути контроллером. И как разделить функции вида и контроллера при такой архитектуре - мне непонятно.
Вовсе нет, вид в Qt, через стандартный интерфейс модели, может немного ей управлять, например, менять сортировку. Но это все настраиваемое поведение и вы всегда можете его переназначить на удобное вам.
А дальше вам ничто не мешает создавать свои контроллеры, которые и будут управлять моделями.

В том то и дело, что контроллер создать не проблема. Проблема в том, что в Qt вид взаимодействует с пользователем, и по-сути является контроллером в классической парадигме MVC.

Смотрим на рисунок из википедии:



Видим, что стрелочки от пользователя к виду нет (но есть стрелочка от вида к пользователю). То есть, пользователь не может взаимодействовать с видом, он вид только видит. И по классической парадигме MVC пользователь должен оказывать влиение только на контроллер.

А в Qt пользователь взаимодействует с видом. Кликает на элементы, и действия пользователя обрабатывает вид. И это рвет все шаблоны. С этим я и не могу разобраться. Точнее, кое-как разобрался - у меня код контроллера скапливается в виде. Но это же неправильно.
Записан

Собираю информацию по крупицам
http://webhamster.ru
vbv
Чайник
*
Offline Offline

Сообщений: 59


Просмотр профиля
« Ответ #10 : Май 27, 2015, 12:18 »


Я пробовал сделать класс proxy-модели через множественное наследование - унаследовался от QSortFilterProxyModel и от моей модели (имеющей в качестве родителя QAbstractTableModel). Это нужно было для того, чтобы у proxy-модели работали нестандартные методы. Но так как моя модель имеет родителя QAbstractTableModel, а тот в свою очередь унаследован от QAbstractItemMode, и QSortFilterProxyModel унаследован от QAbstractProxyModel и следовательно от QAbstractItemMode, то скомпилять такое не смог, ибо непонятно, что делать с одинаковыми стандартными методами.


Сие работать не будет, хотя компильнуть можно!

А давайте поставим вопрос по другому:
1. Чего мы хотим от proxyModel?
2. Чего, нестандартного, умеет recordModel?
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4349



Просмотр профиля
« Ответ #11 : Май 27, 2015, 14:20 »

А в Qt пользователь взаимодействует с видом. Кликает на элементы, и действия пользователя обрабатывает вид. И это рвет все шаблоны. С этим я и не могу разобраться. Точнее, кое-как разобрался - у меня код контроллера скапливается в виде. Но это же неправильно.
Конечно не правильно.
Пользователь кликает на элементы, вид или QItemSelectionModel посылает сигнал об этих событиях, эти сигналы должен получать и обрабатывать не вид, а тот самый объект контроллера.
Записан
kandrey
Гость
« Ответ #12 : Май 28, 2015, 23:33 »

Знакомые терзания. При всей внешней красоте идеи прокси моделей - их отладка невыносима, особенно корректный маппинг индексов.

Цитировать
После внедрения прокси-модели, он уже этого сделать не может. Как выходить из положения?
- думаю, надо проксировать все нестандартные методы

Цитировать
Если proxy модель хочется использовать только для сортировки, то лучше сразу забыть про это.
Ибо больше проблем, чем бонусов
- пришел к такому же выводу. Если честно - я забил на прокси модели, по крайней мере для своих моделей, скорей всего не хватает квалификации для построения таких компонентов. Другое дело, если исходную модель нельзя править - тогда, наверное, другого выхода нет как писать прокси.
« Последнее редактирование: Май 29, 2015, 02:02 от kandrey » Записан
Akon
Гость
« Ответ #13 : Май 29, 2015, 16:58 »

Цитировать
А теперь пойдем дальше. Нам нужно у вида подменить нашу удобную модель на прокси-модель. Мы создаем прокси-модель, устанавливаем ей соурс-модель через setSourceModel(), устанавливаем эту прокси-модель в вид. И тут обнаруживаем, что прокси-модель не умеет вызывать наши нестандартные методы.
Родственная душа Улыбающийся

Попробую формализовать задачу, дабы иметь четкое понимание и не распыляться на ненужное. Итак, имеется исходная модель, в которой есть "нестандартный" метод x().
Код:
class Model : public QAbstaractItemModel
{
public:
int x() const;

// Other QAbstaractItemModel's mandatory methods implementation, like data(), etc.
// ...
};

Некий виджет, отображающий данные из модели, принимает на вход (в качестве параметра конструктора) исходную модель и использует "нестандартный" метод x():
Код:
class ModelWidget : public QWidget
{
public:
ModelWidget(Model& model) :
model_(model)
{
treeView_->setModel(&model);
model.x();
}

private:
Model& model_;
QTreeView* treeView_;
};

Теперь мы хотим, например, отсортировать или отфильтровать данные с помощью прокси-модели (в общем случае с помощью цепочки прокси-моделей). Но возникает проблема: если подавать прокси-модель по месту исходной модели, то первая не иммет  "нестандартного" метода x().

Для решения проблемы я рассматривал 3 варианта:

1. Интерфейс. Все "нестандартного" методы группируются в интерфейс (или интерфейсы). В этом случае наши классы приобретают следующий вид:
Код:
class Xable
{
public:
virtual int x() const = 0;
};

class Model : public QAbstractItemModel, public Xable
{
public:
// Xable's implementation
int x() const {...}

// Other QAbstaractItemModel's mandatory methods implementation, like data(), etc.
// ...
};

class SortFilterProxyModel : public QSortFilterProxyModel, public Xable
{
public:
// Xable's implementation
int x() const
{
if (!sourceModel()) return 0;  // return something suitable or throw an exeption

// We assume only Model as possible source model
Model* sourceModel = boost::polymorphic_downcast<Model*>(sourceModel());
return sourceModel->x();
}

// Other QSortFilterProxyModel's methods
// ...
};

class ModelWidget : public QWidget
{
public:
ModelWidget(QAbstractItemModel& sourceModel) :
model_(model)
{
treeView_->setModel(model);
modelAsXable().x();
}

private:
QAbstractItemModel& model_;
QTreeView* treeView_;

Xable& modelAsXable()
{
return dynamic_cast<Xable&>(model_);
}
};
Обратите внимание, что на вход ModelWidget подается теперь QAbstractItemModel как общий тип исходной и прокси моделей и внутри виджета ссылка имеет тип QAbstractItemModel, а сам виджет имеет функцию modelAsXable() для доступа к "нестандартному" функционалу. Также хочу отметить, что SortFilterProxyModel в смысле Xable реализована исключительно через Model (SortFilterProxyModel::x() вызывает Model::x(), а не берет данные каким-нибудь другим способом).

Я не остановился на данном варианте, ввиду большого синтаксического оверхеда и создания дополнительного интерфейса.

2. "Раскрутка" прокси-модели до исходной модели. В этом варианте на вход виджета подается QAbstractItemModel. Это, как и в предыдущем случае, может быть либо исходная модель, либо прокси.
Код:
class ModelWidget : public QWidget
{
public:
ModelWidget(QAbstractItemModel& model) :
model_(model)
{
treeView_->setModel(model);
modelAsXable().x();
}

private:
QAbstractItemModel& model_;
QTreeView* treeView_;

Xable& modelAsXable()
{
return dynamic_cast<Xable&>(initialSourceModel(model_));
}
};
Перегруженная ф-я initialSourceModel() возвращает исходную модель, имея на входе либо исходную модель, либо прокси модель (цепочку прокси моделей). Эта функция мною была сделана внешней (для использования в нескольких виджетах):
Код:
/// \file
/// \brief Initial source model
/// \author Akon
/// \date 01.07.2009

/* ##todo:
*/

#pragma once

#include <QtGui/QAbstractProxyModel>

namespace Akon {
namespace QtGui {

/// Returns initial (first) source model of passed proxy model.
inline QAbstractItemModel* initialSourceModel(const QAbstractProxyModel& model)
{
QAbstractItemModel* result = model.sourceModel();
while (qobject_cast<QAbstractProxyModel*>(result))
result = static_cast<QAbstractProxyModel*>(result)->sourceModel();
return result;
}

/// If \a model is a proxy model returns initial (first) source model; else returns \a model
/// itself as non-const pointer.
inline QAbstractItemModel* initialSourceModel(const QAbstractItemModel& model)
{
return qobject_cast<const QAbstractProxyModel*>(&model) ?
initialSourceModel(static_cast<const QAbstractProxyModel&>(model)) :
const_cast<QAbstractItemModel*>(&model);
}

}  // QtGui::
}  // Akon::

Этот вариант в итоге я и использовал.

3. Выделить аспект проксирования в модели в миксин. Миксин (mix-in) это шаблонный класс, в котором параметр шаблона фигурирует в списке базовых классов. Применительно к прокси-модели это выглядело бы след. образом:
Код:
template <class BaseT>
class SortFilterModelAspect : public <BaseT>
{
// All functional of QSortFilterProxyModel is implemented in this class template
};
И используется это следующим образом:
Код:
Model model;  // source model with x() "non-standard" function

SortFilterModelAspect<Model> proxyModel;  // source model with x() "non-standard" function + proxy features
proxyModel.setFilterRegExp(...);
Уже не помню всех причин, по которым я не пошел по этому пути. Возможно, существенную роль играли принципиальные сложности, например, такие как неподдержка MOCом шаблонов.

Напросившийся вывод: проблемы нет, если относиться к исходным моделям как к сущностям, обеспечивающим исключительно поддержку видам, т.е. если оставайться в рамках QAbstractItemModel без использования "нестандартных" методов. Все "нестандартные" методы присутствуют в другом соответствующем классе. Модель не предоставляет транзитивную ссылку на этот класс (иначе, уже появляется "нестандартный" метод). Как модель, так и этот класс подаются на вход виджета:
Код:
class ModelWidget : public QWidget
{
public:
ModelWidget(QAbstractItemModel& model, X& x) :
model_(model),
x_(x)
{
treeView_->setModel(model);
x.x();
}

private:
QAbstractItemModel& model_;
X& x_;
QTreeView* treeView_;
};
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Май 31, 2015, 16:57 »

Напросившийся вывод: проблемы нет, если относиться к исходным моделям как к сущностям, обеспечивающим исключительно поддержку видам, т.е. если оставайться в рамках QAbstractItemModel без использования "нестандартных" методов. Все "нестандартные" методы присутствуют в другом соответствующем классе. Модель не предоставляет транзитивную ссылку на этот класс (иначе, уже появляется "нестандартный" метод). Как модель, так и этот класс подаются на вход виджета:
Ой как "подвели бвзу" Улыбающийся А может сказать прямо, типа "ото приводите указатель на свой класс" - по существу это все.

Со всем этим я знаком весьма поверхностно (не работаю с СУБД - и слава богу). Делал несколько таблиц и один листбокс (правда навороченный). Ну открыл букварь, прочитал, сделал, никаких трудностей не испытал. Вся работа была в стиле (как рисовать хитрый листбокс, где там "раскладушка", давить мышу и.т.п.). К Qt претензий никаких, наоборот. В общем я уже успел забыть все что делал (легко пришло - легко ушло, нормально). В памяти осталась одна деталь, о ней подробнее

Почитав букварь я не особо терзался "отсутствием контроллера" (пробой в идеологии?), но быстро понял - общая конструкция (QAbstracItemModel) позволяет делать все, а конкретная реализация (в QTreeWidget для моего листбокса)  - "жирная", по существу - для лохов. Прикинул сколько айтемов может иметь мой листбокс. Обычно юзер пополняет литстбоксы сам - практически это до 10 айтемов. Правда есть 2-3 случая (из примерно 50) когда это не так - могут быть тысячи. Но здравый смысл быстро победил - и я задействовал жирную реализацию для лохов. И все бы хорошо если бы не одно "но"

Меня вполне устраивает QTreeWidget (и его модель), но вот QTreeWidgetItem у меня свой (перекрыт) - и в нем масса нестандартных методов "x"(y, z) - а вот достучаться к ним напрямую из моего листбокса я не могу, ведь он оперирует с  QTreeWidgetItem. Ну пришлось приводить (что в общем не есть хорошо/чисто), и лучше сразу сделать это приведение методом моего класса листбокс. Размышляя об этом я так и не нашел лучшего решения - возможно это принципиальное ограничение наследования.

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

Спасибо
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.064 секунд. Запросов: 23.