пятница, 2 апреля 2010 г.

Plugins for Redmine


Ох что-то я давно не писал сюда. Да и ни куда не писал. Будем исправлять.

Пару недель назад появилась необходимость установить новый redmine (Redmine 0.9.1.stable). По традиции и для сокращения времени на установку использовался bitnami стек (http://bitnami.org/stack/redmine). Позволяет очень быстро всё развернуть не парясь ни о чем. Вот с такой-же лёгкостью хотелось поставить и все нужные плагины.
Список нужных плагинов:

  • Charts Plugin (http://github.com/mszczytowski/redmine_charts/)
  • Redmine Code Review plugin (http://www.r-labs.org/projects/show/codereview)
  • Subtasks plugin (http://github.com/hron/redmine_subtasks)
  • Timesheet Plugin (https://projects.littlestreamsoftware.com/projects/redmine-timesheet)


    И это действительно полезные плугины.
    Всю эту красоту вы можете установить легко и просто, загрузив архив здесь. распаковав его в папку {bitnami_redmine_root}/plugins
    Далее потребуется только запустить на выполнение install.sh


    Читать далее
  • вторник, 5 января 2010 г.

    Новогоднее дерево и исходники.

    Ну, как всегда, я немного опоздал. В качестве компенсации - древо новогоднее. (Кстати, это дерево есть правильная ёлка - http://www.artlebedev.ru/everything/brain/2009/12/23/ )
    another 3d fractal tree


    Исходники брать тут. Так-же в архиве пара data-файлов фракталов (Куб Гильберта и дерево). Если исходники на народе умрут - сообщите, повторю. Если сильно надо - могу разместить на гитхабе.

    P.s. Данный проект развивать далее не собираюсь.



    Читать далее

    понедельник, 21 декабря 2009 г.

    Хитрый новогодний план

    Ну вы конечно-же понимаете что я хочу выложить скрипты для рисования ёлок под новый год. =)
    Вот с делами разберусь и выложу! Числа так 30-ого. Скорее всего. наверно.
    А пока, Куб Гильберта:


    и маленький:


    Читать далее

    пятница, 18 сентября 2009 г.

    3d L-system. Пара ёлок.

    Пара деревьев, созданных с использованием L-систем. Для аппетита, так сказать. Скоро выложу скрипты и ещё фрактальчики.

    tree1

    Ещё маленькая ёлка. 6 или 7 итераций.
    tree2



    Читать далее

    четверг, 20 августа 2009 г.

    2D фракталы на основе L-систем

    В ходе игр с python испытывал некую нехватку интересных задач, как собственно при изучении любого нового языка программирования. И тут друг предложил интересную задачу с народно-хозяйственным эффектом - написать прогу для рисования фракталов на основе L-систем. Мне понравилась идея, и понравилось использовать pyhton для её реализации.

    Вот пример осенне-фрактального древа:



    Далее ещё пара картинок и, собственно, сами скрипты.


    Кривая Пеано-Госпера:

    Peano-Gosper curve

    кривая Гильберта:

    Hilbert Curve

    Прежде всего для работы с png необходим PNGCanvas.


    Данная программа разбита на два модуля - LSystem.py и Turtle.py.
    Первый отвечает за генерацию инструкций по L-системе для второго модуля, который, как видно по названию, является черепахой и просто рисует на основе инструкций.

    Работа скрипта занимает вдвое больше времени чем надо только для отрисовки. Это связанно с тем что черепаха (предсказывает будущее) проходит по интсрукциям дважды, сперва вычисляя размеры холста, а затем уже рисует.

    L-системы задаются в data-файлах

    Пример файла для кривой Гильберта
    ---------------------------
    L
    4
    6
    2
    L=+ R F - L F L - F R +
    R=- L F + R F R + F L -
    ---------------------------

    L #это аксиома, необходимо разделять слова пробелами.
    4 #шаг изменения угла операциями + и - равен 360/4
    6 #длинна штриха оставляемого черепахой по команде F
    2 #начальный угол черепахи =360/2 (пожалуйста, не ставьте там ноль =) )
    L=+ R F - L F L - F R + #правило, необходимо разделять слова пробелами, и писать слитно со знаком равно

    Скачать все скрипты и PNGCanvas в одном архиве.

    LSystem.py

    1. #!/usr/bin/python
    2. # -*- encoding: utf-8 -*-
    3. """L-System parser"""
    4. __version__="0.3"
    5. __author__ = "Anton Valter (avalter.blogspot.com)"
    6. __copyright__ = "CC Attribution Anton Valter"
    7.  
    8.  
    9.  
    10. class LSystem(object):
    11.   def __init__(self,funcs,deep=3):
    12.     self.x=0
    13.     self.y=0
    14.     self.leftB=0
    15.     self.rightB=0
    16.     self.topB=0
    17.     self.bottomB=0
    18.     self.rules = dict()
    19.     self.deep = deep
    20.     self.funcs=funcs
    21.     print self.funcs
    22.  
    23.   def setAxiom(self,axiom):
    24.     self.axiom = axiom.split()
    25.  
    26.   def registerRule(self,name,rule):
    27.     self.rules[name]=rule.split()
    28.  
    29.   def setDeep(self,deep):
    30.     self.deep = deep
    31.  
    32.   def interSteps(self,string,deep):
    33.     for c in string:
    34.       if c in self.rules:
    35.         if deep<self.deep:
    36.            for ch in self.interSteps(self.rules[c],deep+1):
    37.             yield ch
    38.         elif c in self.funcs:
    39.           yield c
    40.       else:
    41.         yield c
    42.  
    43.   def steps(self):
    44.     return self.interSteps(self.axiom,1)
    45.  
    46. # vim:set tabstop=4 softtabstop=4 shiftwidth=4 expandtab:
    * This source code was highlighted with Source Code Highlighter.


    Turtle.py:

    #!/usr/bin/python
    # -*- encoding: utf-8 -*-

    """2D Turtle for L-Systems"""
    __version__ = "0.8"
    __author__ = "Anton Valter (avalter.blogspot.com)"
    __copyright__ = "CC Attribution Anton Valter"


    from PNGCanvas import PNGCanvas
    import math
    import re
    from LSystem import LSystem

    maxw = 4000 #максимальные размеры изображения
    maxh = 4000 #при привышении - прерывание

    class Turtle(object):
      def __init__(self):
        self.x=float(0)
        self.y=float(0)
        self.leftB=0
        self.rightB=0
        self.topB=0
        self.bottomB=0
        self.stack = list()
        self.p=0
        self.alpha=0
        self.step=50
        self.backcolor=[0,0,0,0]
        self.basedrawcolor=(0,0,0,0xff)
        self.drawcolor=self.basedrawcolor
        self.stepcount=0
        self.baseAlpha=0
        self.colorstep=50
        self.basestep=10

      def setBorders(self,left,right,top,bottom):
        self.leftB=left
        self.rightB=right
        self.topB=top
        self.bottomB=bottom

      def prepareCanvas(self,width,height):
        if width<2:
          width=5
        if height<2:
          height=5
        self.c = PNGCanvas(width,height)
        self.c.color = self.backcolor
    #    self.c.filledRectangle(0,0,width-1,height-1) // раскоменть если надо фон не прозрачный
        if width<2:
          width=5
        if height<2:
          height=5
        self.c.blendRect(1,1,width-1,height-1,width,0,self.c)
        self.c.color = self.drawcolor


      def produceSteps(self,drawFunction,t):
        self.alpha=self.baseAlpha
        self.drawcolor = self.basedrawcolor
        self.step = self.basestep
        for action in t.steps():
          if action=='+':
            self.alpha=self.alpha+self.p
          elif action=='-':
            self.alpha=self.alpha-self.p
          elif action=='[' or action=='{':
            self.stack.append((self.x,self.y,self.alpha,self.drawcolor,self.step))
          elif action==']' or action=='}':
            pos = self.stack.pop()
            self.x=pos[0]
            self.y=pos[1]
            self.alpha=pos[2]
            if action=='}':
              self.drawcolor = pos[3]
            self.step = pos[4]
          elif action=='F':
            newPosx=(self.x+math.cos(self.alpha)*self.step)
            newPosy=(self.y+math.sin(self.alpha)*self.step)
            drawFunction(newPosx,newPosy)
            self.x=newPosx
            self.y=newPosy
          elif action=='f':
            newPosx=(self.x+math.cos(self.alpha)*self.step)
            newPosy=(self.y+math.sin(self.alpha)*self.step)
            self.calcSize(newPosx,newPosy)
            self.x=newPosx
            self.y=newPosy
          elif re.match('\(([-+])?,([-+])?,([-+])?,([-+])?\)',action):
            m = re.match('\(([-+])?,([-+])?,([-+])?,([-+])?\)',action)
            r,g,b,a = m.group(1),m.group(2),m.group(3),m.group(4)
            rv,gv,bv,av=self.drawcolor

            def modifyColor(color,step,value):
              if color=='+':
                return min(value+step,0xff)
              if color=='-':
                return max(value-step,0x00)
              return value
             
            rv = modifyColor(r,self.colorstep,rv)
            gv = modifyColor(g,self.colorstep,gv)
            bv = modifyColor(b,self.colorstep,bv)
            av = modifyColor(a,self.colorstep,av)
            self.drawcolor=(rv,gv,bv,av)
          elif re.match('\((\d+)?,(\d+)?,(\d+)?,(\d+)?\)',action):
            m = re.match('\((\d+)?,(\d+)?,(\d+)?,(\d+)?\)',action)
            r,g,b,a = m.group(1),m.group(2),m.group(3),m.group(4)
            rv,gv,bv,av=self.drawcolor
            
            def setColor(color,value):
              if color:
                return min(max(int(color),0x00),0xff)
              return value

            rv = setColor(r,rv)
            gv = setColor(g,gv)
            bv = setColor(b,bv)
            av = setColor(a,av)
            self.drawcolor = rv,gv,bv,av
          elif re.match('\@(I)?(Q)?(\d+(?:\.\d+)?)',action):
            m = re.match('\@(I)?(Q)?(\d+(?:\.\d+)?)',action)
            i,q,n=m.group(1),m.group(2),m.group(3)
            num = float(n)
            if q:
              num = math.sqrt(num)
            if i:
              if num > 0.00001:
                num = 1.0/num
            self.step=self.step*num
              

      def draw(self,newposx,newposy):
        self.c.color=self.drawcolor
        self.stepProduced+=1    
        strStep = str(self.stepProduced)+'/'+str(self.stepcount-1)
        print strStep,self.c.color,self.step
        self.c.line(self.x,self.y,newposx,newposy)

      def calcSize(self,newposx,newposy):
        self.stepcount+=1
        if newposx > self.rightB:
          self.rightB=newposx
        if newposx < self.leftB:
          self.leftB=newposx
        if newposy > self.topB:
          self.topB=newposy
        if newposy < self.bottomB:
          self.bottomB=newposy

      def start(self,pngFileName,rulesFileName,deep):
        rules = open(rulesFileName,'r')
        t = LSystem(['F','-','+','f','[',']','(',')','{','}'],deep)
        t.setAxiom(rules.readline())
        self.p = 2*math.pi/float(rules.readline())
        self.basestep = float(rules.readline())
        self.baseAlpha = 2*math.pi/float(rules.readline())
        while 1:
          str = rules.readline()
          print str
          if not str: break
          rule=str.split('=')
          t.registerRule(rule[0],rule[1])
        rules.close()
        self.strlen=0
        self.calcSize(self.x,self.y)
        self.stepProduced=0
        self.produceSteps(self.calcSize,t)
        width=self.rightB-self.leftB
        height=self.topB-self.bottomB
        if height > maxh or width > maxw:
           return
        self.x=abs(self.leftB)
        self.y=abs(self.bottomB)
        self.prepareCanvas(int(width+1),int(height+1))
        self.produceSteps(self.draw,t)
        file = open(pngFileName,'wb')
        file.write(self.c.dump())
        file.close()


    if __name__=='__main__':
      png = raw_input('outputfile:')
    #  png = 'test.png'
      data = raw_input('datafile:')
      deep = int(raw_input('deep:'))
      t = Turtle()
      t.start(png,data,deep)
    # vim:set tabstop=4 softtabstop=4 shiftwidth=4 expandtab:


    * This source code was highlighted with Source Code Highlighter.




    Читать далее

    среда, 29 июля 2009 г.

    Паттерны проектирования UI

    При проектировании пользовательского интерфейса необходимо добиваться не только функциональной полноты интерфейса, но и удобства использования данного интерфейса и эстетической красоты. Существует несколько уже устоявшихся приёмов проектирования UI, паттернов проектирования. Их мы рассмотрим немного позже. Сперва займёмся приятным - красотой. Удобнее всего рассматривать принципы построения эстетически приятных интерфейсов на примерах, на примерах эстетически страшненьких интерфейсов.
    Взглянем на один из диалогов системы 1С:




    Функционально, здесь, видимо, всё на месте. С этим диалогом можно будет работать, но он с браком. Он не красив, встретив данный диалог, хочется его как можно быстрее закрыть. Давайте укажем на ошибки.
    Прежде всего рассмотрим внутреннюю панель:



    Отступы просто ужасны, они разные. Внутреннее содержание панели болтается в ней.



    Да и сама панель криво лежит на форме. Данные отступы явно не красят данный интерфейс. Но это только эстетический брак. Следующая же ошибка вызывает ещё и проблемы с точностью работы пользователя, проблемы не критические, но далеко не приятные.



    Практически нет расстояния между элементами управления.
    Между элементами управлениями всегда должно быть пространство, не реагирующее на щелчок мыши. Особенно между терминационными кнопками диалога. Будет обидно вместо "OK" нажать "Cancel", и повторить работу с диалогом.

    Возьмем другой пример:



    Что-то здесь не так. Очевидно, опять не верные отступы.





    Исправим отступы.

    До:



    После:



    Изменили мелочи, но из-за этих мелочей портилось всё впечатление от данного интерфейса.

    И ещё один пример. Утилита для настройки видео драйвера от ATI.



    Здесь видно, что над интерфейсом поработали художники, нарисовали достаточно интересный интерфейс. Но он не опрятен, даже слегка ужасен. Всё из-за мелочей, которые не увидели разработчики (или дизайнеры) и не исправили. Начнём:



    Справа отступа нет вообще. Ни у баннера, ни у выпадающего списка.



    Логотип болтается, все отступы различны, пропорции не подходят под данный интерфейс. Достаточно было-б немного увеличить логотип по ширине, что-б он занял всё пространство между серыми линиями. И сделать равные отступы по вертикали.



    Панели имеют различные отступы слева и справа.



    Да и вообще элементы брошены на форму как попало. Если логотип и не разрешат растянуть (как никак, работа художника), то что мешает уменьшить баннер, обрезав его по красным линиям? Да и название адаптера вряд ли будет столь длинным, что не уместится в выпадающий список, если мы его уменьшим, опять-же, до красной линии.

    Всех приведённых ошибок можно легко избежать если использовать направляющие линий при построении интерфейса.
    Некоторые средства разработки пользовательских интерфейсов первоначально построены на использовании layouting`а, на привязке к направляющим, к сетке. Но и там надо аккуратно следить за отступами. Во-первых, padding может быть выставлен произвольно, во-вторых, граница элемента, по которой проводиться привязка, может не совпадать с визуальной границей данного контрола (Ярким примером является label контрол в Windows Forms.)


    Использование стандартных элементов.
    В конце прошлого поста были описаны некоторые базовые правила построения интерфейсов. Одно из правил было об использовании стандартных элементов управления, или элементов, взаимодействие с которыми похоже на стандартное. Взгляните на следующую иллюстрацию:



    К чему относится треугольник между "Записать" и "Печать"? Сходу назвать ответ невозможно (ну если только Вы не работали с 1С). Все стандартные контролы если используют вспомогательные элементы, то размещают их справа. Вспомните DropDownList, календари. Плюс ко всему, здесь нарушается правило разделения элементов управления.

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

    Теперь перейдём к паттернам проектирования интерфейсов.

    Паттерны проектирования пользовательского интерфейса

    Начнём с паттерна wizard.




    Данный паттерн желательно использовать в случае если операция:
    * выполняется достаточно редко
    * сложна, требуется много действий
    * имеет разбиение по шагам
    * не может быть отменена после завершения
    * требуется выполнение операций в определённом порядке
    * ветвится, операции которые необходимо совершить зависят от действий, совершенных на прошлых шагах

    Существует два типа мастеров - одностраничный (в основном на для используется в веб приложениях), многостраничный

    Одностраничный
    + Пользователь видит все шаги которые необходимо совершить, сразу осознаёт то, какие данные ему понадобятся для завершения операции.
    + Предельно простая навигация между шагами
    - Не подходит в случае ветвящихся операций
    - Одновременное отображение большого количества информации может напугать пользователя.



    Многостраничный
    + Скрывает сложность операции и позволяет пользователю сфокусироваться на одном конкретном шаге
    + На каждой странице можно разместить детальные инструкции
    - Пользователь не в состоянии увидеть всю ситуацию целиком


    Общая реализация:
    * Необходимо разбить операцию на логичные, именованные шаги
    * Количество шагов должно быть минимальным, но сами шаги не должны быть слишком обширными. Диалог с более чем 5-6 шагами делать не стоит, особенно если это многостраничный мастер.
    * Очищаем интерфейс от всех не нужных элементов, которые могут отвлечь пользователя
    * Мастер должен быть гибок, т.е. надо реализовать возможность пропуска необязательных шагов, систему перемещения между шагами
    * Было-бы неплохо реализовать альтернативный способ выполнения операции для опытных пользователей.
    * Ввод опциональных данных должен быт отделён от ввода обязательной информации.

    Одностраничный мастер можно сделать более динамическим двумя способами:
    1) следующий шаг появляется при завершении текущего. в данном случае мы можем использовать одностраничный мастер для ветвящихся операций



    2) использовать паттерн аккордеон для не активных сокрытия шагов.



    (это пример, вместо текста здесь должны быть размещены элементы мастера)

    Многостраничный мастер можно сделать удобнее если:
    1) Отображать прогресс выполнения операции, причем позволяя пользователю возвращаться к прошлым шагам по клику на данном индикаторе
    2) Показывать итоговый результат на последнем шаге мастера

    Уточнение поиска.

    Позволяет пользователю уточнять результат поиска используя фильтры, до тех пор пока не будет найдены нужные данные.



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

    Существует два варианта данного паттерна по расположению:
    1) вертикальный - критерии поиска находятся в вертикальном столбце слева, а результаты справа. Используется в случае если существует много критериев.
    2) горизонтальный - критерив поиска не много, а найденные данные занимают много места по горизонтали (например, табличные данные с достаточно большим количеством столбцов)



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

    * Результат поиска необходимо обновлять по прошествии некоторого времени после настройки фильтра, для избежания посылки ненужных запросов и задержек.
    * Отображать количество объектов удовлетворяющих критериям
    * Дать возможность пользователю сортировать результаты поиска
    * Необходимо показать пользователю что его запрос выполняется с помощью прогресс бара, анимации, иконки


    Значения по умолчанию


    Значения по умолчанию помогают пользователю заполнить форму, заполнив некоторые поля логичными, корректными значениями.



    Используется для заполнения полей, которые пользователь вряд-ли изменит. Значения по умолчанию не должны раздражать пользователя. Не стоит заполнять, например, пол, пароль, автоматически ставить галочку "подписка на рассылку".
    Необходимо попробовать извлечь всю возможную информацию, полезную для установки значений, например, из профиля пользователя, его ip адреса, системных настроек.

    Динамическое добавление полей

    Данный паттерн позволяет минимизировать количество отображаемых полей.



    Ярким примером является добавление файлов к письму в Gmail.
    Необходимо показывать пользователю только необходимое количество полей и добавить кнопку или ссылку для добавления новых полей. Желательно использовать анимацию (или подсвечивать вновь добавленное поле цветом в течении короткого времени) для того, что-бы пользователь мог понять, что произошло.

    Индикатор прогресса

    Сообщает пользователю о том, что выполняется длительная операция и пользователю придётся подождать.



    Необходимо показать пользователю, что выполнение задачи не зависло, для этого удобно использовать анимацию. В данном случае, если даже индикатор застрянет на некоторое время на одном значении, пользователь поймёт, что всё работает.
    Если возможно, не стоит блокировать весь интерфейс. Позвольте пользователю продолжать свою работу.
    Если операция долгая (более 3 секунд) то прежде всего, надо её ускорить. Не заставляйте пользователя ждать!
    Сообщайте пользователю, сколько ещё потребуется времени, но не приукрашивайте. Лучше сделать оценку сверху, чем в течении часа говорить, что осталось подождать всего минуточку.
    Всегда давайте пользователю возможность отменить операцию. Пользователь может захотеть прервать операцию, например, если увидит, что оставшееся время выполнения не укладывается в его рабочий день.
    Если операции очень длительная - давайте пользователю пояснения о том, чем сейчас занимается система.



    Читать далее

    Проверка на сериализуемость объектов для копирования в Clipboard.

    При помещении объекта в буфер обмена, системе требуется сериализовать его. Однако Clipboard.SetData ни слова не говорит в случае неудачной сериализации, а просто кладёт в буфер null. Для проверки сериализуем-ли объект удобно во время разработки использовать следующий метод:


    private static bool IsSerializable(object obj)
    {
      System.IO.MemoryStream mem = new System.IO.MemoryStream();
      BinaryFormatter bin = new BinaryFormatter();
      try {
       bin.Serialize(mem, obj);
       return true;
      } catch(Exception ex) {
       MessageBox.Show("Объект не может быть сериализован. \n" + ex.ToString());
       return false;
     }
    }


    * This source code was highlighted with Source Code Highlighter.


    А потом спокойно помечать атрибутом [Serializable] все классы, на который будет ругаться вышеописанная функция.
    Естественно, в релизе использование данного метода лучше избегать, ибо лишние расходы.


    Читать далее