Code covered by the BSD License  

Highlights from
matlab-ClassInheritanceBrowser

image thumbnail
from matlab-ClassInheritanceBrowser by Andreas
A tool for managing and organizing classes, with a simple interface and (optional) tree diagram

classInheritance.browse
classdef browse < handle
    % Creates the class inheritance browser GUI
    
    % Copyright 2012 Eric Lee, Clayton Ernst, Andrew Hagen and Andreas Kotowicz
    % Written for Engineering 177 Spring 2010, Final Project. Professor:
    % Andy Packard, UC Berkeley.
    
    properties (Access = private)
        trees
        metadata
        liststr
        indx
        guiHan
        bioinfo_toolbox = 0;
        NODIRSTRING = 'No dir found. Go up.';
    end
    
    %% PUBLIC methods
    methods
        function obj = browse(inputdir)
            if nargin==0
                inputdir = '.';
            end
            
            % bioinformatics toolbox checks.
            try
                % Check if bioinfo toolbox exists
                a = ver('bioinfo');
                
                % Check also if network license is available.
                % Need to return "errormessage", otherwise user sees an
                % ugly "License checkout failed." error message.
                % 
                try % matlab >= 2011a: `[TF, errmsg] = license('checkout',feature)`
                    [b, errormessage] = license('checkout', 'bioinformatics_toolbox'); %#ok<NASGU>
                catch me %#ok<NASGU> % matlab <= 2010b: `result = license('checkout',feature)`
                    b = license('checkout', 'bioinformatics_toolbox');
                end
                if numel(a) > 0 && b == 1
                    obj.bioinfo_toolbox = 1;
                end
            catch me %#ok<NASGU>
                obj.bioinfo_toolbox = 0;
            end
            
            if obj.bioinfo_toolbox == 0
                disp('Bioinformatics Toolbox not found.');
            end
            
            % initalize data fields
            ok = init_data(obj, inputdir);
            
            % quit right away if there was a problem.
            if ok == 0
                return;
            end

            % show class viewer
            if obj.bioinfo_toolbox == 1
                obj.trees.view();
            end
            
            % build main gui
            setup_gui(obj);
            
            % update class listbox
            update_class_listbox(obj)

            % fill gui with information
            displayinfo(obj, 1, obj.liststr{1});
        end
        
        function display(obj) %#ok<MANU>
            display('Class Inheritance Analyzer/Browser')
        end
        
        function closefunc(src,evt,obj) %#ok<INUSL,MANU>
            if ~isempty(obj.trees) && ~isempty(obj.trees.directory)
                close(findobj('name',['Class Inheritance tree for ', obj.trees.directory]))
            end
            delete(obj.guiHan.f)
        end
        
        function KeyPressFcn_browse(src, evnt, obj) %#ok<MANU>
            try % maybe not all kpfs are committed.
                switch evnt.Key
                    
                    case 'm'
                        [name, value] = get_selected_class_name(obj);
                        ak.create_metauml(obj.metadata{value});
                    otherwise
                        
                end
            catch me %#ok<NASGU> % no error message - just ignore the error
            end
        end
        
        function LOCALbrowseCb(src, evnt, obj) %#ok<INUSL,MANU>
            % allow user to browse to different directory
            start_path = get(obj.guiHan.dirH, 'String');
            dialog_title = 'Please select new directory';
            try
                folder_name = uigetdir(start_path,dialog_title);
            catch  %#ok<CTCH>
                folder_name = uigetdir('', dialog_title);
            end
            set(obj.guiHan.dirH, 'String', folder_name);
        end
        
        
        function LOCALdirCb(src,evt,obj)
            flag = 1;
            
            % check for keypresses if pushbotton didn't get pressed
            if ~strcmp(get(src,'style'),'pushbutton')
                if strcmp(evt.Key,'return')
                    flag = 1;
                else
                    flag = 0;
                end
            end
            
            % user didn't press the pushbutton and didn't hit 'return'
            if flag == 0
                return;
            end
            
            drawnow();
            inputdir = get(obj.guiHan.dirH, 'String');
            if isempty(inputdir)
                inputdir = '.';
            end
            
            if ~isempty(obj.trees) && ~isempty(obj.trees.directory)
                close(findobj('name', ['Class Inheritance tree for ', obj.trees.directory]));                
            end
            
            a = dir(inputdir);
            a([a.isdir] == 0) = [];
            a(strcmp({a.name}, '.')) = [];
            a(strcmp({a.name}, '..')) = [];
            nbr_entries = numel(a);
            if nbr_entries > 0
                entries = cell(1, nbr_entries + 1);
                for j = 1 : nbr_entries
                    entries{1, j+1} = a(j).name;
                end
                entries{1, 1} = 'Go up';
                
                set(obj.guiHan.directoryList, 'String', entries);
                
            else
                set(obj.guiHan.directoryList, 'String', obj.NODIRSTRING, 'Value', 1);
            end
            
            % initalize data fields
            ok = init_data(obj, inputdir);
            % quit if there was a problem.
            if ok == 0
                return;
            end
            
            if obj.bioinfo_toolbox == 1
                % show class viewer
                obj.trees.view();
            end
            
            % update bioviewer
            setup_gui_biograph_viewer(obj);
            
            % update class listbox
            update_class_listbox(obj);
            
            % populate remaining listboxes.
            displayinfo(obj, 1, obj.liststr{1});

        end
        
        function LOCALmethCb(src, evt, obj) %#ok<MANU,INUSL>
            % opens currently selected method (if it's in a separate file)
            % otherwise it opens the file containing the method
            
            selected_class = get_selected_class_name(obj);
            selected_method = get_selected_method_name(obj);
            % method list is empty (can happen in old matlab versions)
            if isempty(selected_method)
                return;
            end
            
            dd = filesep();
            class_name_and_method = [selected_class dd selected_method];
            class_name_and_method_private = [selected_class dd 'private' dd selected_method];
            [result, which_file] = classInheritance.browse.resolvePath(class_name_and_method, class_name_and_method_private);

            
            % found a file.
            if result == 1
                [pathstr, name] = fileparts(which_file); %#ok<ASGLU>
                % method is in a separate file - no need to look up the
                % line number
                if strcmp(name, selected_method)
                    line_number = 0;
                else
                    % method is inside a class file.
                    % read-in file and find the line where this method is
                    % defined
                    content = textread(which_file, '%s', 'delimiter', '\n');
                    regexp_rule = ['(.*function.*' selected_method '.*)'];
                    s = regexp(content, regexp_rule, 'start');
                    line_number = find(cellfun(@(x) ~isempty(x), s), 1);
                    if isempty(line_number)
                        line_number = 0;
                    end
                end
                % open file at correct line_number
                opentoline(which_file, line_number);
            end
        end
        
        function LOOCALpropLb(src, evt, obj)
            
            % find out what item user clicked on
            selected_entry = get(src, 'Value');
            subdirs = get(src, 'String');
            curr_dir = get(obj.guiHan.dirH, 'String');
            
            new_dir_selected = false;
            
            % check whether user wants to navigate up to parent level.
            if selected_entry == 1 && ((~iscell(subdirs) && strcmp(subdirs, obj.NODIRSTRING) == 1) || (strcmp(subdirs{1}, 'Go up'))  )
                % disp('gotta ignore this.');
                [parent, this_dir] = fileparts(curr_dir);
                if strcmp(parent, this_dir)
                    return;
                else
                    new_dir_selected = true;
                    new_dir = parent;
                end
            end
            
            % new directory wasn't selected, so let's build it here.
            if new_dir_selected == false
                new_dir = [curr_dir filesep() subdirs{selected_entry}];
            end
            
            % set the new path.
            set(obj.guiHan.dirH, 'String', new_dir);
           
            % run the 'Go' callback, fake the 'return' key being pressed.
            evt.Key = 'return';
            LOCALdirCb(src, evt, obj);

        end
        
        
        function LOCALpropCb(src, evt, obj) %#ok<MANU,INUSL>
            % prints help text of currently selected property to console
            
            selected_property = get_selected_property_name(obj);
            % property list might be empty
            if isempty(selected_property)
                return;
            end
            
            selected_class = get_selected_class_name(obj);
            class_name_and_property = [selected_class '.' selected_property];
            
            % calling 'help' is very expensive. That's why we only do it
            % here once the user clicks on a property.
            help_string = help(class_name_and_property);
            if isempty(help_string)
                disp(['No description available for: ' class_name_and_property]);
            else
                disp([class_name_and_property ':' help_string]);
            end
            
        end
        
        function LOCALlistCb(src,evt,obj) %#ok<INUSL,MANU>
            obj.indx = get(obj.guiHan.classH,'UserData');
            [name, value] = get_selected_class_name(obj);
            
            % check if bioinfo toolbox is available.
            if obj.bioinfo_toolbox == 1
                % restart class viewer if it was closed by user
                if ~obj.trees.view_running()
                    obj.trees.view();
                end
                
                % 'trees.h' might be empty, if only one class is given.
                if ~isempty(obj.trees.h)
                    nodes = get(obj.trees.h.Nodes);
                    oldind = [];
                    for j=1:length(nodes)
                        if isequal(2,nodes(j).LineWidth)
                            oldind = j;
                        end
                    end
                    if ~isempty(oldind)
                        set(obj.trees.h.Nodes(oldind),'LineWidth',1)
                        set(obj.trees.h.Nodes(oldind),'LineColor',[.3 .3 1])
                    end
                    nodename = cell(1,length(nodes));
                    for i=1:length(nodes); nodename{i} = nodes(i).ID; end
                    newind = strcmp(name,nodename);
                    set(obj.trees.h.Nodes(newind),'LineWidth',2)
                    set(obj.trees.h.Nodes(newind),'LineColor',[1 0 0])
                end
            end
            
            displayinfo(obj,value,name);
        end
        
        function LOCALsearchCb(src,evt,obj)
            flag = 1;
            
            % check for keypresses if pushbotton didn't get pressed
            if ~strcmp(get(src,'style'),'pushbutton')
                if strcmp(evt.Key,'return')
                    flag = 1;
                else
                    flag = 0;
                end
            end

            % user didn't press the pushbutton and didn't hit 'return'
            if flag == 0
                return;
            end
            
            drawnow;
            set(obj.guiHan.classH,'string',obj.liststr);
            str=get(obj.guiHan.searchH,'string');
            if isempty(str)
                set(obj.guiHan.classH,'string',obj.liststr);
                set(obj.guiHan.classH,'UserData',1:length(obj.metadata));
                value = get(obj.guiHan.classH,'Value');
                str = get(obj.guiHan.classH,'String');
                name = str{value};
                displayinfo(obj,value,name)
            else
                [strmatchs,obj.indx] = classInheritance.browse.wholewordizer(str,obj.liststr);
                set(obj.guiHan.classH,'UserData',obj.indx);
                if ~isempty(strmatchs)
                    set(obj.guiHan.classH,'string',strmatchs);
                    set(obj.guiHan.classH,'Value',1);
                    displayinfo(obj,1,strmatchs{1});
                else
                    set(obj.guiHan.classH,'string','');
                    set(obj.guiHan.propH,'string','');
                    set(obj.guiHan.methH,'string','');
                end
            end

        end
    end
    
    %% PRIVATE methods
    methods (Access = private)
        function displayinfo(obj,value,name)
            % takes a class name, and puts the methods and properties in respective
            % lists in the info figure.
            
            % make sure the class name has the correct format
            name = classInheritance.iTree.format(name);
            
            obj.indx = get(obj.guiHan.classH,'UserData');
            indxvalue = obj.indx(value);
            
            props=obj.metadata{indxvalue}.Properties;
            meth=obj.metadata{indxvalue}.Methods;
            sup=obj.metadata{indxvalue}.SuperClasses;
            
            nbr_props = length(props);
            nbr_meth = length(meth);
            
            propstr = cell(nbr_props, 1);
            for i = 1:nbr_props
                % show only properties that were defined in this class
                if strcmp(props{i}.DefiningClassName, name)
                    propstr{i, 1} = props{i}.Name;
                end
            end
            % remove empty cells
            propstr = classInheritance.browse.remove_empty_cell_entries(propstr);
            
            methstr = cell(nbr_meth, 1);
            for i = 1:nbr_meth
                if strcmp(meth{i}.DefiningClassName, name)
                    methstr{i, 1} = meth{i}.Name;
                end
            end
            % remove empty cells
            methstr = classInheritance.browse.remove_empty_cell_entries(methstr);
            
            
            supstr = cell(length(sup), 1);
            for i = 1:length(sup)
                supstr{i, 1} = classInheritance.iTree.unformat(sup{i}.Name);
            end
            
            set(obj.guiHan.propH, 'String', propstr);
            set(obj.guiHan.methH, 'String', methstr);
            set(obj.guiHan.supH, 'String', supstr);
            set(obj.guiHan.propH, 'Value', 1);
            set(obj.guiHan.methH, 'Value', 1);
            set(obj.guiHan.supH, 'Value', 1);
            
            nbr_props = size(propstr, 1);
            nbr_meth = size(methstr, 1);
            
            % enable / disable listboxes and their context menus
            do = 'on';
            if nbr_props == 0
                do = 'off';
            end
            obj.enable_disable_listbox_and_contextmenu(obj.guiHan.propH, do);
            
            do = 'on';
            if nbr_meth == 0
                do = 'off';
            end
            obj.enable_disable_listbox_and_contextmenu(obj.guiHan.methH, do);
            
            % update number of entries per category
            set(obj.guiHan.PropertiesH, 'String', ['Properties: ' num2str(nbr_props)]);
            set(obj.guiHan.MethodsH, 'String', ['Methods: ' num2str(nbr_meth)]);
            set(obj.guiHan.SuperClassesH, 'String', ['Super Classes: ' num2str(size(supstr, 1))]);
            
        end
        
        function [selected_property, value] = get_selected_property_name(obj)
            this_list = get(obj.guiHan.propH, 'String');
            if isempty(this_list)
                selected_property = [];
                value = 0;
                return;
            end
            value = get(obj.guiHan.propH, 'Value');
            selected_property = this_list{value};
        end
        
        function [selected_class, value] = get_selected_class_name(obj)
            str = get(obj.guiHan.classH, 'String');
            if isempty(str)
                selected_class = [];
                value = 0;
                return;
            end
            value = get(obj.guiHan.classH, 'Value');
            selected_class = str{value};
        end
        
        function [selected_method, value] = get_selected_method_name(obj)
            str = get(obj.guiHan.methH, 'String');
            if isempty(str)
                selected_method = [];
                value = 0;
                return;
            end            
            value = get(obj.guiHan.methH, 'Value');
            selected_method = str{value};
        end
        
        function update_class_listbox(obj)
            set(obj.guiHan.classH, 'UserData', obj.indx);
            set(obj.guiHan.classH, 'Value', 1);
            set(obj.guiHan.classH, 'String', obj.liststr)
            nbr_classes = numel(obj.indx);
            set(obj.guiHan.ClassesH, 'String', ['Classes: ' num2str(nbr_classes)]);
        end
        
        function ok = init_data(obj, inputdir)
            
            ok = 1;
            
            for i=1:length(obj.metadata)
                clear(obj.metadata{i}.Name)
            end
            
            % clear out old data
            obj.metadata = [];
            % make sure to delete the trees object if one exists -
            % otherwise we might get nasty errors.
            if isobject(obj.trees)
                delete(obj.trees);
            end

            obj.trees = [];
            
            % make sure we catch all possible errors in iTree
            try
                obj.trees = classInheritance.iTree(inputdir);
            catch me
                switch me.identifier
                    case 'classInheritance:iTreeClassNOTFOUND'
                        disp('Error: No class found. If if you think this is a mistake, please check if class name is correct or try again using full path to class.');
                    otherwise
                        disp('unkown error:');
                        disp(me.message);
                end
                ok = 0;
                return;
            end
            
            obj.metadata = obj.trees.metalist;
            obj.indx = 1:length(obj.metadata);
            obj.liststr = cell(1,length(obj.metadata));
            
            for i = 1:length(obj.metadata)
                obj.liststr{i} = classInheritance.iTree.unformat(obj.metadata{i}.Name);
            end
        end
        
        function setup_gui_biograph_viewer(obj)
            if obj.bioinfo_toolbox == 1
                % set name and windowbuttondown function of class viewer
                child_handles = allchild(0);
                names = get(child_handles, 'Name');
                k = strncmp('Biograph Viewer', names, 15);
                obj.guiHan.bioH = child_handles(k);
                set(obj.guiHan.bioH, 'Name',['Class Inheritance tree for ', obj.trees.directory]);
                set(obj.guiHan.bioH, 'WindowButtonDownFcn',{})
            end
        end
        
        function setup_gui(obj)
            % builds the GUI
            left = 10;
            bottom = 100;
            width = 855;
            height = 470;
            obj.guiHan.f = figure('Position',[left bottom width height],'menubar','none','name','Class Information');
            searchBox = uipanel('Title','Search','units','pixel','BackgroundColor','white','Position',[10 420 315 40]);
            dirBox = uipanel('Title','Directory','units','pixel','BackgroundColor','white','Position',[330 420 315 40]);
            infoBox = uipanel('Title','Class Information','units','pixel','BackgroundColor','white','Position',[10 10 642 400]);
            
            obj.guiHan.searchH = uicontrol('style','edit','position',[5 5 200 20],'parent',searchBox);
            obj.guiHan.dirH = uicontrol('style','edit','position',[5 5 200 20],'parent',dirBox,'string',obj.trees.fullpath);
            obj.guiHan.searchButtonH = uicontrol('style','pushbutton' ,'position',[210 5 100 20],'string','Search','parent',searchBox);
            
            % list box for quickly browsing through subdirectories.
            list_box_width = 200; 
            obj.guiHan.directoryList = uicontrol('style', 'listbox', ...
                'position', [width-list_box_width 0 list_box_width height], ...
                'string', obj.NODIRSTRING, 'Parent', obj.guiHan.f, ...
                'Callback', {@LOOCALpropLb obj}, ...
                'TooltipString', 'Browse through directories by clicking on them.');
            
            obj.guiHan.dirsearchButtonH = uicontrol('style', 'pushbutton', ...
                'position', [210 5 45 20], 'string', 'Go', ...
                'parent', dirBox, 'TooltipString', 'Inspect this directory');
            
            obj.guiHan.browsedirButtonH = uicontrol('style', 'pushbutton', ...
                'position', [256 5 53 20], 'string', 'Browse', ...
                'parent', dirBox, 'TooltipString', 'Browse to different directory');

            % panels used as encasing for both the listbox, and the text
            % above it.
            uipanel('units','pixel','position',[3 3 154 380],'parent',infoBox);
            uipanel('units','pixel','position',[163 3 154 380],'parent',infoBox);
            uipanel('units','pixel','position',[323 3 154 380],'parent',infoBox);
            uipanel('units','pixel','position',[483 3 154 380],'parent',infoBox);            

            % labels
            obj.guiHan.ClassesH = uicontrol('style','text','position',[5 365 150 15],'String','Classes','parent',infoBox);
            obj.guiHan.PropertiesH = uicontrol('style','text','position',[165 365 150 15],'String','Properties','parent',infoBox);
            obj.guiHan.MethodsH = uicontrol('style','text','position',[325 365 150 15],'String','Methods','parent',infoBox);
            obj.guiHan.SuperClassesH = uicontrol('style','text','position',[485 365 150 15],'String','Super Classes','parent',infoBox);            
            
            % listboxes below
            obj.guiHan.classH = uicontrol('style','listbox','position',[5 5 150 355],'parent',infoBox);
            obj.guiHan.propH = uicontrol('style','listbox','position',[165 5 150 355],'parent',infoBox);
            obj.guiHan.methH = uicontrol('style','listbox','position',[325 5 150 355],'parent',infoBox);
            obj.guiHan.supH = uicontrol('style','listbox','position',[485 5 150 355],'parent',infoBox);

            % context menus for listboxes
            cmenu = uicontextmenu;
            uimenu(cmenu, 'Label', 'Print property description to console', 'Callback', {@LOCALpropCb obj});
            set(obj.guiHan.propH, 'UIContextMenu', cmenu);            
            
            cmenu = uicontextmenu;
            uimenu(cmenu, 'Label', 'Open method in editor', 'Callback', {@LOCALmethCb obj});
            set(obj.guiHan.methH, 'UIContextMenu', cmenu);

            % assign Callbacks and other functions
            set(obj.guiHan.f,'CloseRequestFcn',{@closefunc obj})
            set(obj.guiHan.f,'KeyPressFcn',{@KeyPressFcn_browse obj})
            set(obj.guiHan.classH, 'Callback',{@LOCALlistCb obj});
            set(obj.guiHan.searchH, 'KeyPressFcn',{@LOCALsearchCb obj});
            set(obj.guiHan.dirH,'KeyPressFcn',{@LOCALdirCb obj},'Interruptible','off');
            set(obj.guiHan.dirsearchButtonH,'Callback',{@LOCALdirCb obj},'Interruptible','off');
            set(obj.guiHan.browsedirButtonH,'Callback',{@LOCALbrowseCb obj},'Interruptible','off');
            set(obj.guiHan.searchButtonH,'Callback',{@LOCALsearchCb obj});
            
            % update bioviewer
            setup_gui_biograph_viewer(obj);
        end
        
    end
    
    %% STATIC methods, for this class only
    methods (Static = true, Access = protected)
        function [strg,indx] = wholewordizer(sadword,str)
            % searches a cellarray of strings (str) for partial match of a single string (sadword)
            % returns a cell array of strings that match as well as their index in str
            % accounts of partial words and ignores upper case
            
            hits = 0;
            indx = [];
            strg = {};
            strL = cell(size(str));
            
            sadword = lower(sadword);
            
            for i = 1:length(str)
                strL{i} = lower(str{i});
            end
            
            for i = 1:length(strL)
                if ~isempty(strfind(strL{i},sadword));
                    hits = hits +  1;
                    strg = {strg{:} str{i}};
                    indx = [indx i];
                end
            end
        end
        
        function this_cell = remove_empty_cell_entries(this_cell)
            % checks for empty cells and removes them
            indices = cellfun(@(x) isempty(x), this_cell, 'UniformOutput', true);
            this_cell(indices) = [];
        end
        
        function enable_disable_listbox_and_contextmenu(handle, on_off)
            % the listbox itself
            set(handle, 'Enable', on_off);
            % disable the Uimenu entries that belong to the uicontextmenu
            % of the listbox
            set(get(get(handle, 'UIContextMenu'), 'Children'), 'Enable', on_off);
        end
    end
    
    %% STATIC methods
    methods (Static = true)
        function [result, absPathname] = resolvePath(class_and_method, class_and_method_private)
            % returns the absolute path for a class/method combination
            % "which(class_and_method)" will fail here most of the time.
            
            % the idea for this function is hijacked from:
            % toolbox/matlab/codetools/edit.m
            result = 0;
            absPathname = [];
            
            % "helpUtils.splitClassInformation" needs to be called
            % differently for the different Matlab version. I'm using try /
            % catch here which seems easier than checking for the 
            % appropriate matlab version.
            %
            % Call to 'splitClassInformation' with matlab <= 2012b:
            % [classInfo, whichTopic, malformed] = splitClassInformation(topic, helpPath, implementor, justChecking)
            % Call to 'splitClassInformation' with matlab == 2013a:
            % function [classInfo, whichTopic, malformed] = splitClassInformation(topic, helpPath, justChecking)            
            %
            try % works with matlab versions <= 2012b
                [classInfo, whichTopic] = helpUtils.splitClassInformation(class_and_method, '', true, false);
            catch me %#ok<NASGU> matlab == 2013a (and matlab >= 2013a?)
                [classInfo, whichTopic] = helpUtils.splitClassInformation(class_and_method, '', false);
            end
            
            % try again with possible 'private' path
            if isempty(classInfo)
                try % works with matlab versions <= 2012b
                    [classInfo, whichTopic] = helpUtils.splitClassInformation(class_and_method_private, '', true, false); %#ok<ASGLU>
                catch me %#ok<NASGU> matlab == 2013a (and matlab >= 2013a?)
                    [classInfo, whichTopic] = helpUtils.splitClassInformation(class_and_method_private, '', false); %#ok<ASGLU>
                end
            end
            
            if exist(whichTopic, 'file') == 2
                result = 1;
                absPathname = whichTopic;
                return;
            end
            
            % user should never see this message, because he / she can only
            % select methods that do exist. The only reason for not finding
            % this method is, that this is an abstract method.
            % maybe we should think about reading out the method properties
            % (abstract, strict, etc) so we can show them to the user.
            disp(['Could not find file for class/method: ' class_and_method]);
            disp('This is probably an abstract method.');
            
        end
    end
end

% Class Inheritance Browser Copyright Clayton Ernst, Andrew Hagen, Eric Lee and Andreas Kotowicz 2012. 

Contact us