Салимóненко Дмитрий Александрович

Операционные системы

Задание 8: Создание графического интерфейса пользователя (GUI), часть 2

(C++, Linux)

Введение

Это задание является продолжением задания 7 - 2-я часть. Здесь мы продолжим изучения графической библиотеки FLTK (Linux), попытаемся приспособить программу из предыдущего задания для создания GUI для консольной системной оболочки. Т.е. попробуем соорудить нечто похожее на графический интерфейс для задания по Операционным системам «Создание консольной системной оболочки». Точнее, не весь интерфейс, а лишь подходы к его реализации. Во многом это задание напоминает задание 7. В частности, в дополнение к последнему, в этом задании продемонстрированы:

Вот исходный текст программы:

  1. // g++ -std=c++11 fltk4.cpp -o fltk4 `fltk-config --cxxflags --ldstaticflags`  && ./fltk4
  2. #include <FL/Fl.H>
  3. #include <FL/Fl_Window.H>
  4. #include <FL/Fl_Box.H>
  5. #include <FL/Fl_Button.H>
  6. #include <FL/Fl_Light_Button.H>
  7. #include <FL/Fl_Text_Display.H>
  8. #include <FL/Fl_Select_Browser.H>
  9. #include <FL/Fl_Double_Window.H>
  10. #include <FL/fl_ask.H>
  11. #include <FL/Fl_Menu_Bar.H>
  12. #include <FL/Fl_Input.H>
  13. #include <FL/Fl_Button.H>
  14. #include <FL/Fl_Return_Button.H>
  15. #include <FL/filename.H>
  16. #include <FL/Fl_Output.H>

  1. #include <iostream>
  2. #include <cstring>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <unistd.h>  
  6. #include <sys/types.h>
  7. #include <dirent.h>
  8. #include <errno.h>

  1. using namespace std;

  1. Fl_Text_Buffer *buff; // = (Fl_Text_Buffer *)0;
  2. Fl_Light_Button *but; //=(Fl_Light_Button *)0;
  3. Fl_Text_Display *disp; //  = (Fl_Text_Display *)0;
  4. Fl_Output *field;
  5. Fl_Select_Browser *browser;

  1. string ex = "";
  2. char* str;
  3. const char* file_name ;
  4. int i = 0;
  5. string iStr;

  1. char  filename[FL_PATH_MAX] = "";
  2. Fl_Text_Buffer     *textbuf = 0;
  3. int  changed = 0;



  1. // functions and class...
  2. void copy21_cb(Fl_Widget*, void* v);
  3. void copy22_cb(Fl_Widget*, void*);
  4. void cancel_cb(Fl_Widget*, void*);

  1. class CopyWindow : public Fl_Window { // Класс для (всплывающего) окна, выполняющего копирование файла
  2.   public:
  3.     CopyWindow(int x, int y, int w, int h,  const char* file_name);
  4.     ~CopyWindow();

  1.     Fl_Window          *copy_dlg; // "Обычное" окно
  2.     Fl_Input           *copy_input; // Поле ввода символом (данных)
  3.     Fl_Input           *copy_output;
  4.     Fl_Button          *copy_21;
  5.     Fl_Return_Button   *copy_22; // Эта кнопка будет отображаться с изображением стрелки и она будет срабатывать по умолчанию (если нажать клавишу Enter)
  6.     Fl_Button          *copy_cancel;
  7. };

  1. CopyWindow::CopyWindow(int x, int y, int w, int h, const char* file_name) : Fl_Window(x, y, w, h, file_name) { // Конструктор
  2.   copy_dlg = new Fl_Window(350, 105, "Копируем файл:"); // Окно для выполнения операции копирования
  3.     copy_input = new Fl_Input(130, 10, 210, 25, "Исходное имя:");
  4.     copy_input->align(FL_ALIGN_LEFT);

  1.     copy_output = new Fl_Input(130, 40, 210, 25, "Новое имя:");
  2.     copy_output->align(FL_ALIGN_LEFT);

  1.     copy_21 = new Fl_Button(10, 70, 120, 25, "Копировать 2.1");
  2.     copy_21->callback((Fl_Callback *)copy21_cb, this);

  1.     copy_22 = new Fl_Return_Button(135, 70, 120, 25, "Сopy 2.2");
  2.     copy_22->callback((Fl_Callback *)copy22_cb, this);

  1.     copy_cancel = new Fl_Button(265, 70, 60, 25, "Cancel");
  2.     copy_cancel->callback((Fl_Callback *)cancel_cb, this);
  3.     copy_dlg->set_modal(); // Делаем окно модальным
  4.   copy_dlg->end();
  5. }

  1. CopyWindow::~CopyWindow() {  // Деструктор
  2.   delete copy_dlg;
  3. }


  1. void copy1_cb(Fl_Widget* w, void* v) { // Процедура, выполняющая копирование файла 1-м способом
  2.   const char *val;
  3.     val = fl_input("Исходный файл:", file_name);
  4.     if (val != NULL) {
  5.     fl_alert("Выбран файл: \'%s  Новое имя файла: %s\' ", file_name, val);
  6. // А здесь, зная имена исходного (т.е. который выбран) и нового файлов, добавьте процедуру копирования (как обычно, создаем новый файл, открываем исходный, читаем его, записываем в новый файл и т.д.).
  7.     }
  8.   return;
  9. }


  1. void copy_cb(Fl_Widget*, void* v) { // Процедура создания окна для выполнения операции копирования
  2. //  CopyWindow* e = (CopyWindow*)v;
  3.     CopyWindow* e = (CopyWindow*)v;
  4.     e->copy_dlg->hide(); // Закрываем окно на всякий случай, если оно было открыто (если окна не было, ничего не произойдет)
  5.     e->copy_dlg->show(); // Показать окно
  6.     e->copy_input->value(file_name) ; // Устанавливаем начальное значение окна ввода исходного имени файла
  7. }


  1. void copy21_cb(Fl_Widget*, void* v) { // Макет для процедуры копирования 1-м способом
  2.   CopyWindow* e = (CopyWindow*)v;

  1. // e->copy_input->value(file_name) ;
  2. const char *inputFile = e->copy_input->value();  // Если в copy_input введено (вручную) новое исходное имя, то inputFile станет равным этому новому имени
  3. const char *copy = e->copy_output->value();

  1.     fl_alert("%s  !  %s", inputFile, file_name);

  1.     if (inputFile[0] == '\0') {
  2.     // Filename string is blank; get a new one...
  3.     e->copy_dlg->show();
  4.     return;
  5.     }
  6. // Здесь, зная имена исходного (т.е. который выбран) и нового файлов, добавьте "обычную" (т.е. так, как делали ее в консольной программе) процедуру копирования (как обычно, создаем новый файл, открываем исходный, читаем его, записываем в новый файл и т.д.).
  7.     e->copy_dlg->hide();
  8.     fl_alert("Файл успешно скопирован 2.1-м способом");
  9.   return;
  10. }


  1. void copy22_cb(Fl_Widget*, void* v) { // Макет для процедуры копирования 2-м способом
  2.   CopyWindow* e = (CopyWindow*)v;
  3.   const char *inputFile = e->copy_input->value();
  4.   const char *copy = e->copy_output->value();

  1.     if (inputFile[0] == '\0') {
  2.     // Filename string is blank; get a new one...
  3.     e->copy_dlg->show();
  4.     return;
  5.     }
  6. // Здесь также добавьте процедуру копирования файла, но функционирующую иначе. А именно, следует из этого места вызвать консольную системную оболочку, передав ей, в качестве параметров, имя команды копирования, имена исходного и конечного файлов. После успешного выполнения копирования работа оболочки должна быть завершена. Вывод системной оболочки перенаправьте в fl_alert.
  7. // Вызов консольной системной оболочки можно сделать, создав для нее новый поток и применив функцию типа exec. А можно и через fork(), путем создания нового процесса; в этом случае для обмена данными между оболочкой и данной программой можно использовать, например, сокеты UNIX или именованные каналы (pipes). Правда, в последнем случае придется вносить изменения в системную оболочку. Идеология запуска другой программы через fork() показана, к примеру, в задании по програмной инженерии, а также в этом задании.
  8.     e->copy_dlg->hide(); // Удаляем окно
  9.     fl_alert("Файл успешно скопирован 2.2-м способом");
  10.   return;
  11. }


  1. void cancel_cb(Fl_Widget*, void* v) { // Кнопка "Cancel"
  2.   CopyWindow* e = (CopyWindow*)v;
  3.   e->copy_dlg->hide(); // Удаляем окно выполнения копирования с экрана
  4. }


  1. void changer(Fl_Widget* w,   void *){ // Процедура рисования самого GUI (экран, кнопки, окна)
  2.   Fl_Widget* p = w->parent(); // Обращаемся к родительскому виджету, т.е. к главному окну
  3.     p->color(p->color() == FL_WHITE ? FL_BLUE : FL_WHITE); // Меняем цвет при очередном нажатии кнопки
  4.     p->redraw(); // Перерисовываем главное окно

  1. printf(" Fl_Light_Button = %d \n", ((Fl_Light_Button*)but)->value()); // Флаг нажатия кнопки (0 или 1) выводим в консоль (если программа запущена из консоли)
  2.   i=i+1; // Предыдущее значение i будет сохраняться при очередном запуске процедуры changer
  3.     iStr = to_string(i); // Работает в -std=c++11
  4.     ex = "№ нажатия: " + iStr + "\n";
  5.   str = (char*)ex.c_str();

  1. DIR *dir;
  2. int u;
  3. struct dirent *sdir;
  4. dir=opendir(".");
  5. if(dir==NULL) {cout<<"Error: "<<strerror(errno)<<"\n"; }
  6. chdir("."); // Или в другой каталог - тогда следует указать к нему путь
  7.     browser->clear(); // Очищаем окно "браузера", т.к. иначе там будут оставаться данные, помещенные в процессе предыдущих срабатываний функции changer

  1. while((sdir=readdir(dir))!=NULL) // Как обычно, читаем каталог до тех пор, пока ядро не вернет функции readdir значение NULL. А это случится после того, как будет прочитан последний элемент каталога и более нечего будет читать
  2.   {
  3. // if(strcmp(".", sdir->d_name)==0 || strcmp("..", sdir->d_name)==0)
  4. // continue;
  5.     ex = ex + sdir->d_name + "\n";
  6.     str =  (char*)ex.c_str();

  1.     browser->add(sdir->d_name, 0); // Добавляем в окно "браузера" очередную строку текста
  2.   }
  3.   cout << ex << "\n" ; // Выводим в консоль содержимое (текущего) каталога
  4.   closedir(dir);

  1. disp->buffer(buff); // прикрепляем текстовый буфер к display widget
  2. buff->text((const char*)str); // добавляем текст (cодержимое каталога сразу, в виде единой строки) в текстовый буфер
  3. //   buff->text("rfrfrfrf"); // Попробуйте раскомментировать: несмотря на дублирование команды, добавление текста в буфер сработает только 1 раз при каждом очередном срабатывании функции changer
  4. }

  1. void b_cb(Fl_Widget* o, void*v) { // Вывод в консоль информации о строке "браузера" при клике мышью на ней
  2. int browLine = ((Fl_Browser*)o)->value();
  3.   printf("callback, selection = %d, name = %s, event_clicks = %d\n",
  4.   browLine, ((Fl_Browser*)o)->text(browLine), Fl::event_clicks()); // Выводим в консоль номер строки окна "браузера", имя элемента каталога, содержащееся в строке, по которой был сделан клик кнопкой мыши в окне "браузера" и число БЫСТРЫХ кликов мышью по одной и той же строке. Т.е. можно идентифицировать одиночные, двойные, тройные, четверные и т.д. клики.
  5.   file_name = ((Fl_Browser*)o)->text(browLine);
  6. }


  1. void showFile_cb(Fl_Widget*, void*) { // Выводим имя файла
  2.   cout << "Нажата кнопка Вывести имя файла . По ее нажатию в консоль выводится число нажатий кнопки с надписью \"Нажмите!\" : " << "   " << iStr <<endl;
  3.     field->value(file_name); // Выводим имя элемента каталога в текстовое поле
  4.   return;  
  5. }

  1. Fl_Menu_Item menuitems[] = { // Меню "File"
  2.   {"File", 0, 0, 0, FL_SUBMENU },
  3.     {"&Показать имя файла", 0, (Fl_Callback *)showFile_cb },
  4.     // 1-й способ копирования файла
  5.     {"1 &Copy File", FL_ALT + 'c', (Fl_Callback *)copy1_cb, 0, FL_MENU_DIVIDER },
  6.     // 2-й способ копирования файла
  7.     {"2 C&opy File", FL_COMMAND + 'o', copy_cb },
  8.     {0},
  9.   {0}
  10.   };

  1. Fl_Window* new_view(char* str) { // Вывод окна программы
  2.   CopyWindow* window = new CopyWindow(100, 100, 700, 600, (const char*)file_name);
  3.   window->begin();

  1.     Fl_Box *box = new Fl_Box(20,30,350,120,"Это  \n просто  \n надпись");
  2.   box->box(FL_UP_BOX); // Определяем тип
  3.   box->labelfont(FL_BOLD+FL_ITALIC); // и параметры шрифта в box
  4.   box->labelsize(22);
  5.   box->labeltype(FL_SHADOW_LABEL);

  1.   window->color(FL_WHITE); // Первоначально устанавливаем белый цвет главного окна

  1. //Fl_Text_Buffer *
  2.   buff = new Fl_Text_Buffer();
  3. //Fl_Text_Display *
  4.   disp = new Fl_Text_Display(420, 40, 170, 280);
  5.     disp->buffer(buff);
  6.     buff->text(str);

  1.     but =  new Fl_Light_Button(40, 180, 170, 50, "Нажмите!");
  2.     but->callback(changer);

  1. browser = new Fl_Select_Browser(40,400,200,150,0);
  2. browser->type(FL_MULTI_BROWSER);
  3. browser->callback(b_cb); // Устанавливаем обработчик клика мыши на окне "браузера"

  1.     field = new Fl_Output(90, 350, 600, 25, "Имя файла:"); // Текстовое поле вывода
  2.     field->value("Нажмите! -> кликните по имени файла -> File -> Показать имя файла");

  1.     Fl_Menu_Bar *m = new Fl_Menu_Bar(0, 0, 640, 30);
  2.     m->copy(menuitems, window);
  3.   return window;
  4. }


  1. int main(int argc, char **argv) {
  2.   ex = "main begining...";
  3.   str = (char*)ex.c_str();

  1.     setlocale(LC_ALL, "UTF-8");

  1.     Fl_Window* window = new_view(str);
  2.     window->label("GUI - title"); // Заголовок окна программы
  3.     window->end();
  4.     window->show(argc, argv);
  5. return Fl::run();
  6. }

Вот такая вот, возможно, немного громоздкая, но идейно простая программа.

Компиляция

Компиляция делается примерно также, как и в задании 7:

g++ -std=c++11 fltk4.cpp -o fltk4 `fltk-config --cxxflags --ldstaticflags`  && ./fltk4

Как и ранее, в одной команде объединены два действия: собственно компиляция и, если она пройдет без ошибок, сразу запуск программы на исполнение.

Как работает программа

Отображение имени файла

Следует нажать кнопку "Нажмите!" сколько-то раз. Затем, нужно кликнуть по имени какого-нибудь файла в окне внизу слева. Пусть это будет, к примеру, файл fltk-1.3.5-docs-pdf.tar.gz. Далее, нажимаем Файл, Показать имя файла. При этом имя файла, по которому кликнули ранее (оно будет отображаться, залитым темно-синим фоном), отобразится также в окне, расположенном примерно посередине (имеющем надпись "Имя файла:").

Так как файл выбран, с ним можно производить копирование. Нажимаем Файл, 1 Copy File. После чего получится примерно следующее:


Копирование файла 1-м способом

Копирование файла 2-м способом

Можно еще несколько раз понажимать кнопку "Нажмите!", для эксперимента. Затем опять кликнем по имени того же самого файла и нажмем Файл, 2 Copy File - для копирования файла 2-м способом. Примерно вот что получится в итоге:

В поле "Новое имя:" можно ввести новое имя файла. Например, qwertyuio. После чего нажать одну из кнопок Копировать 2.1 (для копирования файла 2.1-м способом) или Copy 2.2 - для копирования 2.2-м способом. Понятно, что фактически ни одна из операций копирования выполнена не будет, так как GUI выполнен пока только в виде заготовки, а самих-то процедур, которые, собственно, и выполняют копирование, нет. Эти процедуры следует реализовать и тогда получится полноценная программа - графическая оболочка, выполняющая копирование файла разными способами.



Задание для самостоятельной разработки:

  1. Следует разобраться в работе программы и затем дополнить ее процедурами копирования файла (см. стр. 77) и вызова консольной оболочки через системный вызов exec, передав ей в качестве параметров имя команды копирования, а также имена исходного (input) и конечного (output) файлов (стр.113-114). Т.е. после выполнения у Вас должен получиться полноценный GUI для операции копирования файла.
  2. То же самое попробуйте сделать и для всех остальных операций, реализованных в консольной системной оболочке. Это, в частности: создание, переименование, удаление, вывод свойств файла; создание и удаление каталога. Эти операции выполните вторым способом, т.е. через вызов системной оболочки и затем exec. В результате получится полноценный GUI для всех основных операций по работе с файлами и каталогами. При этом, что важно, графическая часть будет отделена от функциональной.

С уважением, Салимоненко Д.А.