c++ graphic programming You are to implement a basic trip planner. First draw a
ID: 3667473 • Letter: C
Question
c++ graphic programming
You are to implement a basic trip planner. First draw a graphical representation (simplified map) of the cities listed below. Then let a user choose a series of destinations in the map. Assuming the user will start the journey at Acapulco, after every click (representing the next destination), you should:
1.draw a line joining the current destination to the next destination;
2.display the distance (in Km) between the current and next destination; and
3.indicate graphically whether the user will be travelling east or west.
You may assume:
The user will not click outside of the map.
The user will end the trip when a location within 20km of Mexico city is selected as the next destination.
Once the user has selected Mexico City as the next and final destination, you should report at the bottom of the screen (in a location outside of the map) the total distance traveled in Kilometers. Also, since it is a well-known fact that Cuernavaca (my hometown) is the center of the universe, you should display a message indicating how many times the user passes within 50 Km of the center of the universe. Trips that start within 50 Km of Cuernavaca should not count as a pass. Once this process has been completed you should ask the user whether or not to continue and proceed accordingly.
The locations of the cities that are to be represented are given below. You should declare these points as global constants. You are not allowed to use any other global variable in your program.
Cuernavaca (0,0)
Mexico City (0,75)
Acapulco (-50,-225)
Ixtapa (-300,-200)
Salamanca (-225,250)
Morelia (-250,125)
Casitas (300,225)
The bare minimum your program should show is listed below:
The 7 cities listed above with their names;
Tick marks every 100 Kilometers and labels every 200 Kilometers.
Some graphical representation of the direction of travel (east or west). I have chosen to draw a truck facing left or right accordingly. Whatever you choose must not be a simple shape like an arrow or a message. The representation must include at least one rectangle and at least one circle.
I strongly recommend using the following functions:
drawMap -- to draw the basic map of central Mexico on the screen.
drawTruck -- to draw a basic representation of the direction of travel.
connectCities -- to draw a line between two adjacent destinations.
distanceBetweenCities -- to compute the distance between two adjacent destinations.
distanceToCuernavaca (alternatively: distanceToCenterOfTheUniverse) -- to compute the closest distance from a line segment to the center of the map.
Explanation / Answer
The TripPlanner class is derived from Ui::TripPlanner (which is generated by uic fromtripplanner.ui) in addition to QDialog. The tcpSocket member variable encapsulates the TCP connection. The nextBlockSize variable is used when parsing the blocks received from the server.
n the constructor, we initialize the date and time editors based on the current date and time. We also hide the progress bar, because we want to show it only when a connection is active. InQt Designer, the progress bar's minimum and maximum properties were both set to 0. This tells the QProgressBar to behave as a busy indicator instead of as a standard percentage-based progress bar.
Also in the constructor, we connect the QTcpSocket's connected(), disconnected(),readyRead(), and error(QAbstractSocket::SocketError) signals to private slots.
The connectToServer() slot is executed when the user clicks Search to start a search. We call connectToHost() on the QTcpSocket object to connect to the server, which we assume is accessible at port 6178 on the fictitious host tripserver.zugbahn.de. (If you want to try the example on your own machine, replace the host name with QHostAddress::LocalHost.) The connectToHost() call is asynchronous; it always returns immediately. The connection is typically established later. The QTcpSocket object emits the connected() signal when the connection is up and running, or error(QAbstractSocket::SocketError) if the connection failed.
Next, we update the user interface, in particular making the progress bar visible.
Finally, we set the nextBlockSize variable to 0. This variable stores the length of the next block received from the server. We have chosen to use the value of 0 to mean that we don't yet know the size of the next block.
The sendRequest() slot is executed when the QTcpSocket object emits the connected() signal, indicating that a connection has been established. The slot's task is to generate a request to the server, with all the information entered by the user.
We first write the data to a QByteArray called block. We can't write the data directly to the QTcpSocket because we won't know the size of the block, which must be sent first, until after we have put all the data into the block.
We initially write 0 as the block size, followed by the rest of the data. Then we call seek(0) on the I/O device (a QBuffer created by QDataStream behind the scenes) to move back to the beginning of the byte array, and overwrite the initial 0 with the size of the block's data. The size is calculated by taking the block's size and subtracting sizeof(quint16) (i.e., 2) to exclude the size field from the byte count. After that, we call write() on the QTcpSocket to send the block to the server.
The updateTableWidget() slot is connected to the QTcpSocket's readyRead() signal, which is emitted whenever the QTcpSocket has received new data from the server. The server sends us a list of possible train trips that match the user's criteria. Each matching trip is sent as a single block, and each block starts with a size. Figure 15.2 illustrates a stream of such blocks. The forever loop is necessary because we don't necessarily get one block of data from the server at a time.
At the end, we reset the nextBlockSize variable to 0 to indicate that the next block's size is unknown and needs to be read.
The closeConnection() private function closes the connection to the TCP server and updates the user interface. It is called from updateTableWidget() when the 0xFFFF is read and from several other slots that we will cover shortly.
The stopSearch() slot is connected to the Stop button's clicked() signal. Essentially, it just calls closeConnection().
The connectionClosedByServer() slot is connected to QTcpSocket's disconnected()signal. If the server closes the connection and we have not yet received the 0xFFFF end-of-data marker, we tell the user that an error occurred. We call closeConnection() as usual to update the user interface.
The error() slot is connected to QTcpSocket's error(QAbstractSocket::SocketError)signal. We ignore the error code and instead use the QIODevice::errorString() function, which returns a human-readable error message for the last error that occurred.
This is all for the TripPlanner class. The main() function for the Trip Planner application looks just as we would expect:
Now let's implement the server. The server consists of two classes: TripServer andClientSocket. The TripServer class is derived from QTcpServer, a class that allows us to accept incoming TCP connections. ClientSocket reimplements QTcpSocket and handles a single connection. At any one time, there are as many ClientSocket objects in memory as there are clients being served.
The TripServer class reimplements the incomingConnection() function from QTcpServer. This function is called whenever a client attempts to connect to the port the server is listening to.
The TripServer constructor is trivial.
In incomingConnection(), we create a ClientSocket object as a child of the TripServerobject, and we set its socket descriptor to the number provided to us. The ClientSocketobject will delete itself automatically when the connection is terminated.
The ClientSocket class is derived from QTcpSocket and encapsulates the state of a single client.
In the constructor, we establish the necessary signal–slot connections, and we set thenextBlockSize variable to 0, indicating that we do not yet know the size of the block sent by the client.
The disconnected() signal is connected to deleteLater(), a QObject-inherited function that deletes the object when control returns to Qt's event loop. This ensures that theClientSocket object is deleted when the socket connection is closed.
The readClient() slot is connected to QTcpSocket's readyRead() signal. IfnextBlockSize is 0, we start by reading the block size; otherwise, we have already read it, and instead we check to see whether a whole block has arrived. Once an entire block is ready for reading, we read it in one go. We use the QDataStream directly on the QTcpSocket (thethis object) and read the fields using the >> operator.
Once we have read the client's request, we are ready to generate a reply. If this were a real application, we would look up the information in a train schedule database and try to find matching train trips. But here we will be content with a function calledgenerateRandomTrip() that will generate a random trip. We call the function a random number of times, and then we send 0xFFFF to signify the end of the data. At the end, we close the connection.
The generateRandomTrip() function shows how to send a block of data over a TCP connection. This is very similar to what we did in the client in the sendRequest() function (p. 374). Once again, we write the block to a QByteArray so that we can determine its size before we send it using write().
In main(), we create a TripServer object and a QPushButton that enables the user to stop the server. We start the server by calling QTcpSocket::listen(), which takes the IP address and port number on which we want to accept connections. The special address 0.0.0.0 (QHostAddress::Any) signifies any IP interface present on the local host.
Using a QPushButton to represent the server is convenient during development. However, a deployed server would be expected to run without a GUI, as a Windows service or as a Unix daemon. Trolltech provides a commercial add-on for this purpose, called QtService.
This completes our client–server example. In this case, we used a block-oriented protocol that allows us to use QDataStream for reading and writing. If we wanted to use a line-oriented protocol, the simplest approach would be to use QTcpSocket's canReadLine() andreadLine() functions in a slot connected to the readyRead() signal:
We would then process each line that has been read. As for sending data, that can be done using a QTextStream on the QTcpSocket.
The server implementation that we have used doesn't scale very well when there are lots of connections. The problem is that while we are processing a request, we don't handle the other connections. A more scalable approach would be to start a new thread for each connection. The Threaded Fortune Server example located in Qt's
Related Questions
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.