Bernhard Häussner
Journal: Neueste Artikel erscheinen hier (Seite 7 von 23)

Qt4 Designer und Ruby - erste Schritte

06.01.2010, 11:23

Vor Kurzem habe ich ein bisschen mit Ruby herumgespielt und festgestellt, dass es keine ordentliche und aktuelle Kurzeinführung gibt, wie man die Interfaces aus dem Qt4 Designer mit Ruby Programmen verbindet. Darum habe ich die ersten Schritte hier kurz zusammengefasst:

Als erstes installiert man die Qt4-Development-Tools und die Ruby-Qt-Komponenten mit dem Paketmanager.

Interface im Qt4 Designer erstellen

Dann kann man sich ans Buttons zusammenklicken machen. Das Beispielinterface sieht im Qt Designer so aus:

Ich habe zwei QPushButtons und ein QListWidget in Layouts gepackt. Was es an Widgets gibt und was diese können steht in der Qt4 Dokumentation. In der Ruby API heißen die Klassen stets Qt::Name stat QName. Beim Basteln wichtig ist, dass ObjectName irgendwie Sinn hat, weil damit die Widgets dann benutzt werden können.

Rubycode mit rbuic erstellen

Als nächstes muss man die vom Designer erzeugte XML-Datei mit der Endung .ui in Ruby-Code umwandeln. Dazu gitb es rbuic bzw. rbuic4:

rbuic4 Sample.ui -o Sample.ui.rb

Jetzt haben wir ein Rubyscript in dem das Interface erstellt wird.

Interface in Rubyscript einbinden

Jetzt ist es Zeit dem Interface etwas Leben einzuhauchen. Dazu verbindet man die Signale - wer Webinterfaces Programmiert kennt das als „Events“ - mit den Entsprechenden Slots, die so ähnlich wie callbacks funktionieren. Das geht recht fix mit QMetaObject::connectSlotsByName. Damit dies funktioniert müssen die Funktionen, die aufgerufen werden sollen das Namensmuster on_ObjectName_Signal einhalten, wobei der ObjectName vorher im Designer festgelegt wurde und das Signal heißt z.B. triggered.

Wie man die GUI-Komponente in der Anwendung benutzt, z.B. hier durch Subclassing, und anderes will ich direkt am Beispiel erklären:

#!/usr/bin/ruby

#Laden der Qt-Klasse:
require 'Qt'

#Manchmal auch:
#require 'Qt4'

#Laden des generierten Interfaces:
require 'Sample.ui.rb'

#Initialisieren der Anwendung
app = Qt::Application.new ARGV

#Eine Klasse, um das Verhalten des Interfaces festzulegen:
class AppCommunicate < Qt::MainWindow
  #Die Liste der Slots
  #Sie werden von setupUi automatisch verbunden
  slots :on_actionQuit_triggered, :on_add_clicked, :on_rem_clicked
  def initialize
    puts "init"
    @item_count=0
    #Da wir vererben initialisieren wir so:
    super
    #Jetzt erstellen wir das Interface
    @ui=Ui_MainWindow.new
    #Und weisen es dem MainWindow zu
    @ui.setupUi self
    #Man könnte Slots auch manuell verbinden:
    #connect a.actionQuit, SIGNAL('triggered()'), $qApp, SLOT('quit()')
  end
  #Ein Handler für das Beenden:
  def on_actionQuit_triggered
    puts "Shutting down."
    #So wird die Anwendung gestoppt:
    $qApp.quit
  end
  # "add" ist der name des Buttons, bei click wird dieses signal gesendet
  def on_add_clicked
    puts "Adding item"
    #Es gibt attr_readers für alle Widgets
    #So können wir ihre Methoden aufrufen
    @ui.listWidget.addItem("Hallo Welt ##{@item_count} at #{Time.now.ctime}")
    #Hier könnten z.B. Daten der Anwendung ausgegeben werden
    #Zu demonstrationszwecken nummerieren wir die Einträge sichtbar
    @item_count+=1
  end
  def on_rem_clicked
    puts "Removing item"
    #In cpp könten wir die items deleten
    #Aber in ruby loopen wir über die markierten indizes
    @ui.listWidget.selectedIndexes.each do |i|
      #und nehmen sie aus der Liste
      @ui.listWidget.takeItem i.row
      #da wir keine Referenz speichern löscht sie der GC
    end
  end
end

#Wir erstellen eine Instanz unseres Verhaltens
wdg=AppCommunicate.new do |w|
    #Wir müssen das Interface zeigen
    #Wird auch alle child-widgets zeigen
    w.show
end

#Hier wird die GUI-Schleife gestartet:
app.exec

#TADA!

Wer das ganze ausprobieren will, kann sich die Beispieldateien hier herunterladen:

Ich hoffe das war hilfreich, ich finde es doch hin und wieder ganz nett ein Interface zu haben, vor allem für Scripte, die länger laufen und interaktiv sein sollen.

Kommentare: keine

Pagination - Design von Umblätterern und Seitenlisten

20.12.2009, 16:51
Pagination Widgets

Pagination Widgets

Obwohl das Umblätten von Seiten mit entsprechenden Widgets etwas sehr alltägliches im Web ist, gibt es noch immer einige konkurrierende Widget-Designs. Darum habe ich mir Gedanken gemacht, welche Prinzipien diesen zu Grunde liegen, und jeweils Vor- und Nachteile anhand von 10 Beispielen erörtert.

Die Art und Weise auf die Wordpress das Blättern löst, nämlich mit Vor- und Zurücklinks, die als ältere/jüngere Bezeichnet sind, findet sich zwar heute überall, hat aber trotzdem einige Probleme, die den Leser orientierungslos in der Seitenfolge zurücklassen:

Der Leser weiß z.B. nicht wohin er blättert, da die Knöpfe nicht mit vor/zurück, sondern mit der Metainformation älter/jünger verknüpft sind. Zudem weiß der Leser genauso wenig, wohin der überhaupt blättern kann, da nicht kommuniziert wird, wie umfangreich der Seitenstapel ist.

Das nächste Problem tritt erst gar nicht auf: In einem unbegrenzten Seitengewirr interessiert es den Benutzer natürlich auch nicht, wo er sich in diesem befindet. Überflüssig zu erwähnen, dass dies nur behelfsmäßig aus der URL entnommen werden kann.

Wenn man beginnt diese Probleme zu lösen kommt man schnell auf die Idee, einfach alle Seiten(zahlen) anzuzeigen. So hat man schnell einen Überblick über die Anzahl der Seiten. Nur dumm, wenn man sich dann nicht mehr traditionell immer weiter durchklicken kann, sondern Nachdenken erfordert wird: Man muss das Kurzzeitgedächtnis üben, da man nach dem Lesen der Seite noch wissen muss, welche Nummer diese Seite hatte. Entschuldigend muss man sagen, dass das Design von TAB, wo ich dieses Negativbeispiel gefunden habe noch ganz neu und ziemlich experimentell ist, denn der Fokus liegt hier auf der Anzeige des Textes in Spalten, deren Anzahl sich an die Browserfenstergröße anpassen sollte. Das Paging ist hierbei noch nur halbherzig gelöst.

Hier eine traditionelle Lösung, diesmal mit auffalllend gestalteten Blätter-Links. Leider versäumt auch kunstmark.com dem Surfer zu zeigen, wo er sich denn nun befindet. Hier bahnt sich auch schon langsam ein weiteres Problem an: Mit wachsender Seitenzahl nimmt das Blätter-Widget immer mehr Platz ein. Vorteilhaft ist natürlich, dass man auf jeden Fall immer zu jeder Seite kommt.

Das Problem mit der Riesengröße lässt sich beheben, indem einige Seiten weggelassen werden, wie auf Nerdcore. Hier ist auch dank roter Hinterlegung eindeutig zu Erkennen, auf welcher Seite man denn ist.

Ein weiterer Lösungsansatz ist es, dem Leser die Gesamtzahl der Seiten und auch die aktuelle Seite anzuzeigen. So etwas findet sich auf Gamestar.com in geschickter Kombination mit letze/erste Seite Links, welche benötigt werden, wenn diese Seiten nicht numerisch repräsentiert sind. Ein kleines assoziations-erschwerendes Manko ist vielleicht, dass Blätter-Links und Seitenübersicht getrennt auf die beiden Seitenränder verteilt sind.

Eine komplexe Lösung, die sehr auf das Surfverhalten angepasst ist, benutzt vBulletin, hier auf unixboard.de. Einige Seiten werden als Quicklinks angeziegt, unzweifelhaft wird angezeigt wo man ist und wie viele Seiten es gibt, und Links zur ersten und letzen Seite ermöglichen schnelles Springen. Außerdem öffnet ein kleiner Button ein Eingabefeld, mittels dem man zu jeder Seite springen kann.

Dieses Widget ist gut angepasst auf die Verhaltensweise des Benutzers: Anfangs wird man vielleicht noch jede Seite des Threads lesen, dann vielleicht etwas springen. Auch der Quereinsteiger kann sich dank blau hervorgehobener Orientierungshilfe sofort ein Bild seiner Position machen. Da den Leser von Foren oft auch nur interessiert, was zuletzt passiert ist, findet sich auch der Link zur letzten Seite an Prominenter Stelle.

Dieser reiche Funktionsumfang zieht natürlich auch Probleme mit sich (KISS). So sind die in der Liste weggelassenen Seiten nicht repräsentiert und die Vor/Zurück Pfeilchen fallen etwas klein aus, obwohl dies wohl die meist benutze Funktion sein dürfte. Das Widget benötigt also etwas Eingewöhnung.

Ein - wie ich hoffe nahezu perfektes - Design findet sich auf den Seiten meines Journals. Die Vor/Zurück-Links sind auffallend gestaltet und schleißen die Seitenliste ein, in welcher nur das direkte Umfeld der aktuellen Seite angezeigt wird, alle anderen Seitenzahlen werden weggelassen, was durch Auslassungspunkte angedeutet wird. Erste und letzte Seite werden dann wieder angezeigt. Zudem finden sich miniatur-Pfeilchen, welche das Überspringen von einigen Seiten ermöglichen bzw. den Bereich der angezeigten Zeitenzahlen verschieben.

Damit hoffe ich alle benötigen Funktionen und Informationen in einem klaren, platzsparenden Widget zu vereinen.

Technisch gesehen benutze ich eine kleine Modifikation des von Sweetcron verwendeten PaginateIt von Brady Vercher.

Sehr einfach aber natürlich mit dem geringsten Funktionsumfang funktioniert das umblättern bei Twitter - auf Knopfruck werden weitere Einträge gealden. Das ist hier möglich, da der Fokus auf Aktualität und „realtime“ liegt, es gibt also keinen Grund zwischen Seiten hin und her zu springen. Die Seitenzahlen würden sich auch ständig ändern.

Sehr abstrakt und gut für Menschen mit neugierigem Forschungsdrang geeignet, ist das Paging auf Shimones Lifestream. Statt Seitenzahlen finden sich jeweils drei kleine Kreischen, die aktuelle Seite wird durch den gefüllten Kreis angezeigt. Wie man sieht lösen sich einige Probleme, wenn es nur 3 Seiten gitbt von selbst.

Der Grund, warum ich mich mit Ursachen und Wirkung von Usability-Problemen im Berich der Pagination beschäftigt habe, ist die Tatsache, dass mein Lifestram (powered by Sweetcron) mit einem denkbar unpraktischem Pager kam: Keine Vor-/Zurück-Buttons, daneben unechte Vor-/Zurück-Buttons, und hier im Beispiel von simon.dynamicmushroom.com befinden wir und übrigens auf Seite 16.

Ungelöstes Problem

Ein Problem, das ich noch nicht wirklich lösen konnte, ist die Verschiebung von Seiten und die unlogische Leserichtung bei chronologisch rückwärts sortierten Journalen. Wenn neue Einträge hinzu kommen, verschieben sich die älteren um deren Anzahl nach hinten und damit beständig auf andere Seiten.

Bei Twitter und Wordpress wird das gelöst, indem die Seitenzahlen weggelassen werden und Permalinks auf Einträge angeboten werden.

Man könnte zum Beispiel auch die Seiten Rückwärts aufbauen, also bei der Seite mit der höchsten Zahl beginnen und dann nach 1 blättern, so würden sich die Seiten nicht mehr verschieben. Es wäre interessant zu sehen, wie das der User aufnimmt.

Wo möglich sollte man im Web das Aufteilen auf Seiten sowieso unterlassen. Schließlich ist der Traffic für das Laden von Text heute vernachlässigbar. Probleme ergeben sich natürlich, wenn viele Medien eingebettet sind. Ich mag darum z.B. nicht die Gravatars und Kommentarseiten.

Kommentare: 1 Einträge

Symmetrisch Zeichnen

17.12.2009, 10:43

Neben Symmetrie in der Natur bin ich auch begeistert von den Symmetrien, die der Computer erstellen kann. Und was wäre diese Symmetrie ohne die, für den Computer typische, Interaktivität? Also habe ich (zunächst mit Processing aber dann mit) HTML5-Canvas eine kleine Javascript-Sache gebastelt, mit der man direkt im Browser symmetrisch zeichnen kann.

Irgendwie macht das Spaß und, naja, es sieht auch oft erstaunlich gut aus.

[Updated 2011-09-29] Da mir das einfache herumzeichnen schnell zu langweilig wurde, habe ich das Script immmer weiter ausgebaut. Deswegen finden sich unter dem Link nun mehrere Variationen, die es sich auszuporbieren lohnt![/Updated]

Zum Zeichenbrett

Und hier noch ein Video:


Drawing Symmetry (6)

Viel Spaß beim Malen!

Kommentare: 2 Einträge

XMPP (Jabber) mit Ruby am Beispiel FelicianXMPPBot

15.12.2009, 21:53
FelicianXMPPBot in action

FelicianXMPPBot in action

Das Protokoll XMPP für den (Nahezu-)Echtzeitaustausch von XML-Nachrichten (meist verwendet für IM, als „Jabber“) steht in Konkurrenz und Verbindung mit einigen aktuell mehr oder weniger neuen Technologien, die mit XML und realtime zu tun haben, wie RSS/Atom pings, realtime web oder AJAX. Und ein paar Spielereien auf diesem Gebiet können sicher nicht schaden, also habe ich mit XMPP etwas herumgebastelt.

Weil dies mit PHP irgendwie seltsam wäre und David Strohmayers Text und Präsentation über XMPP mit Java noch nicht existierte, habe ich das ganze mit Ruby gebastelt, da gibt es nämlich den Gem xmpp4r, der mit vielen Beispielen kommt. So konnte ich mal ein bisschem mit Ruby herumspielen. (Zwei Fliegen mit einer Klappe usw.)

Aus den Experimenten ist ein kleiner Server geworden, der einen Jabber-Bot betreibt und über ein einfaches TCP-Protokoll erreicht werden kann, sodass aus allen TCP-Fähigen Skripten (bash mit netcat, PHP…) einfach Nachrichten an einen Jabber-Endpunkt versendet werden können.

Code und Funktionalität bestehen aus drei Teilen:

Die Klasse Netx kümmert sich um den TCP-Server und parst das einfache Protokoll: Man schickt Zeilen und beendet einzelne Nachrichten mit einem Punkt, und die Verbindung (und damit auch die aktuelle Nachricht) mit zwei Punkten. So lassen sich die TCP-Clients leicht implementieren.

Die Klasse XmppRoute bietet nur ein wenig Grundfunktion für den Jabber-Bot. Sie vereinfacht Verbinden und Senden noch weiter, zudem extrahiert sie beim Empfang einer Nachricht einen Befehlsteil.

Der Rest kümmert sich um die Verbindung der beiden Komponenten und um das Parsen von Startup-Parametern.

Aber genug der vielen Worte, jetzt folgt der Code:

jabot.rb

#!/usr/bin/ruby

# Felician 0.9 - XMPP Wrapper
# (c) 2009 by Bernhard Häussner
# Licence: GPLv2+ http://www.gnu.org/licenses/gpl-2.0.txt
#
# This jabber wrapper will recieve messages via simple tcp.
#
# Quit by sending "exit" via jabber. 
# Send "info" via jabber to get running parameters. 
# Send "feedback [msg]" via jabber to post from jabber. 
# Connect to specified port and send messages terminated by lone "." in a line to post via TCP. 
# Send a line with ".." to quit connection. 
#
# Based on xmpp4r-0.5/data/doc/xmpp4r/examples/basic
#

require 'optparse'
require 'XmppRoute'
require 'Netx'

class FelicianXMPPBot
  def start(his_JID,my_txt_JID,my_password,listen_port)
    puts "FELC: Started. Sending to #{his_JID} from #{my_txt_JID} listening to #{listen_port}."
    my_JID=JID.new(my_txt_JID)
    netx=Netx.new
    XmppRoute.connect(my_JID,my_password) do
      on_recieve do |from,cmd,param|
        if from.strip.to_s.eql?his_JID then
          case cmd.downcase
            when 'exit' then
              send(from,"-> I've got to go!")
              puts "FELC: Got shutdown signal, waiting for children to terminate..."
              netx.halt
            when 'feedback' then send(from,param)
            when 'info'then send(from,"-> Felician sending to #{his_JID} listening to #{listen_port}. ")
            else send(from,"-> I don't know: #{cmd} #{param}")
          end
        else
          puts "XMPP: Ignored a message. "
          send(from,"Hi, #{from.strip.to_s}. Sorry, could not be deliverd. Try again later! ")
        end
      end
      
      netx.on_sended do |msg|
        send(his_JID,msg.strip)
        puts "XMPP: Client message sent. "
      end
      netx.listen(listen_port)
    end
    puts "FELC: Shutting down. "
  end
end

if $0 == __FILE__
  #defaults:
  listen_port=2000
  his_JID = "bot_admin@gmx.de"
  my_txt_JID = "i_am_a_bot@jabber.ccc.de/felician"
  my_password = "top_secret"
  # cli settings
  OptionParser.new do |opts|
  opts.banner = 'Usage: ruby jabot.rb -j jid/ressource -p password -t reciever_jid -l port'
  opts.separator ''
  opts.on('-j', '--jid JID', 'sets the sender jid') { |j| my_txt_JID = j }
  opts.on('-p', '--password PASSWORD', 'sets the sender password') { |p| my_password = p }
  opts.on('-t', '--to-jid JID', 'sets the message reciever') { |t| his_JID = t }
  opts.on('-l', '--listen PORT', 'sets the server listen port') { |l| listen_port = l }
    opts.on_tail('-h', '--help', 'Show this message') {
    puts opts
    exit
  }
  opts.parse!(ARGV)
  end
  FelicianXMPPBot.new.start(his_JID,my_txt_JID,my_password,listen_port)
end

XmppRoute.rb

require 'rubygems'
require 'xmpp4r/client'
include Jabber

class XmppRoute
  def initialize(jid,pwd)
    @cmd=Proc.new { |a,b,c| puts "XMPP: No reciever. " }
    @cl = Client.new(jid)
    @cl.connect
    @cl.auth(pwd)
    @cl.send(Presence.new)
    puts "XMPP: Connected! send messages to #{jid.strip.to_s}."
    @cl.add_message_callback { |m| process m }
  end
  def close
    @cl.close
  end
  def on_recieve(&cb)
    @cmd=cb
  end
  def send(to,mes)
    ms = Message.new(to,mes) # [_CY4_L8-R_4LLY-G4T3-R_]
    ms.type = :chat
    @cl.send(ms)
  end
  private
  def process(m)
    puts "XMPP: Got msg of type #{m.type} / #{m.name} with -#{m.body}- "
    if m.type == :chat && ! m.body.nil?
      index=(m.body.index(" ") || 0)-1
      com=m.body[0..index]
      param=(index==-1?"":m.body[index+2..-1])
      puts "XMPP: Call #{com} with --#{param}-- (caused from #{m.from})"
      @cmd.call(m.from,com,param)
    end
  end
  def self.connect(jid,pwd,&block)
    bot=XmppRoute.new(jid,pwd)
    bot.instance_eval(&block)
    bot.close
  end
end

Netx.rb

require 'socket' # Get sockets from stdlib

class Netx
  def initialize; @run=true; end
  def listen(port)
    puts "NETX: Bind to Port #{port}"
    #server = TCPServer.open(port)
    #only local:
    server = TCPServer.open("127.0.0.1",port) # Socket to listen
    while @run
      # this timeout allows us to kill this lop every few seconds...
      begin
        session = Timeout::timeout(10) { server.accept }
        Thread.start(session) { |c| connection c }
      rescue TimeoutError
        #puts "NETX: Refreshing TCPServer."
      end
    end
  end
  def on_sended(&cb); @client_cb=cb; end
  def halt; @run=false; end
  private
  def connection (client)
    puts "NETX: Client conntected..."
    client.puts("-- Hello on Felician at #{Time.now.ctime}. ") # Send the helo & time to the client
    conn_open=true;
    msg_buffer="";
    while conn_open do
      l = client.gets
      if (l.strip.eql?'..') || (l.strip.eql?'.') then
        puts "NETX: Client issued a message..."
        client.puts("-- accepted. ")
        @client_cb.call(msg_buffer)
        msg_buffer="";
        conn_open=false if l.strip.eql?'..'
      else
        msg_buffer << l
      end
    end
  ensure
    client.puts "-- Closing the connection. Bye!"
    puts "NETX: Client disconnect. "
    client.close # Disconnect from the client
  end
end

Erwartet nicht zu viel von dem Code, ist schließlich mein erster Ruby-Code, abgesehen von einigen Hello-World-Sachen. Funktioniert dafür allerdings erstaunlich gut und könnte sich schon fast eigenen, um z.B. einen Systemadministrator über wichtige Aktivitäten und Warnungen aus den verschiedensten Programmen und Wartungsscripten schnell zu informieren.

Man beachte vielleicht noch das alles in dem Code, was in anderen Programmiersprachen als schlechter Stil gälte, hier eher „the ruby way“ ist ;).

Benutzung

Eine Nachricht von der Shell schicken:

09:15  burny@home:~> echo -e "Hallo Welt\n.." | netcat localhost 2000
-- Hello on Felician at Tue Dec 15 21:16:02 2009.
-- accepted.
-- Closing the connection. Bye!

Mal sehen, ob ich das Ganze noch weiter entwickle, meistens ist doch das Senden einer ganz normalen eMail einfacher.

Kommentare: 2 Einträge

École

10.12.2009, 16:34
Router

Router

Wenn ich mal nicht an meiner Facharbeit arbeite, arbeite ich meistens nicht zu produktiv. Aber immerhin...

Mich fasziniert immernoch die Symmetrie, die man mit dem Computer in Zeichnungen und Fotos schaffen kann. Mit GIMP lässt sich das machen:

Diese Bilder sind übrigens mehr oder weniger Weiterentwicklungen derer von den Italien-Reisen. Andere Inspirationsquellen: 1984, Ella und schätzungsweise Geometrie.

[Updated 2009-12-13] Genauso interessant scheint es die von Pixel suggerierte Symmetrie und Rechteckigkeit wieder zu brechen:

[Updated 2010-03-11] Auch noch etwas im Kontext der Italienreisen zu sehen ist dieses:

Kommentare: keine
 
Χρόνογραφ
© 2008-2017 by Bernhard Häussner - Impressum - Login
Kurz-Link zu dieser Seite: http://1-co.de/bj