Русский
Русский
English
Статистика
Реклама

Gtk

Пишем онлайн-радио на языке Vala

15.03.2021 06:18:42 | Автор: admin

Привет всем! В своем первом посте я хочу рассказать о создании простого радио на языке Vala. Я ни разу не программист, а скорее любитель и все свои разработки я писал на Java, но так как на компьютере использую GNU/Linux, а именно старый добрый Debian, да еще на GNOME, то подумал, а почему бы не попробовать написать что-нибудь под эту систему.

Подготовка

Сказано сделано! Писать решил в родном текстовом редакторе GEdit. Установил компилятор и пакет для разработки на GTK:

sudo apt install valac libgtk-3-dev

Также нам потребуется пакет GStreamer для разработчиков. Установим и его тоже:

sudo apt install libgstreamer1.0-dev

Идем дальше! В редакторе нужно включить поддержку встроенного терминала и отобразить нижнюю панель, где терминал и появится. Также для удобства следует включить дополнение слов, скобок и другие функции. Они все есть в списке модулей. Найти можно в настройках редактора. Очень удобные штуки. По сути они превращают обычный текстовый редактор в простейшую среду разработки.

Графический интерфейс

Файлы

Итак, редактор у нас готов. С чего начать? Конечно, с интерфейса! GUI будем писать ручками в редакторе. Но сначала о самом коде. У нас будет два файла. Один называется Application.vala и имеет такое содержание:

   namespace Raddiola {    public class Application : Gtk.Application {        public MainWindow app_window;    public Application() {        Object(flags: ApplicationFlags.FLAGS_NONE, application_id: "com.github.alexkdeveloper.raddiola");    }    protected override void activate() {        if(get_windows().length() > 0) {            app_window.present();            return;        }        app_window = new MainWindow(this);        app_window.show_all();    }    public static int main(string[] args) {        Gst.init (ref args);        var app = new Raddiola.Application();        return app.run(args);    }}}

Он предназначен для инициализации и запуска всех компонентов программы. Второй файл называется MainWindow.vala и содержит описание пользовательского интерфейса и логику приложения.

Внешний вид

Программа при первом запуске отображает список станций по умолчанию, который пользователь может изменять по своему усмотрению. Он может удалить станцию, изменить ее или добавить новую.

Все элементы управления, включая кнопки "play" и "stop", я решил разместить в хидербаре. А список станций занял все оставшееся место. Вот так это все выглядит:

Для добавления и изменения станций имеется вторая страница, которая открывается при нажатии на соответствующую кнопку. Вот она:

Хидербар

После объявления всех необходимых компонентов и указания начальных свойств окна, создаем хидербар:

using Gtk;using Gst;namespace Raddiola {public class MainWindow : Gtk.ApplicationWindow {private Stack stack;private Box vbox_player_page;private Box vbox_edit_page;private dynamic Element player;private Gtk.ListStore list_store;private TreeView tree_view;private GLib.List<string> list;private Entry entry_name;private Entry entry_url;private Button back_button;private Button add_button;private Button delete_button;private Button edit_button;private Button play_button;private Button stop_button;private string directory_path;private string item;private int mode;    public MainWindow(Gtk.Application application) {        GLib.Object(application: application,                     title: "Raddiola",                     window_position: WindowPosition.CENTER,                     resizable: true,                     height_request: 500,                     width_request: 500,                     border_width: 10);    }        construct {        Gtk.HeaderBar headerbar = new Gtk.HeaderBar();headerbar.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT);headerbar.show_close_button = true;set_titlebar(headerbar);back_button = new Gtk.Button ();    back_button.set_image (new Gtk.Image.from_icon_name ("go-previous-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    back_button.vexpand = false;add_button = new Gtk.Button ();    add_button.set_image (new Gtk.Image.from_icon_name ("list-add-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    add_button.vexpand = false;delete_button = new Gtk.Button ();    delete_button.set_image (new Gtk.Image.from_icon_name ("list-remove-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    delete_button.vexpand = false;edit_button = new Gtk.Button ();    edit_button.set_image (new Gtk.Image.from_icon_name ("document-edit-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    edit_button.vexpand = false;play_button = new Gtk.Button();    play_button.set_image (new Gtk.Image.from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    play_button.vexpand = false;stop_button = new Gtk.Button();    stop_button.set_image (new Gtk.Image.from_icon_name ("media-playback-stop-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    stop_button.vexpand = false;  

Мы назначили иконки для кнопок в хидербаре. Теперь нам нужно добавить к кнопкам всплывающие подсказки, обработчики нажатия и добавить наконец эти кнопки в хидербар.

        back_button.set_tooltip_text("back");        add_button.set_tooltip_text("add station");        delete_button.set_tooltip_text("delete station");        edit_button.set_tooltip_text("edit station");        play_button.set_tooltip_text("play");        stop_button.set_tooltip_text("stop");        back_button.clicked.connect(on_back_clicked);        add_button.clicked.connect(on_add_clicked);        delete_button.clicked.connect(on_delete_dialog);        edit_button.clicked.connect(on_edit_clicked);        play_button.clicked.connect(on_play_station);        stop_button.clicked.connect(on_stop_station);        headerbar.pack_start(back_button);        headerbar.pack_start(add_button);        headerbar.pack_start(delete_button);        headerbar.pack_start(edit_button);        headerbar.pack_end(stop_button);        headerbar.pack_end(play_button);

Далее, с помощью методаset_widget_visible мы скрываем на время ненужные кнопки:

set_widget_visible(back_button,false);set_widget_visible(stop_button,false);

Код метода:

private void set_widget_visible (Gtk.Widget widget, bool visible) {         widget.no_show_all = !visible;         widget.visible = visible;  }

Стек

Теперь добавим стек, в который будем загружать отдельные страницы приложения. Всего страниц две. Сразу после создания стека загрузим в него список:

stack = new Stack();          stack.set_transition_duration (600);          stack.set_transition_type (StackTransitionType.SLIDE_LEFT_RIGHT);          add (stack);//добавили стек   list_store = new Gtk.ListStore(Columns.N_COLUMNS, typeof(string));//начали создавать список           tree_view = new TreeView.with_model(list_store);           var text = new CellRendererText ();           var column = new TreeViewColumn ();           column.pack_start (text, true);           column.add_attribute (text, "markup", Columns.TEXT);           tree_view.append_column (column);           tree_view.set_headers_visible (false);           tree_view.cursor_changed.connect(on_select_item);   var scroll = new ScrolledWindow (null, null);        scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);        scroll.add (this.tree_view);   vbox_player_page = new Box(Orientation.VERTICAL,20);   vbox_player_page.pack_start(scroll,true,true,0);   stack.add(vbox_player_page);//добавили в стек контейнер со списком

Вторая страница

Теперь можно приступать к созданию второй страницы. Она содержит два текстовых поля и кнопку. В одно поле пользователь вводит название станции, а в другое ее URL. Поля содержат значки, при нажатии на которые поля можно очистить от содержимого.

entry_name = new Entry();        entry_name.set_icon_from_icon_name (Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic");        entry_name.icon_press.connect ((pos, event) => {        if (pos == Gtk.EntryIconPosition.SECONDARY) {              entry_name.set_text("");//очистка поля ввода названия станции           }        });        var label_name = new Label.with_mnemonic ("_Name:");        var hbox_name = new Box (Orientation.HORIZONTAL, 20);        hbox_name.pack_start (label_name, false, true, 0);        hbox_name.pack_start (entry_name, true, true, 0);        entry_url = new Entry();        entry_url.set_icon_from_icon_name (Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic");        entry_url.icon_press.connect ((pos, event) => {        if (pos == Gtk.EntryIconPosition.SECONDARY) {              entry_url.set_text("");//очистка поля ввода URL           }        });        var label_url = new Label.with_mnemonic ("_URL:");        var hbox_url = new Box (Orientation.HORIZONTAL, 20);        hbox_url.pack_start (label_url, false, true, 0);        hbox_url.pack_start (entry_url, true, true, 0);        var button_ok = new Button.with_label("OK");        button_ok.clicked.connect(on_ok_clicked);        vbox_edit_page = new Box(Orientation.VERTICAL,20);        vbox_edit_page.pack_start(hbox_name,false,true,0);        vbox_edit_page.pack_start(hbox_url,false,true,0);        vbox_edit_page.pack_start(button_ok,false,true,0);        stack.add(vbox_edit_page);        stack.visible_child = vbox_player_page;//показываем первую страницу приложения

Логика

Методы для создания дефолтных станций и их показа

Создание GUI завершено. Дальше нам понадобится фабрика для создания объекта player, c помощью которого мы будем воспроизводить станции. Также нам необходима директория где будут хранится станции в виде текстовых файлов. При создании этой директории нужно создать в ней файлы станций по умолчанию и показать этот список пользователю.

 player = ElementFactory.make ("playbin", "play");//создаем нужный объект с помощью фабрики   directory_path = Environment.get_home_dir()+"/.stations_for_radio_app";   GLib.File file = GLib.File.new_for_path(directory_path);   if(!file.query_exists()){     try{        file.make_directory();//создали директорию     }catch(Error e){        stderr.printf ("Error: %s\n", e.message);     }     create_default_stations();//создание станций по умолчанию   }   show_stations();//показываем список станций }

Метод create_default_stations.Создаем два массива. В одном хранятся названия десяти станций, а в другом их URL. С помощью цикла создаем десять текстовых документов. Название документа это название станции, а содержимое документа ее URL.

private void create_default_stations(){          string[] name_station = {"NonStopPlay","Classical Music","Fip Radio","Jazz Legends","Joy Radio","Live-icy","Music Radio","Radio Electron","Dubstep","Trancemission"};          string[] url_station = {"http://stream.nonstopplay.co.uk/nsp-128k-mp3","http://stream.srg-ssr.ch/m/rsc_de/mp3_128","http://direct.fipradio.fr/live/fip-midfi.mp3","http://jazz128legends.streamr.ru/","http://airtime.joyradio.cc:8000/airtime_192.mp3","http://live-icy.gss.dr.dk:8000/A/A05H.mp3","http://ice-the.musicradio.com/CapitalXTRANationalMP3","http://radio-electron.ru:8000/128","http://air.radiorecord.ru:8102/dub_320","http://air.radiorecord.ru:8102/tm_320"};          for(int i=0;i<10;i++){            try {                 FileUtils.set_contents (directory_path+"/"+name_station[i], url_station[i]);              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);             }          }   }

Метод show_stations:

private void show_stations () {           list_store.clear();           list = new GLib.List<string> ();            try {            Dir dir = Dir.open (directory_path, 0);            string? name = null;            while ((name = dir.read_name ()) != null) {                list.append(name);            }        } catch (FileError err) {            stderr.printf (err.message);        }         TreeIter iter;           foreach (string item in list) {               list_store.append(out iter);               list_store.set(iter, Columns.TEXT, item);//показываем список станций           }       }   

Сообщения пользователю и запуск/остановка воспроизведения

Для вывода сообщений пользователю в приложении используется метод alert. Вот его содержимое:

private void alert (string str){          var dialog_alert = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, str);          dialog_alert.set_title("Message");          dialog_alert.run();          dialog_alert.destroy();       }   

Запуск и остановка воспроизведения:

private void on_play_station(){ //воспроизведение         var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a station");               return;           }      string uri;        try {            FileUtils.get_contents (directory_path+"/"+item, out uri);        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }      player.uri = uri;      player.set_state (State.PLAYING);      set_widget_visible(play_button,false);      set_widget_visible(stop_button,true);   }   private void on_stop_station(){//остановка воспроизведения      player.set_state (State.READY);      set_widget_visible(play_button,true);      set_widget_visible(stop_button,false);   }   

Добавление и изменение станций

Нажатия на кнопки добавления и изменения станции:

private void on_add_clicked () {//к добавлению станции              stack.visible_child = vbox_edit_page;              set_buttons_on_edit_page();              mode = 1;              if(!is_empty(entry_name.get_text())){                    entry_name.set_text("");              }              if(!is_empty(entry_url.get_text())){                    entry_url.set_text("");              }  }   private void on_edit_clicked(){//к изменению станции         var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a station");               return;           }        stack.visible_child = vbox_edit_page;        set_buttons_on_edit_page();        mode = 0;        entry_name.set_text(item);//показываем название станции        string url;        try {            FileUtils.get_contents (directory_path+"/"+item, out url);        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }        entry_url.set_text(url);//показываем URL станции   }

Нажатие на кнопку ОК на второй странице приложения. По значению переменной modeопределяем какой режим обрабатывать. Есть два режима: режим добавления и режим изменения станции. Также присутствует логика не позволяющая добавить станцию с уже существующим названием или поменять название станции на уже имеющееся в списке.

private void on_ok_clicked(){         if(is_empty(entry_name.get_text())){            alert("Enter the name");                    entry_name.grab_focus();                    return;        }        if(is_empty(entry_url.get_text())){           alert("Enter the url");                   entry_url.grab_focus();                   return;        }        switch(mode){//выбор по значению переменной            case 0://изменение станции        GLib.File select_file = GLib.File.new_for_path(directory_path+"/"+item);        GLib.File edit_file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip());        if (select_file.get_basename() != edit_file.get_basename() && !edit_file.query_exists()){                FileUtils.rename(select_file.get_path(), edit_file.get_path());                if(!edit_file.query_exists()){                    alert("Rename failed");                    return;                }                try {                 FileUtils.set_contents (edit_file.get_path(), entry_url.get_text().strip());              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);            }            }else{                if (select_file.get_basename() != edit_file.get_basename()) {                    alert("A station with the same name already exists");                    entry_name.grab_focus();                    return;                }                try {                 FileUtils.set_contents (edit_file.get_path(), entry_url.get_text().strip());              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);             }            }            show_stations();            break;            case 1://добавление станции    GLib.File file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip());        if(file.query_exists()){            alert("A station with the same name already exists");            entry_name.grab_focus();            return;        }        try {            FileUtils.set_contents (file.get_path(), entry_url.get_text().strip());        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }        if(!file.query_exists()){           alert("Add failed");           return;        }else{           show_stations();        }        break;      }      on_back_clicked();   }

Удаление станций и другие методы

Для удаления станций применяется такой метод:

private void on_delete_dialog(){       var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a station");               return;           }           GLib.File file = GLib.File.new_for_path(directory_path+"/"+item);         var dialog_delete_station = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL,Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, "Delete station "+file.get_basename()+" ?");         dialog_delete_station.set_title("Question");         Gtk.ResponseType result = (ResponseType)dialog_delete_station.run ();         dialog_delete_station.destroy();         if(result==Gtk.ResponseType.OK){         FileUtils.remove (directory_path+"/"+item);         if(file.query_exists()){            alert("Delete failed");         }else{             show_stations();//показываем список станций после удаления         }      }   }

Для отображения кнопок в хидербаре на разных страницах используются следующие методы:

   private void set_buttons_on_player_page(){//кнопки на странице списка       set_widget_visible(back_button,false);       set_widget_visible(add_button,true);       set_widget_visible(delete_button,true);       set_widget_visible(edit_button,true);   }   private void set_buttons_on_edit_page(){//кнопки на странице изменения/добавления       set_widget_visible(back_button,true);       set_widget_visible(add_button,false);       set_widget_visible(delete_button,false);       set_widget_visible(edit_button,false);   }

Для получения содержимого позиции в списке нам нужна переменная item. Вот код, который ее перезаписывает каждый раз когда пользователь выбирает какую-нибудь станцию:

private void on_select_item () {           var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               return;           }           TreePath path = model.get_path(iter);           var index = int.parse(path.to_string());           if (index >= 0) {               item = list.nth_data(index);//записываем значение позиции в списке в переменную           }       }

Следующий код возвращает пользователя назад к списку станций:

private void on_back_clicked(){       stack.visible_child = vbox_player_page;       set_buttons_on_player_page();   }

Для определения пустоты текстового поля используется метод is_empty:

private bool is_empty(string str){        return str.strip().length == 0;      }

Перечислитель для списка:

private enum Columns {           TEXT, N_COLUMNS       }

Компиляция и запуск

Чтобы скомпилировать приложение нужно в терминале перейти в директорию исходников программы и ввести такую команду:

valac --pkg gtk+-3.0 --pkg gstreamer-1.0 Application.vala MainWindow.vala

Для запуска приложения командуем:

./Application

Ссылка на репозиторий GitHub: https://github.com/kalexal-kaa/gtk-radio

Ссылка на SourceForge: https://sourceforge.net/projects/gtk-radio/

Вот пожалуй и все! Если пост понравится, то напишу и про другие свои разработки.

Подробнее..

Программа для создания desktop-файлов

31.03.2021 16:16:13 | Автор: admin

В дистрибутивах GNU/Linux значки приложений в меню описываются специальными текстовыми файлами. Эти файлы имеют расширение .desktop и при установке приложения создаются автоматически. Но иногда бывают ситуации когда нужно самому создать такой файл. Это может быть когда у вас на руках имеется только исполняемый файл приложения, то есть когда приложение не упаковано должным образом. В некоторых дистрибутивах из коробки имеются программы для создания значков запуска, а в некоторых их нет и нужно искать такие приложения в репозиториях. Я создал свой вариант такой программы и в этом посте расскажу, что она из себя представляет.

Немного о desktop-файлах

Вот пример desktop-файла для консольной игры nsnake:

[Desktop Entry]Version=1.1Type=ApplicationName=nsnakeGenericName=Classic snake game on the terminalNoDisplay=false//отображать в менюIcon=nsnakeExec=nsnakeTerminal=true//запускать в терминалеActions=Categories=ActionGame;Game;Keywords=snake;ncurses;textual;terminal;

Тут и так все понятно, но я все-таки прокомментировал пару позиций. Самое главное, что нужно прописать это название приложения (Name), путь до исполняемого файла (Exec) и путь до иконки (Icon). Если иконки нет и нет желания ее искать или создавать, то некоторые окружения рабочего стола установят иконку по умолчанию.

Краткое описание

Исходники приложения находятся здесь. Программа довольно простая. Выглядит она вот так:

Нужно заполнить необходимые поля, отметить нужные чекбоксы и нажать кнопку CREATE. Программа запросит подтверждение и после согласия оповестит об успешном или неуспешном создании файла.

В хидербаре находится кнопка, при нажатии на которую в файловом менеджере откроется папка, находящаяся по пути /.local/share/applications. Именно там программасохраняет созданные файлы.

Как работает

Приложение написано на языке Vala с помощью среды разработки GNOME Builder. Устанавливал самую свежую версию среды (40.0) из репозитория Flathub. Оказалось, что визуальный дизайнер в этой версии еще багованнее, чем в предыдущей, поэтому интерфейс делал в Glade.

После того как созданы значки в полях ввода и к ним, также как и к другим кнопкам, привязаны необходимые действия, записываем в переменную directory_path путьдо папки с desktop-файлами. Также на всякий случай надо проверить наличие этой папки перед запуском приложения и если папка по каким-то причинам отсутствует, то вывести специальное сообщение и деактивировать кнопку CREATE, так как в этом случае в ней нет необходимости.

        button_open.clicked.connect(on_open_directory);        button_create.clicked.connect(on_create_file);        directory_path = Environment.get_home_dir()+"/.local/share/applications";        GLib.File file = GLib.File.new_for_path(directory_path);         if(!file.query_exists()){//проверяем существует ли директория            alert("Error!\nPath "+directory_path+" is not exists!\nThe program will not be able to perform its functions.");            button_create.set_sensitive(false);//деактивация кнопки CREATE           }

При нажатии на кнопку CREATE вызывается метод on_create_file:

private void on_create_file (){       if(is_empty(entry_name.get_text())){//проверяем введено ли имя файла             alert("Enter the name");             entry_name.grab_focus();//устанавливаем фокус             return;         }         GLib.File file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip()+".desktop");         if(file.query_exists()){//проверяем есть ли файл с таким именем            alert("A file with the same name already exists");            entry_name.grab_focus();            return;         }         var dialog_create_desktop_file = new Gtk.MessageDialog(this,Gtk.DialogFlags.MODAL,Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, "Create file "+file.get_basename()+" ?");          dialog_create_desktop_file.set_title("Question");          Gtk.ResponseType result = (Gtk.ResponseType)dialog_create_desktop_file.run ();          dialog_create_desktop_file.destroy();          if(result==Gtk.ResponseType.OK){              create_desktop_file();//создаем файл          }   }

Он предназначен для проверки ввода имени файла и проверки возможных совпадений с именами уже существующих в папке файлов, а также для вывода запроса подтверждения на создание файла. Если все проверки пройдены и пользователь подтвердил создание файла, то вызывается метод create_desktop_file:

private void create_desktop_file(){         string display;         if(checkbutton_no_display.get_active()){//проверяем первый чекбокс             display="true";         }else{             display="false";         }         string terminal;         if(checkbutton_terminal.get_active()){//проверяем второй чекбокс             terminal="true";         }else{             terminal="false";         }         string desktop_file="[Desktop Entry]Encoding=UTF-8Type=ApplicationNoDisplay="+display+"Terminal="+terminal+"Exec="+entry_exec.get_text().strip()+"Icon="+entry_icon.get_text().strip()+"Name="+entry_name.get_text().strip()+"Comment="+entry_comment.get_text().strip()+"Categories="+entry_categories.get_text().strip();//записываем содержимое будущего файла в переменную        string path=directory_path+"/"+entry_name.get_text()+".desktop";        try {            FileUtils.set_contents (path, desktop_file);//создаем файл        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }        GLib.File file = GLib.File.new_for_path(path);         if(file.query_exists()){//проверяем существование файла             alert("File "+file.get_basename()+" is created!\nPath: "+path);         }else{             alert("Error! Could not create file");         }       }

Чтобы просмотреть готовые файлы существует метод on_open_directory. Он срабатывает при нажатии на кнопку в хидербаре.

private void on_open_directory(){            try{                Gtk.show_uri_on_window(this, "file://"+directory_path, Gdk.CURRENT_TIME);            }catch(Error e){                alert("Error!\n"+e.message);            }       }

Для выбора исполняемого файла используется следующий код:

private void on_open_exec(){        var file_chooser = new Gtk.FileChooserDialog ("Choose a file", this, Gtk.FileChooserAction.OPEN, "_Cancel", Gtk.ResponseType.CANCEL, "_Open", Gtk.ResponseType.ACCEPT);        if (file_chooser.run () == Gtk.ResponseType.ACCEPT) {            entry_exec.set_text(file_chooser.get_filename());        }        file_chooser.destroy ();   }

А для выбора иконки код сложнее, так как данный диалог, помимо фильтра, содержит функционал предварительного просмотра изображения:

private void on_open_icon () {        var file_chooser = new Gtk.FileChooserDialog ("Select image file", this, Gtk.FileChooserAction.OPEN, "_Cancel", Gtk.ResponseType.CANCEL, "_Open", Gtk.ResponseType.ACCEPT);    Gtk.FileFilter filter = new Gtk.FileFilter ();file_chooser.set_filter (filter);//установка фильтра для изображенийfilter.add_mime_type ("image/jpeg");        filter.add_mime_type ("image/png");        Gtk.Image preview_area = new Gtk.Image ();file_chooser.set_preview_widget (preview_area);//установка области предпросмотраfile_chooser.update_preview.connect (() => {string uri = file_chooser.get_preview_uri ();string path = file_chooser.get_preview_filename();if (uri != null && uri.has_prefix ("file://") == true) {try {Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file_at_scale (path, 250, 250, true);preview_area.set_from_pixbuf (pixbuf);//установка изображенияpreview_area.show ();//показываем область предпросмотра} catch (Error e) {preview_area.hide ();//скрываем область предпросмотра}} else {preview_area.hide ();}});        if (file_chooser.run () == Gtk.ResponseType.ACCEPT) {            entry_icon.set_text(file_chooser.get_filename());        }        file_chooser.destroy ();       }

Метод для вывода сообщений пользователю:

private void alert (string str){          var dialog_alert = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, str);          dialog_alert.set_title("Message");          dialog_alert.run();          dialog_alert.destroy();       }

Чтобы проверить введено ли какое-либо значение в текстовое поле используется такой метод:

private bool is_empty(string str){          return str.strip().length == 0;        }

На этом все! Надеюсь, что пост был для Вас полезен.

Дополнительная ссылка на SourceForge. До встречи в следующих постах!

Подробнее..

Блокнот на языке Vala

25.04.2021 20:19:15 | Автор: admin

В этом посте я расскажу о простом блокноте на языке программирования Vala. Программа создавалась с использованием среды разработки GNOME Builder и редактора интерфейсов Glade.

Внешний вид

Вот так приложение выглядит:

Слева можно видеть список всех заметок, которые автоматически сохраняются в специальной папке. Имя по умолчанию для любой заметки имеет вид даты и времени ее создания. Нажав на последнюю кнопку в хидербаре, можно назначить другое имя. Исходный код приложения находится здесь.

Иерархия элементов интерфейса в редакторе Glade:

Создание заметки

Нажав на первую кнопку, пользователь сразу же создает заметку с именем по умолчанию. За это отвечает следующий метод:

private void on_add_clicked(){        GLib.File file = GLib.File.new_for_path(directory_path+"/"+date_time());        try {            FileUtils.set_contents (file.get_path(), "");//создаем пустой файл        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }        if(!is_empty(text_view.buffer.text)){              text_view.buffer.text = "";        }         show_notes();//показываем список заметок      }

Метод date_time, который дает имя заметке:

private string date_time(){         var now = new DateTime.now_local ();         return now.format("%d")+"."+now.format("%m")+"."+now.format("%Y")+"  "+now.format("%H")+":"+now.format("%M")+":"+now.format("%S");    }

Идем дальше. Метод для показа списка:

private void show_notes () {           list_store.clear();           list = new GLib.List<string> ();            try {            Dir dir = Dir.open (directory_path, 0);            string? file_name = null;            while ((file_name = dir.read_name ()) != null) {                list.append(file_name);            }        } catch (FileError err) {            stderr.printf (err.message);        }         Gtk.TreeIter iter;           foreach (string item in list) {               list_store.append(out iter);               list_store.set(iter, Columns.TEXT, item);           }       }

Метод вызывается каждый раз, когда нужно обновить список.

Удаление заметки

За удаление заметок отвечает вот такой метод:

private void on_delete_clicked(){         var selection = tree_view.get_selection();           selection.set_mode(Gtk.SelectionMode.SINGLE);           Gtk.TreeModel model;           Gtk.TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a note");               return;           }           GLib.File file = GLib.File.new_for_path(directory_path+"/"+item);         var dialog_delete_file = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL,Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, "Delete note "+file.get_basename()+" ?");         dialog_delete_file.set_title("Question");         Gtk.ResponseType result = (Gtk.ResponseType)dialog_delete_file.run ();         dialog_delete_file.destroy();         if(result==Gtk.ResponseType.OK){         FileUtils.remove (directory_path+"/"+item);//удаляем файл         if(file.query_exists()){            alert("Delete failed");//не получилось удалить         }else{             show_notes();             text_view.buffer.text = "";//очищаем текстовую область         }      }   }

Удаление происходит только после подтверждения этого действия пользователем.

Сохранение заметок

Для сохранение заметок существует следующий код:

private void on_save_clicked(){         var selection = tree_view.get_selection();           selection.set_mode(Gtk.SelectionMode.SINGLE);           Gtk.TreeModel model;           Gtk.TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a note");               return;           }         if(is_empty(text_view.buffer.text)){             alert("Nothing to save");             return;         }         GLib.File file = GLib.File.new_for_path(directory_path+"/"+item);        var dialog_save_file = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL,Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, "Save note "+file.get_basename()+" ?");         dialog_save_file.set_title("Question");         Gtk.ResponseType result = (Gtk.ResponseType)dialog_save_file.run ();         if(result==Gtk.ResponseType.OK){         try {            FileUtils.set_contents (file.get_path(), text_view.buffer.text);//записываем в файл содержимое текстовой области        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }          show_notes();      }      dialog_save_file.destroy();      }

Здесь, при сохранении сначала идет проверка, а есть ли вообще, что сохранять. В случае, если текст не обнаружен, пользователь получает соответствующее сообщение.

Сохранение заметок под другим именем

Для того чтобы поменять имя заметки используется такой метод:

private void on_save_as_clicked(){        var selection = tree_view.get_selection();           selection.set_mode(Gtk.SelectionMode.SINGLE);           Gtk.TreeModel model;           Gtk.TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a note");//нужно выбрать заметку из списка               return;           }        if(is_empty(text_view.buffer.text)){             alert("Nothing to save");//нечего сохранять             return;         }        var dialog_save_note = new Gtk.Dialog.with_buttons ("Save note", this, Gtk.DialogFlags.MODAL);var content_area = dialog_save_note.get_content_area ();        entry_name = new Gtk.Entry();        var label_name = new Gtk.Label.with_mnemonic ("_Name:");        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 20);        hbox.set_border_width(15);        hbox.pack_start (label_name, false, true, 0);        hbox.pack_start (entry_name, true, true, 0);content_area.add (hbox);dialog_save_note.add_button ("OK", Gtk.ResponseType.OK);dialog_save_note.add_button ("CLOSE", Gtk.ResponseType.CLOSE);dialog_save_note.response.connect (on_save_response);dialog_save_note.show_all ();      }

В вышеприведенном коде создается простенькое диалоговое окно для ввода нового имени заметки:

Для обработки нажатий на кнопки OK и CLOSE понадобится метод on_save_response:

private void on_save_response (Gtk.Dialog dialog, int response_id) {        switch (response_id) {case Gtk.ResponseType.OK:if(is_empty(entry_name.get_text())){    alert("Enter the name");//нужно ввести имя        entry_name.grab_focus();        return;}GLib.File select_file = GLib.File.new_for_path(directory_path+"/"+item);GLib.File edit_file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip());if (select_file.get_basename() != edit_file.get_basename() && !edit_file.query_exists()){                FileUtils.rename(select_file.get_path(), edit_file.get_path());//переименовываем файл                if(!edit_file.query_exists()){                    alert("Rename failed");//не получилось переименовать                    return;                }                try {                 FileUtils.set_contents (edit_file.get_path(), text_view.buffer.text);              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);            }            }else{                if (select_file.get_basename() != edit_file.get_basename()) {                    alert("A note with the same name already exists");//такое имя уже есть                    entry_name.grab_focus();                    return;                }                try {                 FileUtils.set_contents (edit_file.get_path(), text_view.buffer.text);              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);             }            }            show_notes();        dialog.destroy();break;case Gtk.ResponseType.CLOSE:        dialog.destroy();        break;case Gtk.ResponseType.DELETE_EVENT:dialog.destroy();break;}}

Обязательно нужно проверить существует ли заметка с таким же именем и если существует, то предложить пользователю ввести другое имя.

Отображение текста заметки

Чтобы показать содержимое заметки используется такой код:

private void on_select_item () {           var selection = tree_view.get_selection();           selection.set_mode(Gtk.SelectionMode.SINGLE);           Gtk.TreeModel model;           Gtk.TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               return;           }           Gtk.TreePath path = model.get_path(iter);           var index = int.parse(path.to_string());           if (index >= 0) {               item = list.nth_data(index);           }          string text;            try {                FileUtils.get_contents (directory_path+"/"+item, out text);            } catch (Error e) {               stderr.printf ("Error: %s\n", e.message);            }            text_view.buffer.text = text;//показываем текст заметки       }

На этом все! До встречи в следующих постах!


Дата-центр ITSOFT размещение и аренда серверов и стоек в двух дата-центрах в Москве. За последние годы UPTIME 100%. Размещение GPU-ферм и ASIC-майнеров, аренда GPU-серверов, лицензии связи, SSL-сертификаты, администрирование серверов и поддержка сайтов.

Подробнее..

GTK Как выглядит первый запуск анализатора в цифрах

04.01.2021 10:13:21 | Автор: admin

Внедрение статического анализатора в проект для некоторых людей выглядит непреодолимой преградой. Почему-то очень распространено мнение, что объём выданных результатов анализа при первом запуске настолько велик, что рассматривается только два варианта: не связываться с этим или переключить всех людей на исправление предупреждений. В этой статьей попробуем развеять этот миф, проведя внедрение и настройку анализатора на проекте GTK.

Введение

GTK кроссплатформенная библиотека элементов интерфейса. Недавно состоялся релиз GTK 4, что стало хорошим инфоповодом изучить качество кода проекта с помощью статического анализатора кода PVS-Studio. Такая деятельность для нас является регулярной, и нам часто приходится настраивать анализатор с нуля на многих проектах перед исследованием качества кода. В этой заметке я поделюсь опытом быстрой настройки PVS-Studio на C++ проекте.

Анализ GTK

Первые результаты

Получаем свой первый отчёт анализатора и видим следующие результаты для диагностик общего назначения:

4 (Fails) + 1102 (High) + 1159 (Medium) + 3093 (Low) = 5358 предупреждений.

Далее быстро прокручиваем отчёт, выявляем всплески неинтересных предупреждений и принимаем решение для дальнейшей настройки анализатора.

Исключаем директории

Рассмотрим такое предупреждение:

V530 [CWE-252] The return value of function 'g_strrstr_len' is required to be utilized. strfuncs.c 1803

/* Testing functions bounds */static voidtest_bounds (void){  ....  g_strrstr_len (string, 10000, "BUGS");  g_strrstr_len (string, 10000, "B");  g_strrstr_len (string, 10000, ".");  g_strrstr_len (string, 10000, "");  ....}

Это код тестов, причём не относящихся непосредственно к GTK, поэтому составляем список директорий для исключения из анализа и перезапускаем PVS-Studio.

При следующем запуске из анализа будут исключены следующие директории:

gtk/_build/gtk/subprojects/gtk/tests/gtk/testsuite/

Открываем отчёт и получаем следующий результат:

2 (Fails) + 819 (High) + 461 (Medium) + 1725 (Low) = 3007 предупреждений.

Ещё один положительный эффект, который мы получили после такой настройки, это ускорение анализа.

Исключаем макросы

Макросы, пожалуй, - одна из основных причин аномального количества срабатываний у некоторых диагностик. Поверхностно смотрим наш отчёт и замечаем множество подобных предупреждений:

V501 There are identical sub-expressions '* (& pipe->ref_count)' to the left and to the right of the '^' operator. gdkpipeiostream.c 65

static GdkIOPipe *gdk_io_pipe_ref (GdkIOPipe *pipe){  g_atomic_int_inc (&amp;pipe->ref_count);  return pipe;}

Вносить изменения в макросы обычно сложнее всего - вряд ли за это кто-то возьмётся в компании. Первое время точно нет. Поэтому воспользуемся механизмом отключения диагностик на них. Быстро просмотрев отчёт, составляем такой файл настроек:

#V501//-V:g_atomic_int_:501#V547//-V:GTK_IS_:547//-V:GDK_IS_:547//-V:G_IS_:547//-V:G_VALUE_HOLDS:547#V568//-V:g_set_object:568

Всего несколько строчек, которые покрывают большинство проблемных макросов для V501, V547 и V568.

Смотрим результат:

2 (Fails) + 773 (High) + 417 (Medium) + 1725 (Low) = 2917 предупреждений.

Отключаем диагностики

Некоторые диагностики изначально выдают неподходящие предупреждения для конкретного проекта. Рассмотрим предупреждение V1042:

V1042 [CWE-1177] This file is marked with copyleft license, which requires you to open the derived source code. main.c 12

Это очень полезная диагностика для проектов с закрытым кодом, чтобы случайно не включить в них код с недопустимой лицензией. Но для GTK это неинтересная диагностика, поэтому отключим её и получим скорректированный результат:

2 (Fails) + 164 (High) + 417 (Medium) + 1725 (Low) = 2308 предупреждений.

Изучаем фейлы

В проекте имеются 2 предупреждения типа Fails:

  • V002 Some diagnostic messages may contain incorrect line number in this file. gdkrectangle.c 1

  • V002 Some diagnostic messages may contain incorrect line number in this file. gdktoplevelsize.c 1

Эта диагностика предупреждает, что срабатывания на этих файлах могут указывать не на те строки кода, которые нужно. Обычно разница составляет 1-2 строки, и происходит это из-за неверного раскрытия макросов компилятором. По нашему опыту, чаще всего за этим был замечен компилятор MSVC.

Эти предупреждения можно просто проигнорировать.

Выводы

Итоговый результат такой:

164 (High) + 417 (Medium) + 1725 (Low) = 2306 предупреждений.

Наверняка, там ещё есть, что настроить, но я уже решил задачу, которую ставил перед собой: быстро получить отчёт анализатора, в котором легко найти ошибки. Например, предупреждение с номером V501 теперь всего одно во всём отчёте, и оно хорошее:

V501 There are identical sub-expressions 'G_PARAM_EXPLICIT_NOTIFY' to the left and to the right of the '|' operator. gtklistbase.c 1151

static voidgtk_list_base_class_init (GtkListBaseClass *klass){  ....  properties[PROP_ORIENTATION] =    g_param_spec_enum ("orientation",                       P_("Orientation"),                       P_("The orientation of the orientable"),                       GTK_TYPE_ORIENTATION,                       GTK_ORIENTATION_VERTICAL,                       G_PARAM_READWRITE |                       G_PARAM_EXPLICIT_NOTIFY |  // &lt;=                       G_PARAM_EXPLICIT_NOTIFY);  // &lt;=  ....}

Это отличный результат! И показатели других диагностик тоже значительно выросли. Мизерными настройками удалось уменьшить отчёт анализатора на целых 57%. Соответственно, показатель верных предупреждений к ложным тоже значительно вырос.

Кстати, это одна из причин, по которой в интернете отсутствуют сравнения результатов разных анализаторов кода. Потому что нет единого мнения, стоит ли показывать работу анализатора как есть, или всё же надо провести первичную настройку. А у всех анализаторов свои механизмы для этого, что многократно усложняет сложность задачи сравнения.

А теперь время передать эстафету моему коллеге Андрею Карпову.

Примечание Андрея Карпова

Даже такая простая и быстрая фильтрация результатов, описанная в этой статье, существенно облегчает работу с отчётом. Например, взяв этот отчёт, я в течение одного вечера смог бегло его просмотреть и выписать фрагменты кода с ошибками, в количестве достаточном для написания статьи. Собственно, её написанием я и займусь на новогодних праздниках.

Конечно, моя задача проще и сильно отличается от процесса настройки и внедрения анализатора в реальный проект. Мне достаточно поверхностно пробежаться по списку предупреждений и выписать явные ошибки, игнорируя ложные срабатывания или непонятные предупреждения в сложных участках кода. При реальном использовании понадобится больше времени, чтобы настроить анализатор, точечно подавить ложные срабатывания, улучшить макросы и так далее. Но на самом деле, это не страшно. Например, в статье про проверку проекта EFL Core Libraries я показал, что можно достаточно легко настроить анализатор, чтобы он выдавал всего 10-15% ложных предупреждений. Согласитесь, неплохо, когда на каждые 1-2 ложных срабатывания вы исправляете 8-9 ошибок.

Ну и не забывайте, что вам всегда доступен механизм массового подавления срабатываний анализатора. Это позволяет начать быстро использовать анализатор даже в большом проекте. Все предупреждения объявляются техническим долгом и пока не показываются. И команда начинает работать только с предупреждениями, относящимися к новому или изменённому коду. Подробнее про это предлагаю почитать в статье "Как внедрить статический анализатор кода в legacy проект и не демотивировать команду".

Спасибо за внимание и приходите через пару недель читать статью про найденные ошибки.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. GTK: The First Analyzer Run in Figures.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru