<commit_before>/***************************************************************************
 *   Copyright (c) Yorik van Havre          (yorik@uncreated.net) 2015     *
 *   Copyright (c) 2016 WandererFan    (wandererfan@gmail.com)             *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"

#ifndef _PreComp_
# include <sstream>
#endif

#include <iomanip>

#include <App/Application.h>
#include <App/Property.h>
#include <App/PropertyStandard.h>
#include <App/PropertyUnits.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/FileInfo.h>
#include <Base/Parameter.h>

#include "DrawViewSpreadsheet.h"

#include <Mod/Spreadsheet/App/Cell.h>
#include <Mod/Spreadsheet/App/Sheet.h>

using namespace TechDraw;
using namespace std;

//===========================================================================
// DrawViewSpreadsheet
//===========================================================================

PROPERTY_SOURCE(TechDraw::DrawViewSpreadsheet, TechDraw::DrawViewSymbol)

DrawViewSpreadsheet::DrawViewSpreadsheet(void)
{
    static const char *vgroup = "Spreadsheet";

    Base::Reference<ParameterGrp> hGrp = App::GetApplication().GetUserParameter()
        .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Labels");
    std::string fontName = hGrp->GetASCII("LabelFont", "Sans");

    ADD_PROPERTY_TYPE(Source ,(0),vgroup,App::Prop_None,"Spreadsheet to view");
    ADD_PROPERTY_TYPE(CellStart ,("A1"),vgroup,App::Prop_None,"The top left cell of the range to display");
    ADD_PROPERTY_TYPE(CellEnd ,("B2"),vgroup,App::Prop_None,"The bottom right cell of the range to display");
    ADD_PROPERTY_TYPE(Font ,((fontName.c_str())),vgroup,App::Prop_None,"The name of the font to use");
    ADD_PROPERTY_TYPE(TextColor,(0.0f,0.0f,0.0f),vgroup,App::Prop_None,"The default color of the text and lines");
    ADD_PROPERTY_TYPE(TextSize,(12.0),vgroup,App::Prop_None,"The size of the text");
    ADD_PROPERTY_TYPE(LineWidth,(0.35),vgroup,App::Prop_None,"The thickness of the cell lines");
    //ADD_PROPERTY_TYPE(Symbol,(""),vgroup,App::Prop_Hidden,"The SVG image of this spreadsheet");

    EditableTexts.setStatus(App::Property::Hidden,true);

}

DrawViewSpreadsheet::~DrawViewSpreadsheet()
{
}

void DrawViewSpreadsheet::onChanged(const App::Property* prop)
{
    if (!isRestoring()) {
        if (prop == &Source ||
            prop == &CellStart ||
            prop == &CellEnd ||
            prop == &Font ||
            prop == &TextSize ||
            prop == &TextColor ||
            prop == &LineWidth) {
            try {
                App::DocumentObjectExecReturn *ret = recompute();
                delete ret;
            }
            catch (...) {
            }
        }
    }
    TechDraw::DrawView::onChanged(prop);
}

App::DocumentObjectExecReturn *DrawViewSpreadsheet::execute(void)
{
    App::DocumentObject* link = Source.getValue();
    std::string scellstart = CellStart.getValue();
    std::string scellend = CellEnd.getValue();
    if (!link)
        return new App::DocumentObjectExecReturn("No spreadsheet linked");
    if (!link->getTypeId().isDerivedFrom(Spreadsheet::Sheet::getClassTypeId()))
        return new App::DocumentObjectExecReturn("The linked object is not a spreadsheet");
    if ( (scellstart.empty()) || (scellend.empty()) )
        return new App::DocumentObjectExecReturn("Empty cell value");

    Symbol.setValue(getSheetImage());

    requestPaint();
    return TechDraw::DrawView::execute();
}

std::vector<std::string> DrawViewSpreadsheet::getAvailColumns(void)
{
    // build a list of available colums: A, B, C, ... AA, AB, ... ZY, ZZ.
    std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    std::vector<std::string> availcolumns;
    for (int i=0; i<26; ++i) {
        std::stringstream s;
        s << alphabet[i];
        availcolumns.push_back(s.str());
    }
    for (int i=0; i<26; ++i) {
        for (int j=0; i<26; ++i) {
            std::stringstream s;
            s << alphabet[i] << alphabet[j];
            availcolumns.push_back(s.str());
        }
    }
    return availcolumns;
}

//note: newlines need to be double escaped for python, but single for C++
std::string DrawViewSpreadsheet::getSVGHead(void)
{
    std::string head = std::string("<svg\n") +
                       std::string("	xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n") +
                       std::string("	xmlns:freecad=\"http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace\">\n");
    return head;
}

std::string DrawViewSpreadsheet::getSVGTail(void)
{
    std::string tail = "\n</svg>";
    return tail;
}

std::string DrawViewSpreadsheet::getSheetImage(void)
{
    std::stringstream result;

    App::DocumentObject* link = Source.getValue();
    std::string scellstart = CellStart.getValue();
    std::string scellend = CellEnd.getValue();

    std::vector<std::string> availcolumns = getAvailColumns();

    // build rows range and columns range
    std::vector<std::string> columns;
    std::vector<int> rows;
    try {
        for (unsigned int i=0; i<scellstart.length(); ++i) {
            if (isdigit(scellstart[i])) {
                columns.push_back(scellstart.substr(0,i));
                rows.push_back(std::atoi(scellstart.substr(i,scellstart.length()-1).c_str()));
            }
        }
        for (unsigned int i=0; i<scellend.length(); ++i) {
            if (isdigit(scellend[i])) {
                std::string startcol = columns.back();
                std::string endcol = scellend.substr(0,i);
                bool valid = false;
                for (std::vector<std::string>::const_iterator j = availcolumns.begin(); j != availcolumns.end(); ++j) {
                    if ( (*j) == startcol) {
                        if ( (*j) != endcol) {
                            valid = true;
                        }
                    } else {
                        if (valid) {
                            if ( (*j) == endcol) {
                                columns.push_back((*j));
                                valid = false;
                            } else {
                                columns.push_back((*j));
                            }
                        }
                    }
                }
                int endrow = std::atoi(scellend.substr(i,scellend.length()-1).c_str());
                for (int j=rows.back()+1; j<=endrow; ++j) {
                    rows.push_back(j);
                }
            }
        }
    } catch (std::exception) {
        Base::Console().Error("Invalid cell range for %s\n",getNameInDocument());
        return result.str();
    }

    // create the containing group
    std::string ViewName = Label.getValue();

    result << getSVGHead();

    App::Color c = TextColor.getValue();
    result  << "<g id=\"" << ViewName << "\">" << endl;

    // fill the cells
    float rowoffset = 0.0;
    float coloffset = 0.0;
    float cellheight = 100;
    float cellwidth = 100;
    std::string celltext;
    Spreadsheet::Sheet* sheet = static_cast<Spreadsheet::Sheet*>(link);
    std::vector<std::string> skiplist;
    for (std::vector<std::string>::const_iterator col = columns.begin(); col != columns.end(); ++col) {
        // create a group for each column
        result << "  <g id=\"" << ViewName << "_col" << (*col) << "\">" << endl;
        for (std::vector<int>::const_iterator row = rows.begin(); row != rows.end(); ++row) {
            // get cell size
            std::stringstream srow;
            srow << (*row);
            App::CellAddress address((*col) + srow.str());
            cellwidth = sheet->getColumnWidth(address.col());
            cellheight = sheet->getRowHeight(address.row());
            celltext = "";
            // get the text
            App::Property* prop = sheet->getPropertyByName(address.toString().c_str());
            std::stringstream field;
            if (prop != 0) {
                if (prop->isDerivedFrom((App::PropertyQuantity::getClassTypeId())))
                    field << static_cast<App::PropertyQuantity*>(prop)->getValue();
                else if (prop->isDerivedFrom((App::PropertyFloat::getClassTypeId())))
                    field << static_cast<App::PropertyFloat*>(prop)->getValue();
                else if (prop->isDerivedFrom((App::PropertyString::getClassTypeId())))
                    field << static_cast<App::PropertyString*>(prop)->getValue();
                else
                    assert(0);
                celltext = field.str();
            }
            // get colors, style, alignment and span
            int alignment = 0;
            std::string bcolor = "none";
            std::string fcolor = c.asCSSString();
            std::string textstyle = "";
            Spreadsheet::Cell* cell = sheet->getCell(address);
            if (cell) {
                App::Color f,b;
                std::set<std::string> st;
                int colspan, rowspan;
                if (cell->getBackground(b)) {
                    bcolor = b.asCSSString();
                }
                if (cell->getForeground(f)) {
                    fcolor = f.asCSSString();
                }
                if (cell->getStyle(st)) {
                    for (std::set<std::string>::const_iterator i = st.begin(); i != st.end(); ++i) {
                         if ((*i) == "bold")
                            textstyle = textstyle + "font-weight: bold; ";
                        else if ((*i) == "italic")
                            textstyle = textstyle + "font-style: italic; ";
                        else if ((*i) == "underline")
                            textstyle = textstyle + "text-decoration: underline; ";
                    }
                }
                if (cell->getSpans(rowspan,colspan)) {
                    for (int i=0; i<colspan; ++i) {
                        for (int j=0; j<rowspan; ++j) {
                            App::CellAddress nextcell(address.row()+j,address.col()+i);
                            if (i > 0)
                                cellwidth = cellwidth + sheet->getColumnWidth(nextcell.col());
                            if (j > 0)
                                cellheight = cellheight + sheet->getRowHeight(nextcell.row());
                            if ( (i > 0) || (j > 0) )
                                skiplist.push_back(nextcell.toString());
                        }
                    }
                }
                cell->getAlignment(alignment);
            }
            // skip cell if found in skiplist
            if (std::find(skiplist.begin(), skiplist.end(), address.toString()) == skiplist.end()) {
                result << "    <rect x=\"" << coloffset << "\" y=\"" << rowoffset << "\" width=\"" << cellwidth
                       << "\" height=\"" << cellheight << "\" style=\"fill:" << bcolor << ";stroke-width:"
                       << LineWidth.getValue()/getScale() << ";stroke:" << c.asCSSString() << ";\" />" << endl;
                if (alignment & Spreadsheet::Cell::ALIGNMENT_LEFT)
                    result << "    <text style=\"" << textstyle << "\" x=\"" << coloffset + TextSize.getValue()/2 << "\" y=\"" << rowoffset + 0.75 * cellheight << "\" font-family=\"" ;
                if (alignment & Spreadsheet::Cell::ALIGNMENT_HCENTER)
                    result << "    <text text-anchor=\"middle\" style=\"" << textstyle << "\" x=\"" << coloffset + cellwidth/2 << "\" y=\"" << rowoffset + 0.75 * cellheight << "\" font-family=\"" ;
                if (alignment & Spreadsheet::Cell::ALIGNMENT_RIGHT)
                    result << "    <text text-anchor=\"end\" style=\"" << textstyle << "\" x=\"" << coloffset + (cellwidth - TextSize.getValue()/2) << "\" y=\"" << rowoffset + 0.75 * cellheight << "\" font-family=\"" ;
                result << Font.getValue() << "\"" << " font-size=\"" << TextSize.getValue() << "\""
                       << " fill=\"" << fcolor << "\">" << celltext << "</text>" << endl;
            }
            rowoffset = rowoffset + cellheight;
        }
        result << "  </g>" << endl;
        rowoffset = 0.0;
        coloffset = coloffset + cellwidth;
    }

    // close the containing group
    result << "</g>" << endl;

    result << getSVGTail();

    return result.str();
}

// Python Drawing feature ---------------------------------------------------------

namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawViewSpreadsheetPython, TechDraw::DrawViewSpreadsheet)
template<> const char* TechDraw::DrawViewSpreadsheetPython::getViewProviderName(void) const {
    return "TechDrawGui::ViewProviderSpreadsheet";
}
/// @endcond

// explicit template instantiation
template class TechDrawExport FeaturePythonT<TechDraw::DrawViewSpreadsheet>;
}
<commit_msg>0003010: Spreadsheet -> Drawing, maximum rows<commit_after>/***************************************************************************
 *   Copyright (c) Yorik van Havre          (yorik@uncreated.net) 2015     *
 *   Copyright (c) 2016 WandererFan    (wandererfan@gmail.com)             *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"

#ifndef _PreComp_
# include <sstream>
#endif

#include <iomanip>

#include <App/Application.h>
#include <App/Property.h>
#include <App/PropertyStandard.h>
#include <App/PropertyUnits.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/FileInfo.h>
#include <Base/Parameter.h>

#include "DrawViewSpreadsheet.h"

#include <Mod/Spreadsheet/App/Cell.h>
#include <Mod/Spreadsheet/App/Sheet.h>

using namespace TechDraw;
using namespace std;

//===========================================================================
// DrawViewSpreadsheet
//===========================================================================

PROPERTY_SOURCE(TechDraw::DrawViewSpreadsheet, TechDraw::DrawViewSymbol)

DrawViewSpreadsheet::DrawViewSpreadsheet(void)
{
    static const char *vgroup = "Spreadsheet";

    Base::Reference<ParameterGrp> hGrp = App::GetApplication().GetUserParameter()
        .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Labels");
    std::string fontName = hGrp->GetASCII("LabelFont", "Sans");

    ADD_PROPERTY_TYPE(Source ,(0),vgroup,App::Prop_None,"Spreadsheet to view");
    ADD_PROPERTY_TYPE(CellStart ,("A1"),vgroup,App::Prop_None,"The top left cell of the range to display");
    ADD_PROPERTY_TYPE(CellEnd ,("B2"),vgroup,App::Prop_None,"The bottom right cell of the range to display");
    ADD_PROPERTY_TYPE(Font ,((fontName.c_str())),vgroup,App::Prop_None,"The name of the font to use");
    ADD_PROPERTY_TYPE(TextColor,(0.0f,0.0f,0.0f),vgroup,App::Prop_None,"The default color of the text and lines");
    ADD_PROPERTY_TYPE(TextSize,(12.0),vgroup,App::Prop_None,"The size of the text");
    ADD_PROPERTY_TYPE(LineWidth,(0.35),vgroup,App::Prop_None,"The thickness of the cell lines");
    //ADD_PROPERTY_TYPE(Symbol,(""),vgroup,App::Prop_Hidden,"The SVG image of this spreadsheet");

    EditableTexts.setStatus(App::Property::Hidden,true);

}

DrawViewSpreadsheet::~DrawViewSpreadsheet()
{
}

void DrawViewSpreadsheet::onChanged(const App::Property* prop)
{
    if (!isRestoring()) {
        if (prop == &Source ||
            prop == &CellStart ||
            prop == &CellEnd ||
            prop == &Font ||
            prop == &TextSize ||
            prop == &TextColor ||
            prop == &LineWidth) {
            try {
                App::DocumentObjectExecReturn *ret = recompute();
                delete ret;
            }
            catch (...) {
            }
        }
    }
    TechDraw::DrawView::onChanged(prop);
}

App::DocumentObjectExecReturn *DrawViewSpreadsheet::execute(void)
{
    App::DocumentObject* link = Source.getValue();
    std::string scellstart = CellStart.getValue();
    std::string scellend = CellEnd.getValue();
    if (!link)
        return new App::DocumentObjectExecReturn("No spreadsheet linked");
    if (!link->getTypeId().isDerivedFrom(Spreadsheet::Sheet::getClassTypeId()))
        return new App::DocumentObjectExecReturn("The linked object is not a spreadsheet");
    if ( (scellstart.empty()) || (scellend.empty()) )
        return new App::DocumentObjectExecReturn("Empty cell value");

    Symbol.setValue(getSheetImage());

    requestPaint();
    return TechDraw::DrawView::execute();
}

std::vector<std::string> DrawViewSpreadsheet::getAvailColumns(void)
{
    // build a list of available colums: A, B, C, ... AA, AB, ... ZY, ZZ.
    std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    std::vector<std::string> availcolumns;
    for (int i=0; i<26; ++i) {
        std::stringstream s;
        s << alphabet[i];
        availcolumns.push_back(s.str());
    }
    for (int i=0; i<26; ++i) {
        for (int j=0; i<26; ++i) {
            std::stringstream s;
            s << alphabet[i] << alphabet[j];
            availcolumns.push_back(s.str());
        }
    }
    return availcolumns;
}

//note: newlines need to be double escaped for python, but single for C++
std::string DrawViewSpreadsheet::getSVGHead(void)
{
    std::string head = std::string("<svg\n") +
                       std::string("	xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n") +
                       std::string("	xmlns:freecad=\"http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace\">\n");
    return head;
}

std::string DrawViewSpreadsheet::getSVGTail(void)
{
    std::string tail = "\n</svg>";
    return tail;
}

std::string DrawViewSpreadsheet::getSheetImage(void)
{
    std::stringstream result;

    App::DocumentObject* link = Source.getValue();
    std::string scellstart = CellStart.getValue();
    std::string scellend = CellEnd.getValue();

    std::vector<std::string> availcolumns = getAvailColumns();

    // build rows range and columns range
    std::vector<std::string> columns;
    std::vector<int> rows;
    try {
        for (unsigned int i=0; i<scellstart.length(); ++i) {
            if (isdigit(scellstart[i])) {
                columns.push_back(scellstart.substr(0,i));
                rows.push_back(std::atoi(scellstart.substr(i,scellstart.length()-1).c_str()));
            }
        }
        for (unsigned int i=0; i<scellend.length(); ++i) {
            if (isdigit(scellend[i])) {
                std::string startcol = columns.back();
                std::string endcol = scellend.substr(0,i);
                bool valid = false;
                for (std::vector<std::string>::const_iterator j = availcolumns.begin(); j != availcolumns.end(); ++j) {
                    if ( (*j) == startcol) {
                        if ( (*j) != endcol) {
                            valid = true;
                        }
                    } else {
                        if (valid) {
                            if ( (*j) == endcol) {
                                columns.push_back((*j));
                                valid = false;
                            } else {
                                columns.push_back((*j));
                            }
                        }
                    }
                }
                int endrow = std::atoi(scellend.substr(i,scellend.length()-1).c_str());
                for (int j=rows.back()+1; j<=endrow; ++j) {
                    rows.push_back(j);
                }
            }
        }
    } catch (std::exception) {
        Base::Console().Error("Invalid cell range for %s\n",getNameInDocument());
        return result.str();
    }

    // create the containing group
    std::string ViewName = Label.getValue();

    result << getSVGHead();

    App::Color c = TextColor.getValue();
    result  << "<g id=\"" << ViewName << "\">" << endl;

    // fill the cells
    float rowoffset = 0.0;
    float coloffset = 0.0;
    float cellheight = 100;
    float cellwidth = 100;
    std::string celltext;
    Spreadsheet::Sheet* sheet = static_cast<Spreadsheet::Sheet*>(link);
    std::vector<std::string> skiplist;
    for (std::vector<std::string>::const_iterator col = columns.begin(); col != columns.end(); ++col) {
        // create a group for each column
        result << "  <g id=\"" << ViewName << "_col" << (*col) << "\">" << endl;
        for (std::vector<int>::const_iterator row = rows.begin(); row != rows.end(); ++row) {
            // get cell size
            std::stringstream srow;
            srow << (*row);
            App::CellAddress address((*col) + srow.str());
            cellwidth = sheet->getColumnWidth(address.col());
            cellheight = sheet->getRowHeight(address.row());
            celltext = "";
            // get the text
            App::Property* prop = sheet->getPropertyByName(address.toString().c_str());
            std::stringstream field;
            if (prop != 0) {
                if (prop->isDerivedFrom((App::PropertyQuantity::getClassTypeId())))
                    field << static_cast<App::PropertyQuantity*>(prop)->getValue();
                else if (prop->isDerivedFrom((App::PropertyFloat::getClassTypeId())))
                    field << static_cast<App::PropertyFloat*>(prop)->getValue();
                else if (prop->isDerivedFrom((App::PropertyString::getClassTypeId())))
                    field << static_cast<App::PropertyString*>(prop)->getValue();
                else
                    assert(0);
                celltext = field.str();
            }
            // get colors, style, alignment and span
            int alignment = 0;
            std::string bcolor = "none";
            std::string fcolor = c.asCSSString();
            std::string textstyle = "";
            Spreadsheet::Cell* cell = sheet->getCell(address);
            if (cell) {
                App::Color f,b;
                std::set<std::string> st;
                int colspan, rowspan;
                if (cell->getBackground(b)) {
                    bcolor = b.asCSSString();
                }
                if (cell->getForeground(f)) {
                    fcolor = f.asCSSString();
                }
                if (cell->getStyle(st)) {
                    for (std::set<std::string>::const_iterator i = st.begin(); i != st.end(); ++i) {
                         if ((*i) == "bold")
                            textstyle = textstyle + "font-weight: bold; ";
                        else if ((*i) == "italic")
                            textstyle = textstyle + "font-style: italic; ";
                        else if ((*i) == "underline")
                            textstyle = textstyle + "text-decoration: underline; ";
                    }
                }
                if (cell->getSpans(rowspan,colspan)) {
                    for (int i=0; i<colspan; ++i) {
                        for (int j=0; j<rowspan; ++j) {
                            App::CellAddress nextcell(address.row()+j,address.col()+i);
                            if (i > 0)
                                cellwidth = cellwidth + sheet->getColumnWidth(nextcell.col());
                            if (j > 0)
                                cellheight = cellheight + sheet->getRowHeight(nextcell.row());
                            if ( (i > 0) || (j > 0) )
                                skiplist.push_back(nextcell.toString());
                        }
                    }
                }
                cell->getAlignment(alignment);
            }
            // skip cell if found in skiplist
            if (std::find(skiplist.begin(), skiplist.end(), address.toString()) == skiplist.end()) {
                result << "    <rect x=\"" << coloffset << "\" y=\"" << rowoffset << "\" width=\"" << cellwidth
                       << "\" height=\"" << cellheight << "\" style=\"fill:" << bcolor << ";stroke-width:"
                       << LineWidth.getValue()/getScale() << ";stroke:" << c.asCSSString() << ";\" />" << endl;
                if (alignment & Spreadsheet::Cell::ALIGNMENT_LEFT)
                    result << "    <text style=\"" << textstyle << "\" x=\"" << coloffset + TextSize.getValue()/2 << "\" y=\"" << rowoffset + 0.75 * cellheight << "\" font-family=\"" ;
                if (alignment & Spreadsheet::Cell::ALIGNMENT_HCENTER)
                    result << "    <text text-anchor=\"middle\" style=\"" << textstyle << "\" x=\"" << coloffset + cellwidth/2 << "\" y=\"" << rowoffset + 0.75 * cellheight << "\" font-family=\"" ;
                if (alignment & Spreadsheet::Cell::ALIGNMENT_RIGHT)
                    result << "    <text text-anchor=\"end\" style=\"" << textstyle << "\" x=\"" << coloffset + (cellwidth - TextSize.getValue()/2) << "\" y=\"" << rowoffset + 0.75 * cellheight << "\" font-family=\"" ;
                if ((alignment & Spreadsheet::Cell::ALIGNMENT_LEFT) ||
                    (alignment & Spreadsheet::Cell::ALIGNMENT_HCENTER) ||
                    (alignment & Spreadsheet::Cell::ALIGNMENT_RIGHT)) {
                    result << Font.getValue() << "\"" << " font-size=\"" << TextSize.getValue() << "\""
                           << " fill=\"" << fcolor << "\">" << celltext << "</text>" << endl;
                }
            }
            rowoffset = rowoffset + cellheight;
        }
        result << "  </g>" << endl;
        rowoffset = 0.0;
        coloffset = coloffset + cellwidth;
    }

    // close the containing group
    result << "</g>" << endl;

    result << getSVGTail();

    return result.str();
}

// Python Drawing feature ---------------------------------------------------------

namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawViewSpreadsheetPython, TechDraw::DrawViewSpreadsheet)
template<> const char* TechDraw::DrawViewSpreadsheetPython::getViewProviderName(void) const {
    return "TechDrawGui::ViewProviderSpreadsheet";
}
/// @endcond

// explicit template instantiation
template class TechDrawExport FeaturePythonT<TechDraw::DrawViewSpreadsheet>;
}
<|endoftext|>


<commit_before>#include "ContextAndroid.h"

using namespace std;
using namespace canvas;

AndroidSurface::AndroidSurface(AndroidCache * _cache, unsigned int _logical_width, unsigned int _logical_height, unsigned int _actual_width, unsigned int _actual_height, InternalFormat _format)
  : Surface(_logical_width, _logical_height, _actual_width, _actual_height, _format), cache(_cache), paint(_cache) {
  // creates an empty canvas
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "AndroidSurface widthheight constructor called");

  cache->resetCache();
  cache->initJava();

  //set bitmap config according to internalformat
  jobject argbObject;

  JNIEnv * env = cache->getJNIEnv();

  if (_format == LUMINANCE_ALPHA) {
    __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "setting imageformat to alpha 8");
    argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_alpha_8);
  } else if (_format == RGB565) {
    __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "setting imageformat to argb565");
    argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_rgb_565);
  } else {
    __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "setting imageformat to argb8888");
    argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_argb_8888);
  }

  bitmap = (jobject) env->NewGlobalRef(env->CallStaticObjectMethod(cache->bitmapClass, cache->bitmapCreateMethod, _actual_width, _actual_height, argbObject));
}

AndroidSurface::AndroidSurface(AndroidCache * _cache, const Image & image)
  : Surface(image.getWidth(), image.getHeight(), image.getWidth(), image.getHeight(), RGBA8), cache(_cache), paint(_cache) {
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "Surface Image constructor");

  cache->resetCache();
  cache->initJava();

  JNIEnv * env = cache->getJNIEnv();

  // creates a surface with width, height and contents from image
  bitmap = (jobject) env->NewGlobalRef(imageToBitmap(image));
}

AndroidSurface::AndroidSurface(AndroidCache * _cache, const std::string & filename)
  : Surface(0, 0, 0, 0, RGBA8), cache(_cache), paint(_cache) {
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "Surface filename constructor");

  cache->resetCache();
  cache->initJava();

  JNIEnv * env = cache->getJNIEnv();

  //Get inputStream from the picture(filename)
  jobject inputStream = env->CallObjectMethod(cache->getAssetManager(), cache->managerOpenMethod, env->NewStringUTF(filename.c_str()));

  //Create BitmapFactory options to make the created bitmap mutable straight away
  jobject factoryOptions = env->NewObject(cache->bitmapOptionsClass, cache->bitmapOptionsConstructor);
  env->SetBooleanField(factoryOptions, cache->optionsMutableField, JNI_TRUE);

  //Create a bitmap from the inputStream
  bitmap = (jobject) env->NewGlobalRef(env->CallStaticObjectMethod(cache->factoryClass, cache->factoryDecodeMethod2, inputStream, NULL, factoryOptions));

  int bitmapWidth = env->CallIntMethod(bitmap, cache->bitmapGetWidthMethod);
  int bitmapHeigth = env->CallIntMethod(bitmap, cache->bitmapGetHeightMethod);
  Surface::resize(bitmapWidth, bitmapHeigth, bitmapWidth, bitmapHeigth, RGBA8);
}

AndroidSurface::AndroidSurface(AndroidCache * _cache, const unsigned char * buffer, size_t size)
  : Surface(0, 0, 0, 0, RGBA8), cache(_cache), env(_env), paint(_cache) {

  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "AndrodiSurface constructor (buffer)  called");

  cache->resetCache();
  cache->initJava();

  JNIEnv * env = cache->getJNIEnv();

  int arraySize = size;

  __android_log_print(ANDROID_LOG_INFO, "Sometrik", "size = %i", size);

  jbyteArray array = env->NewByteArray(arraySize);
  env->SetByteArrayRegion(array, 0, arraySize, (const jbyte*) buffer);
  jclass thisClass = env->FindClass("android/graphics/BitmapFactory");
  jmethodID thisMethod = env->GetStaticMethodID(env->FindClass("android/graphics/BitmapFactory"), "decodeByteArray", "([BII)Landroid/graphics/Bitmap;");
  jobject firstBitmap = env->CallStaticObjectMethod(thisClass, thisMethod, array, 0, arraySize);

  //make this with factory options instead
  jobject argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_argb_8888);
  bitmap = (jobject) env->NewGlobalRef(env->CallObjectMethod(firstBitmap, cache->bitmapCopyMethod, argbObject, JNI_TRUE));

  int bitmapWidth = env->CallIntMethod(bitmap, cache->bitmapGetWidthMethod);
  int bitmapHeigth = env->CallIntMethod(bitmap, cache->bitmapGetHeightMethod);
  Surface::resize(bitmapWidth, bitmapHeigth, bitmapWidth, bitmapHeigth, RGBA8);

  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "AndrodiSurface constructor (buffer)  called");
}

jobject
ContextAndroid::imageToBitmap(const Image & _img) {
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", " ImageToBitmap called");

  const unsigned char * buf = _img.getData();

  int length;
  if (_img.getImageFormat() == ImageFormat::RGB565) {
    length = _img.getWidth() * _img.getHeight() * 2;
  } else if (_img.getImageFormat() == ImageFormat::RGB24 || _img.getImageFormat() == ImageFormat::RGB32) {
    length = _img.getWidth() * _img.getHeight() * 3;
  } else {
    length = _img.getWidth() * _img.getHeight() * 4;
  }

  __android_log_print(ANDROID_LOG_INFO, "Sometrik", "length = %i", length);

  JNIEnv * env = cache->getJNIEnv();

  jbyteArray jarray = env->NewByteArray(length);
  env->SetByteArrayRegion(jarray, 0, length, (jbyte*) (buf));

  jobject argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_argb_8888);
  jobject drawableBitmap = env->CallObjectMethod(cache->bitmapClass, cache->bitmapCreateMethod2, jarray, _img.getWidth(), _img.getHeight(), argbObject);

  return drawableBitmap;
}
<commit_msg>refactor size calculation<commit_after>#include "ContextAndroid.h"

using namespace std;
using namespace canvas;

AndroidSurface::AndroidSurface(AndroidCache * _cache, unsigned int _logical_width, unsigned int _logical_height, unsigned int _actual_width, unsigned int _actual_height, InternalFormat _format)
  : Surface(_logical_width, _logical_height, _actual_width, _actual_height, _format), cache(_cache), paint(_cache) {
  // creates an empty canvas
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "AndroidSurface widthheight constructor called");

  cache->resetCache();
  cache->initJava();

  //set bitmap config according to internalformat
  jobject argbObject;

  JNIEnv * env = cache->getJNIEnv();

  if (_format == LUMINANCE_ALPHA) {
    __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "setting imageformat to alpha 8");
    argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_alpha_8);
  } else if (_format == RGB565) {
    __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "setting imageformat to argb565");
    argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_rgb_565);
  } else {
    __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "setting imageformat to argb8888");
    argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_argb_8888);
  }

  bitmap = (jobject) env->NewGlobalRef(env->CallStaticObjectMethod(cache->bitmapClass, cache->bitmapCreateMethod, _actual_width, _actual_height, argbObject));
}

AndroidSurface::AndroidSurface(AndroidCache * _cache, const Image & image)
  : Surface(image.getWidth(), image.getHeight(), image.getWidth(), image.getHeight(), RGBA8), cache(_cache), paint(_cache) {
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "Surface Image constructor");

  cache->resetCache();
  cache->initJava();

  JNIEnv * env = cache->getJNIEnv();

  // creates a surface with width, height and contents from image
  bitmap = (jobject) env->NewGlobalRef(imageToBitmap(image));
}

AndroidSurface::AndroidSurface(AndroidCache * _cache, const std::string & filename)
  : Surface(0, 0, 0, 0, RGBA8), cache(_cache), paint(_cache) {
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "Surface filename constructor");

  cache->resetCache();
  cache->initJava();

  JNIEnv * env = cache->getJNIEnv();

  //Get inputStream from the picture(filename)
  jobject inputStream = env->CallObjectMethod(cache->getAssetManager(), cache->managerOpenMethod, env->NewStringUTF(filename.c_str()));

  //Create BitmapFactory options to make the created bitmap mutable straight away
  jobject factoryOptions = env->NewObject(cache->bitmapOptionsClass, cache->bitmapOptionsConstructor);
  env->SetBooleanField(factoryOptions, cache->optionsMutableField, JNI_TRUE);

  //Create a bitmap from the inputStream
  bitmap = (jobject) env->NewGlobalRef(env->CallStaticObjectMethod(cache->factoryClass, cache->factoryDecodeMethod2, inputStream, NULL, factoryOptions));

  int bitmapWidth = env->CallIntMethod(bitmap, cache->bitmapGetWidthMethod);
  int bitmapHeigth = env->CallIntMethod(bitmap, cache->bitmapGetHeightMethod);
  Surface::resize(bitmapWidth, bitmapHeigth, bitmapWidth, bitmapHeigth, RGBA8);
}

AndroidSurface::AndroidSurface(AndroidCache * _cache, const unsigned char * buffer, size_t size)
  : Surface(0, 0, 0, 0, RGBA8), cache(_cache), env(_env), paint(_cache) {

  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "AndrodiSurface constructor (buffer)  called");

  cache->resetCache();
  cache->initJava();

  JNIEnv * env = cache->getJNIEnv();

  int arraySize = size;

  __android_log_print(ANDROID_LOG_INFO, "Sometrik", "size = %i", size);

  jbyteArray array = env->NewByteArray(arraySize);
  env->SetByteArrayRegion(array, 0, arraySize, (const jbyte*) buffer);
  jclass thisClass = env->FindClass("android/graphics/BitmapFactory");
  jmethodID thisMethod = env->GetStaticMethodID(env->FindClass("android/graphics/BitmapFactory"), "decodeByteArray", "([BII)Landroid/graphics/Bitmap;");
  jobject firstBitmap = env->CallStaticObjectMethod(thisClass, thisMethod, array, 0, arraySize);

  //make this with factory options instead
  jobject argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_argb_8888);
  bitmap = (jobject) env->NewGlobalRef(env->CallObjectMethod(firstBitmap, cache->bitmapCopyMethod, argbObject, JNI_TRUE));

  int bitmapWidth = env->CallIntMethod(bitmap, cache->bitmapGetWidthMethod);
  int bitmapHeigth = env->CallIntMethod(bitmap, cache->bitmapGetHeightMethod);
  Surface::resize(bitmapWidth, bitmapHeigth, bitmapWidth, bitmapHeigth, RGBA8);

  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", "AndrodiSurface constructor (buffer)  called");
}

jobject
ContextAndroid::imageToBitmap(const Image & _img) {
  __android_log_print(ANDROID_LOG_VERBOSE, "Sometrik", " ImageToBitmap called");

  const unsigned char * buf = _img.getData();
  int length = _img.calculateOffset(1);

  __android_log_print(ANDROID_LOG_INFO, "Sometrik", "length = %i", length);

  JNIEnv * env = cache->getJNIEnv();

  jbyteArray jarray = env->NewByteArray(length);
  env->SetByteArrayRegion(jarray, 0, length, (jbyte*) (buf));

  jobject argbObject = env->GetStaticObjectField(cache->bitmapConfigClass, cache->field_argb_8888);
  jobject drawableBitmap = env->CallObjectMethod(cache->bitmapClass, cache->bitmapCreateMethod2, jarray, _img.getWidth(), _img.getHeight(), argbObject);

  return drawableBitmap;
}
<|endoftext|>


<commit_before>#include <Arduino.h>

#include "DollhousePanel.h"

#include <Adafruit_TLC59711.h>
#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal.h>
#include <SPI.h>
#include <Fsm.h>
#include <EnableInterrupt.h>

const char NUM_TLC59711 = 1;
const char TLC_DATA = 12;
const char TLC_CLK = 13;

const char PIXEL_COUNT = 3;
const char PIXEL_PIN = 8;

enum events {
  CHANGE_LIGHT_MODE,
  NEXT_ROOM,
  PREVIOUS_ROOM,
  RESET_ROOMS
};

// Lighting modes finite state machine
State state_lighting_mode(on_lighting_mode_enter, &on_lighting_mode_exit);
State state_party_mode(on_party_mode_enter, &on_party_mode_exit);
State state_nitelite_mode(on_nitelite_mode_enter, &on_nitelite_mode_exit);
State state_off_mode(on_off_mode_enter, &on_off_mode_exit);
Fsm modes(&state_off_mode);

enum Modes {
  LIGHTING_MODE,
  PARTY_MODE,
  NITELITE_MODE,
  OFF_MODE
};

String modeNames[] = {"Lighting", "Party", "Nitelite", "Off"};

// Rooms finite state machine
State state_all_rooms(on_all_enter, &on_all_exit);
State state_hall(on_hall_enter, &on_hall_exit);
State state_living_room(on_living_room_enter, &on_living_room_exit);
State state_kitchen(on_kitchen_enter, &on_kitchen_exit);
State state_bedroom(on_bedroom_enter, &on_bedroom_exit);
State state_bathroom(on_bathroom_enter, &on_bathroom_exit);
State state_attic(on_attic_enter, &on_attic_exit);
Fsm rooms(&state_all_rooms);

// LastROOM is included to make it easier to figure out the size of the enum
// for things like sizing the brightness state array
enum Rooms {
  ALL_ROOMS,
  LIVING_ROOM,
  HALL,
  KITCHEN,
  BEDROOM,
  BATHROOM,
  ATTIC,
  LastROOM
};

String roomNames[] = {"All", "Living", "Hall", "Kitchen", "Bedroom", "Bathroom", "Attic"};

// NeoPixels (for the attic & !!!PARTY MODE!!!)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

// PWM board (controls the room lights)
Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711, TLC_CLK, TLC_DATA);

// 16x2 LCD display
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

// Panel RGB LED pins
const char RED_PIN = 9;
const char GREEN_PIN = 10;
const char BLUE_PIN = 11;

int brightness = 90;
int deltaLevel = 30;
int minLevel = 0;
int maxLevel = 180;
int roomBrightness[LastROOM];
int currentRoom = ALL_ROOMS;
int currentMode = OFF_MODE;

int debounceDelay = 100;

void setup() {
  // Fire up the LCD display
  lcd.begin(16, 2);
  lcd.print("Doll house");
  lcd.setCursor(0,1);
  lcd.print("lighting!");

  pinMode(RED_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);

  // initialize the NeoPixel strand
  strip.begin();
  strip.show();

  // Initialize the PWM board
  tlc.begin();
  tlc.write();

  // set defualt room brightness
  setDefaultLightLevel();

  // enable interrupts on buttons
  // The button interface is a Smartmaker 5A5 (annoying, but it works)
  enableInterrupt(A0, handleButtonOne, FALLING);
  enableInterrupt(A1, handleButtonTwo, FALLING);
  enableInterrupt(A2, handleButtonThree, FALLING);
  enableInterrupt(A3, handleButtonFour, FALLING);
  enableInterrupt(A4, handleButtonFive, FALLING);

  // mode FSM transitions
  modes.add_transition(&state_off_mode, &state_lighting_mode, CHANGE_LIGHT_MODE, NULL);
  modes.add_transition(&state_lighting_mode, &state_party_mode, CHANGE_LIGHT_MODE, NULL);
  modes.add_transition(&state_party_mode, &state_nitelite_mode, CHANGE_LIGHT_MODE, NULL);
  modes.add_transition(&state_nitelite_mode, &state_off_mode, CHANGE_LIGHT_MODE, NULL);

  // rooms FSM transitions
  // looping "forward" through the rooms
  rooms.add_transition(&state_all_rooms, &state_hall, NEXT_ROOM, NULL);
  rooms.add_transition(&state_hall, &state_living_room, NEXT_ROOM, NULL);
  rooms.add_transition(&state_living_room, &state_kitchen, NEXT_ROOM, NULL);
  rooms.add_transition(&state_kitchen, &state_bedroom, NEXT_ROOM, NULL);
  rooms.add_transition(&state_bedroom, &state_bathroom, NEXT_ROOM, NULL);
  rooms.add_transition(&state_bathroom, &state_attic, NEXT_ROOM, NULL);
  rooms.add_transition(&state_attic, &state_all_rooms, NEXT_ROOM, NULL);

  // looping "backward" through the rooms
  rooms.add_transition(&state_all_rooms, &state_attic, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_attic, &state_bathroom, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_bathroom, &state_bedroom, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_bedroom, &state_kitchen, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_kitchen, &state_living_room, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_living_room, &state_hall, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_hall, &state_all_rooms, PREVIOUS_ROOM, NULL);

  // reseting to the default room (all rooms)
  rooms.add_transition(&state_hall, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_living_room, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_kitchen, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_bedroom, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_bathroom, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_attic, &state_all_rooms, RESET_ROOMS, NULL);
}

// ***** Button event handlers ***** //
void handleButtonOne() {
  lcd.clear();
  rooms.trigger(RESET_ROOMS);
  modes.trigger(CHANGE_LIGHT_MODE);
  delay(debounceDelay);
}

void handleButtonTwo() {
  setRoomBrightness(currentRoom, min(roomBrightness[currentRoom] + deltaLevel, maxLevel));
  printCurrentRoom();
  delay(debounceDelay);
}

void handleButtonThree() {
  lcd.clear();
  rooms.trigger(PREVIOUS_ROOM);
  delay(debounceDelay);
}

void handleButtonFour() {
  setRoomBrightness(currentRoom, max(roomBrightness[currentRoom] - deltaLevel, minLevel));
  printCurrentRoom();
  delay(debounceDelay);
}

void handleButtonFive() {
  lcd.clear();
  rooms.trigger(NEXT_ROOM);
  delay(debounceDelay);
}

// ***** helpers ***** //

void setRGBColor(int red, int green, int blue) {
  int myRed = constrain(red, 0, 255);
  int myGreen = constrain(green, 0, 255);
  int myBlue = constrain(blue, 0, 255);

  analogWrite(RED_PIN, myRed);
  analogWrite(GREEN_PIN, myGreen);
  analogWrite(BLUE_PIN, myBlue);
}

void setRoomBrightness(int room, int level) {
  setRGBColor(0,0,level);
  roomBrightness[room] = level;
  tlc.setPWM(room * 3, roomBrightness[room] * maxLevel);
  tlc.write();
}

void setDefaultLightLevel() {
  setRGBColor(0,0,brightness);
  for (int i = 0; i != LastROOM; i++) {
    roomBrightness[i] = brightness;
  }
}

void setCurrentMode(int mode) {
  currentMode = mode;
  printCurrentMode();
}

void printCurrentMode() {
  lcd.clear();
  lcd.print("Mode: ");
  lcd.print(modeNames[currentMode]);
}

void setCurrentRoom(int room) {
  currentRoom = room;
  setRGBColor(0,0,roomBrightness[room]);
  printCurrentRoom();
}

void printCurrentRoom() {
  lcd.clear();
  lcd.print("room: ");
  lcd.print(roomNames[currentRoom]);
  lcd.setCursor(0,1);
  lcd.print("brightness: ");
  lcd.print(roomBrightness[currentRoom]);
}

// ***** FSM event handlers ***** //

// ---- lighting mode states ---- //

void on_lighting_mode_enter(){
  setCurrentMode(LIGHTING_MODE);
}

void on_lighting_mode_exit(){

}

void on_party_mode_enter(){
  setCurrentMode(PARTY_MODE);
}

void on_party_mode_exit(){

}

void on_nitelite_mode_enter(){
  setCurrentMode(NITELITE_MODE);
}

void on_nitelite_mode_exit(){

}

void on_off_mode_enter(){
  setCurrentMode(OFF_MODE);
}

void on_off_mode_exit(){

}

// ---- room selection states ---- //
void on_all_enter() {
  setCurrentRoom(ALL_ROOMS);
}

void on_all_exit() {

}

void on_hall_enter() {
  setCurrentRoom(HALL);
}

void on_hall_exit() {

}

void on_living_room_enter() {
  setCurrentRoom(LIVING_ROOM);
}

void on_living_room_exit() {

}

void on_kitchen_enter() {
  setCurrentRoom(KITCHEN);
}

void on_kitchen_exit() {

}

void on_bathroom_enter() {
  setCurrentRoom(BATHROOM);
}

void on_bathroom_exit() {

}

void on_bedroom_enter() {
  setCurrentRoom(BEDROOM);
}

void on_bedroom_exit() {

}

void on_attic_enter() {
  setCurrentRoom(ATTIC);
}

void on_attic_exit() {

}

void loop() {
  // do nothing; everything is handled via FSM events
}
<commit_msg>Conform to arduino-fsm 2.2.0 API<commit_after>#include <Arduino.h>

#include "DollhousePanel.h"

#include <Adafruit_TLC59711.h>
#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal.h>
#include <SPI.h>
#include <Fsm.h>
#include <EnableInterrupt.h>

const char NUM_TLC59711 = 1;
const char TLC_DATA = 12;
const char TLC_CLK = 13;

const char PIXEL_COUNT = 3;
const char PIXEL_PIN = 8;

enum events {
  CHANGE_LIGHT_MODE,
  NEXT_ROOM,
  PREVIOUS_ROOM,
  RESET_ROOMS
};

// Lighting modes finite state machine
State state_lighting_mode(on_lighting_mode_enter, NULL, &on_lighting_mode_exit);
State state_party_mode(on_party_mode_enter, NULL, &on_party_mode_exit);
State state_nitelite_mode(on_nitelite_mode_enter, NULL, &on_nitelite_mode_exit);
State state_off_mode(on_off_mode_enter, NULL, &on_off_mode_exit);
Fsm modes(&state_off_mode);

enum Modes {
  LIGHTING_MODE,
  PARTY_MODE,
  NITELITE_MODE,
  OFF_MODE
};

String modeNames[] = {"Lighting", "Party", "Nitelite", "Off"};

// Rooms finite state machine
State state_all_rooms(on_all_enter, NULL, &on_all_exit);
State state_hall(on_hall_enter, NULL, &on_hall_exit);
State state_living_room(on_living_room_enter, NULL, &on_living_room_exit);
State state_kitchen(on_kitchen_enter, NULL, &on_kitchen_exit);
State state_bedroom(on_bedroom_enter, NULL, &on_bedroom_exit);
State state_bathroom(on_bathroom_enter, NULL, &on_bathroom_exit);
State state_attic(on_attic_enter, NULL, &on_attic_exit);
Fsm rooms(&state_all_rooms);

// LastROOM is included to make it easier to figure out the size of the enum
// for things like sizing the brightness state array
enum Rooms {
  ALL_ROOMS,
  LIVING_ROOM,
  HALL,
  KITCHEN,
  BEDROOM,
  BATHROOM,
  ATTIC,
  LastROOM
};

String roomNames[] = {"All", "Living", "Hall", "Kitchen", "Bedroom", "Bathroom", "Attic"};

// NeoPixels (for the attic & !!!PARTY MODE!!!)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

// PWM board (controls the room lights)
Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711, TLC_CLK, TLC_DATA);

// 16x2 LCD display
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

// Panel RGB LED pins
const char RED_PIN = 9;
const char GREEN_PIN = 10;
const char BLUE_PIN = 11;

int brightness = 90;
int deltaLevel = 30;
int minLevel = 0;
int maxLevel = 180;
int roomBrightness[LastROOM];
int currentRoom = ALL_ROOMS;
int currentMode = OFF_MODE;

int debounceDelay = 100;

void setup() {
  // Fire up the LCD display
  lcd.begin(16, 2);
  lcd.print("Doll house");
  lcd.setCursor(0,1);
  lcd.print("lighting!");

  pinMode(RED_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);

  // initialize the NeoPixel strand
  strip.begin();
  strip.show();

  // Initialize the PWM board
  tlc.begin();
  tlc.write();

  // set defualt room brightness
  setDefaultLightLevel();

  // enable interrupts on buttons
  // The button interface is a Smartmaker 5A5 (annoying, but it works)
  enableInterrupt(A0, handleButtonOne, FALLING);
  enableInterrupt(A1, handleButtonTwo, FALLING);
  enableInterrupt(A2, handleButtonThree, FALLING);
  enableInterrupt(A3, handleButtonFour, FALLING);
  enableInterrupt(A4, handleButtonFive, FALLING);

  // mode FSM transitions
  modes.add_transition(&state_off_mode, &state_lighting_mode, CHANGE_LIGHT_MODE, NULL);
  modes.add_transition(&state_lighting_mode, &state_party_mode, CHANGE_LIGHT_MODE, NULL);
  modes.add_transition(&state_party_mode, &state_nitelite_mode, CHANGE_LIGHT_MODE, NULL);
  modes.add_transition(&state_nitelite_mode, &state_off_mode, CHANGE_LIGHT_MODE, NULL);

  // rooms FSM transitions
  // looping "forward" through the rooms
  rooms.add_transition(&state_all_rooms, &state_hall, NEXT_ROOM, NULL);
  rooms.add_transition(&state_hall, &state_living_room, NEXT_ROOM, NULL);
  rooms.add_transition(&state_living_room, &state_kitchen, NEXT_ROOM, NULL);
  rooms.add_transition(&state_kitchen, &state_bedroom, NEXT_ROOM, NULL);
  rooms.add_transition(&state_bedroom, &state_bathroom, NEXT_ROOM, NULL);
  rooms.add_transition(&state_bathroom, &state_attic, NEXT_ROOM, NULL);
  rooms.add_transition(&state_attic, &state_all_rooms, NEXT_ROOM, NULL);

  // looping "backward" through the rooms
  rooms.add_transition(&state_all_rooms, &state_attic, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_attic, &state_bathroom, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_bathroom, &state_bedroom, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_bedroom, &state_kitchen, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_kitchen, &state_living_room, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_living_room, &state_hall, PREVIOUS_ROOM, NULL);
  rooms.add_transition(&state_hall, &state_all_rooms, PREVIOUS_ROOM, NULL);

  // reseting to the default room (all rooms)
  rooms.add_transition(&state_hall, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_living_room, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_kitchen, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_bedroom, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_bathroom, &state_all_rooms, RESET_ROOMS, NULL);
  rooms.add_transition(&state_attic, &state_all_rooms, RESET_ROOMS, NULL);
}

// ***** Button event handlers ***** //
void handleButtonOne() {
  lcd.clear();
  rooms.trigger(RESET_ROOMS);
  modes.trigger(CHANGE_LIGHT_MODE);
  delay(debounceDelay);
}

void handleButtonTwo() {
  setRoomBrightness(currentRoom, min(roomBrightness[currentRoom] + deltaLevel, maxLevel));
  printCurrentRoom();
  delay(debounceDelay);
}

void handleButtonThree() {
  lcd.clear();
  rooms.trigger(PREVIOUS_ROOM);
  delay(debounceDelay);
}

void handleButtonFour() {
  setRoomBrightness(currentRoom, max(roomBrightness[currentRoom] - deltaLevel, minLevel));
  printCurrentRoom();
  delay(debounceDelay);
}

void handleButtonFive() {
  lcd.clear();
  rooms.trigger(NEXT_ROOM);
  delay(debounceDelay);
}

// ***** helpers ***** //

void setRGBColor(int red, int green, int blue) {
  int myRed = constrain(red, 0, 255);
  int myGreen = constrain(green, 0, 255);
  int myBlue = constrain(blue, 0, 255);

  analogWrite(RED_PIN, myRed);
  analogWrite(GREEN_PIN, myGreen);
  analogWrite(BLUE_PIN, myBlue);
}

void setRoomBrightness(int room, int level) {
  setRGBColor(0,0,level);
  roomBrightness[room] = level;
  tlc.setPWM(room * 3, roomBrightness[room] * maxLevel);
  tlc.write();
}

void setDefaultLightLevel() {
  setRGBColor(0,0,brightness);
  for (int i = 0; i != LastROOM; i++) {
    roomBrightness[i] = brightness;
  }
}

void setCurrentMode(int mode) {
  currentMode = mode;
  printCurrentMode();
}

void printCurrentMode() {
  lcd.clear();
  lcd.print("Mode: ");
  lcd.print(modeNames[currentMode]);
}

void setCurrentRoom(int room) {
  currentRoom = room;
  setRGBColor(0,0,roomBrightness[room]);
  printCurrentRoom();
}

void printCurrentRoom() {
  lcd.clear();
  lcd.print("room: ");
  lcd.print(roomNames[currentRoom]);
  lcd.setCursor(0,1);
  lcd.print("brightness: ");
  lcd.print(roomBrightness[currentRoom]);
}

// ***** FSM event handlers ***** //

// ---- lighting mode states ---- //

void on_lighting_mode_enter(){
  setCurrentMode(LIGHTING_MODE);
}

void on_lighting_mode_exit(){

}

void on_party_mode_enter(){
  setCurrentMode(PARTY_MODE);
}

void on_party_mode_exit(){

}

void on_nitelite_mode_enter(){
  setCurrentMode(NITELITE_MODE);
}

void on_nitelite_mode_exit(){

}

void on_off_mode_enter(){
  setCurrentMode(OFF_MODE);
}

void on_off_mode_exit(){

}

// ---- room selection states ---- //
void on_all_enter() {
  setCurrentRoom(ALL_ROOMS);
}

void on_all_exit() {

}

void on_hall_enter() {
  setCurrentRoom(HALL);
}

void on_hall_exit() {

}

void on_living_room_enter() {
  setCurrentRoom(LIVING_ROOM);
}

void on_living_room_exit() {

}

void on_kitchen_enter() {
  setCurrentRoom(KITCHEN);
}

void on_kitchen_exit() {

}

void on_bathroom_enter() {
  setCurrentRoom(BATHROOM);
}

void on_bathroom_exit() {

}

void on_bedroom_enter() {
  setCurrentRoom(BEDROOM);
}

void on_bedroom_exit() {

}

void on_attic_enter() {
  setCurrentRoom(ATTIC);
}

void on_attic_exit() {

}

void loop() {
  // do nothing; everything is handled via FSM events.
  // We also don't need to call the ".run_machine" methods of
  // the FSMs as there are no "on_state" handlers or timed transitions
}
<|endoftext|>


<commit_before>#pragma once

#ifdef __has_include
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
#endif
#endif

#include <stdexcept>

namespace Hippocrates::Utility {

	class const_error : public std::logic_error {
		using std::logic_error::logic_error;
	};

	template<typename T>
	class FillableRef {
	public:
		FillableRef(const T& ref) : isConstRef{ true }, asPointer{ &ref } {}
		FillableRef(T& ref) : asPointer{ &ref } {}
		FillableRef(T&& value) : asValue{ std::forward<T>(value) } {}

		auto IsShared() const { return !asValue.has_value(); }

		const auto& operator*() const { return Get(); }
		auto& operator*() { return Get(); }

		const auto& Get() const {
			if (isConstRef)
				throw const_error("");
			return const_cast<FillableRef*>(this)->Get();
		}
		auto& Get() { return IsShared() ? *asPointer : *asValue; }

	private:
		std::optional<T> asValue;
		bool isConstRef = false;
		T* asPointer = nullptr;
	};

}<commit_msg>Sugar the syntax up<commit_after>#pragma once

#ifdef __has_include
#if __has_include(<optional>)
#include <optional>
#elif __has_include(<experimental/optional>)
	#include <experimental/optional>
	using optional = experimental::optional;
#endif
#endif

#include <stdexcept>

namespace Hippocrates::Utility {

	class const_error : public std::logic_error {
		using std::logic_error::logic_error;
	};

	template<typename T>
	class FillableRef {
	public:
		FillableRef(const T& ref) : isConstRef{ true }, asPointer{ &ref } {}
		FillableRef(T& ref) : asPointer{ &ref } {}
		FillableRef(T&& value) : asValue{ std::forward<T>(value) } {}

		auto IsShared() const { return !asValue.has_value(); }

		const auto& operator*() const { return Get(); }
		auto& operator*() { return Get(); }

		const auto& Get() const {
			if (isConstRef)
				throw const_error("");
			return const_cast<FillableRef*>(this)->Get();
		}
		auto& Get() { return IsShared() ? *asPointer : *asValue; }

	private:
		std::optional<T> asValue;
		bool isConstRef = false;
		T* asPointer = nullptr;
	};

}<|endoftext|>


<commit_before>#include "qpathedit.h"
#include <QLineEdit>
#include <QToolButton>
#include <QHBoxLayout>
#include <QCompleter>
#include <QValidator>
#include <QStandardPaths>
#include <QFileSystemModel>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QEvent>
#include <QTimer>
#include <QAction>
#include <QPainter>
#include <functional>
#include <QKeyEvent>
#include <dialogmaster.h>

//HELPER CLASSES

class PathValidator : public QValidator
{
public:
	PathValidator(QObject *parent);
	void setMode(QPathEdit::PathMode mode);
	void setAllowEmpty(bool allow);
	State validate(QString &text, int &) const override;
private:
	QPathEdit::PathMode mode;
	bool allowEmpty;
};

//QPATHEDIT IMPLEMENTATION

QPathEdit::QPathEdit(QWidget *parent, QPathEdit::Style style) :
	QPathEdit(ExistingFile, parent, style)
{}

QPathEdit::QPathEdit(QPathEdit::PathMode pathMode, QWidget *parent, QPathEdit::Style style) :
	QWidget(parent),
	edit(new QLineEdit(this)),
	pathCompleter(new QCompleter(this)),
	completerModel(new QFileSystemModel(this)),
	pathValidator(new PathValidator(this)),
	dialog(new QFileDialog(this)),
	currentValidPath(),
	wasPathValid(true),
	uiStyle(style),
	mode(ExistingFile),
	defaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)),
	allowEmpty(true),
	toolButton(new QToolButton(this)),
	dialogAction(new QAction(getDefaultIcon(), tr("Open File-Dialog"), this)),
	hasCustomIcon(false)
{
	//setup dialog
	dialog->setOptions(0);
	DialogMaster::masterDialog(dialog);
	setPathMode(pathMode);
	connect(dialog, &QFileDialog::fileSelected, this, &QPathEdit::dialogFileSelected);

	//setup completer
	completerModel->setRootPath(QStringLiteral(""));
	completerModel->setNameFilterDisables(false);
	connect(completerModel, &QFileSystemModel::directoryLoaded, pathCompleter, [this](QString){
		pathCompleter->complete();
	});
	pathCompleter->setModel(completerModel);

	//setup this
	QHBoxLayout *layout = new QHBoxLayout(this);
	layout->setContentsMargins(QMargins());
	layout->setSpacing(0);
	layout->addWidget(edit);
	layout->addWidget(toolButton);
	setLayout(layout);
	//setup lineedit
	edit->installEventFilter(this);
	edit->setCompleter(pathCompleter);
	edit->setValidator(pathValidator);
	edit->setDragEnabled(true);
	edit->setReadOnly(true);
	connect(edit, &QLineEdit::editingFinished, this, &QPathEdit::editTextUpdate);
	connect(edit, &QLineEdit::textChanged, this, &QPathEdit::updateValidInfo);
	//setup "button"
	connect(dialogAction, &QAction::triggered, this, &QPathEdit::showDialog);
	toolButton->setDefaultAction(dialogAction);
	int height = edit->sizeHint().height();
#ifdef Q_OS_WIN
	height += 2;
#endif
	toolButton->setFixedSize(height, height);
	QT_WARNING_PUSH
	QT_WARNING_DISABLE_GCC("-Wimplicit-fallthrough")
	switch(style) {
	case JoinedButton:
		edit->addAction(dialogAction, QLineEdit::TrailingPosition);
	case NoButton:
		toolButton->setVisible(false);
		break;
	default:
		break;
	}
	QT_WARNING_POP

	QWidget::setTabOrder(edit, toolButton);
	setFocusPolicy(edit->focusPolicy());
	setFocusProxy(edit);
}

QPathEdit::QPathEdit(QPathEdit::PathMode pathMode, QString defaultDirectory, QWidget *parent, QPathEdit::Style style) :
	QPathEdit(pathMode, parent, style)
{
	setDefaultDirectory(defaultDirectory);
}

QPathEdit::PathMode QPathEdit::pathMode() const
{
	return mode;
}

void QPathEdit::setPathMode(PathMode pathMode)
{
	mode = pathMode;
	pathValidator->setMode(pathMode);
	currentValidPath.clear();
	emit pathChanged(QString());
	edit->clear();
	switch(pathMode) {
	case ExistingFile:
		dialog->setAcceptMode(QFileDialog::AcceptOpen);
		dialog->setFileMode(QFileDialog::ExistingFile);
		completerModel->setFilter(QDir::AllEntries | QDir::AllDirs | QDir::NoDotAndDotDot);
		break;
	case ExistingFolder:
		dialog->setAcceptMode(QFileDialog::AcceptOpen);
		dialog->setFileMode(QFileDialog::Directory);
		completerModel->setFilter(QDir::Drives | QDir::Dirs | QDir::NoDotAndDotDot);
		break;
	case AnyFile:
		dialog->setAcceptMode(QFileDialog::AcceptSave);
		dialog->setFileMode(QFileDialog::AnyFile);
		completerModel->setFilter(QDir::AllEntries | QDir::AllDirs | QDir::NoDotAndDotDot);
		break;
	default:
		Q_UNREACHABLE();
	}
}

QFileDialog::Options QPathEdit::dialogOptions() const
{
	return dialog->options();
}

void QPathEdit::setDialogOptions(QFileDialog::Options dialogOptions)
{
	dialog->setOptions(dialogOptions);
}

bool QPathEdit::isEmptyPathAllowed() const
{
	return allowEmpty;
}

void QPathEdit::setAllowEmptyPath(bool allowEmptyPath)
{
	allowEmpty = allowEmptyPath;
	pathValidator->setAllowEmpty(allowEmptyPath);
}

QString QPathEdit::defaultDirectory() const
{
	return defaultDir;
}

void QPathEdit::setDefaultDirectory(QString defaultDirectory)
{
	defaultDir = defaultDirectory;
}

QString QPathEdit::path() const
{
	return currentValidPath;
}

QUrl QPathEdit::pathUrl() const
{
	return QUrl::fromLocalFile(currentValidPath);
}

bool QPathEdit::hasAcceptableInput() const
{
	return wasPathValid;
}

bool QPathEdit::setPath(QString path, bool allowInvalid)
{
	if (edit->text() == path)
		return true;

	if(allowInvalid)
		edit->setText(path);

	int pseudo = 0;
	if(pathValidator->validate(path, pseudo) == QValidator::Acceptable) {
		currentValidPath = path.replace(QStringLiteral("\\"), QStringLiteral("/"));
		if(!allowInvalid)
			edit->setText(path);
		emit pathChanged(path);
		return true;
	} else
		return false;
}

void QPathEdit::clear()
{
	edit->clear();
	currentValidPath.clear();
	emit pathChanged(QString());
}

QString QPathEdit::placeholder() const
{
	return edit->placeholderText();
}

void QPathEdit::setPlaceholder(QString placeholder)
{
	edit->setPlaceholderText(placeholder);
}

QStringList QPathEdit::nameFilters() const
{
	return dialog->nameFilters();
}

void QPathEdit::setNameFilters(QStringList nameFilters)
{
	dialog->setNameFilters(nameFilters);
	QStringList tmp = modelFilters(nameFilters);
	completerModel->setNameFilters(tmp);
}

QStringList QPathEdit::mimeTypeFilters() const
{
	return dialog->mimeTypeFilters();
}

void QPathEdit::setMimeTypeFilters(QStringList mimeFilters)
{
	dialog->setMimeTypeFilters(mimeFilters);
	QStringList tmp = modelFilters(dialog->nameFilters());
	completerModel->setNameFilters(tmp);
}

bool QPathEdit::isEditable() const
{
	return !edit->isReadOnly();
}

void QPathEdit::setEditable(bool editable)
{
	edit->setReadOnly(!editable);
}

bool QPathEdit::useCompleter() const
{
	return edit->completer();
}

void QPathEdit::setUseCompleter(bool useCompleter)
{
	edit->setCompleter(useCompleter ? pathCompleter : nullptr);
}

QPathEdit::Style QPathEdit::style() const
{
	return uiStyle;
}

void QPathEdit::setStyle(QPathEdit::Style style, QLineEdit::ActionPosition position)
{
	if (uiStyle == style)
		return;

	switch(style) {
	case SeperatedButton:
		edit->removeAction(dialogAction);
		toolButton->setVisible(true);
		break;
	case JoinedButton:
		edit->addAction(dialogAction, position);
		toolButton->setVisible(false);
		break;
	case NoButton:
		edit->removeAction(dialogAction);
		toolButton->setVisible(false);
		break;
	default:
		Q_UNREACHABLE();
		break;
	}

	uiStyle = style;
	if(!hasCustomIcon)
		dialogAction->setIcon(getDefaultIcon());
}

QIcon QPathEdit::dialogButtonIcon() const
{
	return dialogAction->icon();
}

void QPathEdit::setDialogButtonIcon(const QIcon &icon)
{
	dialogAction->setIcon(icon);
	hasCustomIcon = true;
}

void QPathEdit::resetDialogButtonIcon()
{
	dialogAction->setIcon(QPathEdit::getDefaultIcon());
	hasCustomIcon = false;
}

void QPathEdit::showDialog()
{
	if(dialog->isVisible()) {
		dialog->raise();
		dialog->activateWindow();
		return;
	}

	QString oldPath = edit->text();
	if(oldPath.isEmpty())
		dialog->setDirectory(defaultDir);
	else {
		if(mode == ExistingFolder)
			dialog->setDirectory(oldPath);
		else {
			QFileInfo info(oldPath);
			if(info.isDir())
				dialog->setDirectory(oldPath);
			else {
				dialog->setDirectory(info.dir());
				dialog->selectFile(info.fileName());
			}
		}
	}

	dialog->open();
}

void QPathEdit::updateValidInfo(const QString &path)
{
	completerModel->index(QFileInfo(path).dir().absolutePath());//enforce "directory loading"
	if(edit->hasAcceptableInput()) {
		if(!wasPathValid) {
			wasPathValid = true;
			edit->setPalette(palette());
			emit acceptableInputChanged(wasPathValid);
		}
	} else {
		if(wasPathValid) {
			wasPathValid = false;
			QPalette pal = palette();
			pal.setColor(QPalette::Text, QColor(QStringLiteral("#B40404")));
			edit->setPalette(pal);
			emit acceptableInputChanged(wasPathValid);
		}
	}
}

void QPathEdit::editTextUpdate()
{
	if(edit->hasAcceptableInput()) {
		QString newPath = edit->text().replace(QStringLiteral("\\"), QStringLiteral("/"));
		if(currentValidPath != newPath) {
			currentValidPath = newPath;
			emit pathChanged(currentValidPath);
		}
	}
}

void QPathEdit::dialogFileSelected(const QString &file)
{
	if(!file.isEmpty()) {
		edit->setText(dialog->selectedFiles()
							.first()
							.replace(QStringLiteral("\\"), QStringLiteral("/")));
		editTextUpdate();
	}
}

QStringList QPathEdit::modelFilters(const QStringList &normalFilters)
{
	QStringList res;
	foreach(QString filter, normalFilters) {
		QRegularExpressionMatch match = QRegularExpression(QStringLiteral("^.*\\((.*)\\)$"))
										.match(filter);
		if(match.hasMatch())
			res.append(match.captured(1).split(QRegularExpression(QStringLiteral("\\s"))));
	}
	return res;
}

QIcon QPathEdit::getDefaultIcon()
{
	switch(uiStyle) {
	case SeperatedButton:
	{
		QImage image(16, 16, QImage::Format_ARGB32);
		image.fill(Qt::transparent);
		QPainter painter(&image);
		painter.setFont(font());
		painter.setPen(palette().color(QPalette::ButtonText));
		painter.drawText(QRect(0, 0, 16, 16), Qt::AlignCenter, tr("…"));
		return QPixmap::fromImage(image);
	}
	case JoinedButton:
		return QIcon::fromTheme(QStringLiteral("view-choose"), QIcon(QStringLiteral(":/qpathedit/icons/dialog.ico")));
	case NoButton:
		return QIcon();
	default:
		Q_UNREACHABLE();
	}
}

bool QPathEdit::eventFilter(QObject *watched, QEvent *event)
{
	if (event->type() == QEvent::KeyPress) {
		QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
		if(keyEvent->key() == Qt::Key_Space &&
				keyEvent->modifiers() == Qt::ControlModifier){
			pathCompleter->complete();
			return true;
		} else
			return QObject::eventFilter(watched, event);
	} else
		return QObject::eventFilter(watched, event);
}

//HELPER CLASSES IMPLEMENTATION

PathValidator::PathValidator(QObject *parent) :
	QValidator(parent),
	mode(QPathEdit::ExistingFile),
	allowEmpty(true)
{}

void PathValidator::setMode(QPathEdit::PathMode mode)
{
	this->mode = mode;
}

void PathValidator::setAllowEmpty(bool allow)
{
	allowEmpty = allow;
}

QValidator::State PathValidator::validate(QString &text, int &) const
{
	//check if empty is accepted
	if(text.isEmpty())
		return allowEmpty ? QValidator::Acceptable : QValidator::Intermediate;

	//nonexisting parent dir is not possible
	QFileInfo pathInfo(text);
	if(!pathInfo.dir().exists())
		return QValidator::Invalid;

	switch(mode) {
	case QPathEdit::AnyFile://acceptable, as long as it's not an directoy
		if(pathInfo.isDir())
			return QValidator::Intermediate;
		else
			return QValidator::Acceptable;
	case QPathEdit::ExistingFile://must be an existing file
		if(pathInfo.exists() && pathInfo.isFile())
			return QValidator::Acceptable;
		else
			return QValidator::Intermediate;
	case QPathEdit::ExistingFolder://must be an existing folder
		if(pathInfo.exists() && pathInfo.isDir())
			return QValidator::Acceptable;
		else
			return QValidator::Intermediate;
	default:
		Q_UNREACHABLE();
	}

	return QValidator::Invalid;
}
<commit_msg>- QStringLiteral("") -> QString<commit_after>#include "qpathedit.h"
#include <QLineEdit>
#include <QToolButton>
#include <QHBoxLayout>
#include <QCompleter>
#include <QValidator>
#include <QStandardPaths>
#include <QFileSystemModel>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QEvent>
#include <QTimer>
#include <QAction>
#include <QPainter>
#include <functional>
#include <QKeyEvent>
#include <dialogmaster.h>

//HELPER CLASSES

class PathValidator : public QValidator
{
public:
	PathValidator(QObject *parent);
	void setMode(QPathEdit::PathMode mode);
	void setAllowEmpty(bool allow);
	State validate(QString &text, int &) const override;
private:
	QPathEdit::PathMode mode;
	bool allowEmpty;
};

//QPATHEDIT IMPLEMENTATION

QPathEdit::QPathEdit(QWidget *parent, QPathEdit::Style style) :
	QPathEdit(ExistingFile, parent, style)
{}

QPathEdit::QPathEdit(QPathEdit::PathMode pathMode, QWidget *parent, QPathEdit::Style style) :
	QWidget(parent),
	edit(new QLineEdit(this)),
	pathCompleter(new QCompleter(this)),
	completerModel(new QFileSystemModel(this)),
	pathValidator(new PathValidator(this)),
	dialog(new QFileDialog(this)),
	currentValidPath(),
	wasPathValid(true),
	uiStyle(style),
	mode(ExistingFile),
	defaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)),
	allowEmpty(true),
	toolButton(new QToolButton(this)),
	dialogAction(new QAction(getDefaultIcon(), tr("Open File-Dialog"), this)),
	hasCustomIcon(false)
{
	//setup dialog
	dialog->setOptions(0);
	DialogMaster::masterDialog(dialog);
	setPathMode(pathMode);
	connect(dialog, &QFileDialog::fileSelected, this, &QPathEdit::dialogFileSelected);

	//setup completer
    completerModel->setRootPath(QString());
	completerModel->setNameFilterDisables(false);
	connect(completerModel, &QFileSystemModel::directoryLoaded, pathCompleter, [this](QString){
		pathCompleter->complete();
	});
	pathCompleter->setModel(completerModel);

	//setup this
	QHBoxLayout *layout = new QHBoxLayout(this);
	layout->setContentsMargins(QMargins());
	layout->setSpacing(0);
	layout->addWidget(edit);
	layout->addWidget(toolButton);
	setLayout(layout);
	//setup lineedit
	edit->installEventFilter(this);
	edit->setCompleter(pathCompleter);
	edit->setValidator(pathValidator);
	edit->setDragEnabled(true);
	edit->setReadOnly(true);
	connect(edit, &QLineEdit::editingFinished, this, &QPathEdit::editTextUpdate);
	connect(edit, &QLineEdit::textChanged, this, &QPathEdit::updateValidInfo);
	//setup "button"
	connect(dialogAction, &QAction::triggered, this, &QPathEdit::showDialog);
	toolButton->setDefaultAction(dialogAction);
	int height = edit->sizeHint().height();
#ifdef Q_OS_WIN
	height += 2;
#endif
	toolButton->setFixedSize(height, height);
	QT_WARNING_PUSH
	QT_WARNING_DISABLE_GCC("-Wimplicit-fallthrough")
	switch(style) {
	case JoinedButton:
		edit->addAction(dialogAction, QLineEdit::TrailingPosition);
	case NoButton:
		toolButton->setVisible(false);
		break;
	default:
		break;
	}
	QT_WARNING_POP

	QWidget::setTabOrder(edit, toolButton);
	setFocusPolicy(edit->focusPolicy());
	setFocusProxy(edit);
}

QPathEdit::QPathEdit(QPathEdit::PathMode pathMode, QString defaultDirectory, QWidget *parent, QPathEdit::Style style) :
	QPathEdit(pathMode, parent, style)
{
	setDefaultDirectory(defaultDirectory);
}

QPathEdit::PathMode QPathEdit::pathMode() const
{
	return mode;
}

void QPathEdit::setPathMode(PathMode pathMode)
{
	mode = pathMode;
	pathValidator->setMode(pathMode);
	currentValidPath.clear();
	emit pathChanged(QString());
	edit->clear();
	switch(pathMode) {
	case ExistingFile:
		dialog->setAcceptMode(QFileDialog::AcceptOpen);
		dialog->setFileMode(QFileDialog::ExistingFile);
		completerModel->setFilter(QDir::AllEntries | QDir::AllDirs | QDir::NoDotAndDotDot);
		break;
	case ExistingFolder:
		dialog->setAcceptMode(QFileDialog::AcceptOpen);
		dialog->setFileMode(QFileDialog::Directory);
		completerModel->setFilter(QDir::Drives | QDir::Dirs | QDir::NoDotAndDotDot);
		break;
	case AnyFile:
		dialog->setAcceptMode(QFileDialog::AcceptSave);
		dialog->setFileMode(QFileDialog::AnyFile);
		completerModel->setFilter(QDir::AllEntries | QDir::AllDirs | QDir::NoDotAndDotDot);
		break;
	default:
		Q_UNREACHABLE();
	}
}

QFileDialog::Options QPathEdit::dialogOptions() const
{
	return dialog->options();
}

void QPathEdit::setDialogOptions(QFileDialog::Options dialogOptions)
{
	dialog->setOptions(dialogOptions);
}

bool QPathEdit::isEmptyPathAllowed() const
{
	return allowEmpty;
}

void QPathEdit::setAllowEmptyPath(bool allowEmptyPath)
{
	allowEmpty = allowEmptyPath;
	pathValidator->setAllowEmpty(allowEmptyPath);
}

QString QPathEdit::defaultDirectory() const
{
	return defaultDir;
}

void QPathEdit::setDefaultDirectory(QString defaultDirectory)
{
	defaultDir = defaultDirectory;
}

QString QPathEdit::path() const
{
	return currentValidPath;
}

QUrl QPathEdit::pathUrl() const
{
	return QUrl::fromLocalFile(currentValidPath);
}

bool QPathEdit::hasAcceptableInput() const
{
	return wasPathValid;
}

bool QPathEdit::setPath(QString path, bool allowInvalid)
{
	if (edit->text() == path)
		return true;

	if(allowInvalid)
		edit->setText(path);

	int pseudo = 0;
	if(pathValidator->validate(path, pseudo) == QValidator::Acceptable) {
		currentValidPath = path.replace(QStringLiteral("\\"), QStringLiteral("/"));
		if(!allowInvalid)
			edit->setText(path);
		emit pathChanged(path);
		return true;
	} else
		return false;
}

void QPathEdit::clear()
{
	edit->clear();
	currentValidPath.clear();
	emit pathChanged(QString());
}

QString QPathEdit::placeholder() const
{
	return edit->placeholderText();
}

void QPathEdit::setPlaceholder(QString placeholder)
{
	edit->setPlaceholderText(placeholder);
}

QStringList QPathEdit::nameFilters() const
{
	return dialog->nameFilters();
}

void QPathEdit::setNameFilters(QStringList nameFilters)
{
	dialog->setNameFilters(nameFilters);
	QStringList tmp = modelFilters(nameFilters);
	completerModel->setNameFilters(tmp);
}

QStringList QPathEdit::mimeTypeFilters() const
{
	return dialog->mimeTypeFilters();
}

void QPathEdit::setMimeTypeFilters(QStringList mimeFilters)
{
	dialog->setMimeTypeFilters(mimeFilters);
	QStringList tmp = modelFilters(dialog->nameFilters());
	completerModel->setNameFilters(tmp);
}

bool QPathEdit::isEditable() const
{
	return !edit->isReadOnly();
}

void QPathEdit::setEditable(bool editable)
{
	edit->setReadOnly(!editable);
}

bool QPathEdit::useCompleter() const
{
	return edit->completer();
}

void QPathEdit::setUseCompleter(bool useCompleter)
{
	edit->setCompleter(useCompleter ? pathCompleter : nullptr);
}

QPathEdit::Style QPathEdit::style() const
{
	return uiStyle;
}

void QPathEdit::setStyle(QPathEdit::Style style, QLineEdit::ActionPosition position)
{
	if (uiStyle == style)
		return;

	switch(style) {
	case SeperatedButton:
		edit->removeAction(dialogAction);
		toolButton->setVisible(true);
		break;
	case JoinedButton:
		edit->addAction(dialogAction, position);
		toolButton->setVisible(false);
		break;
	case NoButton:
		edit->removeAction(dialogAction);
		toolButton->setVisible(false);
		break;
	default:
		Q_UNREACHABLE();
		break;
	}

	uiStyle = style;
	if(!hasCustomIcon)
		dialogAction->setIcon(getDefaultIcon());
}

QIcon QPathEdit::dialogButtonIcon() const
{
	return dialogAction->icon();
}

void QPathEdit::setDialogButtonIcon(const QIcon &icon)
{
	dialogAction->setIcon(icon);
	hasCustomIcon = true;
}

void QPathEdit::resetDialogButtonIcon()
{
	dialogAction->setIcon(QPathEdit::getDefaultIcon());
	hasCustomIcon = false;
}

void QPathEdit::showDialog()
{
	if(dialog->isVisible()) {
		dialog->raise();
		dialog->activateWindow();
		return;
	}

	QString oldPath = edit->text();
	if(oldPath.isEmpty())
		dialog->setDirectory(defaultDir);
	else {
		if(mode == ExistingFolder)
			dialog->setDirectory(oldPath);
		else {
			QFileInfo info(oldPath);
			if(info.isDir())
				dialog->setDirectory(oldPath);
			else {
				dialog->setDirectory(info.dir());
				dialog->selectFile(info.fileName());
			}
		}
	}

	dialog->open();
}

void QPathEdit::updateValidInfo(const QString &path)
{
	completerModel->index(QFileInfo(path).dir().absolutePath());//enforce "directory loading"
	if(edit->hasAcceptableInput()) {
		if(!wasPathValid) {
			wasPathValid = true;
			edit->setPalette(palette());
			emit acceptableInputChanged(wasPathValid);
		}
	} else {
		if(wasPathValid) {
			wasPathValid = false;
			QPalette pal = palette();
			pal.setColor(QPalette::Text, QColor(QStringLiteral("#B40404")));
			edit->setPalette(pal);
			emit acceptableInputChanged(wasPathValid);
		}
	}
}

void QPathEdit::editTextUpdate()
{
	if(edit->hasAcceptableInput()) {
		QString newPath = edit->text().replace(QStringLiteral("\\"), QStringLiteral("/"));
		if(currentValidPath != newPath) {
			currentValidPath = newPath;
			emit pathChanged(currentValidPath);
		}
	}
}

void QPathEdit::dialogFileSelected(const QString &file)
{
	if(!file.isEmpty()) {
		edit->setText(dialog->selectedFiles()
							.first()
							.replace(QStringLiteral("\\"), QStringLiteral("/")));
		editTextUpdate();
	}
}

QStringList QPathEdit::modelFilters(const QStringList &normalFilters)
{
	QStringList res;
	foreach(QString filter, normalFilters) {
		QRegularExpressionMatch match = QRegularExpression(QStringLiteral("^.*\\((.*)\\)$"))
										.match(filter);
		if(match.hasMatch())
			res.append(match.captured(1).split(QRegularExpression(QStringLiteral("\\s"))));
	}
	return res;
}

QIcon QPathEdit::getDefaultIcon()
{
	switch(uiStyle) {
	case SeperatedButton:
	{
		QImage image(16, 16, QImage::Format_ARGB32);
		image.fill(Qt::transparent);
		QPainter painter(&image);
		painter.setFont(font());
		painter.setPen(palette().color(QPalette::ButtonText));
		painter.drawText(QRect(0, 0, 16, 16), Qt::AlignCenter, tr("…"));
		return QPixmap::fromImage(image);
	}
	case JoinedButton:
		return QIcon::fromTheme(QStringLiteral("view-choose"), QIcon(QStringLiteral(":/qpathedit/icons/dialog.ico")));
	case NoButton:
		return QIcon();
	default:
		Q_UNREACHABLE();
	}
}

bool QPathEdit::eventFilter(QObject *watched, QEvent *event)
{
	if (event->type() == QEvent::KeyPress) {
		QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
		if(keyEvent->key() == Qt::Key_Space &&
				keyEvent->modifiers() == Qt::ControlModifier){
			pathCompleter->complete();
			return true;
		} else
			return QObject::eventFilter(watched, event);
	} else
		return QObject::eventFilter(watched, event);
}

//HELPER CLASSES IMPLEMENTATION

PathValidator::PathValidator(QObject *parent) :
	QValidator(parent),
	mode(QPathEdit::ExistingFile),
	allowEmpty(true)
{}

void PathValidator::setMode(QPathEdit::PathMode mode)
{
	this->mode = mode;
}

void PathValidator::setAllowEmpty(bool allow)
{
	allowEmpty = allow;
}

QValidator::State PathValidator::validate(QString &text, int &) const
{
	//check if empty is accepted
	if(text.isEmpty())
		return allowEmpty ? QValidator::Acceptable : QValidator::Intermediate;

	//nonexisting parent dir is not possible
	QFileInfo pathInfo(text);
	if(!pathInfo.dir().exists())
		return QValidator::Invalid;

	switch(mode) {
	case QPathEdit::AnyFile://acceptable, as long as it's not an directoy
		if(pathInfo.isDir())
			return QValidator::Intermediate;
		else
			return QValidator::Acceptable;
	case QPathEdit::ExistingFile://must be an existing file
		if(pathInfo.exists() && pathInfo.isFile())
			return QValidator::Acceptable;
		else
			return QValidator::Intermediate;
	case QPathEdit::ExistingFolder://must be an existing folder
		if(pathInfo.exists() && pathInfo.isDir())
			return QValidator::Acceptable;
		else
			return QValidator::Intermediate;
	default:
		Q_UNREACHABLE();
	}

	return QValidator::Invalid;
}
<|endoftext|>