Montag, 13. Mai 2013

ObjectStudio 8 New UI: DateTimePicker (Part 1)

The DateTimePicker (Class UIDateTimePicker) is the first control I am going to have a more detailled look at.

Open a demo like this:

TestUI new testOpenUIDateTimePicker

Do not forget to call UIView initialize before.


We now add the functionality to set a Date before opening the control and to enable the DTS_SHOWNONE style.

Adding a value: method

I do not know how exactly the new UI API to set values will look like, so I just implement a value: method to try it out.

value: aTimestamp
    self dateOrTime: aTimestamp.
    self setSystemTime: aTimestamp.

setSystemTime: aTimestamp
    self privateSetSystemTime: aTimestamp.

 privateSetSystemTime: aTimestamp
    | st res |

    st := self lib SYSTEMTIME gcCalloc.
    aTimestamp isNil ifFalse: [
        st 
            memberAt: #wYear put: aTimestamp date year;
            memberAt: #wMonth put: aTimestamp date months;
            memberAt: #wDay put: aTimestamp date dayOfMonth;
            memberAt: #wHour put: aTimestamp time hours;
            memberAt: #wMinute put: aTimestamp time minutes;
            memberAt: #wSecond put: aTimestamp time seconds;
            memberAt: #wMilliseconds put: aTimestamp time milliseconds.
        ].

    res := self lib
                SendMessage: self windowHandle
                msg: DTM_SETSYSTEMTIME
                wParam: (aTimestamp isNil ifTrue: [GDT_NONE] ifFalse: [GDT_VALID])
                lParam: (st ifNotNil: [st referentAddress]).
    ^res

create
    super create.
    dateOrTime ifNotNil: [
        self setSystemTime: dateOrTime.
    ].

Adding the DTS_SHOWNONE style

To add the DTS_SHOWNONE style we need to add it to the styles before creating the control. Setting it afterwards does not seem to work. In the example code we will close and create the control when the setShowNoneTo: method is called and the control is already open. Better would be to only set this style before creating it, maybe the Designer for the new UI will include some options to add styles.

Closing and (re-)creating the same control does not work in the current preview because the hwnd instance variable is not set to nil after destroying the view (I am not shure if this is by intent or not). So I added the following overwrite to UIView:

UIView>>close
    self privateClose.
    self privateDestroy.
    self hwnd: nil.

In the Date and Time Picker control you need to check some results for GDT_NONE (Checkbox is unset) and GDT_VALID (Checkbox is set and the Date Time is valid). Here is the complete code for UIDateTimePicker.

setShowNoneTo: aBoolean
    aBoolean ifTrue: [
        "SetStyle does not change the style attribute"
        self setStyle: DTS_SHOWNONE.
        self style: (self style bitOr: DTS_SHOWNONE).
    ] ifFalse: [
        self removeStyle: DTS_SHOWNONE.
        self style: (self style bitAnd: DTS_SHOWNONE bitInvert).
    ].

    "Seems we need to recreate to apply this style"
    self isOpen ifTrue: [
        self close; create.
    ].

getShowNone
    self isOpen ifFalse: [^false].
    ^(self privateGetStyle bitAnd: DTS_SHOWNONE) > 0

privateGetSystemTime
    | st res |
    st := self lib SYSTEMTIME gcCalloc.
    res := self lib
                SendMessage: self windowHandle
                msg: DTM_GETSYSTEMTIME
                wParam: 0
                lParam: st referentAddress.
    ^res == GDT_VALID
        ifTrue: [self lib timestampFromSysTime: st contents]
        ifFalse: [nil]

privateDateTimeChange: aNotifyEvent
    | struct oldTs |
    struct := self lib LPNMDATETIMECHANGE cast: aNotifyEvent lParam.

    (struct memberAt: #dwFlags) == GDT_NONE
        ifTrue: 
            [oldTs := self dateOrTime.
            self dateOrTime: nil.
            oldTs ifNotNil: [self announce: (UIChangeAnnouncement before: oldTs after: self dateOrTime)]].

    (struct memberAt: #dwFlags) == GDT_VALID
        ifTrue: 
            [oldTs := self dateOrTime.
            self dateOrTime: (self lib timestampFromSysTime: (struct memberAt: #st)).
            self dateOrTime = oldTs
                ifFalse: [self announce: (UIChangeAnnouncement before: oldTs after: self dateOrTime)]].

Test it

Write a Test class method in TestUI like:

testOpenUIDateTimePickerEx
    | view value |

    value := (Timestamp fromDate: {2000 1 1} asDate andTime: (Time fromSeconds: 0)).
    
    view := self new testOpenUIDateTimePicker.
    view ctrl1 value: value. 

    view ctrl1 setShowNoneTo: true.
    self new ctrl1 getSystemTime out.
    
    view ctrl1 value: nil.
    self new ctrl1 getSystemTime out.

    view ctrl1 setShowNoneTo: false.
    self new ctrl1 getSystemTime out.

    self new testCloseUIDateTimePicker


Or try in using the workspace.


Next I will show how to change the display style (short, long).

Download code from here.

ObjectStudio 8 New UI

The newest preview version of ObjectStudio 8.5 does include a preview package for the new UI framework. You must load the package using the parcel manager (Directories => parcels/preview)

The new UI is based on the Win32 APIs, MFC seems not to be used any more.

The base class is ObjectStudio.UIView. There is a class TestUI which one can use as a demo controller. The API calls are implemented in ObjectStudio.UIView.UILib.

To display a new empty window without any icons:

UIView initialize.
UIView new create.


 

I had to change the create and privateCreate methods in UIView to get this working.

create
    self isOpen ifTrue: [^self].
    UIView critical: 
            [self class createWindow: self.
            "hhk := self hookWindowCreate." 
            [self privateCreate] ensure: 
                    [self class createWindow: nil.
                    "self unhookWindowCreate: hhk"]]

privateCreate

    | aHwnd |
    aHwnd := self lib
                CreateWindowEx: self wndExStyle
                class: (self class wndClassName isString
                        ifTrue: [self class wndClassName gcCopyToHeapUnicode]
                        ifFalse: [self class wndClassName])
                window: self windowName gcCopyToHeapUnicode
                style: self wndStyle 
                x: self position x
                y: self position y
                width: self extent x
                height: self extent y
                parent: (self parent isNil ifTrue: [0] ifFalse: [self parent windowHandle])
                menu: 0
                hInst: 0
                lParam: 0.

    self windowHandle: aHwnd datum .
    self registerWindow: aHwnd datum.

    aHwnd datum = self windowHandle
        ifFalse: 
            [(UICreateWindowExError new)
                originalHwnd: self windowHandle;
                newHwnd: aHwnd datum;
                raiseSignal]


If you want a window with the standard windows icons:

(UIView new)
    style: UIView defaultStyle;
    create;
    setDefaultEvents.


In the TestUI class they do it a bit different (also this code does not display the left icon):

TestUI new testOpenWindow


Download code from here.
See also this post from Cincom.

Xpert.Ivy unter Debian mit Postgresql installieren

Eine kurze Anleitung wie man Xpert.Ivy 4.3.9 unter Debian Linux 6.0 "Squeeze" mit einer Postgresql Datenbank installieren kann.

Das folgende wurde auf einem gehosteten VServer von nine.ch durchgeführt.

Als erstes muss Postgresql installiert werden mit:

# apt-get install postgresql postgresql-client

Um später die Xpert.Ivy Systemdatenbank erstellen zu können, müssen die Init-DBs von Postgres mit UTF-8 Encoding erstellt werden. Da dies bei mir nach der Installation der Packages nicht so war, musste ich das von Hand durchführen.

Zuerst prüfen wir die vorhandenen (deutschen) Locales:
# locale -a | grep de_ 

Anschliessend löschen wir die bei der Installation erstellte Datenbank und erstellen sei neu:

# su postgres
# pg_dropcluster --stop 8.4 main
# pg_createcluster --locale de_CH.UTF-8 --start 8.4 main

Nun muss noch die "plgsql" Sprache installiert werden:

# su postgres
# createlang plpgsql template1


Und zum Schluss muss noch ein Benutzer für Xpert.Ivy erstellt werden:

# su postgres
# createuser ivy
# psql alter user ivy password 'x'


Admin Remote Zugriff
- pgAdmin downloaden
- apt-get install postgresql-contrib
- psql postgres -f adminpack.sql
- postregsql.conf => listen *
- pg_ => host hinzuguegen

 Xpert.Ivy konfigurieren
- wget http://...zip - unzip installieren ?
- Lizenzdatei kopieren
- ./XpertIvyServerConfig.sh -headless

 Links
http://wiki.debian.org/PostgreSql
 http://supriyadisw.net/installing-postgresql-on-debian.html

Montag, 7. Januar 2013

Fälle für Benutzer selektieren

Ich habe letzte Woche einen Prozess so umgestellt, dass er als reiner Hintergrund-Prozess als Benutzer SYSTEM startet. Ein Problem das sich dadurch ergab war, dass die Prozesse in unserem angepassten WorkflowUI nicht mehr in der Fall-Übersicht des Benutzer als "Meine"-Prozesse erschien.

Die eigentliche Selektion findet in der Methode findCases in der Klasse ch.ivyteam.ivy.workflow.ui.utils.WorkflowUIAccessPermissionHandler statt. Dieser Methode kann ein Integer "caseDisplayMode" mitgegeben werden, welcher den Benutzer-Filter steuert:
  • 0 stands for your cases
  • 1 stands for team cases
  • 2 stands for all application cases (ivy.wf)
  • 3 any query cases: it means find all cases that fit to the received criteria (property filter)

Um "Meine"-Prozesse anzuzeigen, wird der Wert 0 übergeben. Die Methode findCases verwendet dann die Ivy-Session Methode findInvolvedCases. Als Involviert gilt ein User wenn:
  • he is the creator of the case
  • he has worked or is working on a task of the case.
  • he can work on a task of the case. Either because he is the activator of the task or the activator of the task is a role he owns.
Wird nun ein Prozess komplett im Hintergrund gestartet, z.Bsp. über ein Event Start, und läuft anschliessend weiter im Hintergrund (als Benutzer SYSTEM), kann er daher nicht direkt einem Benutzer zugeordnet werden.

Zum Ändern des Erstellers eines Cases habe ich keine API Methode gefunden. Daher habe ich momentan folgendes implementiert:
  • Beim Starten des Prozesses über ein File basierter Event Start wird der "Besitzer" des Prozesses aus der Datei gelesen (wurde in diesem Fall bereits mitgeliefert) und in den Case Daten (hier als businessCreatorUser) gespeichert
  • Bei der Anzeige habe ich eine zusätzliche Option "Besitzer" implementiert, welche als caseDisplayMode 3 angibt und einen entsprechende Filter übergibt:
IPropertyFilter filter = in.propertyFilter;
if(in.caseDisplayMode == 3) {
    filter =  ivy.wf.createCasePropertyFilter(
       ch.ivyteam.ivy.workflow.CaseProperty.BUSINESS_CREATOR_USER,
     ch.ivyteam.logicalexpression.RelationalOperator.EQUAL,
     ivy.session.getSessionUserName());
}