Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions

A Complete Canvas Application

This is a complete example program with a main window, menus and toolbars. The main widget is a TQCanvas, and this example demonstrates basic canvas usage.


Project file:

TEMPLATE = app

CONFIG  += warn_on

REQUIRES = full-config

HEADERS += element.h \
           canvastext.h \
           canvasview.h \
           chartform.h \
           optionsform.h \
           setdataform.h
SOURCES += element.cpp \
           canvasview.cpp \
           chartform.cpp \
           chartform_canvas.cpp \
           chartform_files.cpp \
           optionsform.cpp \
           setdataform.cpp \
           main.cpp


Header files:

#ifndef ELEMENT_H
#define ELEMENT_H

#include <ntqcolor.h>
#include <ntqnamespace.h>
#include <ntqstring.h>
#include <ntqvaluevector.h>

class Element;

typedef TQValueVector<Element> ElementVector;

/*
    Elements are valid if they have a value which is > EPSILON.
*/
const double EPSILON = 0.0000001; // Must be > INVALID.


class Element
{
public:
    enum { INVALID = -1 };
    enum { NO_PROPORTION = -1 };
    enum { MAX_PROPOINTS = 3 }; // One proportional point per chart type

    Element( double value = INVALID, TQColor valueColor = TQt::gray,
             int valuePattern = TQt::SolidPattern,
             const TQString& label = TQString::null,
             TQColor labelColor = TQt::black ) {
        init( value, valueColor, valuePattern, label, labelColor );
        for ( int i = 0; i < MAX_PROPOINTS * 2; ++i )
            m_propoints[i] = NO_PROPORTION;
    }
    ~Element() {}

    bool isValid() const { return m_value > EPSILON; }

    double value() const { return m_value; }
    TQColor valueColor() const { return m_valueColor; }
    int valuePattern() const { return m_valuePattern; }
    TQString label() const { return m_label; }
    TQColor labelColor() const { return m_labelColor; }
    double proX( int index ) const;
    double proY( int index ) const;

    void set( double value = INVALID, TQColor valueColor = TQt::gray,
              int valuePattern = TQt::SolidPattern,
              const TQString& label = TQString::null,
              TQColor labelColor = TQt::black ) {
        init( value, valueColor, valuePattern, label, labelColor );
    }
    void setValue( double value ) { m_value = value; }
    void setValueColor( TQColor valueColor ) { m_valueColor = valueColor; }
    void setValuePattern( int valuePattern );
    void setLabel( const TQString& label ) { m_label = label; }
    void setLabelColor( TQColor labelColor ) { m_labelColor = labelColor; }
    void setProX( int index, double value );
    void setProY( int index, double value );

#ifdef TQ_FULL_TEMPLATE_INSTANTIATION
    // xlC 3.x workaround
    TQ_DUMMY_COMPARISON_OPERATOR(Element)
    bool operator!=( const Element& e) const {
        return ( !(e == *this) );
    }
#endif

private:
    void init( double value, TQColor valueColor, int valuePattern,
               const TQString& label, TQColor labelColor );

    double m_value;
    TQColor m_valueColor;
    int m_valuePattern;
    TQString m_label;
    TQColor m_labelColor;
    double m_propoints[2 * MAX_PROPOINTS];
};


TQTextStream &operator<<( TQTextStream&, const Element& );
TQTextStream &operator>>( TQTextStream&, Element& );

#endif
#ifndef CHARTFORM_H
#define CHARTFORM_H

#include "element.h"

#include <ntqmainwindow.h>
#include <ntqstringlist.h>


class CanvasView;

class TQAction;
class TQCanvas;
class TQFont;
class TQPrinter;
class TQString;


class ChartForm: public TQMainWindow
{
    TQ_OBJECT
public:
    enum { MAX_ELEMENTS = 100 };
    enum { MAX_RECENTFILES = 9 }; // Must not exceed 9
    enum ChartType { PIE, VERTICAL_BAR, HORIZONTAL_BAR };
    enum AddValuesType { NO, YES, AS_PERCENTAGE };

    ChartForm( const TQString& filename );
    ~ChartForm();

    int chartType() { return m_chartType; }
    void setChanged( bool changed = TRUE ) { m_changed = changed; }
    void drawElements();

    TQPopupMenu *optionsMenu; // Why public? See canvasview.cpp

protected:
    virtual void closeEvent( TQCloseEvent * );

private slots:
    void fileNew();
    void fileOpen();
    void fileOpenRecent( int index );
    void fileSave();
    void fileSaveAs();
    void fileSaveAsPixmap();
    void filePrint();
    void fileQuit();
    void optionsSetData();
    void updateChartType( TQAction *action );
    void optionsSetFont();
    void optionsSetOptions();
    void helpHelp();
    void helpAbout();
    void helpAboutTQt();
    void saveOptions();

private:
    void init();
    void load( const TQString& filename );
    bool okToClear();
    void drawPieChart( const double scales[], double total, int count );
    void drawVerticalBarChart( const double scales[], double total, int count );
    void drawHorizontalBarChart( const double scales[], double total, int count );

    TQString valueLabel( const TQString& label, double value, double total );
    void updateRecentFiles( const TQString& filename );
    void updateRecentFilesMenu();
    void setChartType( ChartType chartType );

    TQPopupMenu *fileMenu;
    TQAction *optionsPieChartAction;
    TQAction *optionsHorizontalBarChartAction;
    TQAction *optionsVerticalBarChartAction;


    TQString m_filename;
    TQStringList m_recentFiles;
    TQCanvas *m_canvas;
    CanvasView *m_canvasView;
    bool m_changed;
    ElementVector m_elements;
    TQPrinter *m_printer;
    ChartType m_chartType;
    AddValuesType m_addValues;
    int m_decimalPlaces;
    TQFont m_font;
};

#endif


Implementation:

#include "canvasview.h"
#include "chartform.h"
#include "optionsform.h"
#include "setdataform.h"

#include <ntqaction.h>
#include <ntqapplication.h>
#include <ntqcombobox.h>
#include <ntqfile.h>
#include <ntqfiledialog.h>
#include <ntqfont.h>
#include <ntqfontdialog.h>
#include <ntqmenubar.h>
#include <ntqmessagebox.h>
#include <ntqpixmap.h>
#include <ntqpopupmenu.h>
#include <ntqprinter.h>
#include <ntqradiobutton.h>
#include <ntqsettings.h>
#include <ntqspinbox.h>
#include <ntqstatusbar.h>
#include <ntqtoolbar.h>
#include <ntqtoolbutton.h>

#include "images/file_new.xpm"
#include "images/file_open.xpm"
#include "images/file_save.xpm"
#include "images/file_print.xpm"
#include "images/options_setdata.xpm"
#include "images/options_setfont.xpm"
#include "images/options_setoptions.xpm"
#include "images/options_horizontalbarchart.xpm"
#include "images/options_piechart.xpm"
#include "images/options_verticalbarchart.xpm"


const TQString WINDOWS_REGISTRY = "/Trolltech/TQtExamples";
const TQString APP_KEY = "/Chart/";


ChartForm::ChartForm( const TQString& filename )
    : TQMainWindow( 0, 0, WDestructiveClose )
{
    setIcon( TQPixmap( options_piechart ) );

    TQAction *fileNewAction;
    TQAction *fileOpenAction;
    TQAction *fileSaveAction;
    TQAction *fileSaveAsAction;
    TQAction *fileSaveAsPixmapAction;
    TQAction *filePrintAction;
    TQAction *fileQuitAction;
    TQAction *optionsSetDataAction;
    TQAction *optionsSetFontAction;
    TQAction *optionsSetOptionsAction;

    fileNewAction = new TQAction(
            "New Chart", TQPixmap( file_new ),
            "&New", CTRL+Key_N, this, "new" );
    connect( fileNewAction, SIGNAL( activated() ), this, SLOT( fileNew() ) );

    fileOpenAction = new TQAction(
            "Open Chart", TQPixmap( file_open ),
            "&Open...", CTRL+Key_O, this, "open" );
    connect( fileOpenAction, SIGNAL( activated() ), this, SLOT( fileOpen() ) );

    fileSaveAction = new TQAction(
            "Save Chart", TQPixmap( file_save ),
            "&Save", CTRL+Key_S, this, "save" );
    connect( fileSaveAction, SIGNAL( activated() ), this, SLOT( fileSave() ) );

    fileSaveAsAction = new TQAction(
            "Save Chart As", TQPixmap( file_save ),
            "Save &As...", 0, this, "save as" );
    connect( fileSaveAsAction, SIGNAL( activated() ),
             this, SLOT( fileSaveAs() ) );

    fileSaveAsPixmapAction = new TQAction(
            "Save Chart As Bitmap", TQPixmap( file_save ),
            "Save As &Bitmap...", CTRL+Key_B, this, "save as bitmap" );
    connect( fileSaveAsPixmapAction, SIGNAL( activated() ),
             this, SLOT( fileSaveAsPixmap() ) );

    filePrintAction = new TQAction(
            "Print Chart", TQPixmap( file_print ),
            "&Print Chart...", CTRL+Key_P, this, "print chart" );
    connect( filePrintAction, SIGNAL( activated() ),
             this, SLOT( filePrint() ) );

    optionsSetDataAction = new TQAction(
            "Set Data", TQPixmap( options_setdata ),
            "Set &Data...", CTRL+Key_D, this, "set data" );
    connect( optionsSetDataAction, SIGNAL( activated() ),
             this, SLOT( optionsSetData() ) );


    TQActionGroup *chartGroup = new TQActionGroup( this ); // Connected later
    chartGroup->setExclusive( TRUE );

    optionsPieChartAction = new TQAction(
            "Pie Chart", TQPixmap( options_piechart ),
            "&Pie Chart", CTRL+Key_I, chartGroup, "pie chart" );
    optionsPieChartAction->setToggleAction( TRUE );

    optionsHorizontalBarChartAction = new TQAction(
            "Horizontal Bar Chart", TQPixmap( options_horizontalbarchart ),
            "&Horizontal Bar Chart", CTRL+Key_H, chartGroup,
            "horizontal bar chart" );
    optionsHorizontalBarChartAction->setToggleAction( TRUE );

    optionsVerticalBarChartAction = new TQAction(
            "Vertical Bar Chart", TQPixmap( options_verticalbarchart ),
            "&Vertical Bar Chart", CTRL+Key_V, chartGroup, "Vertical bar chart" );
    optionsVerticalBarChartAction->setToggleAction( TRUE );


    optionsSetFontAction = new TQAction(
            "Set Font", TQPixmap( options_setfont ),
            "Set &Font...", CTRL+Key_F, this, "set font" );
    connect( optionsSetFontAction, SIGNAL( activated() ),
             this, SLOT( optionsSetFont() ) );

    optionsSetOptionsAction = new TQAction(
            "Set Options", TQPixmap( options_setoptions ),
            "Set &Options...", 0, this, "set options" );
    connect( optionsSetOptionsAction, SIGNAL( activated() ),
             this, SLOT( optionsSetOptions() ) );

    fileQuitAction = new TQAction( "Quit", "&Quit", CTRL+Key_Q, this, "quit" );
    connect( fileQuitAction, SIGNAL( activated() ), this, SLOT( fileQuit() ) );


    TQToolBar* fileTools = new TQToolBar( this, "file operations" );
    fileTools->setLabel( "File Operations" );
    fileNewAction->addTo( fileTools );
    fileOpenAction->addTo( fileTools );
    fileSaveAction->addTo( fileTools );
    fileTools->addSeparator();
    filePrintAction->addTo( fileTools );

    TQToolBar *optionsTools = new TQToolBar( this, "options operations" );
    optionsTools->setLabel( "Options Operations" );
    optionsSetDataAction->addTo( optionsTools );
    optionsTools->addSeparator();
    optionsPieChartAction->addTo( optionsTools );
    optionsHorizontalBarChartAction->addTo( optionsTools );
    optionsVerticalBarChartAction->addTo( optionsTools );
    optionsTools->addSeparator();
    optionsSetFontAction->addTo( optionsTools );
    optionsTools->addSeparator();
    optionsSetOptionsAction->addTo( optionsTools );

    fileMenu = new TQPopupMenu( this );
    menuBar()->insertItem( "&File", fileMenu );
    fileNewAction->addTo( fileMenu );
    fileOpenAction->addTo( fileMenu );
    fileSaveAction->addTo( fileMenu );
    fileSaveAsAction->addTo( fileMenu );
    fileMenu->insertSeparator();
    fileSaveAsPixmapAction->addTo( fileMenu );
    fileMenu->insertSeparator();
    filePrintAction->addTo( fileMenu );
    fileMenu->insertSeparator();
    fileQuitAction->addTo( fileMenu );

    optionsMenu = new TQPopupMenu( this );
    menuBar()->insertItem( "&Options", optionsMenu );
    optionsSetDataAction->addTo( optionsMenu );
    optionsMenu->insertSeparator();
    optionsPieChartAction->addTo( optionsMenu );
    optionsHorizontalBarChartAction->addTo( optionsMenu );
    optionsVerticalBarChartAction->addTo( optionsMenu );
    optionsMenu->insertSeparator();
    optionsSetFontAction->addTo( optionsMenu );
    optionsMenu->insertSeparator();
    optionsSetOptionsAction->addTo( optionsMenu );

    menuBar()->insertSeparator();

    TQPopupMenu *helpMenu = new TQPopupMenu( this );
    menuBar()->insertItem( "&Help", helpMenu );
    helpMenu->insertItem( "&Help", this, SLOT(helpHelp()), Key_F1 );
    helpMenu->insertItem( "&About", this, SLOT(helpAbout()) );
    helpMenu->insertItem( "About &TQt", this, SLOT(helpAboutTQt()) );


    m_printer = 0;
    m_elements.resize( MAX_ELEMENTS );

    TQSettings settings;
    settings.insertSearchPath( TQSettings::Windows, WINDOWS_REGISTRY );
    int windowWidth = settings.readNumEntry( APP_KEY + "WindowWidth", 460 );
    int windowHeight = settings.readNumEntry( APP_KEY + "WindowHeight", 530 );
    int windowX = settings.readNumEntry( APP_KEY + "WindowX", -1 );
    int windowY = settings.readNumEntry( APP_KEY + "WindowY", -1 );
    setChartType( ChartType(
            settings.readNumEntry( APP_KEY + "ChartType", int(PIE) ) ) );
    m_addValues = AddValuesType(
                    settings.readNumEntry( APP_KEY + "AddValues", int(NO) ));
    m_decimalPlaces = settings.readNumEntry( APP_KEY + "Decimals", 2 );
    m_font = TQFont( "Helvetica", 18, TQFont::Bold );
    m_font.fromString(
            settings.readEntry( APP_KEY + "Font", m_font.toString() ) );
    for ( int i = 0; i < MAX_RECENTFILES; ++i ) {
        TQString filename = settings.readEntry( APP_KEY + "File" +
                                               TQString::number( i + 1 ) );
        if ( !filename.isEmpty() )
            m_recentFiles.push_back( filename );
    }
    if ( m_recentFiles.count() )
        updateRecentFilesMenu();


    // Connect *after* we've set the chart type on so we don't call
    // drawElements() prematurely.
    connect( chartGroup, SIGNAL( selected(TQAction*) ),
             this, SLOT( updateChartType(TQAction*) ) );

    resize( windowWidth, windowHeight );
    if ( windowX != -1 || windowY != -1 )
        move( windowX, windowY );

    m_canvas = new TQCanvas( this );
    m_canvas->resize( width(), height() );
    m_canvasView = new CanvasView( m_canvas, &m_elements, this );
    setCentralWidget( m_canvasView );
    m_canvasView->show();

    if ( !filename.isEmpty() )
        load( filename );
    else {
        init();
        m_elements[0].set( 20, red,    14, "Red" );
        m_elements[1].set( 70, cyan,    2, "Cyan",   darkGreen );
        m_elements[2].set( 35, blue,   11, "Blue" );
        m_elements[3].set( 55, yellow,  1, "Yellow", darkBlue );
        m_elements[4].set( 80, magenta, 1, "Magenta" );
        drawElements();
    }

    statusBar()->message( "Ready", 2000 );
}


ChartForm::~ChartForm()
{
    delete m_printer;
}


void ChartForm::init()
{
    setCaption( "Chart" );
    m_filename = TQString::null;
    m_changed = FALSE;

    m_elements[0]  = Element( Element::INVALID, red );
    m_elements[1]  = Element( Element::INVALID, cyan );
    m_elements[2]  = Element( Element::INVALID, blue );
    m_elements[3]  = Element( Element::INVALID, yellow );
    m_elements[4]  = Element( Element::INVALID, green );
    m_elements[5]  = Element( Element::INVALID, magenta );
    m_elements[6]  = Element( Element::INVALID, darkYellow );
    m_elements[7]  = Element( Element::INVALID, darkRed );
    m_elements[8]  = Element( Element::INVALID, darkCyan );
    m_elements[9]  = Element( Element::INVALID, darkGreen );
    m_elements[10] = Element( Element::INVALID, darkMagenta );
    m_elements[11] = Element( Element::INVALID, darkBlue );
    for ( int i = 12; i < MAX_ELEMENTS; ++i ) {
        double x = (double(i) / MAX_ELEMENTS) * 360;
        int y = (int(x * 256) % 105) + 151;
        int z = ((i * 17) % 105) + 151;
        m_elements[i] = Element( Element::INVALID, TQColor( int(x), y, z, TQColor::Hsv ) );
    }
}

void ChartForm::closeEvent( TQCloseEvent * )
{
    fileQuit();
}


void ChartForm::fileNew()
{
    if ( okToClear() ) {
        init();
        drawElements();
    }
}


void ChartForm::fileOpen()
{
    if ( !okToClear() )
        return;

    TQString filename = TQFileDialog::getOpenFileName(
                            TQString::null, "Charts (*.cht)", this,
                            "file open", "Chart -- File Open" );
    if ( !filename.isEmpty() )
        load( filename );
    else
        statusBar()->message( "File Open abandoned", 2000 );
}


void ChartForm::fileSaveAs()
{
    TQString filename = TQFileDialog::getSaveFileName(
                            TQString::null, "Charts (*.cht)", this,
                            "file save as", "Chart -- File Save As" );
    if ( !filename.isEmpty() ) {
        int answer = 0;
        if ( TQFile::exists( filename ) )
            answer = TQMessageBox::warning(
                            this, "Chart -- Overwrite File",
                            TQString( "Overwrite\n\'%1\'?" ).
                                arg( filename ),
                            "&Yes", "&No", TQString::null, 1, 1 );
        if ( answer == 0 ) {
            m_filename = filename;
            updateRecentFiles( filename );
            fileSave();
            return;
        }
    }
    statusBar()->message( "Saving abandoned", 2000 );
}


void ChartForm::fileOpenRecent( int index )
{
    if ( !okToClear() )
        return;

    load( m_recentFiles[index] );
}


void ChartForm::updateRecentFiles( const TQString& filename )
{
    if ( m_recentFiles.find( filename ) != m_recentFiles.end() )
        return;

    m_recentFiles.push_back( filename );
    if ( m_recentFiles.count() > MAX_RECENTFILES )
        m_recentFiles.pop_front();

    updateRecentFilesMenu();
}


void ChartForm::updateRecentFilesMenu()
{
    for ( int i = 0; i < MAX_RECENTFILES; ++i ) {
        if ( fileMenu->findItem( i ) )
            fileMenu->removeItem( i );
        if ( i < int(m_recentFiles.count()) )
            fileMenu->insertItem( TQString( "&%1 %2" ).
                                    arg( i + 1 ).arg( m_recentFiles[i] ),
                                  this, SLOT( fileOpenRecent(int) ),
                                  0, i );
    }
}


void ChartForm::fileQuit()
{
    if ( okToClear() ) {
        saveOptions();
        tqApp->exit( 0 );
    }
}


bool ChartForm::okToClear()
{
    if ( m_changed ) {
        TQString msg;
        if ( m_filename.isEmpty() )
            msg = "Unnamed chart ";
        else
            msg = TQString( "Chart '%1'\n" ).arg( m_filename );
        msg += "has been changed.";

        int x = TQMessageBox::information( this, "Chart -- Unsaved Changes",
                                          msg, "&Save", "Cancel", "&Abandon",
                                          0, 1 );
        switch( x ) {
            case 0: // Save
                fileSave();
                break;
            case 1: // Cancel
            default:
                return FALSE;
            case 2: // Abandon
                break;
        }
    }

    return TRUE;
}


void ChartForm::saveOptions()
{
    TQSettings settings;
    settings.insertSearchPath( TQSettings::Windows, WINDOWS_REGISTRY );
    settings.writeEntry( APP_KEY + "WindowWidth", width() );
    settings.writeEntry( APP_KEY + "WindowHeight", height() );
    settings.writeEntry( APP_KEY + "WindowX", x() );
    settings.writeEntry( APP_KEY + "WindowY", y() );
    settings.writeEntry( APP_KEY + "ChartType", int(m_chartType) );
    settings.writeEntry( APP_KEY + "AddValues", int(m_addValues) );
    settings.writeEntry( APP_KEY + "Decimals", m_decimalPlaces );
    settings.writeEntry( APP_KEY + "Font", m_font.toString() );
    for ( int i = 0; i < int(m_recentFiles.count()); ++i )
        settings.writeEntry( APP_KEY + "File" + TQString::number( i + 1 ),
                             m_recentFiles[i] );
}


void ChartForm::optionsSetData()
{
    SetDataForm *setDataForm = new SetDataForm( &m_elements, m_decimalPlaces, this );
    if ( setDataForm->exec() ) {
        m_changed = TRUE;
        drawElements();
    }
    delete setDataForm;
}


void ChartForm::setChartType( ChartType chartType )
{
    m_chartType = chartType;
    switch ( m_chartType ) {
        case PIE:
            optionsPieChartAction->setOn( TRUE );
            break;
        case VERTICAL_BAR:
            optionsVerticalBarChartAction->setOn( TRUE );
            break;
        case HORIZONTAL_BAR:
            optionsHorizontalBarChartAction->setOn( TRUE );
            break;
    }
}


void ChartForm::updateChartType( TQAction *action )
{
    if ( action == optionsPieChartAction ) {
        m_chartType = PIE;
    }
    else if ( action == optionsHorizontalBarChartAction ) {
        m_chartType = HORIZONTAL_BAR;
    }
    else if ( action == optionsVerticalBarChartAction ) {
        m_chartType = VERTICAL_BAR;
    }

    drawElements();
}


void ChartForm::optionsSetFont()
{
    bool ok;
    TQFont font = TQFontDialog::getFont( &ok, m_font, this );
    if ( ok ) {
        m_font = font;
        drawElements();
    }
}


void ChartForm::optionsSetOptions()
{
    OptionsForm *optionsForm = new OptionsForm( this );
    optionsForm->chartTypeComboBox->setCurrentItem( m_chartType );
    optionsForm->setFont( m_font );
    switch ( m_addValues ) {
        case NO:
            optionsForm->noRadioButton->setChecked( TRUE );
            break;
        case YES:
            optionsForm->yesRadioButton->setChecked( TRUE );
            break;
        case AS_PERCENTAGE:
            optionsForm->asPercentageRadioButton->setChecked( TRUE );
            break;
    }
    optionsForm->decimalPlacesSpinBox->setValue( m_decimalPlaces );
    if ( optionsForm->exec() ) {
        setChartType( ChartType(
                optionsForm->chartTypeComboBox->currentItem()) );
        m_font = optionsForm->font();
        if ( optionsForm->noRadioButton->isChecked() )
            m_addValues = NO;
        else if ( optionsForm->yesRadioButton->isChecked() )
            m_addValues = YES;
        else if ( optionsForm->asPercentageRadioButton->isChecked() )
            m_addValues = AS_PERCENTAGE;
        m_decimalPlaces = optionsForm->decimalPlacesSpinBox->value();
        drawElements();
    }
    delete optionsForm;
}


void ChartForm::helpHelp()
{
    statusBar()->message( "Help is not implemented yet", 2000 );
}


void ChartForm::helpAbout()
{
    TQMessageBox::about( this, "Chart -- About",
                        "<center><h1><font color=blue>Chart<font></h1></center>"
                        "<p>Chart your data with <i>chart</i>.</p>"
                        );
}


void ChartForm::helpAboutTQt()
{
    TQMessageBox::aboutTQt( this, "Chart -- About TQt" );
}


Main:

#include <ntqapplication.h>
#include "chartform.h"


int main( int argc, char *argv[] )
{
    TQApplication app( argc, argv );

    TQString filename;
    if ( app.argc() > 1 ) {
        filename = app.argv()[1];
        if ( !filename.endsWith( ".cht" ) )
            filename = TQString::null;
    }

    ChartForm *cf = new ChartForm( filename );
    app.setMainWidget( cf );
    cf->show();

    return app.exec();
}

See also Step-by-step Examples.


Copyright © 2007 TrolltechTrademarks
TQt 3.3.8