📜 ⬆️ ⬇️

Japanese QtQuick Crosswords

Skull and Bones, KDPV


I like to prototype something in my free time. This allows you to learn something new. This prototype is a client for the http://www.nonograms.ru/ resource, the developer of which is Cast Iron K.A / KyberPrizrak /. All code is available on GiHub . On the C ++ side, working with HTML, the gallery model. On the QtQuick side, visualization.


This time I decided to pick it up:



Under the cat will be considered:



Screenshots

gallery
Menu


We do without Qt WebKit


The site gives a crossword in the form of a matrix:


var d=[[571,955,325,492], [6,53,49,55], [47,18,55,65], ...]] 

Further JS squeaks create html crossword code. The WebKit module has been marked as deprecated . In exchange, it is proposed to use the Web Engine module based on the Chrome project.


Here a slight disappointment awaits. The Web Engine has no DOM API on the page. To parse the HTML code, I had to use third-party tools ( Parsim HTML in C ++ and Gumbo ).
But we can load the page, render and get the necessary HTML.


 QString getHtml(const QUrl& url) { QWebEnginePage page; QEventLoop loop; QObject::connect(&page, &QWebEnginePage::loadFinished, &loop, &QEventLoop::quit); page.load(url); loop.exec(); QTimer::singleShot(1000, &loop, &QEventLoop::quit); QString html; page.toHtml([&html, &loop](const QString& data){ html = data; loop.quit(); }); loop.exec(); return html; } 

QTimer :: singleShot here is used to wait for the page to complete. The toHtml method is asynchronous and accepts a callback function as an input parameter to get the result.


Building a crossword puzzle


Crossword puzzle
Crossword decided to present as a set of columns and lines. At the top, 10 columns are circled in red, each of size 3. Three lines are circled on the left, each one is 3. Then the code will operate on these values.


Crossword can be done in several ways:



I chose the last option.


Full code
 import QtQuick 2.5 import Qt.labs.controls 1.0 Item { clip:true property int margin: 20 property int fontSize: 12 property int ceilSize: 20; property int incCeilSize: ceilSize + 1 property color borderColor: "#424242" property int rows: 0; property int rowSize: 0; property int column: 0; property int columnSize: 0; implicitHeight : crossGrid.height+margin*2 implicitWidth : crossGrid.width+margin*2 function loadFromNonogramsOrg(url) { console.log("Load:"+url); crossword.formNanogramsOrg(url); } function showOnlyNaturalNumber(val) { return val > 0 ? val: " "; } function drawCrossword(){ var csize = crossword.size; if(csize.column() === 0 || csize.rows() === 0){ return; } console.log(csize.column() + "x" + csize.rows()); hRepeater.model = 0; rRepeater.model = 0; rowSize = crossword.rowSize(); columnSize = crossword.columnSize(); rows = csize.rows(); column = csize.column(); hRepeater.model = crossword.columnSize()*csize.column(); rRepeater.model = crossword.rowSize()*csize.rows(); bgImg.visible = true; } Image{ id: bgImg asynchronous: true visible: false height: parent.height width: parent.width source:"qrc:/wall-paper.jpg" } Grid { id: crossGrid anchors.centerIn: parent columns: 2 spacing: 2 rowSpacing: 0 columnSpacing: 0 Rectangle{ id:topLeftItm width: rowSize * ceilSize height:columnSize * ceilSize border.width: 1 border.color: borderColor color: "transparent" } Grid { id: cGrid rows: columnSize columns: column Repeater { id: hRepeater model: 0 Item { width: ceilSize; height: ceilSize property int rw : Math.floor(index/column) property int cn : Math.floor(index%column) property int prw: rw+1 property int pcm: cn+1 Rectangle{ height: (prw % 5 == 0) || (prw == columnSize) ? ceilSize : incCeilSize width: (pcm % 5 == 0) ? ceilSize : incCeilSize color: "transparent" border.width: 1 border.color: borderColor Text { anchors.centerIn: parent text:showOnlyNaturalNumber( crossword.columnValue(cn,rw)); font{ family: mandarinFont.name pixelSize: fontSize } } } } } } Grid { id: rGrid rows: rows columns: rowSize Repeater { id: rRepeater model: 0 Item { width: ceilSize; height: ceilSize property int rw : Math.floor(index/rowSize) property int cn : Math.floor(index%rowSize) property int prw: rw+1 property int pcn: cn+1 Rectangle{ height: prw % 5 == 0 ? ceilSize : incCeilSize width: (pcn % 5 == 0) || (pcn == rowSize) ? ceilSize : incCeilSize color: "transparent" border.width: 1 border.color: borderColor Text { anchors.centerIn: parent text:showOnlyNaturalNumber( crossword.rowValue(rw,cn)); font{ family: mandarinFont.name pixelSize: fontSize } } } } } } Rectangle{ id: playingField width: column * ceilSize height:rows * ceilSize border.width: 1 border.color: borderColor color: "transparent" Grid{ rows: rows columns:column Repeater { id: bRepeater model: rows * column Item { id: ceilItm width: ceilSize; height: ceilSize property int rw : Math.floor(index/column) property int cn : Math.floor(index%column) state: "default" Rectangle{ id: itmRec height: (rw+1) % 5 == 0 ? ceilSize : incCeilSize width: (cn+1) % 5 == 0 ? ceilSize : incCeilSize color: "transparent" border.width: 1 border.color: borderColor } Text{ id: itmTxt visible:false height: parent.height width: parent.width font.pixelSize: ceilSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text:"+" rotation:45 } MouseArea { anchors.fill: parent onClicked: { if(parent.state == "default"){ parent.state = "SHADED"; }else if(parent.state == "SHADED"){ parent.state = "CLEAR"; }else{ parent.state = "default"; } } } states: [ State{ name:"SHADED" PropertyChanges { target: itmRec; color: "black"; } PropertyChanges { target: itmTxt; visible: false; } }, State{ name:"CLEAR" PropertyChanges { target: itmRec; color: "transparent"; } PropertyChanges { target: itmTxt; visible: true; } } ] } } } } } Text{ visible: bgImg.visible anchors{ right: parent.right rightMargin: 10 bottom: parent.bottom } text:qsTr("Source: ")+"www.nonograms.ru" font{ family: hanZiFont.name pixelSize: 12 } } Connections { target: crossword onLoaded: { drawCrossword(); } } } 

The basis is represented by Item, the size of which is calculated from the size of the crossGrid and the size of the indent ( margin )


 Item { clip:true implicitHeight : crossGrid.height+margin*2 implicitWidth : crossGrid.width+margin*2 /* ... */ Image{ id: bgImg asynchronous: true visible: false height: parent.height width: parent.width source:"qrc:/wall-paper.jpg" } Grid { id: crossGrid anchors.centerIn: parent columns: 2 spacing: 2 /* ... */ } } 

CrossGrid element


crossGrid


 Grid { id: crossGrid anchors.centerIn: parent columns: 2 spacing: 2 rowSpacing: 16 columnSpacing: 16 Rectangle{ id:topLeftItm color: "transparent" border.width: 1 border.color: borderColor /* ... */ } Grid { id: cGrid /* ... */ } Grid { id: rGrid /* ... */ } Rectangle{ id: playingField /* ... */ } } 

topLeftItm a rectangle that fills the space. cGrid and rGrid describe the grid with numbers. playingField field to solve crossword.


Grid construction


If you write this:


 Grid { id: cGrid rows: columnSize columns: column Repeater { id: hRepeater /* ... */ Item { width: ceilSize; height: ceilSize Rectangle{ height: ceilSize width: ceilSize color: "transparent" border.width: 1 border.color: borderColor Text { anchors.centerIn: parent text: index font{ family: mandarinFont.name pixelSize: fontSize } } } } } } 

then we get a doubling of the line


double line


To remove the doubling of the line, use the trick with the size of Item and Rectangle . Item size is fixed so that all elements are located exactly in the repeater. The rectangle is wider and taller by one, depending on the need for a double line.


 Repeater { id: hRepeater model: 0 Item { width: ceilSize; height: ceilSize property int rw : Math.floor(index/column) property int cn : Math.floor(index%column) property int prw: rw+1 property int pcm: cn+1 Rectangle{ height: (prw % 5 == 0) || (prw == columnSize) ? ceilSize : incCeilSize width: (pcm % 5 == 0) ? ceilSize : incCeilSize color: "transparent" border.width: 1 border.color: borderColor Text { anchors.centerIn: parent text:showOnlyNaturalNumber( crossword.columnValue(cn,rw)); font{ family: mandarinFont.name pixelSize: fontSize } } } } } 

Here, on the basis of the index, the row ( rw ) and column ( cn ) are calculated, incremented by one, the remainder of the division by 5 is taken. That is, every 5 cells, the width or height of the Rectangle and Item are the same, which doubles the line.


Crossword puzzle


From the field, we need a grid and click processing. We introduce the state of the grid cell:



We will begin with the inactive state and change on a mouse click in the following sequence
State graph


Cell drawing code:


 Item { id: ceilItm width: ceilSize; height: ceilSize property int rw : Math.floor(index/column) property int cn : Math.floor(index%column) state: "default" Rectangle{ id: itmRec height: (rw+1) % 5 == 0 ? ceilSize : incCeilSize width: (cn+1) % 5 == 0 ? ceilSize : incCeilSize color: "transparent" border.width: 1 border.color: borderColor } Text{ id: itmTxt visible:false height: parent.height width: parent.width font.pixelSize: ceilSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text:"+" rotation:45 } MouseArea { anchors.fill: parent onClicked: { if(parent.state == "default"){ parent.state = "SHADED"; }else if(parent.state == "SHADED"){ parent.state = "CLEAR"; }else{ parent.state = "default"; } } } states: [ State{ name:"SHADED" PropertyChanges { target: itmRec; color: "black"; } PropertyChanges { target: itmTxt; visible: false; } }, State{ name:"CLEAR" PropertyChanges { target: itmRec; color: "transparent"; } PropertyChanges { target: itmTxt; visible: true; } } ] } 

The itmTxt element adds a cross to the cell, displaying it as marked empty. Here we use the ability to describe different states through states .
MouseArea makes the transition. That is why everything was started. No calculations (converting mouse coordinates to grid cells), no manual redrawing.


')

Source: https://habr.com/ru/post/283474/


All Articles