0

Good day,

I'm writing a program 'geocalculator' as an assignment at our university. I faced up with a design problem. An interface contains two groups: A and B. Each group consists of two comboboxes and a table. The first combobox allows user to choose between a couple of reference systems (WGS-84, SK-42, SK-95, etc.). The second - between geodetic, spatial and planimetric coordinate systems. The table is used to input a bunch of points (label, X (or B), Y (or L), Z (or H)). Conversion functions involve a lot of constants and massive formulas.

My question is: what is the best way to organize code, connect comboboxes and functions in order to call appropriate conversion function?

Let's come to the point. I subclassed an abstract model (PointsModel) where all my points are being stored. It has no idea about comboboxes and algorithms. The comboboxes are declared inside of a MainWindow class. My model do have custom delegate: it is used to convert representation of the input coordinates from sexadecimal (degrees, minutes and seconds) to decimal (decimal degrees). I have two buttons (actions on the toolBar): convert points from A to B and vice versa. I imagine the process in the next way:

  1. The user presses 'convert' button and the appropriate function is called.
  2. The general conversion function reads value of the comboboxes and chooses required conversion function. ...

I don't want to do it via "if else" statements! Because there would be a huge number of condition statements (if the one combobox is WGS-84 and the other is SK-42 then do this, if the one combobox is SK-42 and the other is WGS-84 then do this and so on and so forth for a bunch of reference systems).

I have some ideas about declaring functions and corresponding functors. I would like to attach this functors to the comboboxes in some way and then the time comes just call a general method, which would automatically redirect the call to the required function.

  • I didn't read your requirements in depth, but you should strive to not combine user-interface with the internal "business logic" of your application. Look up the `Model-View-Controller` pattern. http://stackoverflow.com/questions/129921/what-is-mvc-model-view-controller – PaulMcKenzie Nov 25 '14 at 19:46
  • @PaulMcKenzie thank you, there is a number of useful links. I will read some. The question is still open, so I wait for ideas. – Nikolay Zhulikov Nov 25 '14 at 19:53
  • 1
    I recommend a `std::map` or table of function objects or of function pointers. – Thomas Matthews Nov 25 '14 at 19:53
  • @ThomasMatthews std::map where the key is a string and the value is a pointer to some function? Well, it might work. Thank you! I will try it. – Nikolay Zhulikov Nov 25 '14 at 19:56
  • @ThomasMatthews wait a moment. Number of functions is greater than number of strings. For two equal comboboxes: if the first is "wgs84" and the second is "pz90" then do this, if the first is "pz90" and the second is "wgs84" then do other thing. The order also serves a purpose. – Nikolay Zhulikov Nov 25 '14 at 20:03
  • See my answer below. – Thomas Matthews Nov 25 '14 at 20:14

2 Answers2

0

There are a number of ways that you can handle this, but I think the best is by setting a functor on each element in your QComboBox.

When you do (QComboBox::addData)[http://qt-project.org/doc/qt-5/qcombobox.html#addItem] you'll notice the defaulted userData parameter. You can assign a functor here.

When you're ready to use it just call (QBomboBox::currentData)[http://qt-project.org/doc/qt-5/qcombobox.html#currentData-prop] to get it back out and use it.

As far as storing your functor in a QVariant, you can either store your functors in your own array and just store an index into that array in the QVariant of you can directly store the functor as a void* using QVariant(int typeId, const void * copy).

EDIT:

From your comment it sounds like something like this might be in order:

Store your functions in a std::map<std::string, std::map<std::string, std::function<X>>> Where the key to the outer map is the from-type and the key to the inner map is the to-type.

Then store the keys in your QComboBox's userData.

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • Okay, but for each element in the combobox there is a set of functions. For example, for element "geodetic" i have two functions: "geodetic to planimetric" and "geodetic to spatial". – Nikolay Zhulikov Nov 25 '14 at 20:12
  • Wow! How about storing a map of functions for each element in the combobox and pass this map next and then using a value from the second combobox choose required function from the map by the key? Whoa! Thank you! It would work, how do you think? – Nikolay Zhulikov Nov 25 '14 at 20:13
  • @NikolayZhulikov Yes I do think that would work, but I think saving the keys as mentioned in my edit would be simpler. Then to get your `std::function` for conversion you could just call: `keysToFunctorMap[fromCombo.currentData().toString().toStdString()][toCombo.currentData().toString().toStdString()]` – Jonathan Mee Nov 25 '14 at 20:27
0

I'll describe two popular choices: Look up table and event handler (a.k.a. button press). The selection of the algorithm depends on your calculator implementation.

Look up table

The fundamentals here is that you want to associate a function with a symbol. For example, your parser wants to evaluate the '+' for the expressions on the stack. Rather than using a switch or an if-elseif ladder, you could look up a function that is associated with '+' and execute the function with the two expressions.

// Synonym for a pointer that accepts two parameters,
// performs an operation, and returns the result as a string.
typedef std::string (*Function_Pointer(const std::string& param1, const std::string& param2))

struct Table_Entry
{
  const char * operator_text;
  Function_Pointer p_operaton_function;
};

const Table_Entry Operation_Table[] =
{
  {"+", Perform_Addition},
  {"-", Perform_Subtraction},
  {"*", Perform_Multiplication},
};

// Or
typedef std::map<std::string, Function_Pointer> Operation_Container;
//...
Operation_Container operations_map;
operations_map["+"] = Perform_Addition;
operations_map["-"] = Perform_Subtraction;

Event Handler

Another idea is to put the computation inside the handler for the button.

void On_Button_Dash(Event& e)
{
  result = operand_1 - operand2;
}

Edit 1: OOP

Another alternative is to create a base class for the operations. The base class would have an abstract method to evaluate or perform the operation. Define child classes for each operation. Create the operation classes during lexing phase and store in a vector of pointers to base classes.

You main loop could be:

std::vector<Base_Operation *> operations;
for (unsigned int i = 0; i < operations.size(); ++i)
{
  operations[i].Evaluate(parameter_1, parameter_2);
}
Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • Wow! Makes me think a little! :) I would like to have a real working example. Anyway, thank you so much for your answer. Now I have some speed and direction. But I don't know how to apply this... The selection process is complicated. I have one list of functions for this value of one combobox and choice is based on value of the second combobox, buuut I also have two comboboxes in addition and that list of function is based on the choice of these comboboxes and these comboboxes stores their own list of functions. I am all adrift!.. – Nikolay Zhulikov Nov 25 '14 at 20:51