Graphische Oberfläche

Proggen.org - Lernprojekt: Duplikatefinder
nufan
Wiki-Moderator
Beiträge: 2443
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Graphische Oberfläche

Beitrag von nufan » Fr Apr 06, 2012 11:05 pm

Bebu hat geschrieben:Mhh, ich stehe gerade etwas auf dem Schlauch. Du startest ein Kernelobjekt, startest deine GUI und rufst von da aus die Kernelfunktionen auf.
Das schon, aber wir arbeiten mit verschiedenen GUI-Bibliotheken (Qt, wxWidgets, NCurses), die alle anders funktionieren. Das Event-Handling von Qt ist ganz anders als jenes von NCurses. Und gerade im Event-Handling sollten die Kernel-Funktionen aufgerufen werden. Deshalb sollten die GUI-Implementierungen alle einem einheitlichen Interface folgen. Zurzeit befindet sich dieses in userinterface/interface.h. Das passt mir aber aus den genannten Gründen nicht. Ich würde das gerne ändern, wenn sonst niemand Einwände hat.

Benutzeravatar
Bebu
Beiträge: 562
Registriert: Mi Okt 21, 2009 6:19 pm
Wohnort: In der Nähe von Salzburg - Bin aber kein Österreicher!

Re: Graphische Oberfläche

Beitrag von Bebu » Fr Apr 06, 2012 11:15 pm

Ich sollte wirklich langsam mal mit GUI anfangen, ich habe seit Delphi nichts mehr mit GUI gemacht und kann dir gerade nicht so wirklich folgen. Aber von meiner Sicht aus kein Problem, die Kommandozeile spricht direkt mit der Kernel, von daher hat diese Änderung keine Auswirkung auf die Funktion und Trunk bleibt funktionsfähig.
Wer immer nach dem Unerreichbaren jagt, der wird irgendwann auf die Schnauze fallen!

nufan
Wiki-Moderator
Beiträge: 2443
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Graphische Oberfläche

Beitrag von nufan » Fr Apr 06, 2012 11:29 pm

Bebu hat geschrieben:Aber von meiner Sicht aus kein Problem
Das wollte ich hören :D
Ich werd das mal ändern und auch die NCurses-Version zumindest kompilierbar halten.

nufan
Wiki-Moderator
Beiträge: 2443
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Graphische Oberfläche

Beitrag von nufan » Sa Apr 07, 2012 2:08 am

So... das wird länger dauern ^^ Ich bin mir unsicher ob ich meinen Code bereits commiten soll. Hier mal grob meine Änderungen:

interface.h:

Code: Alles auswählen

namespace Dedupe
{
  class File;

  namespace GUI
  {
    namespace MainAction
    {
    /**
      * The following Tokens will be send by the GetAction()-method
      * and describe the action the user has selected
      */
    enum MainAction
    {
        Append,   ///< Append button (at list) was pressed
        Remove,   ///< Remove item (at list) was pressed
        Index,    ///< Index button was pressed
        Manage,   ///< Manage button was pressed
        Delete,   ///< Delete items which are selected for deletion
        Edit,     ///< Delete items which are selected for deletion

        Hide,     ///< Hide window
        Show,     ///< Restore hidden window
        Quit      ///< Exit to operating system
      };
    }

    /**
      * This class describes the interface to the main window.
      */
    class Main
    {
      public:
        /**
          * Refresh the main window - add the given File to the list
          * \param file reference to the new file
          */
        virtual void AppendFile( const Dedupe::FileInfo & file ) = 0;

        /**
          * Refresh the main window - remove the given File to the list
          */
        virtual void RemoveFile( const Dedupe::FileInfo & file ) = 0;

      public:
        /**
          * Waits for user interaction
          */
        virtual int exec() = 0;

        Main( int argc, char *argv[] ) { /* Handle Parameters */ }
        virtual ~Main() {}

    protected:
      /**
        * Get several files from the user
        */
        virtual const std::vector<Dedupe::FileInfo>& GetFiles() = 0;
        void OnAppend()
        {
          std::cout << "OnAppend" << std::endl;
          const std::vector<Dedupe::FileInfo>&  files = GetFiles();
          std::cout << "adding to user interface:" << std::endl;
          for( int i = 0; i < files.size(); i++ )
          {
            std::cout << "\t" << files[i].GetPath() << std::endl;
            AppendFile( files[i] );
          }
        }
        void OnRemove()
        {
          std::cout << "OnRemove" << std::endl;
        }
        void OnIndex()
        {
          std::cout << "OnIndex" << std::endl;
        }
        void OnManage()
        {
          std::cout << "OnManage" << std::endl;
        }
        void OnDelete()
        {
          std::cout << "OnDelete" << std::endl;
        }
        void OnEdit()
        {
          std::cout << "OnEdit" << std::endl;
        }
        void OnHide()
        {
          std::cout << "Hiding window" << std::endl;
        }
        void OnShow()
        {
          std::cout << "Showing window" << std::endl;
        }
        void OnQuit()
        {
          std::cout << "Shutting down" << std::endl;
        }
    };
  }
}
Die Main-Instanz erhält nun argc und argv als Parameter. Ich dachte das könnte später mal nützlich sein und ich bau es gleich ein.

Weiters hab ich die Parameter von AppendFile()/RemoveFile() von File auf FileInfo geändert. Ich hab nirgends eine Implementierung von File gefunden?! Außerdem glaube ich nicht, dass man für die Oberfläche die Daten in der Datei braucht.

Zu den Framework-unabhängigen Callbacks. Hier ein Beispiel wie ich mir das vorgestellt habe:

Code: Alles auswählen

void OnAppend()
{
  const std::vector<Dedupe::FileInfo>&  files = GetFiles();
  for( int i = 0; i < files.size(); i++ )
    AppendFile( files[i] );
}
OnAppend() bekommt über die rein virtuelle Methode GetFiles() jene Dateien, die der Benutzer zum Einfügen ausgewählt hat. Danach werden sie über die rein virtuelle Methode AppendFiles() eingefügt. Vorteil: Diese Implementierung ist komplett unabhängig vom darunterliegenden Framework. Nachrteil: Unser Interface ist jetzt kein echtes Interface mehr :/ Die Implementierung hab ich momentan direkt im Header, sollte aber natürlich in eine Quelldatei ausgelagtert werden. Ich wollte jetzt aber nicht auch noch an CMake rumpfuschen.

Über exec() wird nun die Oberfläche "gestartet". NCurses wartet an dieser Stelle auf Events und behandelt diese. Qt übergibt an den Event-Handler und wartet auf dessen Informationen. Das main sieht im Idealfall so aus:

Code: Alles auswählen

int main( int argc, char *argv[] )
{
  Dedupe::GUI::NCursesMain gui( argc, argv );
  return gui.exec();
}
Für Qt muss davor noch ein QApplication-Objekt erstellt werden.
Je nachdem welcher Event ausgelöst wird, wird ein Interface-Callback wie OnAppend() aufgerufen. Dabei wird das interne Event-Handling abgekapselt. Es gibt ja innerhalb von Qt schon 2 Methoden Events zu behandeln.
Hier ein Beispiel wie die 1. Methode abgekapselt wird:

Code: Alles auswählen

void QtMain::closeEvent( QCloseEvent *event )
{
  OnQuit();
  QWidget::closeEvent( event );
}
Wir das Fenster geschlossen (und somit das Programm beendet) wird von Qt die Methode closeEvent() aufgerufen. Darin wird auf das Interface zurückgegriffen und das restliche Event-Handling an die Basisklasse übergeben.
2. Methode über Signals und Slots:

Code: Alles auswählen

connect( Append, SIGNAL( clicked() ),
              this, SLOT( handleButtonClicked() ) );
// ...
void QtMain::handleButtonClicked()
{
  if( sender() == Index )
    OnIndex();
  else if( sender() == Append )
    OnAppend();
  // ...
}
Alle Button-Events werden in der handleButtonClicked()-Methode verarbeitet. In dieser Methode muss nur noch der Auslöser ( = sender() ) festgestellt werden und die entsprechende Interface-Methode aufgerufen werden.

Ich würde gerne die Benutzeroberflächen so schnell wie möglich mit dem Kernel verbinden. Auch um die Entwicklungsumgebung besser nutzen zu können. Ich verwende den Qt-Creator. Mit ihm lassen sich CMake-Programme problemlos bearbeiten und kompilieren. Das zu kompilierende Programm selbst muss Qt nicht zwingend verwenden! Es reicht die höchste CMakeLists.txt mit dem Creator zu öffnen.

Nun habe ich die Qt-Oberfläche soweit, dass sie über das Interface auf Events reagiert. Ich hab jetzt einen Button über den man Dateien auswählen kann, die dann in die (grafische) Liste eingefügt werden. Dabei bekomme ich einen Segmentation Fault. Ich bin mir aber ziemlich sicher, dass der nicht aus meinem Code kommt:

Code: Alles auswählen

==6813== Process terminating with default action of signal 11 (SIGSEGV)
==6813==  Access not within mapped region at address 0x1A
==6813==    at 0x66C14F8: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16)
==6813==    by 0x4098CC: boost::filesystem3::path::path(boost::filesystem3::path const&) (path.hpp:127)
==6813==    by 0x4E6C090: Dedupe::FileInfo::GetPath() const (fileinfo.cpp:98)
==6813==    by 0x409BA6: Dedupe::GUI::Main::OnAppend() (interface.h:79)
==6813==    by 0x40965B: Dedupe::GUI::QtMain::handleButtonClicked() (implementation.cpp:130)
// ...
Das ist die vorhin bereits gezeigte Stelle, hier nochmal etwas ausführlicher da ich vorhin die Debug-Ausgaben weggelassen habe:

Code: Alles auswählen

void OnAppend()
{
  std::cout << "OnAppend" << std::endl;
  const std::vector<Dedupe::FileInfo>&  files = GetFiles();
  std::cout << "adding to user interface:" << std::endl;
  for( int i = 0; i < files.size(); i++ )
  {
    std::cout << "\t" << files[i].GetPath() << std::endl;
    AppendFile( files[i] );
  }
}
Die Datei hat von Qt sicher einen gültigen Pfad bekommen, ich hab ihn mir zuvor ausgeben lassen. Was mir schon mal komisch vorkam:
fileinfo.cpp, erster Konstruktor:

Code: Alles auswählen

Dedupe::FileInfo::FileInfo( Dedupe::FilePath PathToFile ):
Path(""), Size( 0 ), DateChanged( 0 ), Existing( false ),
Type( TNotAvailable ), Hash( 0 ), Keep( false ),
StateOfFile( FileStatusNotSet ), FileSearchErrorMessage( "" )
{
Warum bekommt Path einen leeren String, obwohl doch PathToFile übergeben wird? Verstehe ich etwas falsch? Daran liegts aber nicht, hab schon versucht das zu ändern.

Und das letzte Thema, womit ich mich wahrscheinlich an fat-lobyte richte. Ich bekomme jetzt beim Kompilieren 865 Warnings... Ich weiß es sind nur Warnings, aber trotzdem stört mich die Zahl etwas ^^ Die sind natürlich fast alle nicht von Dedupe selbst. Ich bekomme aber Meldungen wie:

Code: Alles auswählen

/usr/include/qt4/QtGui/qstyleoption.h: At global scope:
/usr/include/qt4/QtGui/qstyleoption.h:747:20: warning: base class ‘class QStyleOptionComplex’ has a non-virtual destructor [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h: In copy constructor ‘QStyleOptionQ3ListView::QStyleOptionQ3ListView(const QStyleOptionQ3ListView&)’:
/usr/include/qt4/QtGui/qstyleoption.h:762:5: warning: ‘QStyleOptionQ3ListView::items’ should be initialized in the member initialization list [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h:762:5: warning: ‘QStyleOptionQ3ListView::viewportPalette’ should be initialized in the member initialization list [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h:762:5: warning: ‘QStyleOptionQ3ListView::viewportBGRole’ should be initialized in the member initialization list [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h:762:5: warning: ‘QStyleOptionQ3ListView::sortColumn’ should be initialized in the member initialization list [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h:762:5: warning: ‘QStyleOptionQ3ListView::itemMargin’ should be initialized in the member initialization list [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h:762:5: warning: ‘QStyleOptionQ3ListView::treeStepSize’ should be initialized in the member initialization list [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h:762:5: warning: ‘QStyleOptionQ3ListView::rootIsDecorated’ should be initialized in the member initialization list [-Weffc++]
Aha. Ich bekomme Fehlermeldungen über den Qt-Quellcode?! Wäre es schlimm den -Weffc++-Flag rauszunehmen? Oder kann man das anders verhindern?

Das wars für heute ^^

Benutzeravatar
Bebu
Beiträge: 562
Registriert: Mi Okt 21, 2009 6:19 pm
Wohnort: In der Nähe von Salzburg - Bin aber kein Österreicher!

Re: Graphische Oberfläche

Beitrag von Bebu » Sa Apr 07, 2012 11:33 am

dani93 hat geschrieben: Weiters hab ich die Parameter von AppendFile()/RemoveFile() von File auf FileInfo geändert. Ich hab nirgends eine Implementierung von File gefunden?! Außerdem glaube ich nicht, dass man für die Oberfläche die Daten in der Datei braucht.
Da hast du Recht, File gibt es nicht, das ist FileInfo. Das Interface habe ich aber auch noch nie wirklich angefasst, es war für mich bisher nicht notwendig. Du bekommst alle Infos über die Datei mitgeliefert, die steckt in dem FileInfo. Es ist aber nicht gewünscht, das du die Daten über die Oberfläche manipulierst, das wird hoffentlich irgendwann nicht mehr möglich sein. Das muss ich intern aber erst vorbereiten, damit ich mit Konstanten Objekten arbeiten lassen kann.
dani93 hat geschrieben: Ich würde gerne die Benutzeroberflächen so schnell wie möglich mit dem Kernel verbinden.
Der Vorsatz gefällt mir. :)
dani93 hat geschrieben: Die Datei hat von Qt sicher einen gültigen Pfad bekommen, ich hab ihn mir zuvor ausgeben lassen. Was mir schon mal komisch vorkam:
fileinfo.cpp, erster Konstruktor:

Code: Alles auswählen

Dedupe::FileInfo::FileInfo( Dedupe::FilePath PathToFile ):
Path(""), Size( 0 ), DateChanged( 0 ), Existing( false ),
Type( TNotAvailable ), Hash( 0 ), Keep( false ),
StateOfFile( FileStatusNotSet ), FileSearchErrorMessage( "" )
{
Warum bekommt Path einen leeren String, obwohl doch PathToFile übergeben wird? Verstehe ich etwas falsch? Daran liegts aber nicht, hab schon versucht das zu ändern.
Nö, die Zeile hat schon ihren Sinn. Path wird nur leer initialisiert, um die Warnings zu fixen. Im Konstruktorkörper wird PathToFile noch verarbeitet und normalisiert, bevor das ganze in die Variable Path wandert. Trotzdem ist der Fehler merkwürdig. Ich verwende aber auch keine normalen Strings an der Stelle, sonder Path Objekte aus der Boost Filesystem Library. Eventuell kommt sich das mit den Pfaden aus QT in die Quere. Lade deinen Code doch mal hoch, dann kann ich auch mal suchen wo es denn hakt. Das kann auch in Trunk passieren, wird momentan ohnehin standardmäßig nicht gebaut.
Wer immer nach dem Unerreichbaren jagt, der wird irgendwann auf die Schnauze fallen!

nufan
Wiki-Moderator
Beiträge: 2443
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Graphische Oberfläche

Beitrag von nufan » Sa Apr 07, 2012 11:36 am

Bebu hat geschrieben:Trotzdem ist der Fehler merkwürdig. Ich verwende aber auch keine normalen Strings an der Stelle, sonder Path Objekte aus der Boost Filesystem Library. Eventuell kommt sich das mit den Pfaden aus QT in die Quere. Lade deinen Code doch mal hoch, dann kann ich auch mal suchen wo es denn hakt. Das kann auch in Trunk passieren, wird momentan ohnehin standardmäßig nicht gebaut.
Ich übergebe einen absoluten Pfad als char-Array an den Konstruktor deines Objekts. Code wird hochgeladen :)

Benutzeravatar
Bebu
Beiträge: 562
Registriert: Mi Okt 21, 2009 6:19 pm
Wohnort: In der Nähe von Salzburg - Bin aber kein Österreicher!

Re: Graphische Oberfläche

Beitrag von Bebu » Sa Apr 07, 2012 12:02 pm

Danke für den Code, sieht ganz gut aus. Ich glaube für den Vector für FileInfos gibt es schon ein typedef, ist aber nicht so wichtig. Ich habe gerade ein Testproblem. Seit einem der letzten Updates von Cmake habe ich einen Fehler im Buildsystem. Er behandelt Sqlite als Target, das er bauen will. Muss mal erst unseren Buildsytem Experten konsultieren, vorher kann ich gar nichts ausprobieren :(
Wer immer nach dem Unerreichbaren jagt, der wird irgendwann auf die Schnauze fallen!

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: Graphische Oberfläche

Beitrag von fat-lobyte » Sa Apr 07, 2012 12:14 pm

Eine Frage, was genau ist "GetFiles()", und wieso gibt es nur eine Referenz zurück? Werden die Daten tatsächlich irgendwo gespeichert? Kommt vielleicht daher der Segfault?

Zweitens: mit Daten zu arbeiten, die irgendwo liegen und eine Referenz darauf zurückzuliefern ist problematisch, und zwar vom Multithreading-Aspekt her. Stell dir vor, du verarbeitest die Daten gerade, und ein anderer Thread entscheidet sich dazu, die Daten zu verändern!

Aha. Ich bekomme Fehlermeldungen über den Qt-Quellcode?! Wäre es schlimm den -Weffc++-Flag rauszunehmen? Oder kann man das anders verhindern?
Ja, das kann jeder für sich einstellen: Du musst die CMake-Variable "DEDUPE_WARNING_LEVEL" auf 1 oder 2 setzen. Das machst du, indem du entweder bei dem Kommandozeilenaufruf von CMake "-DDEDUPE_WARNING_LEVEL=1" mit angibst, oder am besten einfach in der CMake-GUI nach der Variable suchst und dort 1 reinschreibst.

Code: Alles auswählen

/usr/include/qt4/QtGui/qstyleoption.h: At global scope:
/usr/include/qt4/QtGui/qstyleoption.h:747:20: warning: base class ‘class QStyleOptionComplex’ has a non-virtual destructor [-Weffc++]
/usr/include/qt4/QtGui/qstyleoption.h: In copy constructor ‘QStyleOptionQ3ListView::QStyleOptionQ3ListView(const QStyleOptionQ3ListView&)’:
Das ist anscheinend ein GCC Bug (einer von zu vielen ^^), normalerweise sollten Warnungen aus den Headern unterdrückt werden, bei -Weffc++ geschieht das aber nicht. Wenn dein DEDUPE_WARNING_LEVEL < 3, dann wird -Weffc++ nicht übergeben.
Bebu hat geschrieben:Muss mal erst unseren Buildsytem Experten konsultieren, vorher kann ich gar nichts ausprobieren :(
Dann konsultiere! Aber bitte entweder im Buildsystem-Thread, oder gleich als Bugreport im Bugzilla.
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Bebu
Beiträge: 562
Registriert: Mi Okt 21, 2009 6:19 pm
Wohnort: In der Nähe von Salzburg - Bin aber kein Österreicher!

Re: Graphische Oberfläche

Beitrag von Bebu » Sa Apr 07, 2012 12:29 pm

fat-lobyte hat geschrieben: Dann konsultiere! Aber bitte entweder im Buildsystem-Thread, oder gleich als Bugreport im Bugzilla.
Ganz ruhig Tiger, schon erledigt ;) :P
Wer immer nach dem Unerreichbaren jagt, der wird irgendwann auf die Schnauze fallen!

nufan
Wiki-Moderator
Beiträge: 2443
Registriert: Sa Jul 05, 2008 3:21 pm

Re: Graphische Oberfläche

Beitrag von nufan » Sa Apr 07, 2012 5:29 pm

Bebu hat geschrieben:Es ist aber nicht gewünscht, das du die Daten über die Oberfläche manipulierst, das wird hoffentlich irgendwann nicht mehr möglich sein.
Keine Angst, hatte ich nicht vor ;)
fat-lobyte hat geschrieben:Eine Frage, was genau ist "GetFiles()", und wieso gibt es nur eine Referenz zurück? Werden die Daten tatsächlich irgendwo gespeichert? Kommt vielleicht daher der Segfault?

Zweitens: mit Daten zu arbeiten, die irgendwo liegen und eine Referenz darauf zurückzuliefern ist problematisch, und zwar vom Multithreading-Aspekt her. Stell dir vor, du verarbeitest die Daten gerade, und ein anderer Thread entscheidet sich dazu, die Daten zu verändern!
Sorry Bebu, war doch mein Fehler -.- Der lokale Vektor in GetFiles() wird nach dem return zerstört und meine Referenz zeigt ins Nichts...
fat-lobyte hat geschrieben:Ja, das kann jeder für sich einstellen: Du musst die CMake-Variable "DEDUPE_WARNING_LEVEL" auf 1 oder 2 setzen.
Danke, funktioniert tadellos :)

Das neue Interface und die neue Qt-Version sind hochgeladen. Alles sollte auf Stufe 2 ohne Warnings kompilieren. Bei einem Klick auf "Append" werden Dateien ausgesucht und danach in der rechten Liste angezeigt. Das Beispiel ist eher sinnfrei und soll das Event-Handling demonstrieren ^^
In der NCurses-Implementierung sind noch ein paar, aber das ist nicht meine Baustelle ;)

Antworten