Tämä poistaa sivun "5. Simple Notepad"
. Varmista että haluat todella tehdä tämän.
Until now we have seen two different controls: labels and buttons. Labels can show text or images to the user but are not designed for interaction. Buttons allow the user to trigger an event by pressing a mouse button. But none allow the user to insert any data into our application. To do that, we can use a new control called IUP_TEXT. It creates an editable text field and has a lot of different attributes available. But here we will be interested in other control called IUP_MULTILINE. It is an editable text field that supports many lines, which is mandatory to build a simple notepad.
Our starting code for the simple notepad should be as follows.
class SIMPLE_NOTEPAD_1
inherit
IUP_INTERFACE
create {ANY}
make
feature {ANY}
make
local
i: STRING
gui: IUP
ml: IUP_MULTILINE
v: IUP_VBOX
w: IUP_DIALOG
do
gui := iup_open
-- Create multiline
create ml.multiline
ml.expand_both_directions
-- Put the multiline inside a vertical box.
create v.vbox({ARRAY[IUP_WIDGET]} << ml >>)
-- Create the window.
create w.dialog(v)
w.set_title("Simple Notepad")
w.set_predefined_size("QUARTER", "QUARTER")
i := w.show_predefined_xy("IUP_CENTER", "IUP_CENTER")
w.set_user_size(0, 0)
gui.main_loop
gui.close
end
end
The previous code doesn't show exciting news except by the IUP_MULTILINE declaration.
Notice that the size attribute of the dialog was also set. Since the IUP_MULTILINE is a control that does not fit its size to its contents, we have to set an initial size for the dialog, or else the result would be a very small dialog. We use a simple size specification that is a quarter of the screen size in both dimensions. The size attribute will also work as a minimum size, so we reset the user size attribute, after the dialog is shown, to avoid this limitation (0,0 mean no minimum size). Try to comment this line and check out how the dialog interactive resize behaves.
With a few lines of code, we build an application where the user can type a huge text. But, if you type a huge text, you probably would like to save it, and unfortunately our applications offers no such feature. We will handle this in the next sections.
Almost all applications offer a menu where the user can load files, save files, use the clipboard and do a lot of other stuff with his data. Eiffel-IUP also offers this resource to the applications. Menus are divided into four different interface elements: IUP_MENU_ITEM, IUP_MENU, IUP_MENU_SEPARATOR, IUP_SUBMENU.
IUP_MENU_ITEM creates a single item of the menu interface element. When selected, it generates an action.
IUP_MENU_SEPARATOR creates a horizontal line that will appear between two menu items. It is normally used to divide and arrange different groups of menu items.
IUP_SUBMENU creates an item that, when selected, opens another menu.
IUP_MENU creates the menu element by itself as a list of elements. An IUP_MENU can include any number of the other 3 types of menu interface elements: IUP_MENU_ITEM, IUP_SUBMENU and IUP_MENU_SEPARATOR.
Let's add a menu with a few items in our example.
class SIMPLE_NOTEPAD_2
inherit
IUP_INTERFACE
create {ANY}
make
feature {ANY}
exit_cb (widget: IUP_MENU_ITEM): STRING
do
Result := "IUP_CLOSE"
end
make
local
i: STRING
gui: IUP
ml: IUP_MULTILINE
v: IUP_VBOX
w: IUP_DIALOG
item_exit, item_open, item_save_as: IUP_MENU_ITEM
sep: IUP_MENU_SEPARATOR
file_menu, menu: IUP_MENU
sub1_menu: IUP_SUBMENU
do
gui := iup_open
create ml.multiline
ml.expand_both_directions
create v.vbox({ARRAY[IUP_WIDGET]} << ml >>)
create item_open.item("Open")
item_open.set_hide_mark(True)
create item_save_as.item("Save As")
item_save_as.set_hide_mark(True)
create item_exit.item("Exit")
item_exit.set_hide_mark(True)
item_exit.set_cb_action(agent exit_cb)
create sep.separator
create file_menu.menu({ARRAY[IUP_MENU_ELEMENT]} << item_open, item_save_as, sep, item_exit >>)
create sub1_menu.submenu("File", file_menu)
create menu.menu({ARRAY[IUP_MENU_ELEMENT]} << sub1_menu >>)
create w.dialog(v)
w.set_menu_widget(menu)
w.set_title("Simple Notepad")
w.set_predefined_size("QUARTER", "QUARTER")
i := w.show_predefined_xy("IUP_CENTER", "IUP_CENTER")
w.set_user_size(0, 0)
gui.main_loop
gui.close
end
end
Now our example has a few menu element variables and declarations. Also, we used our exit_cb callback to be called when the item_exit menu item is selected. Notice we set the hide mark attribute to True since, be default, menu items show a check box. The next line shows the composition of an IUP_MENU called file_menu. Note that the menu items are passed in order of appearance, which means that item_open will appear above item_save_as and so on. There is also an IUP_SEPARATOR dividing our file menu in two parts, the first takes items that deal direct with files, like open and save, and the second takes the exit item. It's not mandatory to have an IUP_SEPARATOR in your menu. This is used just to keep things more organized. Next line is a little tricky. We created a submenu to store all of our items. Why not use file_menu directly? We could, but it would be used as main menu and would end up being the only menu available in our application. It's a good practice to separate menus in submenus and then pass these submenus as items of the main menu. By doing so, an application could have a file menu, a search menu, a help menu, and others as items of the main menu, as you can see in the main menu declaration on the next line.
At last, once we are done building the main menu, we must set the menu attribute of the main dialog as the menu we have just created.
You should notice that the exit menu item works fine, as we set the Exit menu item action callback, but Open and Save still don't work. That's because we didn't set any callback for them. Those callbacks will use another Eiffel-IUP feature, which is the subject of our next section.
In the previous section, we added a file open and a file save menu items, but they had no callbacks associated. That's because we will use new Eiffel-IUP resources to deal with file handling. These resources are called Pre-defined Dialogs.
Some dialogs are commonly found in a lot of different applications like file selection dialogs, font selection dialogs, color selection dialogs, etc. It would be annoying to have to build the same dialog again every time we need to select a file, or to select a color or a font. So, Eiffel-IUP provides pre-defined dialogs with all the necessary controls to deal with these common tasks.
We will update our last example to handle file input/output and to make use of these Eiffel-IUP pre-defined dialogs.
See code: simple_notepad_3.e
We will need to access the multitext control from inside the menu callbacks. There are many ways to do that; the simplest one is to declare it as an attribute. We will do that to illustrate this example. In the next example, we will show you how to not use an attribute to obtain the same results.
Now we have interesting new functions. First, let's take a look at the new callback called open_cb. This callback will handle the file opening when the user clicks on the Open menu item. For this we will use an Eiffel-IUP predefined dialog of class IUP_FILE_DIALOG. This dialog is a standard file handling dialog with all the features that we need to select a file from the file system, and it will also save a lot of work. Inside the callback we create our IUP_FILE_DIALOG, and set it to be an "OPEN" dialog with feature set_dialog_type. Also we set ext filter attribute to "Text Files|.txt|All Files|.*|", since we want our application to handle text files but we leave the option for listing other files.
Now we use the feature popup_predefined_xy to display the file dialog, which is a function similar to show_predefined_xy, but it restricts the user interaction only in the specified dialog. It is the equivalent of creating a Modal dialog in some toolkits. Its arguments are predefined x and y coordinates that we defined as the center of the screen with "IUP_CENTER".
Then we have a conditional test to check if the dialog was displayed successfully, if this is the case we get the status of the dialog. Once our file dialog returns a valid status (0 mean the user select the OK button), we are able to recover the name of the selected file using the feature get_value. Then we read the file using a simple function and fill in its contents on the multitext control by using the feature set_value (the string is duplicated internally).
Now we are done with this dialog. You can simply execute destroy to remove the file dialog from memory, because we will not need it anymore.
Next there is another callback, saveas_cb, which will select a file name for saving the content of a file. It is very similar to open_cb, but we set the type dialog to "SAVE", so this time it will select a file name for saving. In this case the filename can be also for a new file, if an existing file then the user will be notified of overwriting so it can cancel and start over. After selecting the filename we are going to save the multitext contents to the file.
Now comes the font_cb callback that, as you may have already guessed, will call a predefined dialog to select a font. To do that, we use the class IUP_FONT_DIALOG instead of IUP_FILE_DIALOG. To set the font, just change the font attribute in the multitext control.
The next callback is about_cb, which does nothing special, just display a dialog of class IUP_MESSAGE to display a text to the user.
The following lines don't show anything new, except for the new callbacks registration. But notice that we added "..." to the text of the menu items in which a dialog is open. This is not mandatory, but is highly recommended by common User Interface Guidelines.
Finally, we now have a brand new text editor using Eiffel-IUP. But what happens if the dialog that your application needs is not provided by IUP as a predefined dialog? That will be the subject of our next section.
We saw in the previous section that Eiffel-IUP provides predefined dialogs that can be used by the applications to save a lot of developing time. But if the dialog your application needs is not one of Eiffel-IUP's predefined dialogs, then it's time to built your own dialog. The good news is that you have already made this when building your main dialog. The tricky part here is how to handle more than one dialog at the same time.
For this we will add two new items to our Edit menu: Find and Go To. Find will search the multitext contents while looking for a string and highlight it when found. It will search for this string many times, and the search can also be case sensitive. Go To will position the caret to a specific line in the text.
The first change is the inclusion of one utility function (str_find) that will be used to implement Find, and which are not the object of this tutorial. If you want to understand what is inside this function, take a closer look into the code.
You will notice that several functions had their names changed from the previous example code. We did that to illustrate the importance of function nomenclature in a larger project, so that several callbacks can be easily associated with their respective control. For instance, open_cb became item_open_action_cb, save_as_cb became item_save_as_action_cb, and so on.
Allow me to make a jump in our code and please refer now to the item_find_action_cb function. This callback, despite being almost at the end of the code, is responsible for building one of our custom dialogs. In this dialog, we will use some elements that we have already seen in previous sections and others new: first we will use a text field of class IUP_TEXT (like IUP_MULTILINE but just for one line) to receive the string that the user wants to find, a button to find the next occurrence of this string, a button to close our find dialog, and other two new Eiffel-IUP elements: IUP_TOGGLE and IUP_FILL.
IUP_TOGGLE is a two-state (on/off) button that, when selected, execute a callback. Toggles are normally used to set flags. In this case, we used it to allow the user to decide if the search will be case sensitive or not.
Notice we set the attribute name in IUP_TEXT and IUP_TOGGLE objects. Later you will see the reason of this.
IUP_FILL is a very peculiar element. It is, as the name says, used to fill blank spaces inside our dialog. In other words, it positions and aligns Eiffel-IUP elements. The best way to understand IUP_FILL is to think of it as a coil spring. If you put an IUP_FILL inside an IUP_HBOX, it will expand between the two elements, pushing one to the left and the other to the right. Or if you put it in an IUP_VBOX, above an element, it will push the element all the way down. But IUP_FILL also has a size attribute, that can be used to control how much space will be taken. With experience we will find the correct way to define sizes for IUP_FILL and for other elements as well. In our case, IUP_FILL is being used to push the buttons Find Next and Close to the right, inside our hbox.
Note that our new dialog has a lot of new parameters set. modal dialog frame will remove minbox, maxbox, and it will resize from the corner of the dialog. This will provide a reduced functionality and a standard dialog box appearance. default ENTER defines a button to be activated when the users presses ENTER, in this case it will have the same effect as pressing the next_bt button. default ESC works the same way for the ESC key by activating the close_bt button. Next the attribute parent dialog sets the dialog that holds item_find (our main dialog) as the parent of our new dialog, by using get_dialog, which returns the dialog that contains the element. This will maintain the Find dialog always on top of the main dialog, even if we change the focus to the main dialog. It will also allow us to set the find dialog position at the center of the parent dialog.
In the next line, we use a custom attribute to store an application widget. Each Eiffel-IUP element can hold as many custom attributes as you want (widgets, strings or integers). If your application needs to store some information to be retrieved later, you can just set it as we are doing here. We created a new attribute called FIND_DIALOG in the element find_item, so we will be able to reuse this dialog. Everytime this function is called, the dialog is not created again, since it is created only once.
Next we show our dialog using show_predefined_xy and pass IUP_CURRENT to it. At first, this will center the dialog according to its parent (main dialog as we defined above). Next time it will reuse the last position, since the dialog will not be destroyed when closed.
Now that we have built the Find dialog, it is time to write the callbacks that will effectively do the job to find the string inside our multitext.
Let's turn our attention to the find_next_action_cb callback. This callback is responsible for finding the next occurrence of our string inside the multitext and it has a lot new function calls. We call to get_dialog_child (at button passed as parameter), which is a function that returns the identifier of the child element that has the name attribute in the same dialog hierarchy. We use this to retrieve the IUP_TEXT and IUP_TOGGLE widgets, but it only works for the same dialog. Next we retrieve the text to be found and the case sensitive flag from the respective controls. The search is performed, and if the result is positive, we will save the last found position in a custom attribute, and call set_focus. When we showed our Find dialog, we moved the focus from our multitext to the new dialog. This function restors the focus to the multitext. We then select the text on the multitext. Next we find two calls to text_convert_pos_to_lin_col and text_convert_lin_col_to_pos. These are used to compute the position we use to scroll the multitext, so the selection becomes visible.
Beside next_bt, find dialog also has close_bt, and it also demands a callback. find_close_action_cb closes the Find dialog. In this callback, we made a call to hide. When a dialog is hidden, it is not destroyed, so you can show it again.
The Go To dialog will work in the same way. If you have understood how to create the Find dialog, you should be able to build the Go To dialogs. Notice we change the dialog size, instead "QUARTER" now we use "HALF".
Now that we saw how to use predefined dialogs or how to build our own dialogs, lets see how to implement two other resources present in many other applications: toolbars and statusbars.
Toolbars are a set of buttons, usually positioned side by side in the top of the dialog, just bellow the menu. To build our toolbar, we will use the attribute image of IUP_BUTTON. As in predefined dialogs, Eiffel-IUP also offers a series of predefined images to be used with buttons. These images are part of an additional library called IupImageLib. To use this library, call load_images right after iup_open.
Statusbars normally appear on the bottom of the dialog and usually show some information about what is happening inside the application. To build our statusbar, we will use a set of IUP_LABEL controls arranged side by side. In our statusbar, we will be displaying the caret position in the text, and to achieve this, we will use a IUP_MULTILINE callback called caret, which is called every time the caret position is changed.
See code: simple_notepad_5.e
The first change, as told above, is the inclusion of multitext_caret_cb to our callbacks (IUP_MULTILINE inherits from IUP_TEXT, and all his callbaks are inherited, so the first parameter in the callbacks is of class IUP_TEXT). In this callback, we will make use of the parameter received by the callback. First we retrieve our widget of IUP_LABEL called lbl_statusbar using get_dialog_child in this parameter. Next, we set the label's title by building a string using lin and col parameters, in which the callback provides the caret line and column position.
From this new callback, we will jump to main function where the next change appears. Just after iup_open, you will find a call to load_images. This function will load the image library, so we can use its images in our toolbar.
A few lines after, we will find our lbl_statusbar declaration. This label will play the role of our statusbar. It needs the expand attribute set to HORIZONTAL, so it will occupy all the horizontal space inside the verticall box. Following we will see some button declarations (btn_open, btn_save and btn_find) and some calls to set_image setting each button's image. The images names can be found at the IupImageLib documentation. We then notice that our toolbar is nothing more than an IUP_HBOX containing those buttons. Notice that since we do the same thing from a menu item and a button, we move the code to a new features (open_dialog, save_as_dialog and find_dialog) and call these features from the menu items and buttons callbacks. We do these because we can't set the same callback for a menu item and a button (one receive an IUP_MENU_ITEM widget and the other an IUP_BUTTON). Also notice the changes at callback item_find_action_cb, now we save the custom attribute FIND_DIALOG at the main dialog in the application, so we can retrieve it in the button or menu item callback. We also set the flat attribute for the buttons so their border is removed, and they will look like toolbar buttons. We set the can focus attribute to False for the buttons so they will not receive the keyboard focus as toolbar buttons behave.
The final change will be the inclusion of toolbar_hb and lbl_statusbar in the vertical box that already had our multitext. The toolbar comes first because it is a vertical box and we want it above the multitext. lbl_statusbar goes after because we want it bellow the multitext. That's all. Our application now has both a toolbar and a statusbar. In the next section, we will improve it even more by adding hot keys to our menus.
Applications that have menus always present hotkeys to its users. Eiffel-IUP also offers this resource. To define a hotkey, you could use IUP_DIALOG callback K_ANY. This is a callback common to a lot of Eiffel-IUP elements and is called when a keyboard event occur.
See code: simple_notepad_6.e
Here we set as K_ANY callback the feature key_pressed, that receive two parameters, the dialog and the code of the pressed key, we compare this code with the code returned by IUP feature x_key_ctrl which return the correponding code of combinations of type Ctrl+?, where ? is the key we want use. Then we execute the correspondig feature to each key. Notice how we deal with the call of item_goto_action_cb callback from the callback key_pressed (we add this here just to show different approaches to do the same thing from different callbacks).
This example didn't change much. We just added "%TCtr+?" to each menu item that has a hotkey. Character "%T" will take care of aligning our hotkey text to the right (sometimes you will need add some extra spaces after the item title), and the rest of the string will tell the user which key combinations to press. Note that "?" should be replaced by the key (uppercase) you want.
Since we are improving the user keyboard experience, there is another feature that we can use to aid users. Using the ampersand (&) character in the menu item text, we define a key that can activate the menu item. The next character following the ampersand will be the key. The main menu is reached using the Alt+key combination, for instance Alt+F will activate the File menu. Once the menu is opened, use the 'O' key to activate the file Open menu item. Another example is the Alt+F then 'X' key combination to exit the application; many applications have this key combination enabled.
Finally we add the tip attribute for the toolbar buttons so they will also show the key combination that activate its feature.
Many text editors offer a menu item that holds a list of recent files. We will use an Eiffel-IUP resource called IUP_CONFIG to implement this list and also store other configuration variables. IUP_CONFIG implements a group of functions to load, store and save application configuration variables. For example: the list of Recent Files, the last position and size of a dialog, last used parameters in dialogs, etc. Each variable has a key name, a value and a group that it belongs to.
See code: simple_notepad_7.e
Note that in this new example we have included an attibute cfg of class IUP_CONFIG. We will start this analysis from our main function. After creating our config by calling config, we set the attribute app name. This attribute defines the name of our configuration file. In UNIX, the filename will be "(HOME)/.(APP_NAME)", where "(HOME)" is replaced by the "HOME" environment variable contents, and (APP_NAME) replaced by the app name attribute value. In Windows, the filename will be "(HOMEDRIVE)(HOMEPATH)(APP_NAME).cfg", in which HOMEDRIVE and HOMEPATH are also obtained from environment variables.
After that comes a call to load that will load our config file at startup. This function combined with the save function, which we will see later, will allow our configuration variables to be persistent between different application executions.
A few lines bellow we read the Font variable from the config, if any, and set it to the multiline control. Notice that in item_font_action_cb callback, now we save the selected font at config.
Following, a few lines bellow, we create the recent_menu that will hold our recent items inside. You will see that it works as any other menu creation, except by the fact that we will not add any menu items. They will be provided by a function that we will see soon. We positioned our recent_menu (inside a IUP_SUBMENU) above the item_exit menu item and bellow another IUP_SEPARATOR.
After the menu is created, there is call to set_recent_file_menu. This function is responsible for initializing the recent_menu items from the configuration file entries. The config_recent_cb callback will be called when the user selects a file in the recent list. This feature also defines the number of recent files that will be stored and displayed. In our example, we choose to store 10 files. Also note that both item_open_action_cb and item_saveas_action_cb should change the recent files list. So a call to update_recent_file_menu is necessary to maintain our recent files list updated.
Next line shows a call to show_dialog that replaces show/show_xy/show_predefined_xy. This function will also show the dialog, but it will try to use the last position and size stored in the configuration file. It can be used for any application dialog, just use different names for each dialog.
The feature save_dialog is used to save the last dialog position and size when the dialog is about to be closed, usually inside the dialog close callback, or when the dialog is programmatically hidden. The close callback is called when the user clicks on the dialog close button, usually a 'X' at the top right corner of the dialog. So we set the feature close_dialog_cb as the close callback of our dialog. Inside it we save the dialog postion and size, save the config data calling save and finally destroy it by calling destroy. We do something similar at the item_exit_action_cb callback.
That's all for the main function, so let's turn our attention to the item_recent_cb callback. This callback, as said before, is responsible for handling the selection of a recent file at the menu_recent. Inside it, we get the file name from the title attribute and open it in the same way we do in item_open_action_cb.
Next, we will find some callbacks that handle copy, cut, paste, delete and select all of the new items added to the Edit menu, and a callback to manage activation and deactivation of these items. All are very short callbacks.
item_copy_action_cb, item_paste_action_cb and item_cut_action_cb use a resource called IUP_CLIPBOARD, which is a class that allows access to the clipboard, calling get_clipboard feature that is defined at IUP_INTERFACE. The item_copy_action_cb callback retrieves the selected text attribute from our multitext and copy it to the clipboard using add_text to copy the text selection. item_paste_action_cb retrieves the clipboard using get_text and insert it (paste) in the multitext, where the cursor is positioned, using the insert feature. item_cut_action_cb is almost the same code as copy, except by the fact that it sets set_selected_text to "", removing the selected text from the multitext. item_delete_action_cb does the same as cut, but without using the clipboard. item_select_all_cb call geature select_all, selecting all the text inside the multitext. We don't need add code in callback key_pressed for the hot keys in cut, copy, paste,... since all these are supported in IUP_MULTILINE.
Another callback was created to deal with the initialization of our new menu items. edit_menu_open_cb is associated to the edit_menu's cb_open callback. It is called each time the edit_menu is opened and it will set the cut, paste, copy, and delete items as active or inactive, depending on some conditions. First, it is necessary to obtain the handles of these items. We use the name attribute of each item and the ger_dialog_child feature for this propose, just like we did before for some buttons. We then test if there is text available in the clipboard by calling is_text_availavle. So, if it returns False, it means there is no text in the clipboard, or in other words, there is nothing to paste. Then the Item paste should be disable, by setting set_active to "False". Otherwise, the user should be able to paste, and we should set set_active to "True". The other items follow the same idea, but this time checking the content of the attribute selected text. If there is nothing selected, you can disable cut, copy and delete items. Otherwise, you can enable all items.
See code: simple_notepad_8.e
Now we would like to be able to hide and show some of the complementary dialog elements, such as the Toolbar and the Statusbar. If you simply set their visible attribute to "False", they will be hidden, but their size in the dialog layout will still be reserved, and an empty space will be displayed instead. To avoid it use the floating attribute, along with the visible attribute, then they will be hidden, and its space will be used by the multitext.
To implement this feature, we added a new submenu called "View" at the main menu, with two new items. One for controlling the Toolbar visibility, and another for controlling the Statusbar visibility. And we use the IUP_CONFIG class to store this selection to be persistent between different application executions.
The first change is the inclusion of the features item_toolbar_action_cb and item_statubar_action_cb that handles the changes in visibility in our toolbar and statusbar. When an item in View menu is pressed, if it was checked or, in other words, the bar is visible, set the bar floating attribute to "True", visible to "False" and the item value to False, to hide the bar. If it is not visible, do the opposite. After that, it is necessary to call to refresh to recompute the dialog layout. In both features we call set_variable_string at IUP_CONFIG to store the item state.
The next change will appear only in make function, and it will be the declaration of our new View submenu and its items, and the new callbacks associations.
See code: simple_notepad_9.e
One additional feature that our text editor can have is an external help. Eiffel-IUP shows an external help by simply calling the help feature. It will show an Internet browser in the given page, so the application can display some documentation to the user. In our example, it is just a menu item that activates the item_help_action_cb callback that calls the help feature. This function shows the eiffel-iup website, but it can also show a local HTML file. In Windows, that function is even more flexible allowing opening any kind of document provided that it is associated with an application.
See code: simple_notepad_10.e
Tämä poistaa sivun "5. Simple Notepad"
. Varmista että haluat todella tehdä tämän.